package database

import (
    "github.com/dgraph-io/badger"
    "os"
    "path/filepath"
    "sync"
)

var databasesByName = make(map[string]*databaseImpl)
var getLock sync.Mutex

var ErrKeyNotFound = badger.ErrKeyNotFound

type databaseImpl struct {
    db       *badger.DB
    name     string
    openLock sync.Mutex
}

func Get(name string) (Database, error) {
    getLock.Lock()
    defer getLock.Unlock()

    if database, exists := databasesByName[name]; exists {
        return database, nil
    }

    database := &databaseImpl{
        db:   nil,
        name: name,
    }
    if err := database.Open(); err != nil {
        return nil, err
    }

    databasesByName[name] = database

    return databasesByName[name], nil
}

func (this *databaseImpl) Open() error {
    this.openLock.Lock()
    defer this.openLock.Unlock()

    if this.db == nil {
        directory := *DIRECTORY.Value

        if _, err := os.Stat(directory); os.IsNotExist(err) {
            if err := os.Mkdir(directory, 0700); err != nil {
                return err
            }
        }

        opts := badger.DefaultOptions
        opts.Dir = directory + string(filepath.Separator) + this.name
        opts.ValueDir = opts.Dir
        opts.Logger = &logger{}
        opts.Truncate = true

        db, err := badger.Open(opts)
        if err != nil {
            return err
        }
        this.db = db
    }

    return nil
}

func (this *databaseImpl) Set(key []byte, value []byte) error {
    if err := this.db.Update(func(txn *badger.Txn) error { return txn.Set(key, value) }); err != nil {
        return err
    }

    return nil
}

func (this *databaseImpl) Contains(key []byte) (bool, error) {
    if err := this.db.View(func(txn *badger.Txn) error {
        _, err := txn.Get(key)
        if err != nil {
            return err
        }

        return nil
    }); err == ErrKeyNotFound {
        return false, nil
    } else {
        return err == nil, err
    }
}

func (this *databaseImpl) Get(key []byte) ([]byte, error) {
    var result []byte = nil
    var err error = nil

    err = this.db.View(func(txn *badger.Txn) error {
        item, err := txn.Get(key)
        if err != nil {
            return err
        }

        return item.Value(func(val []byte) error {
            result = append([]byte{}, val...)

            return nil
        })
    })

    return result, err
}

func (this *databaseImpl) Close() error {
    this.openLock.Lock()
    defer this.openLock.Unlock()

    if this.db != nil {
        err := this.db.Close()

        this.db = nil

        if err != nil {
            return err
        }
    }

    return nil
}