package process

import (
	
	

	
)

// Parallel returns a Runnable instance that starts and runs processes in
// parallel. If no processes are given, it returns Nop instance.
//
// The resulting Runnable calls callback after all process dependencies are
// successfully started. If any dependecy fails to start, processes that have
// already started are gracefully stopped. If any dependency fails before the
// main callback returns, the context passed to callback is canceled and all
// processes are gracefully stopped (unless the parent context has expired).
//
// The callbacks of dependencies return after the callback of the resulting
// dependent process. Run returns callback error if it is not nil, otherwise it
// returns combined errors from dependencies.
func ( ...Runnable) Runnable {
	switch len() {
	case 0:
		return Nop()
	case 1:
		return [0]
	}
	return &groupRunnable{
		deps: ,
		exec: task.ParallelExecutor(),
	}
}

// Sequential returns a Runnable instance with the same guarantees as the
// Parallel function, but starts and stops processes in sequential order.
func ( ...Runnable) Runnable {
	switch len() {
	case 0:
		return Nop()
	case 1:
		return [0]
	}
	return &groupRunnable{
		deps: ,
		exec: task.SequentialExecutor(),
	}
}

type groupRunnable struct {
	deps []Runnable
	exec task.Executor
}

func ( *groupRunnable) ( context.Context,  Callback) error {
	var  sync.Once
	var  sync.WaitGroup
	.Add(1)

	// fgctx is passed to callback and cancel is used from child
	// process below to cancel callback invocation after startup.
	,  := context.WithCancel()
	defer ()

	 := func( context.Context,  Callback) error {
		 := ()

		// Propagate process shutdown to main callback and wait
		// for it to return before exiting.
		()
		.Wait()

		return 
	}

	 := len(.deps)
	 := make([]*Process, )
	 := make([]task.Task, 2*)
	 := [0* : 1*]
	 := [1* : 2*]
	for ,  := range .deps {
		 := NewProcess(, Chain(, RunnableFunc()))
		[] = 
		[] = func( context.Context) error {
			 := .Start()
			if  == nil {
				return nil
			}

			// If startup failed, we do not invoke callback.
			// Unblock callbacks for process dependencies
			// that have already started.
			.Do(.Done)

			return 
		}
		[] = func( context.Context) error {
			// We get either ErrProcessInvalidState or p.Err
			// from Stop so it is safe to ignore error here.
			_ = .Stop()
			return .Err()
		}
	}

	 := .exec.Execute(, task.CancelOnError(), ...)

	var  error
	if  == nil {
		 = ()

		// Main callback has returned, unblock callbacks for
		// dependencies.
		.Done()
	}

	 := .exec.Execute(, task.NeverCancel(), ...)

	if  != nil {
		return 
	}

	return 
}