package parsers

import (
	"errors"
	"fmt"
	"io"

	"github.com/johnkerl/pgpg/go/lib/pkg/asts"
	"github.com/johnkerl/pgpg/go/lib/pkg/lexers"
	"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)

type EBNFParser struct {
	lexer      *lexers.LookaheadLexer
	sourceName string
}

const (
	EBNFParserNodeTypeGrammar        asts.NodeType = "grammar"
	EBNFParserNodeTypeRule           asts.NodeType = "rule"
	EBNFParserNodeTypeAlternates     asts.NodeType = "alternates"
	EBNFParserNodeTypeSequence       asts.NodeType = "sequence"
	EBNFParserNodeTypeOptional       asts.NodeType = "optional"
	EBNFParserNodeTypeRepeat         asts.NodeType = "repeat"
	EBNFParserNodeTypeIdentifier     asts.NodeType = "identifier"
	EBNFParserNodeTypeLiteral        asts.NodeType = "literal"
	EBNFParserNodeTypeRange          asts.NodeType = "range"
	EBNFParserNodeTypeWildcard       asts.NodeType = "wildcard"
	EBNFParserNodeTypeHintedSequence asts.NodeType = "hinted_sequence"
	EBNFParserNodeTypeHint           asts.NodeType = "hint"
	EBNFParserNodeTypeHintField      asts.NodeType = "hint_field"
	EBNFParserNodeTypeHintInt        asts.NodeType = "hint_int"
	EBNFParserNodeTypeHintString     asts.NodeType = "hint_string"
	EBNFParserNodeTypeHintArray      asts.NodeType = "hint_array"
	EBNFParserNodeTypeEmpty          asts.NodeType = "empty"
)

func NewEBNFParser() AbstractParser {
	return &EBNFParser{}
}

func NewEBNFParserWithSourceName(sourceName string) AbstractParser {
	return &EBNFParser{sourceName: sourceName}
}

func (parser *EBNFParser) Parse(r io.Reader) (*asts.AST, error) {
	parser.lexer = lexers.NewLookaheadLexer(lexers.NewEBNFLexerWithSourceName(r, parser.sourceName))

	rootNode, err := parser.parseGrammar()
	if err != nil {
		return nil, err
	}

	if err := parser.expect(tokens.TokenTypeEOF); err != nil {
		return nil, err
	}

	return asts.NewAST(rootNode), nil
}

func (parser *EBNFParser) parseGrammar() (*asts.ASTNode, error) {
	var rules []*asts.ASTNode
	for {
		if parser.lexer.LookAhead().Type == tokens.TokenTypeEOF {
			break
		}
		rule, err := parser.parseRule()
		if err != nil {
			return nil, err
		}
		rules = append(rules, rule)
	}
	if len(rules) == 0 {
		return nil, errors.New("syntax error: expected one or more rules")
	}
	return asts.NewASTNode(nil, EBNFParserNodeTypeGrammar, rules), nil
}

func (parser *EBNFParser) parseRule() (*asts.ASTNode, error) {
	accepted, nameToken, err := parser.accept(lexers.EBNFLexerTypeIdentifier)
	if err != nil {
		return nil, err
	}
	if !accepted {
		lookaheadToken := parser.lexer.LookAhead()
		return nil, fmt.Errorf(
			"syntax error: expected rule name at \"%s\"",
			parser.formatTokenLocation(lookaheadToken),
		)
	}

	if err := parser.expect(lexers.EBNFLexerTypeAssign); err != nil {
		return nil, err
	}

	expr, err := parser.parseExpression()
	if err != nil {
		return nil, err
	}

	// Semicolons are optional terminators.
	if _, _, err := parser.accept(lexers.EBNFLexerTypeSemicolon); err != nil {
		return nil, err
	}

	nameNode := asts.NewASTNode(nameToken, EBNFParserNodeTypeIdentifier, nil)
	return asts.NewASTNode(nil, EBNFParserNodeTypeRule, []*asts.ASTNode{nameNode, expr}), nil
}

func (parser *EBNFParser) parseExpression() (*asts.ASTNode, error) {
	// Expression : Sequence ( '|' Sequence )* ;
	left, err := parser.parseSequence()
	if err != nil {
		return nil, err
	}

	var alternates []*asts.ASTNode
	alternates = append(alternates, left)
	for {
		accepted, _, err := parser.accept(lexers.EBNFLexerTypeOr)
		if err != nil {
			return nil, err
		}
		if !accepted {
			break
		}
		right, err := parser.parseSequence()
		if err != nil {
			return nil, err
		}
		alternates = append(alternates, right)
	}

	if len(alternates) == 1 {
		return alternates[0], nil
	}
	return asts.NewASTNode(nil, EBNFParserNodeTypeAlternates, alternates), nil
}

func (parser *EBNFParser) parseSequence() (*asts.ASTNode, error) {
	// Sequence : Term+ [HintBlock] ;
	var terms []*asts.ASTNode
	for {
		term, ok, err := parser.parseTermIfPresent()
		if err != nil {
			return nil, err
		}
		if !ok {
			break
		}
		terms = append(terms, term)
	}
	if len(terms) == 0 {
		return nil, errors.New("syntax error: expected term")
	}

	seqNode := terms[0]
	if len(terms) != 1 {
		seqNode = asts.NewASTNode(nil, EBNFParserNodeTypeSequence, terms)
	}

	hintNode, err := parser.parseHintIfPresent()
	if err != nil {
		return nil, err
	}
	if hintNode != nil {
		return asts.NewASTNode(nil, EBNFParserNodeTypeHintedSequence,
			[]*asts.ASTNode{seqNode, hintNode}), nil
	}
	return seqNode, nil
}

func (parser *EBNFParser) parseTermIfPresent() (*asts.ASTNode, bool, error) {
	accepted, token, err := parser.accept(lexers.EBNFLexerTypeIdentifier)
	if err != nil {
		return nil, false, err
	}
	if accepted {
		if string(token.Lexeme) == "empty" {
			return asts.NewASTNode(nil, EBNFParserNodeTypeEmpty, nil), true, nil
		}
		return asts.NewASTNode(token, EBNFParserNodeTypeIdentifier, nil), true, nil
	}

	accepted, token, err = parser.accept(lexers.EBNFLexerTypeString)
	if err != nil {
		return nil, false, err
	}
	if accepted {
		literalNode := asts.NewASTNode(token, EBNFParserNodeTypeLiteral, nil)
		acceptedDash, _, err := parser.accept(lexers.EBNFLexerTypeDash)
		if err != nil {
			return nil, false, err
		}
		if !acceptedDash {
			return literalNode, true, nil
		}
		acceptedEnd, endToken, err := parser.accept(lexers.EBNFLexerTypeString)
		if err != nil {
			return nil, false, err
		}
		if !acceptedEnd {
			return nil, false, fmt.Errorf("syntax error: expected string literal after '-' at %s", parser.formatTokenLocation(parser.lexer.LookAhead()))
		}
		endNode := asts.NewASTNode(endToken, EBNFParserNodeTypeLiteral, nil)
		return asts.NewASTNode(nil, EBNFParserNodeTypeRange, []*asts.ASTNode{literalNode, endNode}), true, nil
	}

	accepted, token, err = parser.accept(lexers.EBNFLexerTypeDot)
	if err != nil {
		return nil, false, err
	}
	if accepted {
		return asts.NewASTNode(token, EBNFParserNodeTypeWildcard, nil), true, nil
	}

	accepted, _, err = parser.accept(lexers.EBNFLexerTypeLParen)
	if err != nil {
		return nil, false, err
	}
	if accepted {
		expr, err := parser.parseExpression()
		if err != nil {
			return nil, false, err
		}
		if err := parser.expect(lexers.EBNFLexerTypeRParen); err != nil {
			return nil, false, err
		}
		return expr, true, nil
	}

	accepted, _, err = parser.accept(lexers.EBNFLexerTypeLBracket)
	if err != nil {
		return nil, false, err
	}
	if accepted {
		expr, err := parser.parseExpression()
		if err != nil {
			return nil, false, err
		}
		if err := parser.expect(lexers.EBNFLexerTypeRBracket); err != nil {
			return nil, false, err
		}
		return asts.NewASTNode(nil, EBNFParserNodeTypeOptional, []*asts.ASTNode{expr}), true, nil
	}

	accepted, _, err = parser.accept(lexers.EBNFLexerTypeLBrace)
	if err != nil {
		return nil, false, err
	}
	if accepted {
		expr, err := parser.parseExpression()
		if err != nil {
			return nil, false, err
		}
		if err := parser.expect(lexers.EBNFLexerTypeRBrace); err != nil {
			return nil, false, err
		}
		return asts.NewASTNode(nil, EBNFParserNodeTypeRepeat, []*asts.ASTNode{expr}), true, nil
	}

	return nil, false, nil
}

func (parser *EBNFParser) parseHintIfPresent() (*asts.ASTNode, error) {
	accepted, _, err := parser.accept(lexers.EBNFLexerTypeArrow)
	if err != nil {
		return nil, err
	}
	if !accepted {
		return nil, nil
	}

	if err := parser.expect(lexers.EBNFLexerTypeLBrace); err != nil {
		return nil, err
	}

	var fields []*asts.ASTNode
	for {
		// Check for closing brace
		accepted, _, err := parser.accept(lexers.EBNFLexerTypeRBrace)
		if err != nil {
			return nil, err
		}
		if accepted {
			break
		}

		// Comma separator between fields
		if len(fields) > 0 {
			if err := parser.expect(lexers.EBNFLexerTypeComma); err != nil {
				return nil, err
			}
		}

		// Parse "key" : value
		accepted, keyToken, err := parser.accept(lexers.EBNFLexerTypeString)
		if err != nil {
			return nil, err
		}
		if !accepted {
			return nil, fmt.Errorf("syntax error: expected hint field name at %s",
				parser.formatTokenLocation(parser.lexer.LookAhead()))
		}

		if err := parser.expect(lexers.EBNFLexerTypeColon); err != nil {
			return nil, err
		}

		valueNode, err := parser.parseHintValue()
		if err != nil {
			return nil, err
		}

		fieldNode := asts.NewASTNode(keyToken, EBNFParserNodeTypeHintField,
			[]*asts.ASTNode{valueNode})
		fields = append(fields, fieldNode)
	}

	return asts.NewASTNode(nil, EBNFParserNodeTypeHint, fields), nil
}

func (parser *EBNFParser) parseHintValue() (*asts.ASTNode, error) {
	// Integer value
	accepted, token, err := parser.accept(lexers.EBNFLexerTypeInteger)
	if err != nil {
		return nil, err
	}
	if accepted {
		return asts.NewASTNode(token, EBNFParserNodeTypeHintInt, nil), nil
	}

	// String value
	accepted, token, err = parser.accept(lexers.EBNFLexerTypeString)
	if err != nil {
		return nil, err
	}
	if accepted {
		return asts.NewASTNode(token, EBNFParserNodeTypeHintString, nil), nil
	}

	// Array value: [ int, int, ... ]
	accepted, _, err = parser.accept(lexers.EBNFLexerTypeLBracket)
	if err != nil {
		return nil, err
	}
	if accepted {
		var elements []*asts.ASTNode
		for {
			accepted, _, err := parser.accept(lexers.EBNFLexerTypeRBracket)
			if err != nil {
				return nil, err
			}
			if accepted {
				break
			}
			if len(elements) > 0 {
				if err := parser.expect(lexers.EBNFLexerTypeComma); err != nil {
					return nil, err
				}
			}
			accepted, intToken, err := parser.accept(lexers.EBNFLexerTypeInteger)
			if err != nil {
				return nil, err
			}
			if !accepted {
				return nil, fmt.Errorf("syntax error: expected integer in hint array at %s",
					parser.formatTokenLocation(parser.lexer.LookAhead()))
			}
			elements = append(elements, asts.NewASTNode(intToken, EBNFParserNodeTypeHintInt, nil))
		}
		return asts.NewASTNode(nil, EBNFParserNodeTypeHintArray, elements), nil
	}

	return nil, fmt.Errorf("syntax error: expected hint value (integer, string, or array) at %s",
		parser.formatTokenLocation(parser.lexer.LookAhead()))
}

func (parser *EBNFParser) accept(tokenType tokens.TokenType) (bool, *tokens.Token, error) {
	lookaheadToken := parser.lexer.LookAhead()
	if lookaheadToken.Type == tokenType {
		// The current token is of the expected type, but there may or may not have been a lex error
		// getting the next token
		lexerr := parser.getAndValidateLookaheadToken()
		return true, lookaheadToken, lexerr
	}
	// The current token is not of the expected type
	return false, nil, nil
}

func (parser *EBNFParser) expect(tokenType tokens.TokenType) error {
	accepted, _, lexerr := parser.accept(tokenType)
	if lexerr != nil {
		// Lex error getting the next token
		return lexerr
	}
	if !accepted {
		// No lex error getting the next token, but the current
		// token isn't of the expected type
		lookaheadToken := parser.lexer.LookAhead()
		return fmt.Errorf(
			"expect: expected %s; got %s (%q) at %s",
			tokenType,
			lookaheadToken.Type,
			string(lookaheadToken.Lexeme),
			parser.formatTokenLocation(lookaheadToken),
		)
	}
	return nil
}

func (parser *EBNFParser) getAndValidateLookaheadToken() error {
	parser.lexer.Advance()
	lookaheadToken := parser.lexer.LookAhead()
	if lookaheadToken.IsError() {
		return fmt.Errorf("lexer at %s: %s",
			parser.formatTokenLocation(lookaheadToken),
			string(lookaheadToken.Lexeme))
	}

	return nil
}

func (parser *EBNFParser) formatTokenLocation(token *tokens.Token) string {
	if token == nil {
		return "unknown location"
	}
	if parser.sourceName != "" {
		return fmt.Sprintf(
			"%s, line %d, column %d",
			parser.sourceName,
			token.Location.LineNumber,
			token.Location.ColumnNumber,
		)
	}
	return fmt.Sprintf(
		"line %d, column %d",
		token.Location.LineNumber,
		token.Location.ColumnNumber,
	)
}
