package syntax

import (
	"bytes"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"unicode/utf8"

	"code.justin.tv/cplat/twitchling/internal/syntax/ast"
)

type nodeParent byte

const (
	nodeParent_Element = iota
	nodeParent_PluralFunction
)

type parseContext struct {
	parentStack  []nodeParent
	argumentName *string
}

func (p *parseContext) isInContextOf(parent nodeParent) bool {
	for _, current := range p.parentStack {
		if current == parent {
			return true
		}
	}

	return false
}

func (p parseContext) withParent(parent nodeParent) parseContext {
	parentStack := make([]nodeParent, len(p.parentStack))
	copy(parentStack, p.parentStack)

	return parseContext{
		parentStack:  append(parentStack, parent),
		argumentName: p.argumentName,
	}
}

func (p parseContext) withArgument(argument string) parseContext {
	parentStack := make([]nodeParent, len(p.parentStack))
	copy(parentStack, p.parentStack)

	return parseContext{
		parentStack:  parentStack,
		argumentName: &argument,
	}
}

var (
	knownElements map[string]ast.ElementKind = map[string]ast.ElementKind{
		"em": ast.ElementKind_Emphasis,
	}

	identifierMatcher *regexp.Regexp = regexp.MustCompile(`^[_a-zA-Z][_a-zA-Z0-9]*$`)

	pluralFormMapping map[tokenKind]ast.PluralForm = map[tokenKind]ast.PluralForm{
		tokenKind_Zero:  ast.PluralForm_Zero,
		tokenKind_One:   ast.PluralForm_One,
		tokenKind_Two:   ast.PluralForm_Two,
		tokenKind_Few:   ast.PluralForm_Few,
		tokenKind_Many:  ast.PluralForm_Many,
		tokenKind_Other: ast.PluralForm_Other,
	}
)

type ParseResult struct {
	root []ast.Node
}

func (p ParseResult) Root() []ast.Node {
	return p.root
}

type parser struct {
	input string
	lexer *Lexer
}

func Parse(input string) (ParseResult, error) {
	if input == "" {
		return ParseResult{}, errors.New("empty input provided")
	}

	if strings.HasPrefix(input, "\x00") {
		return ParseResult{}, errors.New("string contains invalid NULL terminator")
	}

	if !utf8.ValidString(input) {
		return ParseResult{}, errors.New("input is not valid utf-8")
	}

	lexer := lex(input)
	parser := parser{
		input: input,
		lexer: lexer,
	}

	return parser.parseInput()
}

func (p *parser) parseInput() (ParseResult, error) {
	root, err := p.parseNodes(parseContext{})

	if err != nil {
		return ParseResult{}, err
	}

	return ParseResult{root}, nil
}

func (p *parser) parseNodes(ctx parseContext) ([]ast.Node, error) {
	nodes := []ast.Node{}

parseLoop:
	for token := p.lexer.Peek(); token.Kind() != tokenKind_EOI; token = p.lexer.Peek() {
		var node ast.Node
		var err error

		switch token := token; {
		case token.Kind() == tokenKind_OpenBrace:
			node, err = p.parsePlaceholder(ctx)

		case token.Kind() == tokenKind_Hash && ctx.argumentName != nil && (ctx.isInContextOf(nodeParent_Element) || ctx.isInContextOf(nodeParent_PluralFunction)):
			node, err = p.parseNumberPlaceholder(*ctx.argumentName)

		case token.Kind() == tokenKind_CloseBrace:
			if !ctx.isInContextOf(nodeParent_PluralFunction) { //ctx.parentStack != nodeParent_PluralFunction {
				return nil, p.reportErrorWithHelp("unexpected closing brace", helpMessage_RemoveClosingCurlyBrace, token.Start(), token.End())
			}
			// NOTE: Break out early if the next token is a close brace in the cxontext of parsing a function.
			break parseLoop

		case token.Kind() == tokenKind_ElementOpenStart:
			node, err = p.parseElement(ctx)

		case token.Kind() == tokenKind_ElementCloseStart:
			if !ctx.isInContextOf(nodeParent_Element) { //ctx.parentStack != nodeParent_Element {
				return nil, p.reportError(
					fmt.Sprintf("unexpected start of close element, found: %s", p.valueOfToken(token)),
					token.Start(),
					token.End(),
				)
			}
			// NOTE: Break out early if the next token is a close element in the context of parsing an element.
			break parseLoop

		default:
			node, err = p.parseTextSpan(ctx)
		}

		if err != nil {
			return nil, err
		}

		nodes = append(nodes, node)
	}

	return nodes, nil
}

