package jade

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

func (t *Tree) topParse() {
	t.Root = t.newList(t.peek().pos)
	var (
		ext   bool
		token = t.nextNonSpace()
	)
	if token.typ == itemExtends {
		ext = true
		t.Root.append(t.parseSubFile(token.val))
		token = t.nextNonSpace()
	}
	for {
		switch token.typ {
		case itemInclude:
			t.Root.append(t.parseInclude(token))
		case itemBlock, itemBlockPrepend, itemBlockAppend:
			if ext {
				t.parseBlock(token)
			} else {
				t.Root.append(t.parseBlock(token))
			}
		case itemMixin:
			t.mixin[token.val] = t.parseMixin(token)
		case itemEOF:
			return
		case itemExtends:
			t.errorf(`Declaration of template inheritance ("extends") should be the first thing in the file. There can only be one extends statement per file.`)
		case itemError:
			t.errorf("%s line: %d\n", token.val, token.line)
		default:
			if ext {
				t.errorf(`Only import, named blocks and mixins can appear at the top level of an extending template`)
			}
			t.Root.append(t.hub(token))
		}
		token = t.nextNonSpace()
	}
}

func (t *Tree) hub(token item) (n Node) {
	for {
		switch token.typ {
		case itemDiv:
			token.val = "div"
			fallthrough
		case itemTag, itemTagInline, itemTagVoid, itemTagVoidInline:
			return t.parseTag(token)
		case itemText, itemComment, itemHTMLTag:
			return t.newText(token.pos, []byte(token.val), token.typ)
		case itemCode, itemCodeBuffered, itemCodeUnescaped, itemMixinBlock:
			return t.newCode(token.pos, token.val, token.typ)
		case itemIf, itemUnless:
			return t.parseIf(token)
		case itemFor, itemEach, itemWhile:
			return t.parseFor(token)
		case itemCase:
			return t.parseCase(token)
		case itemBlock, itemBlockPrepend, itemBlockAppend:
			return t.parseBlock(token)
		case itemMixinCall:
			return t.parseMixinUse(token)
		case itemInclude:
			return t.parseInclude(token)
		case itemDoctype:
			return t.newDoctype(token.pos, token.val)
		case itemFilter, itemFilterText:
			return t.parseFilter(token)
		case itemError:
			t.errorf("Error lex: %s line: %d\n", token.val, token.line)
		default:
			t.errorf(`Error hub(): unexpected token  "%s"  type  "%s"`, token.val, token.typ)
		}
	}
}

func (t *Tree) parseFilter(tk item) Node {
	// TODO add golang filters
	return t.newList(tk.pos)
}

func (t *Tree) parseTag(tk item) Node {
	var (
		deep = tk.depth
		tag  = t.newTag(tk.pos, tk.val, tk.typ)
	)
Loop:
	for {
		switch token := t.nextNonSpace(); {
		case token.depth > deep:
			if tag.tagType == itemTagVoid || tag.tagType == itemTagVoidInline {
				break Loop
			}
			tag.append(t.hub(token))
		case token.depth == deep:
			switch token.typ {
			case itemClass:
				tag.attr("class", `"`+token.val+`"`, false)
			case itemID:
				tag.attr("id", `"`+token.val+`"`, false)
			case itemAttrStart:
				t.parseAttributes(tag)
			case itemTagEnd:
				tag.tagType = itemTagVoid
				return tag
			default:
				break Loop
			}
		default:
			break Loop
		}
	}
	t.backup()
	return tag
}

type pAttr interface {
	attr(string, string, bool)
}

