package genny

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"github.com/markbates/safe"
)

type DeleteFn func()

type Step struct {
	as     *Generator
	before []*Generator
	after  []*Generator
	index  int
	moot   *sync.RWMutex
}

func (s *Step) Before(g *Generator) DeleteFn {
	df := func() {
		var a []*Generator
		s.moot.Lock()
		for _, b := range s.before {
			if g.StepName == b.StepName {
				continue
			}
			a = append(a, b)
		}
		s.before = a
		s.moot.Unlock()
	}
	s.moot.Lock()
	s.before = append(s.before, g)
	s.moot.Unlock()
	return df
}

func (s *Step) After(g *Generator) DeleteFn {
	df := func() {
		var a []*Generator
		s.moot.Lock()
		for _, b := range s.after {
			if g.StepName == b.StepName {
				continue
			}
			a = append(a, b)
		}
		s.after = a
		s.moot.Unlock()
	}
	s.moot.Lock()
	s.after = append(s.after, g)
	s.moot.Unlock()
	return df
}

func (s *Step) Run(r *Runner) error {
	for _, b := range s.before {
		if err := s.runGenerator(r, b); err != nil {
			return err
		}
	}

	if err := s.runGenerator(r, s.as); err != nil {
		return err
	}

	for _, b := range s.after {
		if err := s.runGenerator(r, b); err != nil {
			return err
		}
	}

	return nil
}

func (s *Step) runGenerator(r *Runner, g *Generator) error {
	r.curGen = g

	// payload := events.Payload{
	// 	"runner":    r,
	// 	"step":      s,
	// 	"generator": g,
	// }
	if g.Should != nil {
		err := safe.RunE(func() error {
			if !g.Should(r) {
				return io.EOF
			}
			return nil
		})
		if err != nil {
			r.Logger.Debugf("Step: %s [skipped]", g.StepName)
			// events.EmitPayload(EvtStepPrefix+":skipping:"+g.StepName, payload)
			return nil
		}
	}
	r.Logger.Debugf("Step: %s", g.StepName)
	// events.EmitPayload(EvtStepPrefix+":running:"+g.StepName, payload)
	return r.Chdir(r.Root, func() error {
		for _, fn := range g.runners {
			err := safe.RunE(func() error {
				return fn(r)
			})
			if err != nil {
				// events.EmitError(EvtStepPrefix+":running:"+g.StepName+":err", err, payload)
				return err
			}
		}
		return nil
	})
}

func NewStep(g *Generator, index int) (*Step, error) {
	if g == nil {
		return nil, errors.New("generator can not be nil")
	}
	return &Step{
		as:    g,
		index: index,
		moot:  &sync.RWMutex{},
	}, nil
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func stepName() string {
	bb := &bytes.Buffer{}
	bb.WriteString(fmt.Sprint(time.Now().UnixNano()))
	bb.WriteString(strconv.Itoa(rand.Int()))
	bb.WriteString(strconv.Itoa(rand.Int()))
	h := sha1.New()
	h.Write(bb.Bytes())
	return fmt.Sprintf("%x", h.Sum(nil))[:8]
}