func (p *parser) parsePlaceholder(ctx parseContext) (ast.Node, error) {
	p.consume()
	p.consumeMany(tokenKind_WS)

	argumentName, err := p.parseArgumentName()
	if err != nil {
		return nil, err
	}

	p.consumeMany(tokenKind_WS)

	if p.lexer.Peek().Kind() == tokenKind_Comma {
		return p.parseFunction(ctx, argumentName)
	}

	cbToken, ok := p.expectOne(tokenKind_CloseBrace)
	if !ok {
		return nil, p.reportMissingCurly(cbToken)
	}

	return ast.NewArgument(argumentName), nil
}

func (p *parser) parseArgumentName() (string, error) {
	// NOTE: All of the listed token kinds can be valid identifiers for an argument name.
	textToken, ok := p.expectOneOfAny(tokenKind_Text,
		tokenKind_Plural,
		tokenKind_Select,
		tokenKind_Offset,
		tokenKind_Zero,
		tokenKind_One,
		tokenKind_Two,
		tokenKind_Few,
		tokenKind_Many,
		tokenKind_Other,
		tokenKind_FormatNumber,
		tokenKind_FormatTime,
		tokenKind_FormateDate,
		tokenKind_NumberFormatInteger,
		tokenKind_NumberFormatCurrency,
		tokenKind_NumberFormatPercent,
		tokenKind_ElementName)

	if !ok {
		return "", p.reportError(
			fmt.Sprintf("found invalid identifier name for argument: %s", p.valueOfToken(textToken)),
			textToken.Start(),
			textToken.End(),
		)
	}

	argument := strings.Builder{}
	fmt.Fprint(&argument, p.valueOfToken(textToken))

	for peekedToken := p.lexer.Peek(); !isEndOfArgument(peekedToken); peekedToken = p.lexer.Peek() {
		nextToken := p.lexer.Next()

		if nextToken.Kind() == tokenKind_Error {
			return "", p.reportError(nextToken.Message(), nextToken.Start(), nextToken.End())
		}

		_, err := argument.WriteString(p.valueOfToken(nextToken))

		if err != nil {
			return "", fmt.Errorf("failed to write text span to buffer: %v", err)
		}
	}

	argumentName := argument.String()
	if !identifierMatcher.MatchString(argumentName) {
		return "", p.reportError(
			fmt.Sprintf("found invalid identifier name for argument: %s", argumentName),
			textToken.Start(),
			textToken.End(),
		)
	}

	return argumentName, nil
}

func (p *parser) parseFunction(ctx parseContext, argumentName string) (ast.Node, error) {
	p.consume()
	p.consumeMany(tokenKind_WS)

	switch nextToken := p.lexer.Peek(); {
	case nextToken.Kind() == tokenKind_FormatNumber:
		return p.parseNumberFunction(argumentName)

	case nextToken.Kind() == tokenKind_Plural:
		return p.parsePluralFunction(ctx, argumentName)

	default:
		argument := p.valueOfToken(nextToken)
		return nil, p.reportError(fmt.Sprintf("unexpected function argument found: %s", argument), nextToken.Start(), nextToken.End())
	}
}

func (p *parser) parseNumberPlaceholder(argumentName string) (ast.Node, error) {
	p.consume()
	return ast.NewArgument(argumentName), nil
}

func (p *parser) parseNumberFunction(argumentName string) (*ast.NumberFunction, error) {
	p.consume()
	p.consumeMany(tokenKind_WS)

	numberFormat := ast.NumberFormat_Integer

	if p.lexer.Peek().Kind() == tokenKind_Comma {
		p.lexer.Next()
		p.consumeMany(tokenKind_WS)

		formatToken, ok := p.expectOneOfAny(tokenKind_NumberFormatInteger, tokenKind_NumberFormatCurrency, tokenKind_NumberFormatPercent)
		if !ok {
			return nil, p.reportError(
				fmt.Sprintf("expected to find number format (integer, currency, percent), but found: %s", p.valueOfToken(formatToken)),
				formatToken.Start(),
				formatToken.End(),
			)
		}

		switch formatToken.Kind() {
		case tokenKind_NumberFormatCurrency:
			numberFormat = ast.NumberFormat_Currency
		case tokenKind_NumberFormatPercent:
			numberFormat = ast.NumberFormat_Percent
		}

		p.consumeMany(tokenKind_WS)
	}

	cbToken, ok := p.expectOne(tokenKind_CloseBrace)
	if !ok {
		return nil, p.reportMissingCurly(cbToken)
	}

	return ast.NewNumberFunction(argumentName, numberFormat), nil
}

