package parser

import (
	"fmt"

	"github.com/ChrisTrenkamp/goxpath/lexer"
)

type stateType int

const (
	defState stateType = iota
	xpathState
	funcState
	paramState
	predState
	parenState
)

type parseStack struct {
	stack      []*Node
	stateTypes []stateType
	cur        *Node
}

func (p *parseStack) push(t stateType) {
	p.stack = append(p.stack, p.cur)
	p.stateTypes = append(p.stateTypes, t)
}

func (p *parseStack) pop() {
	stackPos := len(p.stack) - 1

	p.cur = p.stack[stackPos]
	p.stack = p.stack[:stackPos]
	p.stateTypes = p.stateTypes[:stackPos]
}

func (p *parseStack) curState() stateType {
	if len(p.stateTypes) == 0 {
		return defState
	}
	return p.stateTypes[len(p.stateTypes)-1]
}

type lexFn func(*parseStack, lexer.XItem)

var parseMap = map[lexer.XItemType]lexFn{
	lexer.XItemAbsLocPath:     xiXPath,
	lexer.XItemAbbrAbsLocPath: xiXPath,
	lexer.XItemAbbrRelLocPath: xiXPath,
	lexer.XItemRelLocPath:     xiXPath,
	lexer.XItemEndPath:        xiEndPath,
	lexer.XItemAxis:           xiXPath,
	lexer.XItemAbbrAxis:       xiXPath,
	lexer.XItemNCName:         xiXPath,
	lexer.XItemQName:          xiXPath,
	lexer.XItemNodeType:       xiXPath,
	lexer.XItemProcLit:        xiXPath,
	lexer.XItemFunction:       xiFunc,
	lexer.XItemArgument:       xiFuncArg,
	lexer.XItemEndFunction:    xiEndFunc,
	lexer.XItemPredicate:      xiPred,
	lexer.XItemEndPredicate:   xiEndPred,
	lexer.XItemStrLit:         xiValue,
	lexer.XItemNumLit:         xiValue,
	lexer.XItemOperator:       xiOp,
	lexer.XItemVariable:       xiValue,
}

var opPrecedence = map[string]int{
	"|":   1,
	"*":   2,
	"div": 2,
	"mod": 2,
	"+":   3,
	"-":   3,
	"=":   4,
	"!=":  4,
	"<":   4,
	"<=":  4,
	">":   4,
	">=":  4,
	"and": 5,
	"or":  6,
}

//Parse creates an AST tree for XPath expressions.
func Parse(xp string) (*Node, error) {
	var err error
	c := lexer.Lex(xp)
	n := &Node{}
	p := &parseStack{cur: n}

	for next := range c {
		if next.Typ != lexer.XItemError {
			parseMap[next.Typ](p, next)
		} else if err == nil {
			err = fmt.Errorf(next.Val)
		}
	}

	return n, err
}

func xiXPath(p *parseStack, i lexer.XItem) {
	if p.curState() == xpathState {
		p.cur.push(i)
		p.cur = p.cur.next
		return
	}

	if p.cur.Val.Typ == lexer.XItemFunction {
		p.cur.Right = &Node{Val: i, Parent: p.cur}
		p.cur.next = p.cur.Right
		p.push(xpathState)
		p.cur = p.cur.next
		return
	}

	p.cur.pushNotEmpty(i)
	p.push(xpathState)
	p.cur = p.cur.next
}

func xiEndPath(p *parseStack, i lexer.XItem) {
	p.pop()
}

func xiFunc(p *parseStack, i lexer.XItem) {
	if p.cur.Val.Typ == Empty {
		p.cur.pushNotEmpty(i)
		p.push(funcState)
		p.cur = p.cur.next
		return
	}
	p.cur.push(i)
	p.cur = p.cur.next
	p.push(funcState)
}

func xiFuncArg(p *parseStack, i lexer.XItem) {
	if p.curState() != funcState {
		p.pop()
	}

	p.cur.push(i)
	p.cur = p.cur.next
	p.push(paramState)
	p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
	p.cur = p.cur.next
}

func xiEndFunc(p *parseStack, i lexer.XItem) {
	if p.curState() == paramState {
		p.pop()
	}
	p.pop()
}

func xiPred(p *parseStack, i lexer.XItem) {
	p.cur.push(i)
	p.cur = p.cur.next
	p.push(predState)
	p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
	p.cur = p.cur.next
}

func xiEndPred(p *parseStack, i lexer.XItem) {
	p.pop()
}

func xiValue(p *parseStack, i lexer.XItem) {
	p.cur.add(i)
}

func xiOp(p *parseStack, i lexer.XItem) {
	if i.Val == "(" {
		p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
		p.push(parenState)
		p.cur = p.cur.next
		return
	}

	if i.Val == ")" {
		p.pop()
		return
	}

	if p.cur.Val.Typ == lexer.XItemOperator {
		if opPrecedence[p.cur.Val.Val] <= opPrecedence[i.Val] {
			p.cur.add(i)
		} else {
			p.cur.push(i)
		}
	} else {
		p.cur.add(i)
	}
	p.cur = p.cur.next
}
