package GOparser

import (
	"errors"
	"io/ioutil"
	"strings"

	"gitlab.wtotem.net/webtotem/backend-library-ycr/lexer"
	"gitlab.wtotem.net/webtotem/backend-library-ycr/token"
)

func pop(yamlSlice []Yaml) (Yaml, []Yaml) {
	// if len(yamlSlice) < 1 {
	// 	return Yaml{}, yamlSlice
	// }
	return yamlSlice[len(yamlSlice)-1], yamlSlice[:(len(yamlSlice) - 1)]
}

// true - is ok
// false - not ok
func (yaml *Yaml) checkIndentSpaces(spaces string) bool {
	valLen := len(spaces)
	return valLen > yaml.Spacing &&
		valLen%2 == 0
}

func (yaml *Yaml) parseFiles(filenames []string) error {
	var err error
	for _, file := range filenames {
		buf, err := ioutil.ReadFile(file)
		if err != nil {
			//fmt.Println(err)
			return err
		}

		str := string(buf)

		l := lexer.LexStart(str)
		l.Following = l.NextToken()

		err = yaml.parseTokens(l)
		if err != nil {
			return err
		}
	}
	return err
}

func newString(value string) (string, error) {
	runVal := []rune(value)
	if len(runVal) == 0 {
		return value, nil
	}
	newVal := make([]rune, len(value))
	j := 0
	isEscape := false
	if runVal[0] == '"' {
		for i, c := range value {
			if isEscape {
				switch c {
				case 't':
					c = '\t'
				case 'b':
					c = '\b'
				case 'n':
					c = '\n'
				case 'r':
					c = '\r'
				case 'f':
					c = '\f'
				case '"':
					c = '"'
				case '\\':
					c = '\\'
				default:
					return value, errors.New("unknown escape")
				}
				isEscape = false
			} else if c == '\\' {
				isEscape = true
				continue
			} else if c == '"' {
				if i == len(value)-1 {
					newVal = newVal[:j]
					return string(newVal), nil
				} else if i == 0 {
					continue
				} else {
					return value, errors.New("unexpected characters after quote")
				}
			}

			newVal[j] = c
			j++
		}
		return value, errors.New("missing quote at the end of string")
	}
	return value, nil
}

func (yaml *Yaml) parseTokens(l *lexer.Lexer) error {

	keyVal := ""
	tempSlice := make([]string, 0, 10)
	yamlSlice := make([]Yaml, 0, 10)

	for {
		cur := l.Following
		l.Following = l.NextToken()

		switch cur.Mod {
		case token.TOKEN_ERROR:
			return errors.New(cur.Value)

		case token.TOKEN_KEY:
			keyVal = strings.TrimSpace(cur.Value)

		case token.TOKEN_VALUE:
			value, err := newString(strings.TrimSpace(cur.Value))
			if err != nil {
				return err
			}
			(*yaml).Map[keyVal] = Property{
				mod: VAL_MOD,
				val: value,
			}
			keyVal = ""
			if len(l.Following.Value) < yaml.Spacing || l.Following.Mod == token.TOKEN_EOF {
				// pop yaml
				if len(yamlSlice) > 0 {
					*yaml, yamlSlice = pop(yamlSlice)
				}
			}

		case token.TOKEN_ARRAY:
			value, err := newString(strings.TrimSpace(cur.Value))
			if err != nil {
				return err
			}
			tempSlice = append(tempSlice, value)
			if l.Following.Mod != token.TOKEN_ARRAY {
				if len(l.Following.Value) <= yaml.Spacing || l.Following.Mod == token.TOKEN_EOF {
					(*yaml).Map[keyVal] = Property{
						mod: ARR_MOD,
						val: tempSlice,
					}
					tempSlice = make([]string, 0, 10)
				}
			}
		case token.TOKEN_COLON:
			if l.Following.Mod == token.TOKEN_SPACES && !yaml.checkIndentSpaces(l.Following.Value) {
				// if colon followed by spaces and indentation is not proper report
				return errors.New("expected value found new line")
			}

		case token.TOKEN_SPACES:
			spaceNum := len(cur.Value)

			// if less spaces pop
			for spaceNum < yaml.Spacing && len(yamlSlice) > 0 {
				*yaml, yamlSlice = pop(yamlSlice)
			}

			// if more spaces and key create map
			if l.Following.Mod == token.TOKEN_KEY {
				if yaml.checkIndentSpaces(cur.Value) && keyVal != "" {
					oldYaml := *yaml
					yamlSlice = append(yamlSlice, oldYaml)
					newYaml := NewYaml()
					newYaml.Spacing = len(cur.Value)
					*yaml = newYaml
					oldYaml.Map[keyVal] = Property{
						mod: MAP_MOD,
						val: *yaml,
					}
					keyVal = ""
				}
			}

		case token.TOKEN_EOF:
			for range yamlSlice {
				*yaml, yamlSlice = pop(yamlSlice)
			}
			return nil
		}
	}
}
