package libnetwork

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/docker/libnetwork/datastore"
)

type endpointCnt struct {
	n        *network
	Count    uint64
	dbIndex  uint64
	dbExists bool
	sync.Mutex
}

const epCntKeyPrefix = "endpoint_count"

func (ec *endpointCnt) Key() []string {
	ec.Lock()
	defer ec.Unlock()

	return []string{epCntKeyPrefix, ec.n.id}
}

func (ec *endpointCnt) KeyPrefix() []string {
	ec.Lock()
	defer ec.Unlock()

	return []string{epCntKeyPrefix, ec.n.id}
}

func (ec *endpointCnt) Value() []byte {
	ec.Lock()
	defer ec.Unlock()

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

func (ec *endpointCnt) SetValue(value []byte) error {
	ec.Lock()
	defer ec.Unlock()

	return json.Unmarshal(value, &ec)
}

func (ec *endpointCnt) Index() uint64 {
	ec.Lock()
	defer ec.Unlock()
	return ec.dbIndex
}

func (ec *endpointCnt) SetIndex(index uint64) {
	ec.Lock()
	ec.dbIndex = index
	ec.dbExists = true
	ec.Unlock()
}

func (ec *endpointCnt) Exists() bool {
	ec.Lock()
	defer ec.Unlock()
	return ec.dbExists
}

func (ec *endpointCnt) Skip() bool {
	ec.Lock()
	defer ec.Unlock()
	return !ec.n.persist
}

func (ec *endpointCnt) New() datastore.KVObject {
	ec.Lock()
	defer ec.Unlock()

	return &endpointCnt{
		n: ec.n,
	}
}

func (ec *endpointCnt) CopyTo(o datastore.KVObject) error {
	ec.Lock()
	defer ec.Unlock()

	dstEc := o.(*endpointCnt)
	dstEc.n = ec.n
	dstEc.Count = ec.Count
	dstEc.dbExists = ec.dbExists
	dstEc.dbIndex = ec.dbIndex

	return nil
}

func (ec *endpointCnt) DataScope() string {
	return ec.n.DataScope()
}

func (ec *endpointCnt) EndpointCnt() uint64 {
	ec.Lock()
	defer ec.Unlock()

	return ec.Count
}

func (ec *endpointCnt) updateStore() error {
	store := ec.n.getController().getStore(ec.DataScope())
	if store == nil {
		return fmt.Errorf("store not found for scope %s on endpoint count update", ec.DataScope())
	}
	// make a copy of count and n to avoid being overwritten by store.GetObject
	count := ec.EndpointCnt()
	n := ec.n
	for {
		if err := ec.n.getController().updateToStore(ec); err == nil || err != datastore.ErrKeyModified {
			return err
		}
		if err := store.GetObject(datastore.Key(ec.Key()...), ec); err != nil {
			return fmt.Errorf("could not update the kvobject to latest on endpoint count update: %v", err)
		}
		ec.Lock()
		ec.Count = count
		ec.n = n
		ec.Unlock()
	}
}

func (ec *endpointCnt) setCnt(cnt uint64) error {
	ec.Lock()
	ec.Count = cnt
	ec.Unlock()
	return ec.updateStore()
}

func (ec *endpointCnt) atomicIncDecEpCnt(inc bool) error {
	store := ec.n.getController().getStore(ec.DataScope())
	if store == nil {
		return fmt.Errorf("store not found for scope %s", ec.DataScope())
	}

	tmp := &endpointCnt{n: ec.n}
	if err := store.GetObject(datastore.Key(ec.Key()...), tmp); err != nil {
		return err
	}
retry:
	ec.Lock()
	if inc {
		ec.Count++
	} else {
		if ec.Count > 0 {
			ec.Count--
		}
	}
	ec.Unlock()

	if err := ec.n.getController().updateToStore(ec); err != nil {
		if err == datastore.ErrKeyModified {
			if err := store.GetObject(datastore.Key(ec.Key()...), ec); err != nil {
				return fmt.Errorf("could not update the kvobject to latest when trying to atomic add endpoint count: %v", err)
			}

			goto retry
		}

		return err
	}

	return nil
}

func (ec *endpointCnt) IncEndpointCnt() error {
	return ec.atomicIncDecEpCnt(true)
}

func (ec *endpointCnt) DecEndpointCnt() error {
	return ec.atomicIncDecEpCnt(false)
}
