// Copyright (c) 2016-2018 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
	"fmt"
	"sync"

	"context"

	"github.com/projectcalico/calico/libcalico-go/lib/backend/model"
)

// SyncStatus represents the overall state of the datastore.
// When the status changes, the Syncer calls OnStatusUpdated() on its callback.
type SyncStatus uint8

const (
	// WaitForDatastore means the Syncer is waiting to connect to the datastore.
	// (Or, it is waiting for the data in the datastore to be ready to use.)
	WaitForDatastore SyncStatus = iota
	// ResyncInProgress means the Syncer is resyncing with the datastore.
	// During the first resync, the Syncer sends updates for all keys that
	// exist in the datastore as well as any updates that occur
	// concurrently.
	ResyncInProgress
	// InSync means the Syncer has now sent all the existing keys in the
	// datastore and the user of the API has the full picture.
	InSync
)

func (s SyncStatus) String() string {
	switch s {
	case WaitForDatastore:
		return "wait-for-ready"
	case InSync:
		return "in-sync"
	case ResyncInProgress:
		return "resync"
	default:
		return fmt.Sprintf("Unknown<%v>", uint8(s))
	}
}

// Client is the interface to the backend datastore.  It makes heavy use of the
// KVPair struct, which contains a key and (optional) value drawn from the
// backend/model package along with opaque revision information that the
// datastore uses to enforce consistency.
type Client interface {
	// Create creates the object specified in the KVPair, which must not
	// already exist. On success, returns a KVPair for the object with
	// revision  information filled-in.
	Create(ctx context.Context, object *model.KVPair) (*model.KVPair, error)

	// Update modifies the existing object specified in the KVPair.
	// On success, returns a KVPair for the object with revision
	// information filled-in.  If the input KVPair has revision
	// information then the update only succeeds if the revision is still
	// current.
	Update(ctx context.Context, object *model.KVPair) (*model.KVPair, error)

	// Apply updates or creates the object specified in the KVPair.
	// On success, returns a KVPair for the object with revision
	// information filled-in.  Revision information is ignored on an Apply.
	Apply(ctx context.Context, object *model.KVPair) (*model.KVPair, error)

	// Delete removes the object specified by the key.  If the call
	// contains revision information, the delete only succeeds if the
	// revision is still current.
	//
	// Some keys are hierarchical, and Delete is a recursive operation.
	//
	// Any objects that were implicitly added by a Create operation should
	// also be removed when deleting the objects that implicitly created it.
	// For example, deleting the last WorkloadEndpoint in a Workload will
	// also remove the Workload.
	Delete(ctx context.Context, key model.Key, revision string) (*model.KVPair, error)

	// DeleteKVP removes the object specified by the KVPair.  If the KVPair
	// contains revision information, the delete only succeeds if the
	// revision is still current.
	//
	// Some keys are hierarchical, and Delete is a recursive operation.
	//
	// Any objects that were implicitly added by a Create operation should
	// also be removed when deleting the objects that implicitly created it.
	// For example, deleting the last WorkloadEndpoint in a Workload will
	// also remove the Workload.
	DeleteKVP(ctx context.Context, object *model.KVPair) (*model.KVPair, error)

	// Get returns the object identified by the given key as a KVPair with
	// revision information.
	Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error)

	// List returns a slice of KVPairs matching the input list options.
	// list should be passed one of the model.<Type>ListOptions structs.
	// Non-zero fields in the struct are used as filters.
	List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error)

	// Watch returns a WatchInterface used for watching a resources matching the
	// input list options.
	Watch(ctx context.Context, list model.ListInterface, revision string) (WatchInterface, error)

	// EnsureInitialized ensures that the backend is initialized
	// any ready to be used.
	EnsureInitialized() error

	// Clean removes Calico data from the backend datastore.  Used for test purposes.
	Clean() error

	// Close the client.
	//Close()
}

type Syncer interface {
	// Starts the Syncer.  May start a background goroutine.
	Start()
	// Stops the Syncer.  Stops all background goroutines. Any cached updates that the syncer knows about
	// are emitted as delete events.
	Stop()
}

type SyncerCallbacks interface {
	// OnStatusUpdated is called when the status of the sync status of the
	// datastore changes.
	OnStatusUpdated(status SyncStatus)

	// OnUpdates is called when the Syncer has one or more updates to report.
	// Updates consist of typed key-value pairs.  The keys are drawn from the
	// backend.model package.  The values are either nil, to indicate a
	// deletion (or failure to parse a value), or a pointer to a value of
	// the associated value type.
	//
	// When a recursive delete is made, deleting many leaf keys, the Syncer
	// generates deletion updates for all the leaf keys.
	OnUpdates(updates []Update)
}

// SyncerParseFailCallbacks is an optional interface that can be implemented
// by a Syncer callback.  Datastores that support it can report a failure to
// parse a particular key or value.
type SyncerParseFailCallbacks interface {
	ParseFailed(rawKey string, rawValue string)
}

// Update from the Syncer.  A KV pair plus extra metadata.
type Update struct {
	model.KVPair
	UpdateType UpdateType
}

type UpdateType uint8

const (
	UpdateTypeKVUnknown UpdateType = iota
	UpdateTypeKVNew
	UpdateTypeKVUpdated
	UpdateTypeKVDeleted
)

// Interface can be implemented by anything that knows how to watch and report changes.
type WatchInterface interface {
	// Stops watching. Will close the channel returned by ResultChan(). Releases
	// any resources used by the watch.
	Stop()

	// Returns a chan which will receive all the events.  This channel is closed when:
	// -  Stop() is called, or
	// In both cases the watcher will be cleaned up, and the client should stop receiving
	// from this channel.
	ResultChan() <-chan WatchEvent

	// HasTerminated returns true if the watcher has terminated and released all
	// resources.  This is used for test purposes.
	HasTerminated() bool
}

// WatchEventType defines the possible types of events.
type WatchEventType string

const (
	WatchAdded    WatchEventType = "ADDED"
	WatchModified WatchEventType = "MODIFIED"
	WatchDeleted  WatchEventType = "DELETED"
	WatchError    WatchEventType = "ERROR"
)

// Event represents a single event to a watched resource.
type WatchEvent struct {
	Type WatchEventType

	// Old is:
	// * If Type is Added or Error: nil
	// * If Type is Modified or Deleted: the previous state of the object
	// New is:
	//  * If Type is Added or Modified: the new state of the object.
	//  * If Type is Deleted or Error: nil
	Old *model.KVPair
	New *model.KVPair

	// The error, if EventType is Error.
	Error error
}

// FakeWatcher is inspired by apimachinery (watch) FakeWatcher
type FakeWatcher struct {
	result  chan WatchEvent
	Stopped bool
	sync.Mutex
}

// NewFake constructs a FakeWatcher
func NewFake() *FakeWatcher {
	return &FakeWatcher{
		result: make(chan WatchEvent),
	}
}

// Stop implements WatchInterface
func (f *FakeWatcher) Stop() {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		close(f.result)
		f.Stopped = true
	}
}

// ResultChan implements WatchInterface
func (f *FakeWatcher) ResultChan() <-chan WatchEvent {
	return f.result
}

// HasTerminated implements WatchInterface
func (f *FakeWatcher) HasTerminated() bool {
	return false
}
