package steps

import (
	"context"
	"fmt"

	"github.com/pingcap/log"
	"golang.org/x/sync/errgroup"
)

// Step represents a test step.
type Step interface {
	fmt.Stringer
	// Execute will perform the real test step.
	Execute(ctx context.Context) error
}

// Serial will execute a bundle of steps in serialized way.
type Serial struct {
	inner []Step
}

func NewSerial(steps ...Step) *Serial {
	return &Serial{inner: steps}
}

// Execute executes all the test steps in serialized way.
func (s *Serial) Execute(ctx context.Context) error {
	for _, step := range s.inner {
		log.Info(step.String())
		err := step.Execute(ctx)
		if err != nil {
			return err
		}
	}

	return nil
}

// String merge all explanations of steps.
func (s *Serial) String() string {
	explain := ""
	for _, step := range s.inner {
		// TODO: we need a better fmt.
		explain += fmt.Sprintf("\n %s", step.String())
	}

	return explain
}

// Parallel will execute a bundle of steps in parallelism way.
type Parallel struct {
	inner []Step
}

func NewParallel(steps ...Step) *Parallel {
    return &Parallel{inner: steps}
}

// Execute executes all the test steps in parallel.
func (p *Parallel) Execute(ctx context.Context) error {
	// FIXME: Maybe we should collect all the errs.

	g, ctx := errgroup.WithContext(ctx)

	for _, step := range p.inner {
		step := step
		g.Go(func() error {
			return step.Execute(ctx)
		})
	}

	return g.Wait()
}

// String merge all explanations of steps.
func (p *Parallel) String() string {
	explain := ""
	for _, step := range p.inner {
		// TODO: we need a better fmt.
		explain += fmt.Sprintf("\n %s", step.String())
	}

	return explain
}
