package kv

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

var (
	errInvalidKey = errors.New("invalid key")
	errNoSuchKey  = errors.New("no such key")
)

type Database struct {
	data    map[string][]byte
	basedir string
	m       sync.RWMutex
}

func NewDatabse(basedir string) (db *Database, err error) {
	err = os.Mkdir(basedir, 0755)
	if err != nil && !os.IsExist(err) {
		return
	}

	return &Database{
		data:    make(map[string][]byte),
		basedir: basedir,
	}, nil
}

func (db *Database) Set(key string, val []byte) (err error) {
	if len(key) < 1 {
		return errInvalidKey
	}

	db.m.Lock()
	defer func() {
		if err != nil {
			delete(db.data, key)
		}
		db.m.Unlock()
	}()

	db.data[key] = val

	err = ioutil.WriteFile(filepath.Join(db.basedir, key), val, 0644)

	return
}

func (db *Database) Get(key string) (val []byte, err error) {
	if len(key) < 1 {
		return nil, errInvalidKey
	}

	db.m.RLock()
	defer db.m.RUnlock()

	data, ok := db.data[key]
	if !ok {
		data, err = ioutil.ReadFile(filepath.Join(db.basedir, key))
		if err != nil {
			err = errNoSuchKey
			return nil, err
		}

		db.data[key] = data
	}

	val = make([]byte, len(data))
	copy(val, data)

	return
}

func (db *Database) Remove(key string) {
	if len(key) < 1 || strings.ContainsRune(key, os.PathSeparator) {
		return
	}

	db.m.Lock()
	defer db.m.Unlock()

	delete(db.data, key)
	os.Remove(filepath.Join(db.basedir, key))

	return
}