package parser

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

func (p *Parser) ParseExpression() (*Node, error) {
	return p.ParseLogicalOr()
}

func (p *Parser) ParseLogicalOr() (*Node, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseLogicalAnd()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "or" {
		p.Inc()

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

		right, err := p.ParseLogicalAnd()
		if err != nil {
			return nil, err
		}
		left = &Node{
			Type:  NODE_TYPE_BINARY_OP,
			Value: "or",
			Left:  left,
			Right: right,
			Loc:   beginLoc,
		}
	}

	return left, nil
}

func (p *Parser) ParseLogicalAnd() (*Node, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseLogicalNot()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "and" {
		p.Inc()

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

		right, err := p.ParseLogicalNot()
		if err != nil {
			return nil, err
		}
		left = &Node{
			Type:  NODE_TYPE_BINARY_OP,
			Value: "and",
			Left:  left,
			Right: right,
			Loc:   beginLoc,
		}
	}

	return left, nil
}

func (p *Parser) ParseLogicalNot() (*Node, error) {
	beginLoc := p.CurrLoc()
	if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "not" {
		p.Inc()

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

		expr, err := p.ParseLogicalNot()
		if err != nil {
			return nil, err
		}

		return &Node{
			Type:  NODE_TYPE_UNARY_OP,
			Value: "not",
			Right: expr,
			Loc:   beginLoc,
		}, nil
	}

	return p.ParseComparison()
}

func (p *Parser) ParseUnary() (*Node, error) {
	beginLoc := p.CurrLoc()
	if p.Match(tokeniser.TOKEN_TYPE_MINUS) {
		p.Inc()

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

		expr, err := p.ParseUnary()
		if err != nil {
			return nil, err
		}

		return &Node{
			Type:  NODE_TYPE_UNARY_OP,
			Value: "-",
			Right: expr,
			Loc:   beginLoc,
		}, nil
	}

	return p.ParseTerm()
}

func (p *Parser) ParseComparison() (*Node, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseAddSub()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_EQUALS_EQUALS, tokeniser.TOKEN_TYPE_NOT_EQUALS, tokeniser.TOKEN_TYPE_LESS, tokeniser.TOKEN_TYPE_GREATER, tokeniser.TOKEN_TYPE_LESS_EQUALS, tokeniser.TOKEN_TYPE_GREATER_EQUALS) {
		op := p.Consume()
		val := ""
		if op.Type == tokeniser.TOKEN_TYPE_EQUALS_EQUALS {
			val = "=="
		} else if op.Type == tokeniser.TOKEN_TYPE_NOT_EQUALS {
			val = "!="
		} else if op.Type == tokeniser.TOKEN_TYPE_LESS {
			val = "<"
		} else if op.Type == tokeniser.TOKEN_TYPE_GREATER {
			val = ">"
		} else if op.Type == tokeniser.TOKEN_TYPE_LESS_EQUALS {
			val = "<="
		} else if op.Type == tokeniser.TOKEN_TYPE_GREATER_EQUALS {
			val = ">="
		}
		for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
			p.Inc()
		}

		right, err := p.ParseAddSub()
		if err != nil {
			return nil, err
		}
		left = &Node{
			Type:  NODE_TYPE_BINARY_OP,
			Value: val,
			Left:  left,
			Right: right,
			Loc:   beginLoc,
		}
	}

	return left, nil
}

func (p *Parser) ParseAddSub() (*Node, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseMulDiv()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_PLUS, tokeniser.TOKEN_TYPE_MINUS) {
		op := p.Consume()
		val := ""
		if op.Type == tokeniser.TOKEN_TYPE_PLUS {
			val = "+"
		} else {
			val = "-"
		}

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

		right, err := p.ParseMulDiv()
		if err != nil {
			return nil, err
		}
		left = &Node{
			Type:  NODE_TYPE_BINARY_OP,
			Value: val,
			Left:  left,
			Right: right,
			Loc:   beginLoc,
		}
	}

	return left, nil
}

