package ipam

import (
	"encoding/json"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/libnetwork/datastore"
	"github.com/docker/libnetwork/types"
)

// Key provides the Key to be used in KV Store
func (aSpace *addrSpace) Key() []string {
	aSpace.Lock()
	defer aSpace.Unlock()
	return []string{aSpace.id}
}

// KeyPrefix returns the immediate parent key that can be used for tree walk
func (aSpace *addrSpace) KeyPrefix() []string {
	aSpace.Lock()
	defer aSpace.Unlock()
	return []string{dsConfigKey}
}

// Value marshals the data to be stored in the KV store
func (aSpace *addrSpace) Value() []byte {
	b, err := json.Marshal(aSpace)
	if err != nil {
		log.Warnf("Failed to marshal ipam configured pools: %v", err)
		return nil
	}
	return b
}

// SetValue unmarshalls the data from the KV store.
func (aSpace *addrSpace) SetValue(value []byte) error {
	rc := &addrSpace{subnets: make(map[SubnetKey]*PoolData)}
	if err := json.Unmarshal(value, rc); err != nil {
		return err
	}
	aSpace.subnets = rc.subnets
	return nil
}

// Index returns the latest DB Index as seen by this object
func (aSpace *addrSpace) Index() uint64 {
	aSpace.Lock()
	defer aSpace.Unlock()
	return aSpace.dbIndex
}

// SetIndex method allows the datastore to store the latest DB Index into this object
func (aSpace *addrSpace) SetIndex(index uint64) {
	aSpace.Lock()
	aSpace.dbIndex = index
	aSpace.dbExists = true
	aSpace.Unlock()
}

// Exists method is true if this object has been stored in the DB.
func (aSpace *addrSpace) Exists() bool {
	aSpace.Lock()
	defer aSpace.Unlock()
	return aSpace.dbExists
}

// Skip provides a way for a KV Object to avoid persisting it in the KV Store
func (aSpace *addrSpace) Skip() bool {
	return false
}

func (a *Allocator) getStore(as string) datastore.DataStore {
	a.Lock()
	defer a.Unlock()

	if aSpace, ok := a.addrSpaces[as]; ok {
		return aSpace.ds
	}

	return nil
}

func (a *Allocator) getAddressSpaceFromStore(as string) (*addrSpace, error) {
	store := a.getStore(as)
	if store == nil {
		return nil, types.InternalErrorf("store for address space %s not found", as)
	}

	pc := &addrSpace{id: dsConfigKey + "/" + as, ds: store, alloc: a}
	if err := store.GetObject(datastore.Key(pc.Key()...), pc); err != nil {
		if err == datastore.ErrKeyNotFound {
			return nil, nil
		}

		return nil, types.InternalErrorf("could not get pools config from store: %v", err)
	}

	return pc, nil
}

func (a *Allocator) writeToStore(aSpace *addrSpace) error {
	store := aSpace.store()
	if store == nil {
		return types.InternalErrorf("invalid store while trying to write %s address space", aSpace.DataScope())
	}

	err := store.PutObjectAtomic(aSpace)
	if err == datastore.ErrKeyModified {
		return types.RetryErrorf("failed to perform atomic write (%v). retry might fix the error", err)
	}

	return err
}

func (a *Allocator) deleteFromStore(aSpace *addrSpace) error {
	store := aSpace.store()
	if store == nil {
		return types.InternalErrorf("invalid store while trying to delete %s address space", aSpace.DataScope())
	}

	return store.DeleteObjectAtomic(aSpace)
}

// DataScope method returns the storage scope of the datastore
func (aSpace *addrSpace) DataScope() string {
	aSpace.Lock()
	defer aSpace.Unlock()

	return aSpace.scope
}
