package pokemonparsing

import (
	"sync"

	p "github.com/ajswis/go-pkparse-server/pokemon"
	"github.com/ajswis/go-pkparse-server/pokemon-parsing/gen7"
)

// NOTE: For unit testing simplicity, perhaps defining a parsing interface and
// wrapper type for all parsers would make isolating tests easier.

// Parse returns either a parsed Pokemon with all applicable struct fields
// populated, or an error detailing what was wrong.
func Parse(rawPokemon p.RawPokemon) (*p.Pokemon, error) {
	// TODO: Conditionally swap to different generation parsers depending on given
	// byte slice.
	return gen7.Parse(rawPokemon)
}

// ParseAll returns either a slice of parsed Pokemon with all applicable struct
// fields populated, or an error detailing what was wrong with any attempt.
//
// Additionally, ParseAll will parallelize large data sets. This concurrency
// might be a preemptive optimization at the moment, but once parsers are
// fully realized, benchmarks can determine the usefulness of parallelization.
func ParseAll(rawPokemon []p.RawPokemon) ([]*p.Pokemon, error) {
	if len(rawPokemon) >= 10 {
		return concurrentParseAll(rawPokemon)
	}
	return serialParseAll(rawPokemon)
}

func serialParseAll(rawPokemon []p.RawPokemon) ([]*p.Pokemon, error) {
	var pkmn = []*p.Pokemon{}

	for _, p := range rawPokemon {
		parsed, err := Parse(p)
		if err != nil {
			return nil, err
		}
		pkmn = append(pkmn, parsed)
	}

	return pkmn, nil
}

type parseResult struct {
	pkmn *p.Pokemon
	err  error
}

// concurrentParseAll fans out the data set to Parse in parallel, but does not
// end early on a failure or cancelled request. Providing done channels can
// resolve this, but it would probably be worth waiting until handling of
// `context.Context`s is applied.
func concurrentParseAll(rawPokemon []p.RawPokemon) ([]*p.Pokemon, error) {
	var wg sync.WaitGroup
	pkmnc := make(chan *parseResult)

	for _, pkmn := range rawPokemon {
		wg.Add(1)
		go func(unparsed p.RawPokemon) {
			defer wg.Done()

			res, err := Parse(unparsed)
			pkmnc <- &parseResult{pkmn: res, err: err}
		}(pkmn)
	}

	go func() {
		wg.Wait()
		close(pkmnc)
	}()

	var parsedPokemon []*p.Pokemon
	for res := range pkmnc {
		if res.err != nil {
			return nil, res.err
		}
		parsedPokemon = append(parsedPokemon, res.pkmn)
	}

	return parsedPokemon, nil
}
