package ring

import (
	"context"
	"errors"
	"sync"
)

// Package level named errors.
var (
	ErrAdaptiveChanClosed           = errors.New("closed AdaptiveChan")
	ErrAdaptiveChanControllerClosed = errors.New("closed AdaptiveChanController")
)

// AdaptiveChan provides a queueing system based on a send-only, a receive-only,
// and an internal ring buffer queue backed by a *Ring. It also provides an
// AdaptiveChanController to provide stats and some control on the internal
// *Ring. Termination is controlled by closing the returned send-only channel.
// After doing so, the receive-only channel will have the chance to receive all
// the items still in the queue and will be immediately closed afterwards. Once
// both channels are closed, the AdaptiveChanController will no longer be usable
// and will only return ErrAdaptiveChanClosed for all its methods. Leaving the
// growth and shrinkage of the internal *Ring apart, which can be controlled
// with the AdaptiveChanController, the implementation is allocation free.
//
// The implementation explicitly returns two channels and a struct, instead of
// just one struct that has the channels, to make a clear statement about the
// intended usage pattern:
//
//  1. Create an adaptive channel.
//  2. Provide the send-only channel to your producer(s). They are responsible
//     for closing this channel when they're done. If more than one goroutine
//     will have access to this channel, then it's the producer's responsibility
//     to coordinate the channel close operation.
//  3. Provide the receive-only channel to your consumer(s), and let them
//     receive from it with the two return value syntax for channels in order to
//     check for termination from the sending side.
//  4. Use the AdaptiveChanController to control the internal buffer behaviour
//     and to monitor stats. This should typically be held by the creator of the
//     adaptive channel. Refrain from holding a reference to the send-only
//     channel to force termination of the producing side. Instead, provide a
//     side mechanism to communicate the intention of terminating the sending
//     side, e.g. providing your producer(s) with a context as well as the
//     send-only channel. An adaptive channel is meant as a queueing system, not
//     as a coordination mechanism for producer(s), consumer(s) and
//     controller(s).
//
// This pattern is designed to maximize decoupling while providing insights and
// granular control on memory usage. While the controller is not meant to make
// any direct changes to the queued data, the Clear method provides the
// opportunity to discard all queued items as an administrative measure. This
// doesn't terminate the queue, though, i.e. it doesn't close the send-only
// channel.
func AdaptiveChan[T any]() (send chan<- T, recv <-chan T, ctrl *AdaptiveChanController) {
	internalSend := make(chan T)
	internalRecv := make(chan T)
	statsChan := make(chan AdaptiveChanStats)
	cmdChan := make(chan acCmd)
	ctrl = &AdaptiveChanController{
		statsChan: statsChan,
		cmdChan:   cmdChan,
	}

	go func() {
		defer close(internalRecv)
		defer close(statsChan)

		var q Ring[T]
		var stats AdaptiveChanStats

		// the loop condition is that we either have items to dequeue or that we
		// have the possibility to receive new items to be queued
		for q.Len() > 0 || internalSend != nil {
			// NOTE: the overhead of writing stats in each iteration is
			// negligible. I tried a two phase stats writing with a chan
			// struct{} to get notified that the controller wanted stats, then
			// updating the stats and finally writing to statsChan. There was no
			// observable difference for just enqueueing and dequeueing after
			// running the benchmarks several times, and reading stats got worse
			// by ~22%
			q.WriteStats(&stats.RingStats)

			// if we don't have anything in the queue, then make the dequeueing
			// branch of the select block indefinitely by providing a nil
			// channel, leaving only the queueing branch available
			dequeueChan := internalRecv
			if q.Len() == 0 {
				dequeueChan = nil
			}

			select {
			case v, ok := <-internalSend: // blocks until something is queued
				if !ok {
					// in was closed, so if we leave it like that the next
					// iteration will keep receiving zero values with ok=false
					// without any blocking. So we set in to nil, so that
					// the next iteration the select will block indefinitely on
					// this branch of the select and leave only the dequeing
					// branch active until all items have been dequeued
					internalSend = nil
				} else {
					q.Enqueue(v)
				}

			case dequeueChan <- q.Peek(): // blocks if nothing to dequeue
				// we don't want to call Dequeue in the `case` above since that
				// would consume the item and it would be lost if the queueing
				// branch was selected, so instead we Peek in the `case` and we
				// do the actual dequeueing here once we know this branch was
				// selected
				q.Dequeue()

			case statsChan <- stats:
				// stats reading
				stats.StatsRead++

			case cmd, ok := <-cmdChan:
				if !ok {
					// AdaptiveChanController was closed. Set cmdChan to nil so
					// this branch blocks in the next iteration
					cmdChan = nil
					continue
				}

				// execute a command on the internal *Ring
				switch cmd.acCmdType {
				case acCmdMin:
					q.Min = cmd.intValue
					stats.Min = cmd.intValue
				case acCmdMax:
					q.Max = cmd.intValue
					stats.Max = cmd.intValue
				case acCmdGrow:
					q.Grow(cmd.intValue)
				case acCmdShrink:
					q.Shrink(cmd.intValue)
				case acCmdClear:
					q.Clear()
				}
			}
		}
	}()

	return internalSend, internalRecv, ctrl
}