func (t *Tree) parseAttributes(tag pAttr) {
	var (
		aname string
		equal bool
		unesc bool
		stack = make([]string, 0, 4)
	)
	for {
		switch token := t.next(); token.typ {
		case itemAttrSpace:
			// skip
		case itemAttr:
			switch {
			case aname == "":
				aname = token.val
			case aname != "" && !equal:
				tag.attr(aname, `"`+aname+`"`, unesc)
				aname = token.val
			case aname != "" && equal:
				stack = append(stack, token.val)
			}
		case itemAttrEqual, itemAttrEqualUn:
			if token.typ == itemAttrEqual {
				unesc = false
			} else {
				unesc = true
			}
			equal = true
			switch len_stack := len(stack); {
			case len_stack == 0 && aname != "":
				// skip
			case len_stack > 1 && aname != "":
				tag.attr(aname, strings.Join(stack[:len(stack)-1], " "), unesc)

				aname = stack[len(stack)-1]
				stack = stack[:0]
			case len_stack == 1 && aname == "":
				aname = stack[0]
				stack = stack[:0]
			default:
				t.errorf("unexpected '='")
			}
		case itemAttrComma:
			equal = false
			switch len_stack := len(stack); {
			case len_stack > 0 && aname != "":
				tag.attr(aname, strings.Join(stack, " "), unesc)
				aname = ""
				stack = stack[:0]
			case len_stack == 0 && aname != "":
				tag.attr(aname, `"`+aname+`"`, unesc)
				aname = ""
			}
		case itemAttrEnd:
			switch len_stack := len(stack); {
			case len_stack > 0 && aname != "":
				tag.attr(aname, strings.Join(stack, " "), unesc)
			case len_stack > 0 && aname == "":
				for _, a := range stack {
					tag.attr(a, a, unesc)
				}
			case len_stack == 0 && aname != "":
				tag.attr(aname, `"`+aname+`"`, unesc)
			}
			return
		default:
			t.errorf("unexpected %s", token.val)
		}
	}
}

func (t *Tree) parseIf(tk item) Node {
	var (
		deep = tk.depth
		cond = t.newCond(tk.pos, tk.val, tk.typ)
	)
Loop:
	for {
		switch token := t.nextNonSpace(); {
		case token.depth > deep:
			cond.append(t.hub(token))
		case token.depth == deep:
			switch token.typ {
			case itemElse:
				ni := t.peek()
				if ni.typ == itemIf {
					token = t.next()
					cond.append(t.newCode(token.pos, token.val, itemElseIf))
				} else {
					cond.append(t.newCode(token.pos, token.val, token.typ))
				}
			default:
				break Loop
			}
		default:
			break Loop
		}
	}
	t.backup()
	return cond
}

func (t *Tree) parseFor(tk item) Node {
	var (
		deep = tk.depth
		cond = t.newCond(tk.pos, tk.val, tk.typ)
	)
Loop:
	for {
		switch token := t.nextNonSpace(); {
		case token.depth > deep:
			cond.append(t.hub(token))
		case token.depth == deep:
			if token.typ == itemElse {
				cond.condType = itemForIfNotContain
				cond.append(t.newCode(token.pos, token.val, itemForElse))
			} else {
				break Loop
			}
		default:
			break Loop
		}
	}
	t.backup()
	return cond
}

func (t *Tree) parseCase(tk item) Node {
	var (
		deep  = tk.depth
		iCase = t.newCond(tk.pos, tk.val, tk.typ)
	)
	for {
		if token := t.nextNonSpace(); token.depth > deep {
			switch token.typ {
			case itemCaseWhen, itemCaseDefault:
				iCase.append(t.newCode(token.pos, token.val, token.typ))
			default:
				iCase.append(t.hub(token))
			}
		} else {
			break
		}
	}
	t.backup()
	return iCase
}

func (t *Tree) parseMixin(tk item) *MixinNode {
	var (
		deep  = tk.depth
		mixin = t.newMixin(tk.pos)
	)
Loop:
	for {
		switch token := t.nextNonSpace(); {
		case token.depth > deep:
			mixin.append(t.hub(token))
		case token.depth == deep:
			if token.typ == itemAttrStart {
				t.parseAttributes(mixin)
			} else {
				break Loop
			}
		default:
			break Loop
		}
	}
	t.backup()
	return mixin
}

