// Copyright 2015 The Macaron Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package macaron

import (
	"regexp"
	"strings"

	"github.com/unknwon/com"
)

type patternType int8

const (
	_PATTERN_STATIC    patternType = iota // /home
	_PATTERN_REGEXP                       // /:id([0-9]+)
	_PATTERN_PATH_EXT                     // /*.*
	_PATTERN_HOLDER                       // /:user
	_PATTERN_MATCH_ALL                    // /*
)

// Leaf represents a leaf route information.
type Leaf struct {
	parent *Tree

	typ        patternType
	pattern    string
	rawPattern string // Contains wildcard instead of regexp
	wildcards  []string
	reg        *regexp.Regexp
	optional   bool

	handle Handle
}

var wildcardPattern = regexp.MustCompile(`:[a-zA-Z0-9]+`)

func isSpecialRegexp(pattern, regStr string, pos []int) bool {
	return len(pattern) >= pos[1]+len(regStr) && pattern[pos[1]:pos[1]+len(regStr)] == regStr
}

// getNextWildcard tries to find next wildcard and update pattern with corresponding regexp.
func getNextWildcard(pattern string) (wildcard, _ string) {
	pos := wildcardPattern.FindStringIndex(pattern)
	if pos == nil {
		return "", pattern
	}
	wildcard = pattern[pos[0]:pos[1]]

	// Reach last character or no regexp is given.
	if len(pattern) == pos[1] {
		return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)
	} else if pattern[pos[1]] != '(' {
		switch {
		case isSpecialRegexp(pattern, ":int", pos):
			pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1)
		case isSpecialRegexp(pattern, ":string", pos):
			pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1)
		default:
			return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)
		}
	}

	// Cut out placeholder directly.
	return wildcard, pattern[:pos[0]] + pattern[pos[1]:]
}

func getWildcards(pattern string) (string, []string) {
	wildcards := make([]string, 0, 2)

	// Keep getting next wildcard until nothing is left.
	var wildcard string
	for {
		wildcard, pattern = getNextWildcard(pattern)
		if len(wildcard) > 0 {
			wildcards = append(wildcards, wildcard)
		} else {
			break
		}
	}

	return pattern, wildcards
}

// getRawPattern removes all regexp but keeps wildcards for building URL path.
func getRawPattern(rawPattern string) string {
	rawPattern = strings.Replace(rawPattern, ":int", "", -1)
	rawPattern = strings.Replace(rawPattern, ":string", "", -1)

	for {
		startIdx := strings.Index(rawPattern, "(")
		if startIdx == -1 {
			break
		}

		closeIdx := strings.Index(rawPattern, ")")
		if closeIdx > -1 {
			rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx+1:]
		}
	}
	return rawPattern
}

func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) {
	pattern = strings.TrimLeft(pattern, "?")
	rawPattern = getRawPattern(pattern)

	if pattern == "*" {
		typ = _PATTERN_MATCH_ALL
	} else if pattern == "*.*" {
		typ = _PATTERN_PATH_EXT
	} else if strings.Contains(pattern, ":") {
		typ = _PATTERN_REGEXP
		pattern, wildcards = getWildcards(pattern)
		if pattern == "(.+)" {
			typ = _PATTERN_HOLDER
		} else {
			reg = regexp.MustCompile(pattern)
		}
	}
	return typ, rawPattern, wildcards, reg
}

func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf {
	typ, rawPattern, wildcards, reg := checkPattern(pattern)
	optional := false
	if len(pattern) > 0 && pattern[0] == '?' {
		optional = true
	}
	return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle}
}

// URLPath build path part of URL by given pair values.
func (l *Leaf) URLPath(pairs ...string) string {
	if len(pairs)%2 != 0 {
		panic("number of pairs does not match")
	}

	urlPath := l.rawPattern
	parent := l.parent
	for parent != nil {
		urlPath = parent.rawPattern + "/" + urlPath
		parent = parent.parent
	}
	for i := 0; i < len(pairs); i += 2 {
		if len(pairs[i]) == 0 {
			panic("pair value cannot be empty: " + com.ToStr(i))
		} else if pairs[i][0] != ':' && pairs[i] != "*" && pairs[i] != "*.*" {
			pairs[i] = ":" + pairs[i]
		}
		urlPath = strings.Replace(urlPath, pairs[i], pairs[i+1], 1)
	}
	return urlPath
}

// Tree represents a router tree in Macaron.
type Tree struct {
	parent *Tree

	typ        patternType
	pattern    string
	rawPattern string
	wildcards  []string
	reg        *regexp.Regexp

	subtrees []*Tree
	leaves   []*Leaf
}

func NewSubtree(parent *Tree, pattern string) *Tree {
	typ, rawPattern, wildcards, reg := checkPattern(pattern)
	return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)}
}

func NewTree() *Tree {
	return NewSubtree(nil, "")
}

func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf {
	for i := 0; i < len(t.leaves); i++ {
		if t.leaves[i].pattern == pattern {
			return t.leaves[i]
		}
	}

	leaf := NewLeaf(t, pattern, handle)

	// Add exact same leaf to grandparent/parent level without optional.
	if leaf.optional {
		parent := leaf.parent
		if parent.parent != nil {
			parent.parent.addLeaf(parent.pattern, handle)
		} else {
			parent.addLeaf("", handle) // Root tree can add as empty pattern.
		}
	}

	i := 0
	for ; i < len(t.leaves); i++ {
		if leaf.typ < t.leaves[i].typ {
			break
		}
	}

	if i == len(t.leaves) {
		t.leaves = append(t.leaves, leaf)
	} else {
		t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...)
	}
	return leaf
}

