package methodfunc

import (
	"bytes"
	"fmt"
	"strings"
	"unicode"
)

const (
	by        = "By"
	wildcard  = "Wildcard"
	paramName = "param"
)

type pathInfo struct {
	GoParamType string
	ParamType   string
	RelPath     string
}

const (
	paramTypeInt    = "int"
	paramTypeLong   = "long"
	paramTypeString = "string"
	paramTypePath   = "path"
)

var macroTypes = map[string]string{
	"int":    paramTypeInt,
	"int64":  paramTypeLong,
	"string": paramTypeString,
	// there is "path" param type but it's being captured "on-air"
	// "file" param type is not supported by the current implementation, yet
	// but if someone ask for it I'll implement it, it's easy.
}

func resolveRelativePath(info FuncInfo) (p pathInfo, ok bool) {
	if info.Trailing == "" {
		// it's valid
		// it's just don't have a relative path,
		// therefore p.RelPath will be empty, as we want.
		return p, true
	}

	var (
		typ     = info.Type
		tr      = info.Trailing
		relPath = resolvePathFromFunc(tr)

		goType, paramType string
	)

	byKeywordIdx := strings.LastIndex(tr, by)
	if byKeywordIdx != -1 && typ.NumIn() == 2 { // first is the struct receiver.
		funcPath := tr[0:byKeywordIdx] // remove the "By"
		goType = typ.In(1).Name()
		afterBy := byKeywordIdx + len(by)
		if len(tr) > afterBy {
			if tr[afterBy:] == wildcard {
				paramType = paramTypePath
			} else {
				// invalid syntax
				return p, false
			}
		} else {
			// it's not wildcard, so check base on our available macro types.
			if paramType, ok = macroTypes[goType]; !ok {
				// ivalid type
				return p, false
			}
		}

		// int and string are supported.
		// as there is no way to get the parameter name
		// we will use the "param" everywhere.
		suffix := fmt.Sprintf("/{%s:%s}", paramName, paramType)
		relPath = resolvePathFromFunc(funcPath) + suffix
	}

	// if GetSomething/PostSomething/PutSomething...
	// we will not check for "Something" because we could
	// occur unexpected behaviors to the existing users
	// who using exported functions for controller's internal
	// functionalities and not for serving a request path.

	return pathInfo{
		GoParamType: goType,
		ParamType:   paramType,
		RelPath:     relPath,
	}, true
}

func resolvePathFromFunc(funcName string) string {
	end := len(funcName)
	start := -1
	buf := &bytes.Buffer{}

	for i, n := 0, end; i < n; i++ {
		c := rune(funcName[i])
		if unicode.IsUpper(c) {
			// it doesn't count the last uppercase
			if start != -1 {
				end = i
				s := "/" + strings.ToLower(funcName[start:end])
				buf.WriteString(s)
			}
			start = i
			continue
		}
		end = i + 1
	}

	if end > 0 && len(funcName) >= end {
		buf.WriteString("/" + strings.ToLower(funcName[start:end]))
	}

	return buf.String()
}