package store

import (
	"reflect"
	"sync"

	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"

	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

// newGenericStore returns a genericStore.
func newGenericStore() *genericStore {
	return &genericStore{Store: make(map[uuid.UUID]store.Storable), storeLock: sync.RWMutex{}}
}

type genericStore struct {
	Store     map[uuid.UUID]store.Storable
	storeLock sync.RWMutex
}

// Exists takes a Storable's UUID and checks its existence in the store.
func (s *genericStore) Exists(id uuid.UUID) bool {
	s.storeLock.RLock()
	defer s.storeLock.RUnlock()
	_, ok := s.Store[id]
	return ok
}

// Add adds a Storable to the Store.
func (s *genericStore) Add(item store.Storable) error {
	if s.Exists(item.ID()) {
		return &customerrs.AlreadyExistsError{Item: item}
	}
	s.storeLock.Lock()
	s.Store[item.ID()] = item
	s.storeLock.Unlock()
	log.WithFields(log.Fields{
		"type": reflect.TypeOf(item),
		"uuid": item.ID(),
	}).Debug("storable was added")
	return nil
}

// Get takes a Storable's UUID and returns the Storable. If the requested
// Storable does not exist an error is returned. Get is only type safe for
// this Storable interface. For type safe get operations on specialised stores
// use GetDevice, GetPND, GetSBI, or GetChange respectively.
func (s *genericStore) Get(id uuid.UUID) (store.Storable, error) {
	if !s.Exists(id) {
		return nil, &customerrs.CouldNotFindError{ID: id}
	}
	log.WithFields(log.Fields{
		"uuid": id,
	}).Debug("storable was accessed")
	s.storeLock.RLock()
	defer s.storeLock.RUnlock()
	return s.Store[id], nil
}

// Delete takes a Storable's UUID and deletes it. If the specified UUID does not
// exist in the Store an error is returned.
func (s *genericStore) Delete(id uuid.UUID) error {
	if !s.Exists(id) {
		return &customerrs.CouldNotFindError{ID: id}
	}
	s.storeLock.Lock()
	delete(s.Store, id)
	s.storeLock.Unlock()
	log.WithFields(log.Fields{
		"uuid": id,
	}).Debug("storable was deleted")
	return nil
}

// UUIDs returns all UUIDs in the store.
func (s *genericStore) UUIDs() []uuid.UUID {
	s.storeLock.RLock()
	defer s.storeLock.RUnlock()
	keys := make([]uuid.UUID, len(s.Store))
	i := 0
	for k := range s.Store {
		keys[i] = k
		i++
	}
	return keys
}