func (p *parser) parsePluralFunction(ctx parseContext, argumentName string) (*ast.PluralFunction, error) {
	spanStart := p.consume().Start()
	p.consumeMany(tokenKind_WS)

	cToken, ok := p.expectOne(tokenKind_Comma)
	if !ok {
		return nil, p.reportError(
			fmt.Sprintf("plurals must have a third argument specifying selectors, found: %s", p.valueOfToken(cToken)),
			cToken.Start(),
			cToken.End(),
		)
	}

	p.consumeMany(tokenKind_WS)

	variants := map[ast.PluralForm][]ast.Node{}

	// TODO: Support numeric variants like =0, =1, etc...
	for {
		pluralToken, ok := p.expectOneOfAny(tokenKind_Zero, tokenKind_One, tokenKind_Two, tokenKind_Few, tokenKind_Many, tokenKind_Other)
		if !ok {
			return nil, p.reportError(
				fmt.Sprintf("expected plural variant (zero, one, two, few, many, other), found: %s", p.valueOfToken(pluralToken)),
				pluralToken.Start(),
				pluralToken.End(),
			)
		}

		p.consumeMany(tokenKind_WS)

		obToken, ok := p.expectOne(tokenKind_OpenBrace)
		if !ok {
			return nil, p.reportError("missing opening brace", obToken.Start(), obToken.End())
		}

		nodeCtx := ctx.withParent(nodeParent_PluralFunction).withArgument(argumentName)
		nodes, err := p.parseNodes(nodeCtx)
		if err != nil {
			return nil, err
		}

		cbToken, ok := p.expectOne(tokenKind_CloseBrace)
		if !ok {
			return nil, p.reportMissingCurly(cbToken)
		}

		form, ok := pluralFormMapping[pluralToken.Kind()]
		// Since the plural was asserted ahead of time this shouldn't happen, but if it does there's a bug!
		if !ok {
			return nil, p.reportError(
				fmt.Sprintf("(BUG) there's a bug in the parser or lexer while parsing a plural form, found: %s", p.valueOfToken(pluralToken)),
				pluralToken.Start(),
				pluralToken.End(),
			)
		}

		if _, ok := variants[form]; ok {
			return nil, p.reportError(
				fmt.Sprintf("duplicate plural form found: %s", p.valueOfToken(pluralToken)),
				pluralToken.Start(),
				pluralToken.End(),
			)
		}

		variants[form] = nodes

		p.consumeMany(tokenKind_WS)
		if p.lexer.Peek().Kind() == tokenKind_CloseBrace {
			break
		}
	}

	cbToken, ok := p.expectOne(tokenKind_CloseBrace)
	if !ok {
		return nil, p.reportMissingCurly(cbToken)
	}

	if _, ok := variants[ast.PluralForm_Other]; !ok {
		return nil, p.reportError(
			"plural function is missing the required other variant",
			spanStart,
			cbToken.End(),
		)
	}

	return ast.NewPluralFunction(argumentName, variants), nil
}

func (p *parser) parseElement(ctx parseContext) (*ast.Element, error) {
	spanStart := p.consume().Start()
	p.consumeMany(tokenKind_WS)

	elementOpenName, ok := p.expectOne(tokenKind_ElementName)
	if !ok {
		if elementOpenName.Kind() == tokenKind_Error {
			return nil, p.reportError(elementOpenName.Message(), elementOpenName.Start(), elementOpenName.End())
		}

		return nil, p.reportError(
			fmt.Sprintf("expected element name, found: %s", p.valueOfToken(elementOpenName)),
			elementOpenName.Start(),
			elementOpenName.End(),
		)
	}

	openValue := p.valueOfToken(elementOpenName)
	elementKind, ok := knownElements[openValue]
	if !ok {
		return nil, p.reportError(
			fmt.Sprintf("unsupported element found: %s", p.valueOfToken(elementOpenName)),
			elementOpenName.Start(),
			elementOpenName.End(),
		)
	}

	p.consumeMany(tokenKind_WS)

	endToken, ok := p.expectOne(tokenKind_ElementEnd)
	if !ok {
		if elementOpenName.Kind() == tokenKind_Error {
			return nil, p.reportError(endToken.Message(), endToken.Start(), endToken.End())
		}

		return nil, p.reportError("missing closing bracket", endToken.Start(), endToken.End())
	}

	nodeCtx := ctx.withParent(nodeParent_Element)
	inner, err := p.parseNodes(nodeCtx)
	if err != nil {
		return nil, err
	}

	ecsToken, ok := p.expectOne(tokenKind_ElementCloseStart)
	if !ok {
		return nil, p.reportError(
			fmt.Sprintf("expcting start of close element </, found: %s", p.valueOfToken(ecsToken)),
			ecsToken.Start(),
			ecsToken.End(),
		)
	}

	p.consumeMany(tokenKind_WS)

	elementCloseName, ok := p.expectOne(tokenKind_ElementName)
	if !ok {
		if elementCloseName.Kind() == tokenKind_Error {
			return nil, p.reportError(elementCloseName.Message(), elementCloseName.Start(), elementCloseName.End())
		}

		return nil, p.reportError(
			fmt.Sprintf("expected elemenet name, found: %s", p.valueOfToken(elementCloseName)),
			elementCloseName.Start(),
			elementCloseName.End(),
		)
	}

	closeValue := p.valueOfToken(elementCloseName)
	_, ok = knownElements[closeValue]
	if !ok {
		return nil, p.reportError(
			fmt.Sprintf("unsupported element found: %s", p.valueOfToken(elementOpenName)),
			elementOpenName.Start(),
			elementOpenName.End(),
		)
	}

	p.consumeMany(tokenKind_WS)

	ecToken, ok := p.expectOne(tokenKind_ElementEnd)
	if !ok {
		if ecToken.Kind() == tokenKind_Error {
			return nil, p.reportError(ecToken.Message(), ecToken.Start(), ecToken.End())
		}

		return nil, p.reportError("missing closing bracket", ecToken.Start(), ecToken.End())
	}

	if openValue != closeValue {
		return nil, p.reportError(
			fmt.Sprintf("mistmatched element pairs. open: %s, close: %s", openValue, closeValue),
			spanStart,
			ecToken.End(),
		)
	}

	return ast.NewElement(elementKind, inner), nil
}

