package framework

import (
	"fmt"
	"io"
	"sync"
	"time"
)

const (
	EventDataUnknown EventDataTypeHint = ""
	EventDataInt     EventDataTypeHint = "int"
	EventDataBool    EventDataTypeHint = "bool"
	EventDataStr     EventDataTypeHint = "string"
	EventDataStrList EventDataTypeHint = "[]string"
	EventDataError   EventDataTypeHint = "error"

	EventAny   EventName = "any"
	EventDebug EventName = "debug"

	// TODO: use class name, not interface name

	EventInputV1GetRangeMetrics EventName = "InputV1.GetRangeMetrics"
	EventInputV1GetTopology     EventName = "InputV1.GetTopology"

	EventOutputV1SetNewClusterTopology EventName = "OutputV1.SetNewClusterTopology"

	EventClusterScalerV1PullNewClusterTopologyBegin EventName = "ClusterScalerV1.PullNewClusterTopology.Begin"
	EventClusterScalerV1PullNewClusterTopologyEnd   EventName = "ClusterScalerV1.PullNewClusterTopology.End"

	EventInputProviderManagerRegisterCreatorOk         EventName = "InputProviderManager.RegisterCreator.Ok"
	EventInputProviderManagerRegisterCreatorDuplicated EventName = "InputProviderManager.RegisterCreator.Duplicated"
	EventInputProviderManagerCreatorNotFound           EventName = "InputProviderManager.CreatorNotFound"
	EventInputProviderManagerCreatedInput              EventName = "InputProviderManager.CreatedInput"

	EventOutputManagerRegisterCreatorOk         EventName = "OutputManager.RegisterCreator.Ok"
	EventOutputManagerRegisterCreatorDuplicated EventName = "OutputManager.RegisterCreator.Duplicated"
	EventOutputManagerCreatorNotFound           EventName = "OutputManager.CreatorNotFound"
	EventOutputManagerCreatedOutput             EventName = "OutputManager.CreatedOutput"

	EventClusterScalerManagerRegisterCreatorOk         EventName = "ClusterScalerManager.RegisterCreator.Ok"
	EventClusterScalerManagerRegisterCreatorDuplicated EventName = "ClusterScalerManager.RegisterCreator.Duplicated"
	EventClusterScalerManagerCreatorNotFound           EventName = "ClusterScalerManager.CreatorNotFound"
	EventClusterScalerManagerCreatedClusterScaler      EventName = "ClusterScalerManager.CreatedClusterScaler"

	EventScalingTaskExecutorManagerRegisterCreatorOk         EventName = "ScalingTaskExecutorManager.RegisterCreator.Ok"
	EventScalingTaskExecutorManagerRegisterCreatorDuplicated EventName = "ScalingTaskExecutorManager.RegisterCreator.Duplicated"
	EventScalingTaskExecutorManagerCreatorNotFound           EventName = "ScalingTaskExecutorManager.CreatorNotFound"
	EventScalingTaskExecutorManagerCreatedExecutor           EventName = "ScalingTaskExecutorManager.CreatedExecutor"

	EventScalerInterfaceV1ExecutorSyncLoopError EventName = "ScalerInterfaceV1Executor.SyncLoopError"

	EventClusterScalerExecutorRunScalingTaskExecutorOk                          EventName = "ClusterScalerExecutor.RunScalingTaskExecutor.Ok"
	EventClusterScalerExecutorRunScalingTaskExecutorError                       EventName = "ClusterScalerExecutor.RunScalingTaskExecutor.Error"
	EventClusterScalerPhasedComponentScalerV1PullNewClusterTopologyBegin        EventName = "ClusterScalerPhasedComponentScalerV1.PullNewClusterTopology.Begin"
	EventClusterScalerPhasedComponentScalerV1PullNewClusterTopologyModification EventName = "ClusterScalerPhasedComponentScalerV1.PullNewClusterTopology.Modification"
	EventClusterScalerPhasedComponentScalerV1PullNewClusterTopologyEnd          EventName = "ClusterScalerPhasedComponentScalerV1.PullNewClusterTopology.End"

	EventClusterScalerPhasedComponentScalerV2PullClusterDecisionBegin        EventName = "ClusterScalerPhasedComponentScalerV2.PullClusterDecision.Begin"
	EventClusterScalerPhasedComponentScalerV2PullClusterDecisionModification EventName = "ClusterScalerPhasedComponentScalerV2.PullClusterDecision.Modification"
	EventClusterScalerPhasedComponentScalerV2PullClusterDecisionEnd          EventName = "ClusterScalerPhasedComponentScalerV2.PullClusterDecision.End"

	EventScalerInterfaceV2ExecutorSyncLoopError EventName = "ScalerInterfaceV2Executor.SyncLoopError"

	EventPhasedComponentScalerManagerRegisterCreatorDuplicated EventName = "PhasedComponentScalerManager.RegisterCreator.Duplicated"
	EventPhasedComponentScalerManagerRegisterCreatorOk         EventName = "PhasedComponentScalerManager.RegisterCreator.Ok"
	EventPhasedComponentScalerManagerCreatorNotFound           EventName = "PhasedComponentScalerManager.CreatorNotFound"
	EventPhasedComponentScalerManagerCreatedScaler             EventName = "PhasedComponentScalerManager.CreatedScaler"

	EventComponentScalerV1PullNewComponentModification EventName = "ComponentScalerV1.PullNewComponentModification"

	EventInputProviderConfigManagerAddItem EventName = "InputProviderConfigManager.AddItem"
	EventOutputConfigManagerAddItem        EventName = "OutputConfigManager.AddItem"
	EventScalerConfigManagerAddItem        EventName = "ScalerConfigManager.AddItem"
	EventClusterScalerConfigManagerAddItem EventName = "ClusterScalerConfigManager.AddItem"
)