func (p *Parser) ParseMulDiv() (*Node, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseUnary()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_ASTERISK, tokeniser.TOKEN_TYPE_SLASH, tokeniser.TOKEN_TYPE_PERCENT) {
		op := p.Consume()
		val := ""
		if op.Type == tokeniser.TOKEN_TYPE_ASTERISK {
			val = "*"
		} else if op.Type == tokeniser.TOKEN_TYPE_SLASH {
			val = "/"
		} else {
			val = "%"
		}
		for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
			p.Inc()
		}

		right, err := p.ParseUnary()
		if err != nil {
			return nil, err
		}
		left = &Node{
			Type:  NODE_TYPE_BINARY_OP,
			Value: val,
			Left:  left,
			Right: right,
			Loc:   beginLoc,
		}
	}

	return left, nil
}

func (p *Parser) ParseTerm() (*Node, error) {
	beginLoc := p.CurrLoc()
	if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "if" {
		expr, err := p.ParseIfExpression()
		if err != nil {
			return nil, err
		}

		return expr, nil
	}

	if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "cast" {
		expr, err := p.ParseCast()
		if err != nil {
			return nil, err
		}

		return expr, nil
	}

	if p.Match(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
		p.Consume()

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

		expr, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}

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

		if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')'")
		}

		return expr, nil
	}

	if p.Match(tokeniser.TOKEN_TYPE_IDENT) {
		p.Inc()
		if p.Match(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
			p.Dec()
			return p.ParseFunctionCall()
		}

		if p.Match(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
			p.Dec()
			return p.ParseStructLiteral()
		}

		p.Dec()
		return p.ParseIdent()
	}

	if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "true" || p.Peek().Value == "false" {
		bLit := p.Consume()
		return &Node{
			Type:  NODE_TYPE_BOOL_LITERAL,
			Value: bLit.Value,
			Loc:   beginLoc,
		}, nil
	}

	if p.Match(tokeniser.TOKEN_TYPE_NUMBER) {
		nLit := p.Consume()
		return &Node{
			Type:  NODE_TYPE_NUMBER_LITERAL,
			Value: nLit.Value,
			Loc:   beginLoc,
		}, nil
	}

	if p.Match(tokeniser.TOKEN_TYPE_CHAR) {
		cLit := p.Consume()
		return &Node{
			Type:  NODE_TYPE_CHAR_LITERAL,
			Value: []byte(cLit.Value)[0],
			Loc:   beginLoc,
		}, nil
	}

	if p.Match(tokeniser.TOKEN_TYPE_STRING) {
		sLit := p.Consume()
		return &Node{
			Type:  NODE_TYPE_STRING_LITERAL,
			Value: sLit.Value,
			Loc:   beginLoc,
		}, nil
	}

	return nil, shared.NewError(p.CurrLoc(), "unexpected token %s", p.Peek())
}

func (p *Parser) ParseCast() (*Node, error) {
	beginLoc := p.CurrLoc()

	if t, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_KEYWORD); !ok || t.Value != "cast" {
		return nil, shared.NewError(beginLoc, "expected 'cast' keyword")
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(")
	}
	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	tpe, err := p.ParseIdent()
	if err != nil {
		return nil, err
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_COMMA) {
		return nil, shared.NewError(p.PrevLoc(), "expected ','")
	}
	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}

	expr, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')")
	}

	return &Node{
		Type:  NODE_TYPE_CAST,
		Left:  &Node{Type: NODE_TYPE_IDENTIFIER, Value: tpe.Value},
		Right: expr,
		Loc:   beginLoc,
	}, nil
}