func (p *parser) parseTextSpan(ctx parseContext) (*ast.TextSpan, error) {
	textBuffer := bytes.Buffer{}

	for peekedToken := p.lexer.Peek(); !isEndOfText(peekedToken); peekedToken = p.lexer.Peek() {
		// NOTE: Bail early if a # is found while in context of a plural function.
		if peekedToken.Kind() == tokenKind_Hash && ctx.isInContextOf(nodeParent_PluralFunction) {
			return ast.NewTextSpan(textBuffer.String()), nil
		}

		nextToken := p.lexer.Next()

		if nextToken.Kind() == tokenKind_Error {
			return nil, p.reportError(nextToken.Message(), nextToken.Start(), nextToken.End())
		}

		_, err := textBuffer.WriteString(p.valueOfToken(nextToken))

		if err != nil {
			return nil, fmt.Errorf("failed to write text span to buffer: %v", err)
		}
	}

	return ast.NewTextSpan(textBuffer.String()), nil
}

func (p *parser) valueOfToken(t Token) string {
	return p.input[t.Start():t.End()]
}

func (p *parser) expectOne(kind tokenKind) (Token, bool) {
	token := p.lexer.Next()

	if token.Kind() != kind {
		return token, false
	}

	return token, true
}

func (p *parser) expectOneOfAny(kinds ...tokenKind) (Token, bool) {
	token := p.lexer.Next()

	for _, kind := range kinds {
		if token.Kind() == kind {
			return token, true
		}
	}

	return token, false
}

func (p *parser) consumeMany(kind tokenKind) {
	for nextToken := p.lexer.Peek(); ; nextToken = p.lexer.Peek() {
		if nextToken.Kind() != kind {
			break
		}

		p.lexer.Next()
	}
}

func (p *parser) consume() Token {
	return p.lexer.Next()
}

func (p *parser) reportError(message string, spanStart int, spanEnd int) error {
	return p.reportErrorWithHelp(message, "", spanStart, spanEnd)
}

func (p *parser) reportErrorWithHelp(message string, help string, spanStart int, spanEnd int) error {
	index := NewIndex(p.input)
	startGrapheme := index.Graphemes[spanStart]
	endGrapheme := index.Graphemes[spanEnd-1]
	lines := index.Lines[startGrapheme.Line() : endGrapheme.Line()+1]

	return &ParseError{
		message:     message,
		help:        help,
		startLine:   startGrapheme.Line(),
		startColumn: startGrapheme.Column(),
		endLine:     endGrapheme.Line(),
		endColumn:   endGrapheme.Column(),
		context:     lines,
	}
}

func (p *parser) reportMissingCurly(token Token) error {
	return p.reportErrorWithHelp("missing closing brace", helpMessage_AddClosingCurlyBrace, token.Start(), token.End())
}

func isEndOfText(token Token) bool {
	return token.Kind() == tokenKind_EOI ||
		token.Kind() == tokenKind_OpenBrace ||
		token.Kind() == tokenKind_CloseBrace ||
		token.Kind() == tokenKind_ElementOpenStart ||
		token.Kind() == tokenKind_ElementCloseStart
}

func isEndOfArgument(token Token) bool {
	return token.Kind() == tokenKind_EOI ||
		token.Kind() == tokenKind_OpenBrace ||
		token.Kind() == tokenKind_CloseBrace ||
		token.Kind() == tokenKind_ElementOpenStart ||
		token.Kind() == tokenKind_ElementCloseStart ||
		token.Kind() == tokenKind_Colon ||
		token.Kind() == tokenKind_Comma ||
		token.Kind() == tokenKind_WS ||
		token.Kind() == tokenKind_Hash ||
		token.Kind() == tokenKind_Equal
}