type EventName string
type EventDataTypeHint string

type EventData struct {
	DataName string
	TypeHint EventDataTypeHint
	Object   Cloneable
}

func (ed EventData) Clone() EventData {
	var objectClone Cloneable
	if ed.Object != nil {
		objectClone = ed.Object.CloneableClone()
	}
	return EventData{
		DataName: ed.DataName,
		TypeHint: ed.TypeHint,
		Object:   objectClone,
	}
}

func EventDataWithType(typeHint EventDataTypeHint, object Cloneable) EventData {
	return EventData{
		TypeHint: typeHint,
		Object:   object,
	}
}

func EventDataWithName(dataName string, object Cloneable) EventData {
	return EventData{
		DataName: dataName,
		Object:   object,
	}
}

func EventDataWithNameType(dataName string, typeHint EventDataTypeHint, object Cloneable) EventData {
	return EventData{
		DataName: dataName,
		TypeHint: typeHint,
		Object:   object,
	}
}

// (most of) events will be notified from main thread (aka, run thread), don't do heavy things in receiver
type EventReceiver interface {
	OnEvent(name EventName, data ...EventData)
}

type EventHub interface {
	RegisterReceiver(receiver EventReceiver)
}

type SyncEventHub struct {
	receivers     []EventReceiver
	asyncReceiver *AsyncEventHub
	locker        sync.Mutex
}

func NewSyncEventHub() *SyncEventHub {
	return &SyncEventHub{}
}

func (hub *SyncEventHub) GetAsyncHub() EventHub {
	hub.locker.Lock()
	defer hub.locker.Unlock()
	if hub.asyncReceiver == nil {
		hub.asyncReceiver = NewAsyncEventHub()
		// put async-receiver at the first place
		hub.receivers = append([]EventReceiver{hub.asyncReceiver}, hub.receivers...)
	}
	return hub.asyncReceiver
}

func (hub *SyncEventHub) RegisterReceiver(receiver EventReceiver) {
	hub.locker.Lock()
	defer hub.locker.Unlock()
	hub.receivers = append(hub.receivers, receiver)
}

func (hub *SyncEventHub) OnEvent(name EventName, data ...EventData) {
	hub.locker.Lock()
	defer hub.locker.Unlock()
	for _, receiver := range hub.receivers {
		receiver.OnEvent(name, data...)
	}
}

type AsyncEventHub struct {
	receivers []EventReceiver
	locker    sync.Mutex
}

func NewAsyncEventHub() *AsyncEventHub {
	return &AsyncEventHub{}
}

func (hub *AsyncEventHub) RegisterReceiver(receiver EventReceiver) {
	hub.locker.Lock()
	defer hub.locker.Unlock()
	hub.receivers = append(hub.receivers, receiver)
}

