package renderer

import (
	"errors"
	"fmt"
	"html/template"
	"net/url"
	"reflect"
	"strings"
	"time"

	"github.com/jinzhu/inflection"
	yml "gopkg.in/yaml.v2"
)

var (
	funcMap = template.FuncMap{
		"query":       query,
		"convertDate": convertDate,
		"modelPath":   modelPath,
		"dict":        dict,
		"yaml":        yaml,
		"create":      create,
		"update":      update,
		"delete":      delete,
		"show":        show,
		"all":         all,
		"execute":     execute,
		"isSlice":     isSlice,
		"toSlice":     toSlice,
		"string":      callString,
		"incr":        incr,
		"mod2":        mod2,
		"toLower":     toLower,
		"anchor":      anchor,
		"html":        html,
		"field":       field,
	}
)

func field(name string, value interface{}) interface{} {
	if value != nil {
		s := reflect.ValueOf(value).Elem()
		return s.FieldByName(name).Interface()
	} else {
		return nil
	}
}

func html(content string) template.HTML {
	return template.HTML(content)
}

func anchor(text, url string) template.HTML {
	return template.HTML(fmt.Sprintf("<a href=\"%s\">%s</a>", url, text))
}

func toLower(text string) string {
	return strings.ToLower(text)
}

func mod2(value int) bool {
	return value%2 == 0
}

func incr(value int) int {
	return value + 1
}

func callString(value interface{}) string {
	if value != nil {
		return reflect.ValueOf(value).MethodByName("String").Interface().(func() string)()
	} else {
		return ""
	}
}

func isSlice(value interface{}) bool {
	return reflect.TypeOf(value).Kind() == reflect.Slice
}

func yaml(content string) (interface{}, error) {
	var result interface{}
	err := yml.Unmarshal([]byte(content), &result)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func dict(values ...interface{}) (map[string]interface{}, error) {
	if len(values)%2 != 0 {
		return nil, errors.New("invalid dict call")
	}
	dict := make(map[string]interface{}, len(values)/2)
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil, errors.New("dict keys must be strings")
		}
		dict[key] = values[i+1]
	}
	return dict, nil
}

func toSlice(values ...string) interface{} {
	var result []string
	result = append(result, values...)
	return result
}

func getType(myvar interface{}) (res string) {
	t := reflect.TypeOf(myvar)
	for t.Kind() == reflect.Ptr {
		t = t.Elem()
		res += "*"
	}
	return res + t.Name()
}

func query(values ...string) template.URL {
	var (
		urlValues url.Values
		format    bool
	)

	urlValues = make(url.Values)

	for i := 0; i < len(values); i += 2 {
		if values[i] == "format" {
			format = true
		}
		urlValues.Add(values[i], values[i+1])
	}

	if !format {
		urlValues.Set("format", "html")
	}

	return template.URL(urlValues.Encode())
}

func convertDate(value interface{}) string {
	t, ok := value.(time.Time)
	if !ok {
		return ""
	}
	return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day())
}

func modelPath(model string, action string, id uint) string {
	var q template.URL

	action = strings.ToLower(action)
	plural := inflection.Plural(strings.ToLower(model))

	if action != "" {
		switch action {
		case "show":
			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
			return fmt.Sprintf("/%s/%d?%s", plural, id, q)
		case "update":
			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural), "update", "true")
			return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
		case "create":
			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural))
			return fmt.Sprintf("/%s/%s/?%s", plural, action, q)
		case "delete":
			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
			return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
		}
	}
	q = query("tpl_layout", "base", "tpl_content", plural)
	return fmt.Sprintf("/%s?%s", plural, q)
}

func all(model string) string {
	plural := inflection.Plural(strings.ToLower(model))
	q := query("tpl_layout", "base", "tpl_content", plural)

	return fmt.Sprintf("/%s?%s", plural, q)
}

func create(model string) string {
	action := "create"
	plural := inflection.Plural(strings.ToLower(model))
	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural))

	return fmt.Sprintf("/%s/%s/?%s", plural, action, q)
}

func show(model string, id uint, format ...string) string {
	action := "show"
	plural := inflection.Plural(strings.ToLower(model))

	args := []string{"tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action)}

	if len(format) > 0 {
		args = append(args, []string{"format", format[0]}...)
	}

	// q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
	q := query(args...)

	return fmt.Sprintf("/%s/%d?%s", plural, id, q)
}

func execute(model string, id uint) string {
	action := "execute"
	plural := inflection.Plural(strings.ToLower(model))
	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))

	return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
}

func update(model string, id uint) string {
	action := "update"
	plural := inflection.Plural(strings.ToLower(model))
	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural), "update", "true")

	return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
}

func delete(model string, id uint) string {
	action := "delete"
	plural := inflection.Plural(strings.ToLower(model))
	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))

	return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
}
