// Copyright (c)  2013 Julien Schmidt, Copyright (c) 2016 Gerasimos Maropoulos,

package iris

import (
	"bytes"
	"strings"

	"github.com/kataras/iris/utils"
)

const (
	isStatic BranchCase = iota
	isRoot
	hasParams
	matchEverything
)

type (
	// PathParameter is a struct which contains Key and Value, used for named path parameters
	PathParameter struct {
		Key   string
		Value string
	}

	// PathParameters type for a slice of PathParameter
	// Tt's a slice of PathParameter type, because it's faster than map
	PathParameters []PathParameter

	// BranchCase is the type which the type of Branch using in order to determinate what type (parameterized, anything, static...) is the perticular node
	BranchCase uint8

	// IBranch is the interface which the type Branch must implement
	IBranch interface {
		AddBranch(string, Middleware)
		AddNode(uint8, string, string, Middleware)
		GetBranch(string, PathParameters) (Middleware, PathParameters, bool)
		GivePrecedenceTo(index int) int
	}

	// Branch is the node of a tree of the routes,
	// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
	// this method is used by the BSD's kernel also
	Branch struct {
		part        string
		BranchCase  BranchCase
		hasWildNode bool
		tokens      string
		nodes       []*Branch
		middleware  Middleware
		precedence  uint64
		paramsLen   uint8
	}
)

var _ IBranch = &Branch{}

// Get returns a value from a key inside this Parameters
// If no parameter with this key given then it returns an empty string
func (params PathParameters) Get(key string) string {
	for _, p := range params {
		if p.Key == key {
			return p.Value
		}
	}
	return ""
}

// String returns a string implementation of all parameters that this PathParameters object keeps
// hasthe form of key1=value1,key2=value2...
func (params PathParameters) String() string {
	var buff bytes.Buffer
	for i := range params {
		buff.WriteString(params[i].Key)
		buff.WriteString("=")
		buff.WriteString(params[i].Value)
		if i < len(params)-1 {
			buff.WriteString(",")
		}

	}
	return buff.String()
}

// ParseParams receives a string and returns PathParameters (slice of PathParameter)
// received string must have this form:  key1=value1,key2=value2...
func ParseParams(str string) PathParameters {
	_paramsstr := strings.Split(str, ",")
	if len(_paramsstr) == 0 {
		return nil
	}

	params := make(PathParameters, 0) // PathParameters{}

	//	for i := 0; i < len(_paramsstr); i++ {
	for i := range _paramsstr {
		idxOfEq := strings.IndexRune(_paramsstr[i], '=')
		if idxOfEq == -1 {
			//error
			return nil
		}

		key := _paramsstr[i][:idxOfEq]
		val := _paramsstr[i][idxOfEq+1:]
		params = append(params, PathParameter{key, val})
	}
	return params
}

// GetParamsLen returns the parameters length from a given path
func GetParamsLen(path string) uint8 {
	var n uint
	for i := 0; i < len(path); i++ {
		if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
			continue
		}
		n++
	}
	if n >= 255 {
		return 255
	}
	return uint8(n)
}

// AddBranch adds a branch to the existing branch or to the tree if no branch has the prefix of
func (b *Branch) AddBranch(path string, middleware Middleware) {
	fullPath := path
	b.precedence++
	numParams := GetParamsLen(path)

	if len(b.part) > 0 || len(b.nodes) > 0 {
	loop:
		for {
			if numParams > b.paramsLen {
				b.paramsLen = numParams
			}

			i := 0
			max := utils.FindLower(len(path), len(b.part))
			for i < max && path[i] == b.part[i] {
				i++
			}

			if i < len(b.part) {
				node := Branch{
					part:        b.part[i:],
					hasWildNode: b.hasWildNode,
					tokens:      b.tokens,
					nodes:       b.nodes,
					middleware:  b.middleware,
					precedence:  b.precedence - 1,
				}

				for i := range node.nodes {
					if node.nodes[i].paramsLen > node.paramsLen {
						node.paramsLen = node.nodes[i].paramsLen
					}
				}

				b.nodes = []*Branch{&node}
				b.tokens = string([]byte{b.part[i]})
				b.part = path[:i]
				b.middleware = nil
				b.hasWildNode = false
			}

			if i < len(path) {
				path = path[i:]

				if b.hasWildNode {
					b = b.nodes[0]
					b.precedence++

					if numParams > b.paramsLen {
						b.paramsLen = numParams
					}
					numParams--

					if len(path) >= len(b.part) && b.part == path[:len(b.part)] {

						if len(b.part) >= len(path) || path[len(b.part)] == '/' {
							continue loop
						}
					}

					return
				}

				c := path[0]

				if b.BranchCase == hasParams && c == '/' && len(b.nodes) == 1 {
					b = b.nodes[0]
					b.precedence++
					continue loop
				}
				//we need the i here to be re-setting, so use the same i variable as we declare it on line 176
				for i := range b.tokens {
					if c == b.tokens[i] {
						i = b.GivePrecedenceTo(i)
						b = b.nodes[i]
						continue loop
					}
				}

				if c != ParameterStartByte && c != MatchEverythingByte {

					b.tokens += string([]byte{c})
					node := &Branch{
						paramsLen: numParams,
					}
					b.nodes = append(b.nodes, node)
					b.GivePrecedenceTo(len(b.tokens) - 1)
					b = node
				}
				b.AddNode(numParams, path, fullPath, middleware)
				return

			} else if i == len(path) {
				if b.middleware != nil {
					return
				}
				b.middleware = middleware
			}
			return
		}
	} else {
		b.AddNode(numParams, path, fullPath, middleware)
		b.BranchCase = isRoot
	}
}

