package nucleus

import (
	"encoding/json"

	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	"github.com/google/uuid"
)

// MemoryPndStore provides a in-memory implementation for a pnd store.
type MemoryPndStore struct {
	Store           map[uuid.UUID]networkdomain.LoadedPnd
	pendingChannels map[uuid.UUID]chan networkelement.Details
}

// NewMemoryPndStore returns a in-memory implementation for a pnd store.
func NewMemoryPndStore() networkdomain.PndStore {
	return &MemoryPndStore{
		Store:           make(map[uuid.UUID]networkdomain.LoadedPnd),
		pendingChannels: make(map[uuid.UUID]chan networkelement.Details),
	}
}

// Add adds a pnd to the store.
func (t *MemoryPndStore) Add(item networkdomain.NetworkDomain) error {
	var pnd networkdomain.LoadedPnd

	b, err := json.Marshal(item)
	if err != nil {
		return err
	}

	err = json.Unmarshal(b, &pnd)
	if err != nil {
		return err
	}

	_, ok := t.Store[uuid.MustParse(pnd.ID)]
	if ok {
		return nil
	}

	t.Store[item.ID()] = pnd

	return nil
}

// Delete deletes a pnd from the store.
func (t *MemoryPndStore) Delete(item networkdomain.NetworkDomain) error {
	delete(t.Store, item.ID())

	return nil
}

// Get provides a the query interface to find a stored pnd.
func (t *MemoryPndStore) Get(query store.Query) (networkdomain.LoadedPnd, error) {
	// First search for direct hit on UUID.
	item, ok := t.Store[query.ID]
	if !ok {
		return item, &customerrs.CouldNotFindError{ID: query.ID, Name: query.Name}
	}

	return item, nil
}

// GetAll returns all pnds currently on the store.
func (t *MemoryPndStore) GetAll() ([]networkdomain.LoadedPnd, error) {
	var allItems []networkdomain.LoadedPnd

	for _, item := range t.Store {
		allItems = append(allItems, item)
	}

	return allItems, nil
}

// PendingChannels holds channels used communicate with pending
// cSBI deployments.
func (t *MemoryPndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan networkelement.Details, error) {
	ch, ok := t.pendingChannels[id]
	if !ok {
		return nil, &customerrs.CouldNotFindError{ID: id}
	}
	return ch, nil
}

// AddPendingChannel adds a pending channel to the map.
func (t *MemoryPndStore) AddPendingChannel(id uuid.UUID, ch chan networkelement.Details) {
	t.pendingChannels[id] = ch
}

// RemovePendingChannel removes a pending channel from the map.
func (t *MemoryPndStore) RemovePendingChannel(id uuid.UUID) {
	delete(t.pendingChannels, id)
}
