package parser

import (
	"encoding/json"
	"fmt"
	"log"
	"unicode"
)

type Node struct {
	Typ         string       `json:"typ"`
	Data        string       `json:"data"`
	Name        string       `json:"name"`
	Start       int          `json:"start"`
	End         int          `json:"end"`
	Line        int          `json:"line"`
	SelfClosing bool         `json:"selfClosing"`
	Value       []MarkupItem `json:"value"`
	Attributes  []MarkupItem `json:"attributes"`
	Children    *[]Node      `json:"children"`
}

type elmStore struct {
	Idx  int
	Node *[]Node
}

type MarkupTreeState struct {
	Stack    []int
	ElmStack []elmStore
}

type MarkupTree struct {
	Path     string           `json:"-"`
	Markup   *Node            `json:"markup"`
	MetaData TemplateMetaData `json:"metaData"`
	State    MarkupTreeState  `json:"-"`
}

type OutputMarkupTree struct {
	HTML MarkupItem `json:"html"`
}

func (mt *MarkupTree) parseMarkupBlock(l *MarkupLexer) OutputMarkupTree {
	for item := range l.items {
		if item.Typ == MarkupItemType[MarkupEOF] {
			break
		}

		mt.writeToTree(item)
	}

	var mut = OutputMarkupTree{
		HTML: createItem(*mt.cleanUp()),
	}

	return mut
}

var lastElmWasOpening = false
var lastElmWasClosing = false
var lastElmWasSelfClosing = false

func (mt *MarkupTree) writeToTree(item MarkupItem) {

	// Handle closing tag
	if item.Typ == MarkupItemType[MUClosingTag] {
		elm := mt.State.ElmStack[len(mt.State.ElmStack)-1]
		(*elm.Node)[elm.Idx].End = item.End

		mt.State.ElmStack = mt.State.ElmStack[:len(mt.State.ElmStack)-1]
		if !lastElmWasOpening {
			mt.State.Stack = mt.State.Stack[:len(mt.State.Stack)-1]
		}

		// TODO: Throw error if closing tag is incorrect

		lastElmWasOpening = false
		lastElmWasClosing = true
		lastElmWasSelfClosing = false

		debug(mt, item, "ClosingTag")
		return
	}

	// Handle self closing elements
	if item.SelfClosing == true {
		// If the first lexeme in the document is a self closing item
		// Append text node
		if len(mt.State.Stack) == 0 {
			*mt.Markup.Children = append(*mt.Markup.Children, createNode(item))
			mt.State.Stack = append(mt.State.Stack, 0)
			lastElmWasSelfClosing = true
			debug(mt, item, "selfClosing")
			return
		}

		if lastElmWasClosing || lastElmWasSelfClosing {
			mt.State.Stack[len(mt.State.Stack)-1] += 1
		}

		i := 0
		branch := mt.Markup.Children
		for {

			// Append text node
			if i == len(mt.State.Stack)-1 {

				if lastElmWasSelfClosing || lastElmWasClosing {
					*branch = append(*branch, createNode(item))
					break
				}

				(*(*branch)[len(*branch)-1].Children) = append((*(*branch)[len(*branch)-1].Children), createNode(item))
				mt.State.Stack = append(mt.State.Stack, 0)
				break
			}

			branch = (*branch)[mt.State.Stack[i]].Children
			i++
		}

		lastElmWasOpening = false
		lastElmWasClosing = false
		lastElmWasSelfClosing = true
		debug(mt, item, "selfClosing")
		return
	}

	printAdjacent := false
	if lastElmWasSelfClosing || lastElmWasClosing {
		mt.State.Stack[len(mt.State.Stack)-1] += 1
		printAdjacent = true
	} else {
		mt.State.Stack = append(mt.State.Stack, 0)
	}
	lastElmWasOpening = true
	lastElmWasClosing = false
	lastElmWasSelfClosing = false

	// Handle elements and inline components
	i := 0
	branch := mt.Markup.Children
	for {

		if i == len(mt.State.Stack)-1 {
			(*branch) = append(*branch, createNode(item))

			mt.State.ElmStack = append(mt.State.ElmStack, elmStore{Idx: len(*branch) - 1, Node: branch})

			break
		}

		branch = (*branch)[mt.State.Stack[i]].Children
		i++
	}

	if printAdjacent {
		debug(mt, item, "adjacentElements")
	} else {
		debug(mt, item, "!adjacentElements")
	}
}

func createNode(item MarkupItem) Node {
	node := Node{
		Typ:         item.Typ,
		Data:        item.Data,
		Name:        item.Name,
		Start:       item.Start,
		End:         item.End,
		Line:        item.Line,
		SelfClosing: item.SelfClosing,
		Value:       item.Value,
		Attributes:  item.Attributes,
		Children:    &[]Node{},
	}

	for _, itm := range item.Children {
		*node.Children = append(*node.Children, createNode(itm))
	}
	return node
}

func createItem(node Node) MarkupItem {
	item := MarkupItem{
		Typ:         node.Typ,
		Data:        node.Data,
		Name:        node.Name,
		Start:       node.Start,
		End:         node.End,
		Line:        node.Line,
		SelfClosing: node.SelfClosing,
		Value:       node.Value,
		Attributes:  node.Attributes,
	}

	if node.Children != nil {
		for _, nod := range *node.Children {
			item.Children = append(item.Children, createItem(nod))
		}
	}
	return item
}

func (mt *MarkupTree) cleanUp() *Node {
	mt.State.Stack = []int{}
	mt.State.ElmStack = []elmStore{}

	lastElmWasClosing = false
	lastElmWasSelfClosing = false

	if len((*mt.Markup.Children)) > 0 && (*mt.Markup.Children)[len((*mt.Markup.Children))-1].Typ == MarkupItemType[MUText] {

		keep := false
		for _, ch := range (*mt.Markup.Children)[len((*mt.Markup.Children))-1].Data {
			if !unicode.IsSpace(ch) {
				keep = true
			}
		}

		if !keep {
			*mt.Markup.Children = (*mt.Markup.Children)[:len(*mt.Markup.Children)-1]
		}
	}

	return mt.Markup
}

func (mt *MarkupTree) Print(pretty bool) string {
	tree := OutputMarkupTree{}
	tree.HTML = createItem(*mt.Markup)

	if pretty {
		tre, err := json.MarshalIndent(tree, "", "  ")
		if err != nil {
			log.Fatalf("\nFailed to marshal tree struct\n%s\n", err)
		}

		return string(tre)
	}

	tre, err := json.Marshal(tree)
	if err != nil {
		log.Fatalf("\nFailed to marshal tree struct\n%s\n", err)
	}

	return string(tre)
}

func (mt *MarkupTree) errorf(format string, args ...interface{}) lexMarkupStateFn {
	log.Fatalf("\n%s\n", fmt.Sprintf(format, args...))
	return nil
}

func debug(mt *MarkupTree, item MarkupItem, pos string) {
	debug := false
	if !debug {
		return
	}

	fmt.Printf("PATH: %+v\n", mt.Path)
	fmt.Println("POS:", pos)
	fmt.Printf("ITEM: %+v\n", item)

	if len(mt.State.ElmStack) > 0 {
		fmt.Printf("STK: %+v\n", mt.State.Stack)
		elm := mt.State.ElmStack[len(mt.State.ElmStack)-1]
		fmt.Printf("ELM: %d %+v\n\n", elm.Idx, (*elm.Node))
	} else {
		fmt.Printf("STK: %+v\n", mt.State.Stack)
		fmt.Printf("ELM: %+v\n\n", mt.State.ElmStack)
	}
}
