package csexp

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"slices"
	"strconv"
	"strings"

	"git.codemonkeysoftware.net/b/gigaparsec"
	pbytes "git.codemonkeysoftware.net/b/gigaparsec/bytes"
	"git.codemonkeysoftware.net/b/peachy-go/shortcircuit"
)

type Sexp interface {
	isSexp()
	WriteTo(w io.Writer) (n int64, err error)
	String() string
	Equal(Sexp) bool
	Clone() Sexp
}

type Atom []byte

func (a Atom) isSexp() {}

func (a Atom) WriteTo(w io.Writer) (int64, error) {
	if scw, ok := w.(*shortcircuit.Writer); ok && scw.Failed() {
		return 0, nil
	}

	n, err := fmt.Fprintf(w, "%d:%s", len(a), []byte(a))
	return int64(n), err
}

func (a Atom) String() string {
	var buf bytes.Buffer
	a.WriteTo(&buf)
	return buf.String()
}

func (a Atom) Equal(s Sexp) bool {
	a2, ok := s.(Atom)
	return ok && bytes.Equal([]byte(a), []byte(a2))
}

func (a Atom) Clone() Sexp {
	return Atom(bytes.Clone([]byte(a)))
}

type List []Sexp

func (l List) isSexp() {}

func (l List) WriteTo(w io.Writer) (int64, error) {
	scw := shortcircuit.EnsureWriter(w)
	if scw.Failed() {
		return 0, nil
	}

	io.WriteString(scw, "(")
	for _, child := range l {
		child.WriteTo(scw)
	}
	io.WriteString(scw, ")")
	return scw.Status()
}

func (l List) String() string {
	var buf bytes.Buffer
	l.WriteTo(&buf)
	return buf.String()
}

func (l List) Equal(s Sexp) bool {
	l2, ok := s.(List)
	return ok && slices.EqualFunc(l, l2, Sexp.Equal)
}

func (l List) Clone() Sexp {
	l2 := make(List, len(l))
	for i, child := range l {
		l2[i] = child.Clone()
	}
	return l2
}

var parseLength = gigaparsec.Map(pbytes.Regexp(`0|[1-9]\d*`), func(s string) uint64 {
	n, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		panic(err)
	}
	return n
})

func acceptN(n uint64) gigaparsec.Parser[byte, []byte] {
	expected := fmt.Sprintf("%d bytes", n)
	return func(s gigaparsec.State[byte]) (gigaparsec.Result[byte, []byte], error) {
		if n == 0 {
			return gigaparsec.Succeed[byte, []byte](true, nil, s, gigaparsec.MessageOK(s.Pos())), nil
		}
		dst := make([]byte, n)
		_, next, err := s.Read(dst)
		if errors.Is(err, io.EOF) {
			return gigaparsec.Fail[byte, []byte](false, gigaparsec.MessageEnd(s.Pos(), expected)), nil
		}
		if err != nil {
			return gigaparsec.Result[byte, []byte]{}, err
		}
		return gigaparsec.Succeed(true, dst, next, gigaparsec.MessageOK(s.Pos())), nil
	}
}

var parseAtom = gigaparsec.Map(gigaparsec.Bind2(
	parseLength,
	gigaparsec.Pipe[byte, byte, uint64](gigaparsec.Match[byte](':')),
	acceptN,
), func(s []byte) Sexp { return Atom(s) })

func parseSexp(input gigaparsec.State[byte]) (gigaparsec.Result[byte, Sexp], error) {
	return gigaparsec.Choose(parseAtom, parseList)(input)
}

func parseRestOfList(input gigaparsec.State[byte]) (gigaparsec.Result[byte, []Sexp], error) {
	return gigaparsec.Choose(
		gigaparsec.Map(gigaparsec.Match[byte](')'), func(byte) []Sexp { return nil }),
		gigaparsec.Bind(
			parseSexp,
			func(s Sexp) gigaparsec.Parser[byte, []Sexp] {
				return gigaparsec.Map(
					parseRestOfList,
					func(rest []Sexp) []Sexp { return append([]Sexp{s}, rest...) },
				)
			},
		),
	)(input)
}

func parseList(input gigaparsec.State[byte]) (gigaparsec.Result[byte, Sexp], error) {
	return gigaparsec.Map(gigaparsec.Seq2(
		gigaparsec.Match[byte]('('),
		parseRestOfList,
		func(_ byte, rest []Sexp) []Sexp { return rest },
	), func(sexps []Sexp) Sexp { return List(sexps) })(input)
}

func Parse(data []byte) (Sexp, error) {
	return parse(bytes.NewReader(data))
}

func ParseString(data string) (Sexp, error) {
	return parse(strings.NewReader(data))
}

func parse(r io.ReaderAt) (Sexp, error) {
	parser := gigaparsec.Seq2(
		parseSexp,
		gigaparsec.End[byte](),
		func(s Sexp, _ struct{}) Sexp { return s },
	)
	result, err := gigaparsec.Run(parser, r)
	if err != nil {
		return nil, fmt.Errorf("csexp.Parse: %w", err)
	}
	return result, nil
}