func (hub *AsyncEventHub) OnEvent(name EventName, data ...EventData) {
	hub.locker.Lock()
	defer hub.locker.Unlock()
	if len(hub.receivers) == 0 {
		return
	}
	// TODO: clone data to avoid race
	var dataClone []EventData
	for _, it := range data {
		dataClone = append(dataClone, it.Clone())
	}
	for _, receiver := range hub.receivers {
		receiver.OnEvent(name, dataClone...)
	}
}

type GeneralLogger struct {
	prefix     string
	w          io.Writer
	timeFormat string
}

func NewGeneralLogger(loggerName string, w io.Writer) *GeneralLogger {
	prefix := loggerName
	if prefix != "" {
		prefix = "[" + loggerName + "] "
	}
	return &GeneralLogger{
		prefix:     prefix,
		w:          w,
		timeFormat: time.DateTime,
	}
}

func (logger *GeneralLogger) OnEvent(name EventName, data ...EventData) {
	timeStr := time.Now().Format(logger.timeFormat)
	str := fmt.Sprintf("[%v] %s%v", timeStr, logger.prefix, name)
	if len(data) != 0 {
		str += ":"
	}
	for _, it := range data {
		str += " "
		if it.DataName != "" {
			str += fmt.Sprintf("'%v' ", it.DataName)
		}
		if it.TypeHint != EventDataUnknown && it.TypeHint != EventDataInt && it.TypeHint != EventDataStr && it.TypeHint != EventDataStrList && it.TypeHint != EventDataBool {
			str += fmt.Sprintf("(type %v) ", it.TypeHint)
		}
		str += fmt.Sprintf("= %v;", it.Object)
	}
	_, _ = fmt.Fprintf(logger.w, "%s\n", str)
}

type EventAllowFilter struct {
	origin    EventReceiver
	allowList map[EventName]bool
}

func NewEventAllowFilter(origin EventReceiver, names ...EventName) *EventAllowFilter {
	eaf := &EventAllowFilter{
		origin:    origin,
		allowList: make(map[EventName]bool),
	}
	for _, name := range names {
		eaf.allowList[name] = true
	}
	return eaf
}

func (eaf *EventAllowFilter) OnEvent(name EventName, data ...EventData) {
	if eaf.allowList[name] {
		eaf.origin.OnEvent(name, data...)
	}
}

type EventDenyFilter struct {
	origin   EventReceiver
	denyList map[EventName]bool
}

func NewEventDenyFilter(origin EventReceiver, names ...EventName) *EventDenyFilter {
	edf := &EventDenyFilter{
		origin:   origin,
		denyList: make(map[EventName]bool),
	}
	for _, name := range names {
		edf.denyList[name] = true
	}
	return edf
}

func (eaf *EventDenyFilter) OnEvent(name EventName, data ...EventData) {
	if eaf.denyList[name] {
		return
	}
	eaf.origin.OnEvent(name, data...)
}

type DragSlow struct {
	dur time.Duration
}

func NewDragSlow(dur time.Duration) *DragSlow {
	return &DragSlow{
		dur: dur,
	}
}

func (drag *DragSlow) OnEvent(name EventName, data ...EventData) {
	time.Sleep(drag.dur)
}

type EventsCounter struct {
	counts map[EventName]int
	locker sync.Mutex
}

func NewEventsCounter(mustHaveNames ...EventName) *EventsCounter {
	// for better dump
	counts := make(map[EventName]int)
	for _, name := range mustHaveNames {
		counts[name] = 0
	}
	return &EventsCounter{
		counts: counts,
	}
}

func (counter *EventsCounter) OnEvent(name EventName, data ...EventData) {
	counter.locker.Lock()
	defer counter.locker.Unlock()
	val := counter.counts[name]
	counter.counts[name] = val + 1
}

func (counter *EventsCounter) Dump(w io.Writer) {
	counter.locker.Lock()
	defer counter.locker.Unlock()
	for name, cnt := range counter.counts {
		_, _ = fmt.Fprintf(w, "%v: %v\n", name, cnt)
	}
}

func (counter *EventsCounter) GetCount(name EventName) int {
	counter.locker.Lock()
	defer counter.locker.Unlock()
	return counter.counts[name]
}