func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf {
	for i := 0; i < len(t.subtrees); i++ {
		if t.subtrees[i].pattern == segment {
			return t.subtrees[i].addNextSegment(pattern, handle)
		}
	}

	subtree := NewSubtree(t, segment)
	i := 0
	for ; i < len(t.subtrees); i++ {
		if subtree.typ < t.subtrees[i].typ {
			break
		}
	}

	if i == len(t.subtrees) {
		t.subtrees = append(t.subtrees, subtree)
	} else {
		t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...)
	}
	return subtree.addNextSegment(pattern, handle)
}

func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf {
	pattern = strings.TrimPrefix(pattern, "/")

	i := strings.Index(pattern, "/")
	if i == -1 {
		return t.addLeaf(pattern, handle)
	}
	return t.addSubtree(pattern[:i], pattern[i+1:], handle)
}

func (t *Tree) Add(pattern string, handle Handle) *Leaf {
	pattern = strings.TrimSuffix(pattern, "/")
	return t.addNextSegment(pattern, handle)
}

func (t *Tree) matchLeaf(globLevel int, url string, params Params) (Handle, bool) {
	url, err := PathUnescape(url)
	if err != nil {
		return nil, false
	}
	for i := 0; i < len(t.leaves); i++ {
		switch t.leaves[i].typ {
		case _PATTERN_STATIC:
			if t.leaves[i].pattern == url {
				return t.leaves[i].handle, true
			}
		case _PATTERN_REGEXP:
			results := t.leaves[i].reg.FindStringSubmatch(url)
			// Number of results and wildcasrd should be exact same.
			if len(results)-1 != len(t.leaves[i].wildcards) {
				break
			}

			for j := 0; j < len(t.leaves[i].wildcards); j++ {
				params[t.leaves[i].wildcards[j]] = results[j+1]
			}
			return t.leaves[i].handle, true
		case _PATTERN_PATH_EXT:
			j := strings.LastIndex(url, ".")
			if j > -1 {
				params[":path"] = url[:j]
				params[":ext"] = url[j+1:]
			} else {
				params[":path"] = url
			}
			return t.leaves[i].handle, true
		case _PATTERN_HOLDER:
			params[t.leaves[i].wildcards[0]] = url
			return t.leaves[i].handle, true
		case _PATTERN_MATCH_ALL:
			params["*"] = url
			params["*"+com.ToStr(globLevel)] = url
			return t.leaves[i].handle, true
		}
	}
	return nil, false
}

func (t *Tree) matchSubtree(globLevel int, segment, url string, params Params) (Handle, bool) {
	unescapedSegment, err := PathUnescape(segment)
	if err != nil {
		return nil, false
	}
	for i := 0; i < len(t.subtrees); i++ {
		switch t.subtrees[i].typ {
		case _PATTERN_STATIC:
			if t.subtrees[i].pattern == unescapedSegment {
				if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {
					return handle, true
				}
			}
		case _PATTERN_REGEXP:
			results := t.subtrees[i].reg.FindStringSubmatch(unescapedSegment)
			if len(results)-1 != len(t.subtrees[i].wildcards) {
				break
			}

			for j := 0; j < len(t.subtrees[i].wildcards); j++ {
				params[t.subtrees[i].wildcards[j]] = results[j+1]
			}
			if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {
				return handle, true
			}
		case _PATTERN_HOLDER:
			if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
				params[t.subtrees[i].wildcards[0]] = unescapedSegment
				return handle, true
			}
		case _PATTERN_MATCH_ALL:
			if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
				params["*"+com.ToStr(globLevel)] = unescapedSegment
				return handle, true
			}
		}
	}

	if len(t.leaves) > 0 {
		leaf := t.leaves[len(t.leaves)-1]
		unescapedURL, err := PathUnescape(segment + "/" + url)
		if err != nil {
			return nil, false
		}
		if leaf.typ == _PATTERN_PATH_EXT {
			j := strings.LastIndex(unescapedURL, ".")
			if j > -1 {
				params[":path"] = unescapedURL[:j]
				params[":ext"] = unescapedURL[j+1:]
			} else {
				params[":path"] = unescapedURL
			}
			return leaf.handle, true
		} else if leaf.typ == _PATTERN_MATCH_ALL {
			params["*"] = unescapedURL
			params["*"+com.ToStr(globLevel)] = unescapedURL
			return leaf.handle, true
		}
	}
	return nil, false
}

func (t *Tree) matchNextSegment(globLevel int, url string, params Params) (Handle, bool) {
	i := strings.Index(url, "/")
	if i == -1 {
		return t.matchLeaf(globLevel, url, params)
	}
	return t.matchSubtree(globLevel, url[:i], url[i+1:], params)
}

func (t *Tree) Match(url string) (Handle, Params, bool) {
	url = strings.TrimPrefix(url, "/")
	url = strings.TrimSuffix(url, "/")
	params := make(Params)
	handle, ok := t.matchNextSegment(0, url, params)
	return handle, params, ok
}

// MatchTest returns true if given URL is matched by given pattern.
func MatchTest(pattern, url string) bool {
	t := NewTree()
	t.Add(pattern, nil)
	_, _, ok := t.Match(url)
	return ok
}