// AddNode adds a branch as children to other Branch
func (b *Branch) AddNode(numParams uint8, path string, fullPath string, middleware Middleware) {
	var offset int

	for i, max := 0, len(path); numParams > 0; i++ {
		c := path[i]
		if c != ParameterStartByte && c != MatchEverythingByte {
			continue
		}

		end := i + 1
		for end < max && path[end] != '/' {
			switch path[end] {
			case ParameterStartByte, MatchEverythingByte:

			default:
				end++
			}
		}

		if len(b.nodes) > 0 {
			return
		}

		if end-i < 2 {
			return
		}

		if c == ParameterStartByte {

			if i > 0 {
				b.part = path[offset:i]
				offset = i
			}

			child := &Branch{
				BranchCase: hasParams,
				paramsLen:  numParams,
			}
			b.nodes = []*Branch{child}
			b.hasWildNode = true
			b = child
			b.precedence++
			numParams--

			if end < max {
				b.part = path[offset:end]
				offset = end

				child := &Branch{
					paramsLen:  numParams,
					precedence: 1,
				}
				b.nodes = []*Branch{child}
				b = child
			}

		} else {
			if end != max || numParams > 1 {
				return
			}

			if len(b.part) > 0 && b.part[len(b.part)-1] == '/' {
				return
			}

			i--
			if path[i] != '/' {
				return
			}

			b.part = path[offset:i]

			child := &Branch{
				hasWildNode: true,
				BranchCase:  matchEverything,
				paramsLen:   1,
			}
			b.nodes = []*Branch{child}
			b.tokens = string(path[i])
			b = child
			b.precedence++

			child = &Branch{
				part:       path[i:],
				BranchCase: matchEverything,
				paramsLen:  1,
				middleware: middleware,
				precedence: 1,
			}
			b.nodes = []*Branch{child}

			return
		}
	}

	b.part = path[offset:]
	b.middleware = middleware
}

// GetBranch is used by the Router, it finds and returns the correct branch for a path
func (b *Branch) GetBranch(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
	params = _params
loop:
	for {
		if len(path) > len(b.part) {
			if path[:len(b.part)] == b.part {
				path = path[len(b.part):]

				if !b.hasWildNode {
					c := path[0]
					for i := range b.tokens {
						if c == b.tokens[i] {
							b = b.nodes[i]
							continue loop
						}
					}

					mustRedirect = (path == Slash && b.middleware != nil)
					return
				}

				b = b.nodes[0]
				switch b.BranchCase {
				case hasParams:

					end := 0
					for end < len(path) && path[end] != '/' {
						end++
					}

					if cap(params) < int(b.paramsLen) {
						params = make(PathParameters, 0, b.paramsLen)
					}
					i := len(params)
					params = params[:i+1]
					params[i].Key = b.part[1:]
					params[i].Value = path[:end]

					if end < len(path) {
						if len(b.nodes) > 0 {
							path = path[end:]
							b = b.nodes[0]
							continue loop
						}

						mustRedirect = (len(path) == end+1)
						return
					}

					if middleware = b.middleware; middleware != nil {
						return
					} else if len(b.nodes) == 1 {
						b = b.nodes[0]
						mustRedirect = (b.part == Slash && b.middleware != nil)
					}

					return

				case matchEverything:
					if cap(params) < int(b.paramsLen) {
						params = make(PathParameters, 0, b.paramsLen)
					}
					i := len(params)
					params = params[:i+1]
					params[i].Key = b.part[2:]
					params[i].Value = path

					middleware = b.middleware
					return

				default:
					return
				}
			}
		} else if path == b.part {
			if middleware = b.middleware; middleware != nil {
				return
			}

			if path == Slash && b.hasWildNode && b.BranchCase != isRoot {
				mustRedirect = true
				return
			}

			for i := range b.tokens {
				if b.tokens[i] == '/' {
					b = b.nodes[i]
					mustRedirect = (len(b.part) == 1 && b.middleware != nil) ||
						(b.BranchCase == matchEverything && b.nodes[0].middleware != nil)
					return
				}
			}

			return
		}

		mustRedirect = (path == Slash) ||
			(len(b.part) == len(path)+1 && b.part[len(path)] == '/' &&
				path == b.part[:len(b.part)-1] && b.middleware != nil)
		return
	}
}

// GivePrecedenceTo just adds the priority of this branch by an index
func (b *Branch) GivePrecedenceTo(index int) int {
	b.nodes[index].precedence++
	_precedence := b.nodes[index].precedence

	newindex := index
	for newindex > 0 && b.nodes[newindex-1].precedence < _precedence {
		tmpN := b.nodes[newindex-1]
		b.nodes[newindex-1] = b.nodes[newindex]
		b.nodes[newindex] = tmpN

		newindex--
	}

	if newindex != index {
		b.tokens = b.tokens[:newindex] +
			b.tokens[index:index+1] +
			b.tokens[newindex:index] + b.tokens[index+1:]
	}

	return newindex
}