package future

import (
	
	
	
)

type CompositeFutureResolver[ any] func(index int, value , err error)

type compositeResolution[ any] struct {
	index int
	value 
}

type compositeErrorResolution struct {
	index int
	err   error
}

func ( *compositeErrorResolution) () string {
	return .err.Error()
}

type waitListener struct {
	count int
	ch    chan struct{}
}

type CompositeFuture[ any] struct {
	ctx         context.Context
	cancel      context.CancelCauseFunc
	resolutions []compositeResolution[]
	mutex       sync.Mutex
	listeners   []waitListener
}

func [ any]( context.Context) (*CompositeFuture[], CompositeFutureResolver[]) {
	,  := context.WithCancelCause()

	 := &CompositeFuture[]{
		ctx:         ,
		cancel:      ,
		resolutions: make([]compositeResolution[], 0),
	}

	return , .resolve
}

func ( *CompositeFuture[]) ( int) <-chan struct{} {
	.mutex.Lock()
	defer .mutex.Unlock()

	 := make(chan struct{})

	 := context.Cause(.ctx)

	// Return immediately if the context is already canceled or the count is already reached
	if len(.resolutions) >=  ||  != nil {
		close()
		return 
	}

	// Register a listener
	.listeners = append(.listeners, waitListener{
		count: ,
		ch:    ,
	})

	return 
}

func ( *CompositeFuture[]) () context.Context {
	return .ctx
}

func ( *CompositeFuture[]) ( error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	// Cancel the context
	.cancel()

	// Notify listeners
	.notifyListeners()
}

func ( *CompositeFuture[]) ( int) ([], error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	,  := .getResult()
	if  != nil ||  != nil {
		return , 
	}

	// Register a listener
	 := make(chan struct{})
	.listeners = append(.listeners, waitListener{
		count: ,
		ch:    ,
	})

	.mutex.Unlock()

	// Wait for the listener to be notified or the context to be canceled
	select {
	case <-:
	case <-.ctx.Done():
	}

	.mutex.Lock()

	return .getResult()
}

func ( *CompositeFuture[]) ( int,  ,  error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	if  < 0 {
		panic(fmt.Errorf("index must be greater than or equal to 0"))
	}

	// Cancel the context if an error occurred
	if  != nil {
		.cancel(&compositeErrorResolution{
			index: ,
			err:   ,
		})
	} else if context.Cause(.ctx) == nil {
		// Save the resolution
		.resolutions = append(.resolutions, compositeResolution[]{
			index: ,
			value: ,
		})
	}

	// Notify listeners
	.notifyListeners()
}

func ( *CompositeFuture[]) ( int) ( [],  error) {

	 := context.Cause(.ctx)

	// If we have enough results, return them
	if  != nil || len(.resolutions) >=  {
		// Get sorted resolution values
		 = make([], )
		for ,  := range .resolutions {
			if .index <  {
				[.index] = .value
			}
		}
	}

	if  == nil {
		return
	}

	if ,  := .(*compositeErrorResolution);  {
		// Unwrap the error resolution
		 = .err
	} else if len(.resolutions) <  {
		// If the context is canceled and we have collected enough results, return nil error
		// because we assume that context cancellation happened after the last resolution.
		 = 
	}

	return
}

func ( *CompositeFuture[]) () {

	 := context.Cause(.ctx)

	// Notify listeners
	for  := 0;  < len(.listeners); ++ {
		 := .listeners[]

		if  != nil || .count <= len(.resolutions) {
			close(.ch)
			.listeners = append(.listeners[:], .listeners[+1:]...)
			--
		}
	}
}