package i18n

import (
	"notabug.org/apiote/amuse/config"

	"bytes"
	"fmt"
	"golang.org/x/text/language"
	"html/template"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/bytesparadise/libasciidoc"
	"github.com/bytesparadise/libasciidoc/pkg/configuration"
	"notabug.org/apiote/gott"
)

type Translation struct {
	Global       map[string]string
	Index        map[string]string
	Search       map[string]string
	Film         map[string]string
	Serie        map[string]string
	Person       map[string]string
	Book         map[string]string
	BookSerie    map[string]string
	About        map[string]string
	Signup       map[string]string
	Signedup     map[string]string
	Login        map[string]string
	Loggedout    map[string]string
	Watchlist    map[string]string
	Readlist     map[string]string
	Tvqueue      map[string]string
	Experiences  map[string]string
	Error        map[string]string
	Account      map[string]string
	PremiereType map[int]string
}

var serverLangs []language.Tag

func LoadServerLangs() error {

	var tags []string
	var defaultLocale string
	defaultLocaleIndex := -1
	err := filepath.Walk(config.DataHome+"/i18n", func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			if filepath.Ext(path) == ".toml" {
				tag := strings.Replace(filepath.Base(path), ".toml", "", 1)
				if tag == "default" {
					p, err := os.Readlink(path)
					if err != nil {
						return err
					}
					defaultLocale = strings.Replace(filepath.Base(p), ".toml", "", 1)
				} else {
					tags = append(tags, tag)
				}
			}
		}
		return nil
	})
	if err != nil {
		return err
	}

	for i, tag := range tags {
		if tag == defaultLocale {
			defaultLocaleIndex = i
		}
	}
	tags[0], tags[defaultLocaleIndex] = tags[defaultLocaleIndex], tags[0]

	for _, tag := range tags {
		serverLangs = append(serverLangs, language.Make(tag))
	}
	return nil
}

func Match(acceptLanguages []language.Tag) (language.Tag, error) {
	var matcher = language.NewMatcher(serverLangs)
	tag, _, _ := matcher.Match(acceptLanguages...)
	b, s, r := tag.Raw()
	t, err := language.Compose(b, s, r)
	return t, err
}

func loadStringsFile(args ...interface{}) (interface{}, error) {
	var (
		strings Translation
		err     error
	)
	if langTag, ok := args[0].(language.Tag); ok {
		language := langTag.String()
		_, err = toml.DecodeFile(filepath.Join(config.DataHome+"/i18n", language+".toml"), &strings)
	}
	args[1] = strings
	return gott.Tuple(args), err
}

func loadDefaultStringsFile(args ...interface{}) (interface{}, error) {
	var strings Translation
	_, err := toml.DecodeFile(filepath.Join(config.DataHome+"/i18n", "default.toml"), &strings)
	args[2] = strings
	return gott.Tuple(args), err
}

func loadStrings(args ...interface{}) interface{} {
	strings := args[1].(Translation)
	defaultTranslation := args[2].(Translation)
	stringsValue := reflect.ValueOf(&strings).Elem()
	stringsType := stringsValue.Type()
	for i := 0; i < stringsValue.NumField(); i++ {
		stringsField := stringsValue.Field(i)
		if i == stringsValue.NumField()-1 {
			continue
		}
		if stringsField.IsNil() {
			stringsField.Set(reflect.MakeMap(reflect.TypeOf(map[string]string{})))
		}
		defaultValue := reflect.ValueOf(&defaultTranslation).Elem()
		for key, entry := range defaultValue.FieldByName(stringsType.Field(i).Name).Interface().(map[string]string) {
			if stringsField.Interface().(map[string]string)[key] == "" {
				stringsField.Interface().(map[string]string)[key] = entry
			}
		}
	}
	args[1] = strings
	return gott.Tuple(args)
}

func loadPremiereTypes(args ...interface{}) interface{} {
	strings := args[1].(Translation)
	strings.PremiereType = map[int]string{
		1: "premiere_type_premiere",
		2: "premiere_type_cinema_limited",
		3: "premiere_type_cinema",
		4: "premiere_type_digital",
		5: "premiere_type_physical",
		6: "premiere_type_tv",
	}
	args[1] = strings
	return gott.Tuple(args)
}

func LoadStrings(language language.Tag) (Translation, error) {
	r, err := gott.
		NewResult(gott.Tuple{language, Translation{}, Translation{}}).
		Bind(loadStringsFile).
		Bind(loadDefaultStringsFile).
		Map(loadStrings).
		Map(loadPremiereTypes).
		Finish()

	if err == nil {
		return r.(gott.Tuple)[1].(Translation), nil
	} else {
		return Translation{}, err
	}
}

func GetPremiereType(index int, translation Translation) string {
	return translation.Film[translation.PremiereType[index]]
}

