package pkg

import (
	"fmt"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
	"golang.org/x/text/number"
	"html/template"
	"io"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const overrideMethodName = "_method"

func templateFile(name string) string {
	return "web/template/" + name
}

func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename string, data interface{}) {
	mustRenderTemplateFragment(wr, r, layout, filename, layout, data)
}

func mustRenderTemplateFragment(wr io.Writer, r *http.Request, layout string, filename string, fragment string, data interface{}) {
	locale := getLocale(r)
	company := getCompany(r)
	user := getUser(r)
	t := template.New(filename)
	t.Funcs(template.FuncMap{
		"gettext":  locale.Get,
		"pgettext": locale.GetC,
		"currentLocale": func() string {
			return locale.Language.String()
		},
		"requestURIMatches": func(uri string) bool {
			return r.RequestURI == uri
		},
		"requestURIHasPrefix": func(uri string) bool {
			return strings.HasPrefix(r.RequestURI, uri)
		},
		"companyURI": func(uri string) string {
			return companyURI(company, uri)
		},
		"formatPrice": func(price string) string {
			return formatPrice(price, locale.Language, locale.CurrencyPattern, company.DecimalDigits, company.CurrencySymbol)
		},
		"formatPriceSpan": func(price string) template.HTML {
			return template.HTML(formatPrice(price, locale.Language, locale.CurrencyPattern, company.DecimalDigits, "<span>"+company.CurrencySymbol+"</span>"))
		},
		"formatDate": func(time time.Time) template.HTML {
			return template.HTML(`<time datetime="` + time.Format("2006-01-02") + `">` + time.Format("02/01/2006") + "</time>")
		},
		"unsafe": func(s string) template.HTML {
			return template.HTML(s)
		},
		"formatPercent": func(value int) string {
			return fmt.Sprintf("%d %%", value)
		},
		"csrfToken": func() template.HTML {
			return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, csrfTokenField, user.CsrfToken))
		},
		"csrfHeader": func() string {
			return fmt.Sprintf(`"%s": "%s"`, csrfTokenHeader, user.CsrfToken)
		},
		"addInputAttr": func(attr string, field *InputField) *InputField {
			field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
			return field
		},
		"addSelectAttr": func(attr string, field *SelectField) *SelectField {
			field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
			return field
		},
		"addTagsAttr": func(attr string, field *TagsField) *TagsField {
			field.Attributes = append(field.Attributes, template.HTMLAttr(attr))
			return field
		},
		"boolToInt": func(b bool) int {
			if b {
				return 1
			} else {
				return 0
			}
		},
		"add": func(y, x int) int {
			return x + y
		},
		"sub": func(y, x int) int {
			return x - y
		},
		"deleteMethod": func() template.HTML {
			return overrideMethodField(http.MethodDelete)
		},
		"putMethod": func() template.HTML {
			return overrideMethodField(http.MethodPut)
		},
		"humanizeBytes": func(bytes int64) string {
			sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
			return humanizeBytes(bytes, 1024, sizes)
		},
		"slugify": func(s string) string {
			return slugify(s)
		},
		"numerusVersion": func() string {
			return Version
		},
	})
	if _, err := t.ParseFiles(templateFile(filename), templateFile(layout), templateFile("form.gohtml")); err != nil {
		panic(err)
	}
	if w, ok := wr.(http.ResponseWriter); ok {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
	}
	if err := t.ExecuteTemplate(wr, fragment, data); err != nil {
		panic(err)
	}
}

func companyURI(company *Company, uri string) string {
	if company == nil {
		return uri
	}
	return "/company/" + company.Slug + uri
}

func overrideMethodField(method string) template.HTML {
	return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, overrideMethodName, method))
}
func mustRenderAppTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
	mustRenderTemplate(w, r, "app.gohtml", filename, data)
}

func mustRenderModalTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
	if IsHTMxRequest(r) {
		mustRenderTemplate(w, r, "modal.gohtml", filename, data)
	} else {
		mustRenderAppTemplate(w, r, filename, data)
	}
}

func mustRenderMainTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
	if IsHTMxRequest(r) {
		mustRenderTemplate(w, r, "main.gohtml", filename, data)
	} else {
		mustRenderAppTemplate(w, r, filename, data)
	}
}

func mustRenderStandaloneTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
	mustRenderStandaloneTemplateFragment(w, r, filename, "standalone.gohtml", data)
}

func mustRenderStandaloneTemplateFragment(w io.Writer, r *http.Request, filename string, fragment string, data interface{}) {
	mustRenderTemplateFragment(w, r, "standalone.gohtml", filename, fragment, data)
}

func humanizeBytes(s int64, base float64, sizes []string) string {
	if s < 10 {
		return fmt.Sprintf("%d B", s)
	}
	e := math.Floor(logn(float64(s), base))
	suffix := sizes[int(e)]
	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
	f := "%.0f %s"
	if val < 10 {
		f = "%.1f %s"
	}

	return fmt.Sprintf(f, val, suffix)
}

func logn(n, b float64) float64 {
	return math.Log(n) / math.Log(b)
}

func formatPrice(price string, language language.Tag, currencyPattern string, decimalDigits int, currencySymbol string) string {
	p := message.NewPrinter(language)
	f, err := strconv.ParseFloat(price, 64)
	if err != nil {
		f = math.NaN()
	}
	return p.Sprintf(currencyPattern, decimalDigits, number.Decimal(f), currencySymbol)
}