package input

import (
	"bytes"
	"fmt"
	"io"
	"math/rand"
	"os"
	"time"

	"github.com/go-yaml/yaml"
)

type Button struct {
	Char byte
	Down bool
}

type Random struct {
	generator func() byte
	down      []Button
}

func NewRandom(generator func() byte) *Random {
	rand.Seed(time.Now().UnixNano())
	return &Random{generator: generator}
}

func (r *Random) Close() {
}

func (r *Random) Read() []Button {
	if len(r.down) > 0 && rand.Int()%2 == 0 {
		was := r.down
		for i := range was {
			was[i].Down = false
		}
		r.down = r.down[:0]
		return was
	} else {
		c := Button{Char: r.generator(), Down: true}
		r.down = append(r.down, c)
		return []Button{c}
	}
}

func randomCharFromRange(start, stop byte) func() byte {
	return func() byte {
		return start + byte(rand.Int()%int(1+stop-start))
	}
}

func randomCharFromWeightFile(p string) func() byte {
	b, err := os.ReadFile(p)
	if err != nil {
		panic(err)
	}
	return randomCharFromWeightReader(bytes.NewReader(b))
}

func randomCharFromWeightReader(r io.Reader) func() byte {
	var m map[string]int
	if err := yaml.NewDecoder(r).Decode(&m); err != nil {
		panic(err)
	}
	byted := map[byte]int{}
	for k, v := range m {
		if len(k) != 1 {
			panic(fmt.Sprintf("keys must be 1 character but got %q", k))
		}
		byted[k[0]] = v
	}
	return randomCharFromWeights(byted)
}

func randomCharFromWeights(m map[byte]int) func() byte {
	type pair struct {
		b byte
		i int
	}
	result := make([]pair, 0, len(m))
	sum := 0
	for k, v := range m {
		result = append(result, pair{b: k, i: v})
		if v < 0 {
			panic("each weight must each be natural")
		}
		sum += v
	}
	if sum <= 0 {
		panic("weights must total nonzero")
	}
	return func() byte {
		n := rand.Int() % sum
		for _, v := range result {
			n -= v.i
			if n <= 0 {
				return v.b
			}
		}
		panic("how")
	}
}