func FormatDate(date time.Time, format string, translation map[string]string) string {
	format = strings.ReplaceAll(format, "%a", translation[date.Weekday().String()+"_short"])
	format = strings.ReplaceAll(format, "%A", translation[date.Weekday().String()])
	format = strings.ReplaceAll(format, "%b", translation[date.Month().String()+"_short"])
	format = strings.ReplaceAll(format, "%B", translation[date.Month().String()])
	// %c intentionally ommitted
	format = strings.ReplaceAll(format, "%C", fmt.Sprintf("%d", date.Year()/100))
	format = strings.ReplaceAll(format, "%d", fmt.Sprintf("%02d", date.Day()))
	format = strings.ReplaceAll(format, "%D", fmt.Sprintf("%02d/%02d/%02d", date.Month(), date.Day(), date.Year()%100))
	format = strings.ReplaceAll(format, "%e", fmt.Sprintf("%2d", date.Day()))
	format = strings.ReplaceAll(format, "%h", translation[date.Month().String()+"_short"])
	format = strings.ReplaceAll(format, "%H", fmt.Sprintf("%02d", date.Hour()))
	hour := date.Hour() % 12
	if hour == 0 {
		hour = 12
	}
	format = strings.ReplaceAll(format, "%I", fmt.Sprintf("%02d", hour))
	format = strings.ReplaceAll(format, "%j", fmt.Sprintf("%03d", date.YearDay()))
	format = strings.ReplaceAll(format, "%m", fmt.Sprintf("%02d", date.Month()))
	format = strings.ReplaceAll(format, "%M", fmt.Sprintf("%02d", date.Minute()))
	format = strings.ReplaceAll(format, "%n", "\n")
	// todo %p
	// %r intentionally ommitted
	format = strings.ReplaceAll(format, "%S", fmt.Sprintf("%02d", date.Second()))
	format = strings.ReplaceAll(format, "%t", "\t")
	format = strings.ReplaceAll(format, "%T", fmt.Sprintf("%02d:%02d:%02d", date.Hour(), date.Minute(), date.Second()%100))
	weekday := date.Weekday()
	if weekday == 0 {
		weekday = 7
	}
	format = strings.ReplaceAll(format, "%u", fmt.Sprintf("%d", weekday))
	// todo %U
	// todo %V
	format = strings.ReplaceAll(format, "%w", fmt.Sprintf("%d", date.Weekday()))
	// todo %W
	// %x intentionally ommitted
	// %X intentionally ommitted
	format = strings.ReplaceAll(format, "%y", fmt.Sprintf("%d", date.Year()%100))
	format = strings.ReplaceAll(format, "%Y", fmt.Sprintf("%d", date.Year()))
	zone, _ := date.Zone()
	format = strings.ReplaceAll(format, "%Z", zone)
	format = strings.ReplaceAll(format, "%%", "%")
	return format
}

func FormatDateNice(datetime time.Time, strings Translation, timezone string) string {
	t := time.Now()
	location, err := time.LoadLocation(timezone)
	if err != nil {
		return strings.Global["unknown"]
	}
	midnightToday := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, location)
	midnightYester := midnightToday.Add(-24 * time.Hour)
	midnightEreyester := midnightYester.Add(-24 * time.Hour)
	midnightWeek := midnightToday.Add(-24 * 7 * time.Hour)
	midnightYear := time.Date(t.Year(), 1, 1, 0, 0, 0, 0, location)
	var dateFormat string
	if datetime.After(midnightToday) {
		dateFormat = strings.Global["experience_format_today"]
	} else if datetime.After(midnightYester) {
		dateFormat = strings.Global["experience_format_yesterday"]
	} else if datetime.After(midnightEreyester) {
		dateFormat = strings.Global["experience_format_ereyester"]
	} else if datetime.After(midnightWeek) {
		dateFormat = strings.Global["experience_format_week"]
	} else if datetime.After(midnightYear) {
		dateFormat = strings.Global["experience_format_year"]
	} else {
		dateFormat = strings.Global["experience_format_earlier"]
	}
	date := FormatDate(datetime, dateFormat, strings.Global)
	return date
}

func RenderAsciiDoc(asciidoc string) template.HTML {
	r := strings.NewReader(asciidoc)
	w := bytes.NewBuffer([]byte{})
	config := configuration.NewConfiguration()

	libasciidoc.Convert(r, w, config)
	output := bytes.ReplaceAll(w.Bytes(), []byte("\n"), []byte(""))

	divRegex, err := regexp.Compile("<\\/?div[^>]*>")
	if err != nil {
		return template.HTML("<span style=\"color: red;\">error rendering asciidoc (div regex)</span>")
	}
	pRegex, err := regexp.Compile("<\\/?p>")
	if err != nil {
		return template.HTML("<span style=\"color: red;\">error rendering asciidoc (p regex)</span>")
	}

	output = divRegex.ReplaceAll(output, []byte(""))
	output = pRegex.ReplaceAll(output, []byte(""))

	return template.HTML(output)
}

func GetErrorData(code int, translation Translation, part string) string {
	key := fmt.Sprintf("%d_%s", code, part)
	return translation.Error[key]
}
