package parser

import (
	"github.com/marzeq/quokka/shared"
	"github.com/marzeq/quokka/tokeniser"
)

type Parser struct {
	pos    int
	tokens []tokeniser.Token
}

func NewParser(tokens []tokeniser.Token) *Parser {
	return &Parser{
		pos:    0,
		tokens: tokens,
	}
}

func (p *Parser) Peek() tokeniser.Token {
	if p.pos >= len(p.tokens) || p.pos < 0 {
		return tokeniser.Token{Type: tokeniser.TOKEN_TYPE_EOF}
	}

	return p.tokens[p.pos]
}

func (p *Parser) Next() tokeniser.Token {
	pos := p.pos + 1

	if pos >= len(p.tokens) || pos < 0 {
		return tokeniser.Token{Type: tokeniser.TOKEN_TYPE_EOF}
	}

	return p.tokens[pos]
}

func (p *Parser) Inc() *Parser {
	p.pos++

	return p
}

func (p *Parser) Dec() *Parser {
	p.pos--

	return p
}

func (p *Parser) PrevLoc() shared.Location {
	l := p.Dec().Peek().Loc
	p.Inc()
	return l
}

func (p *Parser) CurrLoc() shared.Location {
	return p.Peek().Loc
}

func (p *Parser) Consume() tokeniser.Token {
	c := p.Peek()
	p.Inc()

	return c
}

func (p *Parser) Expect(expected tokeniser.TokenType) bool {
	tok := p.Consume()

	ret := tok.Type == expected

	return ret
}

func (p *Parser) ExpectGet(expected tokeniser.TokenType) (*tokeniser.Token, bool) {
	tok := p.Consume()

	if tok.Type != expected {
		return nil, false
	}

	return &tok, true
}

func (p *Parser) Match(ttypes ...tokeniser.TokenType) bool {
	ptype := p.Peek().Type

	for _, ttype := range ttypes {
		if ptype == ttype {
			return true
		}
	}

	return false
}

func (p *Parser) Parse() (*Node, error) {
	rootNode := &Node{Type: NODE_TYPE_ROOT}
	for !p.Match(tokeniser.TOKEN_TYPE_EOF) {
		for p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
			p.Inc()
		}
		e := shared.NewError(p.CurrLoc(), "expected function definition or import statement")
		if !p.Match(tokeniser.TOKEN_TYPE_KEYWORD) {
			return nil, e
		}

		switch p.Peek().Value {
		case "let":
			fnDef, err := p.ParseFunctionDefinition()
			if err != nil {
				return nil, err
			}
			rootNode.Children = append(rootNode.Children, fnDef)
		case "import":
			imp, err := p.ParseImport()
			if err != nil {
				return nil, err
			}
			rootNode.Children = append(rootNode.Children, imp)
		default:
			return nil, e
		}

		if p.Match(tokeniser.TOKEN_TYPE_EOF) {
			break
		}

		if !p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
			return nil, shared.NewError(p.CurrLoc(), "expected ';' or '\\n'")
		}

		for p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
			p.Inc()
		}
	}

	return rootNode, nil
}