package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"unicode"

	"github.com/nicksnyder/go-i18n/i18n/bundle"
	"github.com/nicksnyder/go-i18n/i18n/language"
	"github.com/nicksnyder/go-i18n/i18n/translation"
)

type constantsCommand struct {
	translationFiles []string
	packageName      string
	outdir           string
}

type templateConstants struct {
	ID       string
	Name     string
	Comments []string
}

type templateHeader struct {
	PackageName string
	Constants   []templateConstants
}

var constTemplate = template.Must(template.New("").Parse(`// DON'T CHANGE THIS FILE MANUALLY
// This file was generated using the command:
// $ goi18n constants

package {{.PackageName}}
{{range .Constants}}
// {{.Name}} is the identifier for the following localizable string template(s):{{range .Comments}}
// {{.}}{{end}}
const {{.Name}} = "{{.ID}}"
{{end}}`))

func (cc *constantsCommand) execute() error {
	if len(cc.translationFiles) != 1 {
		return fmt.Errorf("need one translation file")
	}

	bundle := bundle.New()

	if err := bundle.LoadTranslationFile(cc.translationFiles[0]); err != nil {
		return fmt.Errorf("failed to load translation file %s because %s\n", cc.translationFiles[0], err)
	}

	translations := bundle.Translations()
	lang := translations[bundle.LanguageTags()[0]]

	// create an array of id to organize
	keys := make([]string, len(lang))
	i := 0

	for id := range lang {
		keys[i] = id
		i++
	}
	sort.Strings(keys)

	tmpl := &templateHeader{
		PackageName: cc.packageName,
		Constants:   make([]templateConstants, len(keys)),
	}

	for i, id := range keys {
		tmpl.Constants[i].ID = id
		tmpl.Constants[i].Name = toCamelCase(id)
		tmpl.Constants[i].Comments = toComments(lang[id])
	}

	filename := filepath.Join(cc.outdir, cc.packageName+".go")
	f, err := os.Create(filename)
	if err != nil {
		return fmt.Errorf("failed to create file %s because %s", filename, err)
	}

	defer f.Close()

	if err = constTemplate.Execute(f, tmpl); err != nil {
		return fmt.Errorf("failed to write file %s because %s", filename, err)
	}

	return nil
}

func (cc *constantsCommand) parse(arguments []string) {
	flags := flag.NewFlagSet("constants", flag.ExitOnError)
	flags.Usage = usageConstants

	packageName := flags.String("package", "R", "")
	outdir := flags.String("outdir", ".", "")

	flags.Parse(arguments)

	cc.translationFiles = flags.Args()
	cc.packageName = *packageName
	cc.outdir = *outdir
}

func (cc *constantsCommand) SetArgs(args []string) {
	cc.translationFiles = args
}

func usageConstants() {
	fmt.Printf(`Generate constant file from translation file.

Usage:

    goi18n constants [options] [file]

Translation files:

    A translation file contains the strings and translations for a single language.

    Translation file names must have a suffix of a supported format (e.g. .json) and
    contain a valid language tag as defined by RFC 5646 (e.g. en-us, fr, zh-hant, etc.).

Options:

    -package name
        goi18n generates the constant file under the package name.
        Default: R

    -outdir directory
        goi18n writes the constant file to this directory.
        Default: .

`)
	os.Exit(1)
}

// commonInitialisms is a set of common initialisms.
// Only add entries that are highly unlikely to be non-initialisms.
// For instance, "ID" is fine (Freudian code is rare), but "AND" is not.
// https://github.com/golang/lint/blob/master/lint.go
var commonInitialisms = map[string]bool{
	"API":   true,
	"ASCII": true,
	"CPU":   true,
	"CSS":   true,
	"DNS":   true,
	"EOF":   true,
	"GUID":  true,
	"HTML":  true,
	"HTTP":  true,
	"HTTPS": true,
	"ID":    true,
	"IP":    true,
	"JSON":  true,
	"LHS":   true,
	"QPS":   true,
	"RAM":   true,
	"RHS":   true,
	"RPC":   true,
	"SLA":   true,
	"SMTP":  true,
	"SQL":   true,
	"SSH":   true,
	"TCP":   true,
	"TLS":   true,
	"TTL":   true,
	"UDP":   true,
	"UI":    true,
	"UID":   true,
	"UUID":  true,
	"URI":   true,
	"URL":   true,
	"UTF8":  true,
	"VM":    true,
	"XML":   true,
	"XSRF":  true,
	"XSS":   true,
}

func toCamelCase(id string) string {
	var result string

	r := regexp.MustCompile(`[\-\.\_\s]`)
	words := r.Split(id, -1)

	for _, w := range words {
		upper := strings.ToUpper(w)
		if commonInitialisms[upper] {
			result += upper
			continue
		}

		if len(w) > 0 {
			u := []rune(w)
			u[0] = unicode.ToUpper(u[0])
			result += string(u)
		}
	}
	return result
}

func toComments(trans translation.Translation) []string {
	var result []string
	data := trans.MarshalInterface().(map[string]interface{})

	t := data["translation"]

	switch v := reflect.ValueOf(t); v.Kind() {
	case reflect.Map:
		for _, k := range []language.Plural{"zero", "one", "two", "few", "many", "other"} {
			vt := v.MapIndex(reflect.ValueOf(k))
			if !vt.IsValid() {
				continue
			}
			result = append(result, string(k)+": "+strconv.Quote(fmt.Sprint(vt.Interface())))
		}
	default:
		result = append(result, strconv.Quote(fmt.Sprint(t)))
	}

	return result
}