func (t *Tree) parseMixinUse(tk item) Node {
	tMix, ok := t.mixin[tk.val]
	if !ok {
		t.errorf(`Mixin "%s" must be declared before use.`, tk.val)
	}
	var (
		deep  = tk.depth
		mixin = tMix.CopyMixin()
	)
Loop:
	for {
		switch token := t.nextNonSpace(); {
		case token.depth > deep:
			mixin.appendToBlock(t.hub(token))
		case token.depth == deep:
			if token.typ == itemAttrStart {
				t.parseAttributes(mixin)
			} else {
				break Loop
			}
		default:
			break Loop
		}
	}
	t.backup()

	use := len(mixin.AttrName)
	tpl := len(tMix.AttrName)
	switch {
	case use < tpl:
		i := 0
		diff := tpl - use
		mixin.AttrCode = append(mixin.AttrCode, make([]string, diff)...) // Extend slice
		for index := 0; index < diff; index++ {
			i = tpl - index - 1
			if tMix.AttrName[i] != tMix.AttrCode[i] {
				mixin.AttrCode[i] = tMix.AttrCode[i]
			} else {
				mixin.AttrCode[i] = `""`
			}
		}
		mixin.AttrName = tMix.AttrName
	case use > tpl:
		if tpl <= 0 {
			break
		}
		if strings.HasPrefix(tMix.AttrName[tpl-1], "...") {
			mixin.AttrRest = mixin.AttrCode[tpl-1:]
		}
		mixin.AttrCode = mixin.AttrCode[:tpl]
		mixin.AttrName = tMix.AttrName
	case use == tpl:
		mixin.AttrName = tMix.AttrName
	}
	return mixin
}

func (t *Tree) parseBlock(tk item) *BlockNode {
	block := t.newList(tk.pos)
	for {
		token := t.nextNonSpace()
		if token.depth > tk.depth {
			block.append(t.hub(token))
		} else {
			break
		}
	}
	t.backup()
	var suf string
	switch tk.typ {
	case itemBlockPrepend:
		suf = "_prepend"
	case itemBlockAppend:
		suf = "_append"
	}
	t.block[tk.val+suf] = block
	return t.newBlock(tk.pos, tk.val, tk.typ)
}

func (t *Tree) parseInclude(tk item) *ListNode {
	switch ext := filepath.Ext(tk.val); ext {
	case ".jade", ".pug", "":
		return t.parseSubFile(tk.val)
	case ".js", ".css", ".tpl", ".md":
		ln := t.newList(tk.pos)
		ln.append(t.newText(tk.pos, t.read(tk.val), itemText))
		return ln
	default:
		t.errorf(`file extension is not supported`)
		return nil
	}
}

func (t *Tree) parseSubFile(path string) *ListNode {
	var incTree = New(path)
	incTree.block = t.block
	incTree.mixin = t.mixin
	_, err := incTree.Parse(t.read(path))
	if err != nil {
		t.errorf(`%s`, err)
	}
	return incTree.Root
}

func (t *Tree) read(path string) []byte {
	var (
		bb  []byte
		ext string
		err error
	)
	switch ext = filepath.Ext(path); ext {
	case ".jade", ".pug", ".js", ".css", ".tpl", ".md":
		bb, err = ioutil.ReadFile(path)
	case "":
		if _, err = os.Stat(path + ".jade"); os.IsNotExist(err) {
			if _, err = os.Stat(path + ".pug"); os.IsNotExist(err) {
				t.errorf("try " + path + `.*: ".jade" or ".pug" file required`)
			} else {
				ext = ".pug"
			}
		} else {
			ext = ".jade"
		}
		bb, err = ioutil.ReadFile(path + ext)
	default:
		t.errorf(`file extension  %s  is not supported`, ext)
	}
	if err != nil {
		dir, _ := os.Getwd()
		t.errorf(`%s  work dir: %s `, err, dir)
	}

	return bb
}
