package loadcontroller

import (
	"context"
	"fmt"
	"sync"
)

// Prm groups the required parameters of the Controller's constructor.
//
// All values must comply with the requirements imposed on them.
// Passing incorrect parameter values will result in constructor
// failure (error or panic depending on the implementation).
type Prm struct {
	// Iterator over the used space values of the containers
	// collected by the node locally.
	LocalMetrics IteratorProvider

	// Place of recording the local values of
	// the used space of containers.
	LocalAnnouncementTarget WriterProvider

	// Iterator over the summarized used space scores
	// from the various network participants.
	AnnouncementAccumulator IteratorProvider

	// Place of recording the final estimates of
	// the used space of containers.
	ResultReceiver WriterProvider
}

// Controller represents main handler for starting
// and interrupting container volume estimation.
//
// It binds the interfaces of the local value stores
// to the target storage points. Controller is abstracted
// from the internal storage device and the network location
// of the connecting components. At its core, it is a
// high-level start-stop trigger for calculations.
//
// For correct operation, the controller must be created
// using the constructor (New) based on the required parameters
// and optional components. After successful creation,
// the constructor is immediately ready to work through
// API of external control of calculations and data transfer.
type Controller struct {
	prm Prm

	opts *options

	announceMtx  sync.Mutex
	mAnnounceCtx map[uint64]context.CancelFunc

	reportMtx  sync.Mutex
	mReportCtx map[uint64]context.CancelFunc
}

const invalidPrmValFmt = "invalid parameter %s (%T):%v"

func panicOnPrmValue(n string, v any) {
	panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
}

// New creates a new instance of the Controller.
//
// Panics if at least one value of the parameters is invalid.
//
// The created Controller does not require additional
// initialization and is completely ready for work.
func New(prm Prm, opts ...Option) *Controller {
	switch {
	case prm.LocalMetrics == nil:
		panicOnPrmValue("LocalMetrics", prm.LocalMetrics)
	case prm.AnnouncementAccumulator == nil:
		panicOnPrmValue("AnnouncementAccumulator", prm.AnnouncementAccumulator)
	case prm.LocalAnnouncementTarget == nil:
		panicOnPrmValue("LocalAnnouncementTarget", prm.LocalAnnouncementTarget)
	case prm.ResultReceiver == nil:
		panicOnPrmValue("ResultReceiver", prm.ResultReceiver)
	}

	o := defaultOpts()

	for _, opt := range opts {
		opt(o)
	}

	return &Controller{
		prm:          prm,
		opts:         o,
		mAnnounceCtx: make(map[uint64]context.CancelFunc),
		mReportCtx:   make(map[uint64]context.CancelFunc),
	}
}
