package caql

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/antlr/antlr4/runtime/Go/antlr"

	"github.com/SecurityBrewery/catalyst/generated/caql/parser"
)

type Parser struct {
	Searcher Searcher
	Prefix   string
}

func (p *Parser) Parse(aql string) (t *Tree, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%s", r)
		}
	}()
	// Set up the input
	inputStream := antlr.NewInputStream(aql)

	errorListener := &errorListener{}

	// Create the Lexer
	lexer := parser.NewCAQLLexer(inputStream)
	lexer.RemoveErrorListeners()
	lexer.AddErrorListener(errorListener)
	stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)

	// Create the Parser
	aqlParser := parser.NewCAQLParser(stream)

	aqlParser.RemoveErrorListeners()
	aqlParser.AddErrorListener(errorListener)
	aqlParser.SetErrorHandler(antlr.NewBailErrorStrategy())
	if errorListener.errs != nil {
		err = errorListener.errs[0]
	}

	return &Tree{aqlParser: aqlParser, parseContext: aqlParser.Parse(), searcher: p.Searcher, prefix: p.Prefix}, err
}

type Tree struct {
	parseContext parser.IParseContext
	aqlParser    *parser.CAQLParser
	searcher     Searcher
	prefix       string
}

func (t *Tree) Eval(values map[string]any) (i any, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%s", r)
		}
	}()
	interpreter := aqlInterpreter{values: values}

	antlr.ParseTreeWalkerDefault.Walk(&interpreter, t.parseContext)

	if interpreter.errs != nil {
		return nil, interpreter.errs[0]
	}

	return interpreter.stack[0], nil
}

func (t *Tree) String() (s string, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%s", r)
		}
	}()
	builder := aqlBuilder{searcher: t.searcher, prefix: t.prefix}

	antlr.ParseTreeWalkerDefault.Walk(&builder, t.parseContext)

	return builder.stack[0], err
}

func (t *Tree) BleveString() (s string, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%s", r)
		}
	}()
	builder := bleveBuilder{}

	antlr.ParseTreeWalkerDefault.Walk(&builder, t.parseContext)

	if builder.err != nil {
		return "", builder.err
	}

	return builder.stack[0], err
}

type errorListener struct {
	*antlr.DefaultErrorListener
	errs []error
}

func (el *errorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol any, line, column int, msg string, e antlr.RecognitionException) {
	el.errs = append(el.errs, fmt.Errorf("line "+strconv.Itoa(line)+":"+strconv.Itoa(column)+" "+msg))
}

func (el *errorListener) ReportAmbiguity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, exact bool, ambigAlts *antlr.BitSet, configs antlr.ATNConfigSet) {
	el.errs = append(el.errs, errors.New("ReportAmbiguity"))
}

func (el *errorListener) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, conflictingAlts *antlr.BitSet, configs antlr.ATNConfigSet) {
	el.errs = append(el.errs, errors.New("ReportAttemptingFullContext"))
}

func (el *errorListener) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex, prediction int, configs antlr.ATNConfigSet) {
	el.errs = append(el.errs, errors.New("ReportContextSensitivity"))
}