func (p *Parser) ParseFunctionCall() (*Node, error) {
	beginLoc := p.CurrLoc()
	name, err := p.ParseIdent()
	if err != nil {
		return nil, err
	}
	var args []*Node

	p.Consume()

	if !p.Match(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
		for {
			arg, err := p.ParseExpression()
			if err != nil {
				return nil, err
			}
			args = append(args, arg)

			if !p.Match(tokeniser.TOKEN_TYPE_COMMA) {
				break
			}
			p.Inc()

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

	if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
		return nil, shared.NewError(p.PrevLoc(), "expected '('")
	}

	return &Node{
		Type:     NODE_TYPE_FUNCTION_CALL,
		Value:    &Node{Type: NODE_TYPE_IDENTIFIER, Value: name.Value},
		Children: args,
		Loc:      beginLoc,
	}, nil
}

func (p *Parser) ParseIfExpression() (*Node, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TOKEN_TYPE_KEYWORD) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'if' keyword")
	}

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

	condition, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

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

	thenBlock, err := p.ParseBlockExpression()
	if err != nil {
		return nil, err
	}

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

	ifBranch := &IfBranch{
		Condition: condition,
		Node:      thenBlock,
	}

	ifNodeValue := &IfNodeValue{
		IfBranch:       ifBranch,
		ElseIfBranches: []*IfBranch{},
	}

	for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "else" {
		p.Consume()

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

		if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "if" {
			p.Consume()

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

			elseifCondition, err := p.ParseExpression()
			if err != nil {
				return nil, err
			}

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

			elseifBlock, err := p.ParseBlockExpression()
			if err != nil {
				return nil, err
			}

			elseIfBranch := &IfBranch{
				Condition: elseifCondition,
				Node:      elseifBlock,
			}
			ifNodeValue.ElseIfBranches = append(ifNodeValue.ElseIfBranches, elseIfBranch)
		} else {
			elseBlock, err := p.ParseBlockExpression()
			if err != nil {
				return nil, err
			}

			ifNodeValue.ElseBranch = &IfBranch{
				Condition: nil,
				Node:      elseBlock,
			}
			break
		}
	}

	return &Node{
		Type:  NODE_TYPE_IF_EXPR,
		Value: ifNodeValue,
		Loc:   beginLoc,
	}, nil
}

func (p *Parser) ParseBlockExpression() (*Node, error) {
	if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' to start block")
	}

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

	blockExpression, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}

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

	if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}' to end block")
	}

	return blockExpression, err
}

func (p *Parser) ParseStructLiteral() (*Node, error) {
	beginLoc := p.CurrLoc()
	name, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected struct name")
	}

	if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' to start struct literal")
	}

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

	value := &StructLiteralValue{}
	for {
		if p.Match(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
			p.Inc()
			break
		}

		fieldName, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected field name")
		}

		if !p.Expect(tokeniser.TOKEN_TYPE_EQUALS) {
			return nil, shared.NewError(p.PrevLoc(), "expected '='")
		}

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

		fieldValue, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}

		value.Fields = append(value.Fields, shared.Pair[*Node, *Node]{L: &Node{Type: NODE_TYPE_IDENTIFIER, Value: fieldName.Value}, R: fieldValue})

		if !p.Match(tokeniser.TOKEN_TYPE_COMMA, tokeniser.TOKEN_TYPE_NEWLINE) {
			break
		}
		p.Inc()

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

	if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}' to end struct literal")
	}

	return &Node{
		Type:  NODE_TYPE_STRUCT_LITERAL,
		Value: value,
		Left:  &Node{Type: NODE_TYPE_IDENTIFIER, Value: name.Value},
		Loc:   beginLoc,
	}, nil
}

func (p *Parser) ParseIdent() (*Node, error) {
	beginLoc := p.CurrLoc()
	firstIdent, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected identifier")
	}
	node := &Node{
		Type:  NODE_TYPE_IDENTIFIER,
		Value: firstIdent.Value,
		Loc:   beginLoc,
	}
	currnode := node

	for p.Match(tokeniser.TOKEN_TYPE_DOT) {
		p.Inc()

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

		loc := p.CurrLoc()
		nextIdent, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected identifier")
		}
		currnode.Right = &Node{
			Type:  NODE_TYPE_IDENTIFIER,
			Value: nextIdent.Value,
			Loc:   loc,
		}
		currnode = currnode.Right
	}

	return node, nil
}