package data

import (
    "os"
    "maps"
    "errors"
    "encoding/json"
)


// A very simple datastructure, implementing the IDatastore interface. It uses
// a simple text file to the data as json.
type FileDatastore struct {
    path string
    data map[string]string
}

// Creates a new FileDatastore object, creating the storage file in the
// process.
func NewFileDatastore(path string) (*FileDatastore, error) {
    fds := &FileDatastore{ path: path }

    if _, err := fds.readMapObj(); err != nil {
        if err := fds.writeMapObj(make(map[string]string)); err != nil { return nil, err }
    }

    return fds, nil
}

// Read the contents of the storage file and convert to a map object. May throw
// an error, if the file does not exit or the file content can not be
// converted.
func (fds *FileDatastore) readMapObj() (map[string]string, error) {
    if fds.data != nil {
        return fds.data, nil
    }

    dat, err := os.ReadFile(fds.path)
    if err != nil { return nil, err }

    var mapobj map[string]string
    err = json.Unmarshal(dat, &mapobj)
    if err != nil { return nil, err }

    return mapobj, nil
}

// Write the map object to the storage file. Will overwrite the content of the
// file. May throw an error, if the file cannot be created or written to.
func (fds *FileDatastore) writeMapObj(m map[string]string) error {
    file, err := os.Create(fds.path)
    if err != nil { return err }
    defer file.Close()

    encoder := json.NewEncoder(file)
    if err := encoder.Encode(m); err != nil { return err }

    fds.data = m

    return nil
}


// --- implement IDatastore interface ---

// Sets the key value pair given, overwriting if the key already exists. May
// through an error if the file cannot be opened or the contents cannot be
// decoded correctly.
func (fds *FileDatastore) Set(key string, val string) error {
    m, err := fds.readMapObj()
    if err != nil { return err }

    m[key] = val

    err = fds.writeMapObj(m)
    if err != nil { return err }

    return nil
}

// Check if for the given key a entry does exit. May through an error if the
// file cannot be opened or the contents cannot be decoded correctly.
func (fds *FileDatastore) KeyExists(key string) (bool, error) {
    m, err := fds.readMapObj()
    if err != nil { return false, err }
    
    _, ok := m[key]

    return ok, nil 
}

//  Gets the value for the given key. May through an error if the key does not
//  exit, the file cannot be opened or the contents cannot be decoded
//  correctly.
func (fds *FileDatastore) Get(key string) (string, error) {
    m, err := fds.readMapObj()
    if err != nil { return "", err }

    val, ok := m[key]
    if !ok { return "", errors.New("key not found") }

    return val, nil
}

// Gets all the key value pairs from the file and returns them as a map object.
// May through an error if the file cannot be opened or the contents cannot be
// decoded correctly.
func (fds *FileDatastore) GetAll() (map[string]string, error) {
    return fds.readMapObj()
}

// Gets all the key the file and returns them as a map object. May through an
// error if the file cannot be opened or the contents cannot be decoded
// correctly.
func (fds *FileDatastore) GetAllKeys() (map[string]bool, error) {
    m, err := fds.readMapObj()
    if err != nil { return nil, err }

    out := make(map[string]bool)
    for key := range maps.Keys(m) {
        out[key] = true
    }

    return out, nil 
}

// Deletes the entry with the given key. May through an error if the file
// cannot be opened or the contents cannot be decoded or encoded correctly.
func (fds *FileDatastore) Delete(key string) error {
    m, err := fds.readMapObj()
    if err != nil { return err }

    delete(m, key)

    err = fds.writeMapObj(m)
    if err != nil { return err }

    return nil 
}