type acCmdType uint8

const (
	acCmdMin = iota
	acCmdMax
	acCmdClear
	acCmdGrow
	acCmdShrink
)

type acCmd struct {
	acCmdType
	intValue int
}

// AdaptiveChanController provides access to an AdaptiveChan's internal *Ring.
type AdaptiveChanController struct {
	statsChan <-chan AdaptiveChanStats
	cmdChan   chan<- acCmd
	cmdChanMu sync.Mutex
}

// Close releases resources associated with this controller. After calling this
// method, all other methods will return ErrAdaptiveChanControllerClosed. It is
// idempotent. This doesn't affect the queue itself, but rather prevents further
// administrative tasks to be performed through the AdaptiveChanController.
func (r *AdaptiveChanController) Close() {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()

	if r.cmdChan != nil {
		close(r.cmdChan)
		r.cmdChan = nil
	}
}

// Min sets the value of Min in the internal *Ring.
func (r *AdaptiveChanController) Min(ctx context.Context, n int) error {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()

	return sendOrErr(ctx, r.cmdChan, acCmd{
		acCmdType: acCmdMin,
		intValue:  n,
	})
}

// Max sets the value of Max in the internal *Ring.
func (r *AdaptiveChanController) Max(ctx context.Context, n int) error {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()

	return sendOrErr(ctx, r.cmdChan, acCmd{
		acCmdType: acCmdMax,
		intValue:  n,
	})
}

// Grow calls Grow on the internal *Ring.
func (r *AdaptiveChanController) Grow(ctx context.Context, n int) error {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()

	return sendOrErr(ctx, r.cmdChan, acCmd{
		acCmdType: acCmdGrow,
		intValue:  n,
	})
}

// Shrink calls Shrink on the internal *Ring.
func (r *AdaptiveChanController) Shrink(ctx context.Context, n int) error {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()

	return sendOrErr(ctx, r.cmdChan, acCmd{
		acCmdType: acCmdShrink,
		intValue:  n,
	})
}

// Clear calls Clear on the internal *Ring.
func (r *AdaptiveChanController) Clear(ctx context.Context) error {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()

	return sendOrErr(ctx, r.cmdChan, acCmd{
		acCmdType: acCmdClear,
	})
}

// WriteStats writes a snapshot of general stats about the associated
// AdaptiveChan to the given *AdaptiveChanStats.
func (r *AdaptiveChanController) WriteStats(ctx context.Context, s *AdaptiveChanStats) error {
	r.cmdChanMu.Lock()
	defer r.cmdChanMu.Unlock()
	if r.cmdChan == nil {
		return ErrAdaptiveChanControllerClosed
	}

	return recvOrErr(ctx, r.statsChan, s)
}

// AdaptiveChanStats is a snapshot of general stats for an AdaptiveChan.
type AdaptiveChanStats struct {
	RingStats
	// Min is the value of Min in the internal *Ring.
	Min int
	// Max value of Max in the internal *Ring.
	Max int
	// StatsRead is the total number of stats read before this snapshot. If it
	// is zero, it means this snapshot is the first reading.
	StatsRead uint64
}

func sendOrErr[T any](ctx context.Context, c chan<- T, v T) error {
	if c == nil {
		return ErrAdaptiveChanControllerClosed
	}

	select {
	case c <- v:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

func recvOrErr[T any](ctx context.Context, c <-chan T, tptr *T) error {
	select {
	case t, ok := <-c:
		if !ok {
			return ErrAdaptiveChanClosed
		}
		*tptr = t
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}
