// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// Generator for currency-related data.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"

	"golang.org/x/text/cldr"
	"golang.org/x/text/internal"
	"golang.org/x/text/internal/gen"
	"golang.org/x/text/internal/tag"
	"golang.org/x/text/language"
)

var (
	test = flag.Bool("test", false,
		"test existing tables; can be used to compare web data with package data.")
	outputFile = flag.String("output", "tables.go", "output file")

	draft = flag.String("draft",
		"contributed",
		`Minimal draft requirements (approved, contributed, provisional, unconfirmed).`)
)

func main() {
	gen.Init()

	rewriteCommon()

	// Read the CLDR zip file.
	r := gen.OpenCLDRCoreZip()
	defer r.Close()

	d := &cldr.Decoder{}
	d.SetDirFilter("supplemental", "main")
	d.SetSectionFilter("numbers")
	data, err := d.DecodeZip(r)
	if err != nil {
		log.Fatalf("DecodeZip: %v", err)
	}

	w := gen.NewCodeWriter()
	defer w.WriteGoFile(*outputFile, "currency")

	fmt.Fprintln(w, `import "golang.org/x/text/internal/tag"`)

	gen.WriteCLDRVersion(w)
	b := &builder{}
	b.genCurrencies(w, data.Supplemental())
	b.genSymbols(w, data)
}

func rewriteCommon() {
	// Generate common.go
	src, err := ioutil.ReadFile("gen_common.go")
	if err != nil {
		log.Fatal(err)
	}
	const toDelete = "// +build ignore\n\npackage main\n\n"
	i := bytes.Index(src, []byte(toDelete))
	if i < 0 {
		log.Fatalf("could not find %q in gen_common.go", toDelete)
	}
	w := &bytes.Buffer{}
	w.Write(src[i+len(toDelete):])
	gen.WriteGoFile("common.go", "currency", w.Bytes())
}

var constants = []string{
	// Undefined and testing.
	"XXX", "XTS",
	// G11 currencies https://en.wikipedia.org/wiki/G10_currencies.
	"USD", "EUR", "JPY", "GBP", "CHF", "AUD", "NZD", "CAD", "SEK", "NOK", "DKK",
	// Precious metals.
	"XAG", "XAU", "XPT", "XPD",

	// Additional common currencies as defined by CLDR.
	"BRL", "CNY", "INR", "RUB", "HKD", "IDR", "KRW", "MXN", "PLN", "SAR",
	"THB", "TRY", "TWD", "ZAR",
}

type builder struct {
	currencies    tag.Index
	numCurrencies int
}

func (b *builder) genCurrencies(w *gen.CodeWriter, data *cldr.SupplementalData) {
	// 3-letter ISO currency codes
	// Start with dummy to let index start at 1.
	currencies := []string{"\x00\x00\x00\x00"}

	// currency codes
	for _, reg := range data.CurrencyData.Region {
		for _, cur := range reg.Currency {
			currencies = append(currencies, cur.Iso4217)
		}
	}

	sort.Strings(currencies)
	// Unique the elements.
	k := 0
	for i := 1; i < len(currencies); i++ {
		if currencies[k] != currencies[i] {
			currencies[k+1] = currencies[i]
			k++
		}
	}
	currencies = currencies[:k+1]

	// Close with dummy for simpler and faster searching.
	currencies = append(currencies, "\xff\xff\xff\xff")

	// Write currency values.
	fmt.Fprintln(w, "const (")
	for _, c := range constants {
		index := sort.SearchStrings(currencies, c)
		fmt.Fprintf(w, "\t%s = %d\n", strings.ToLower(c), index)
	}
	fmt.Fprint(w, ")")

	// Compute currency-related data that we merge into the table.
	for _, info := range data.CurrencyData.Fractions[0].Info {
		if info.Iso4217 == "DEFAULT" {
			continue
		}
		standard := getRoundingIndex(info.Digits, info.Rounding, 0)
		cash := getRoundingIndex(info.CashDigits, info.CashRounding, standard)

		index := sort.SearchStrings(currencies, info.Iso4217)
		currencies[index] += mkCurrencyInfo(standard, cash)
	}

	// Set default values for entries that weren't touched.
	for i, c := range currencies {
		if len(c) == 3 {
			currencies[i] += mkCurrencyInfo(0, 0)
		}
	}

	b.currencies = tag.Index(strings.Join(currencies, ""))
	w.WriteComment(`
	currency holds an alphabetically sorted list of canonical 3-letter currency
	identifiers. Each identifier is followed by a byte of type currencyInfo,
	defined in gen_common.go.`)
	w.WriteConst("currency", b.currencies)

	// Hack alert: gofmt indents a trailing comment after an indented string.
	// Ensure that the next thing written is not a comment.
	b.numCurrencies = (len(b.currencies) / 4) - 2
	w.WriteConst("numCurrencies", b.numCurrencies)

	// Create a table that maps regions to currencies.
	regionToCurrency := []toCurrency{}

	for _, reg := range data.CurrencyData.Region {
		if len(reg.Iso3166) != 2 {
			log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
		}
		if len(reg.Currency) == 0 {
			continue
		}
		cur := reg.Currency[0]
		if cur.To != "" || cur.Tender == "false" {
			continue
		}
		regionToCurrency = append(regionToCurrency, toCurrency{
			region: regionToCode(language.MustParseRegion(reg.Iso3166)),
			code:   uint16(b.currencies.Index([]byte(cur.Iso4217))),
		})
	}
	sort.Sort(byRegion(regionToCurrency))

	w.WriteType(toCurrency{})
	w.WriteVar("regionToCurrency", regionToCurrency)
}

type toCurrency struct {
	region uint16
	code   uint16
}

type byRegion []toCurrency

func (a byRegion) Len() int           { return len(a) }
func (a byRegion) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byRegion) Less(i, j int) bool { return a[i].region < a[j].region }

func mkCurrencyInfo(standard, cash int) string {
	return string([]byte{byte(cash<<cashShift | standard)})
}

func getRoundingIndex(digits, rounding string, defIndex int) int {
	round := roundings[defIndex] // default

	if digits != "" {
		round.scale = parseUint8(digits)
	}
	if rounding != "" && rounding != "0" { // 0 means 1 here in CLDR
		round.increment = parseUint8(rounding)
	}

	// Will panic if the entry doesn't exist:
	for i, r := range roundings {
		if r == round {
			return i
		}
	}
	log.Fatalf("Rounding entry %#v does not exist.", round)
	panic("unreachable")
}

// genSymbols generates the symbols used for currencies. Most symbols are
// defined in root and there is only very small variation per language.
// The following rules apply:
// - A symbol can be requested as normal or narrow.
// - If a symbol is not defined for a currency, it defaults to its ISO code.
func (b *builder) genSymbols(w *gen.CodeWriter, data *cldr.CLDR) {
	d, err := cldr.ParseDraft(*draft)
	if err != nil {
		log.Fatalf("filter: %v", err)
	}

	const (
		normal = iota
		narrow
		numTypes
	)
	// language -> currency -> type ->  symbol
	var symbols [language.NumCompactTags][][numTypes]*string

	// Collect symbol information per language.
	for _, lang := range data.Locales() {
		ldml := data.RawLDML(lang)
		if ldml.Numbers == nil || ldml.Numbers.Currencies == nil {
			continue
		}

		langIndex, ok := language.CompactIndex(language.MustParse(lang))
		if !ok {
			log.Fatalf("No compact index for language %s", lang)
		}

		symbols[langIndex] = make([][numTypes]*string, b.numCurrencies+1)

		for _, c := range ldml.Numbers.Currencies.Currency {
			syms := cldr.MakeSlice(&c.Symbol)
			syms.SelectDraft(d)

			for _, sym := range c.Symbol {
				v := sym.Data()
				if v == c.Type {
					// We define "" to mean the ISO symbol.
					v = ""
				}
				cur := b.currencies.Index([]byte(c.Type))
				if cur == -1 {
					fmt.Println("Unsupported:", c.Type) // TODO: mark MVP as supported.
					continue
				}

				switch sym.Alt {
				case "":
					symbols[langIndex][cur][normal] = &v
				case "narrow":
					symbols[langIndex][cur][narrow] = &v
				}
			}
		}
	}

	// Remove values identical to the parent.
	for langIndex, data := range symbols {
		for curIndex, curs := range data {
			for typ, sym := range curs {
				if sym == nil {
					continue
				}
				for p := uint16(langIndex); p != 0; {
					p = internal.Parent[p]
					x := symbols[p]
					if x == nil {
						continue
					}
					if v := x[curIndex][typ]; v != nil || p == 0 {
						// Value is equal to the default value root value is undefined.
						parentSym := ""
						if v != nil {
							parentSym = *v
						}
						if parentSym == *sym {
							// Value is the same as parent.
							data[curIndex][typ] = nil
						}
						break
					}
				}
			}
		}
	}

	// Create symbol index.
	symbolData := []byte{0}
	symbolLookup := map[string]uint16{"": 0} // 0 means default, so block that value.
	for _, data := range symbols {
		for _, curs := range data {
			for _, sym := range curs {
				if sym == nil {
					continue
				}
				if _, ok := symbolLookup[*sym]; !ok {
					symbolLookup[*sym] = uint16(len(symbolData))
					symbolData = append(symbolData, byte(len(*sym)))
					symbolData = append(symbolData, *sym...)
				}
			}
		}
	}
	w.WriteComment(`
	symbols holds symbol data of the form <n> <str>, where n is the length of
	the symbol string str.`)
	w.WriteConst("symbols", string(symbolData))

	// Create index from language to currency lookup to symbol.
	type curToIndex struct{ cur, idx uint16 }
	w.WriteType(curToIndex{})

	prefix := []string{"normal", "narrow"}
	// Create data for regular and narrow symbol data.
	for typ := normal; typ <= narrow; typ++ {

		indexes := []curToIndex{} // maps currency to symbol index
		languages := []uint16{}

		for _, data := range symbols {
			languages = append(languages, uint16(len(indexes)))
			for curIndex, curs := range data {

				if sym := curs[typ]; sym != nil {
					indexes = append(indexes, curToIndex{uint16(curIndex), symbolLookup[*sym]})
				}
			}
		}
		languages = append(languages, uint16(len(indexes)))

		w.WriteVar(prefix[typ]+"LangIndex", languages)
		w.WriteVar(prefix[typ]+"SymIndex", indexes)
	}
}
func parseUint8(str string) uint8 {
	x, err := strconv.ParseUint(str, 10, 8)
	if err != nil {
		// Show line number of where this function was called.
		log.New(os.Stderr, "", log.Lshortfile).Output(2, err.Error())
		os.Exit(1)
	}
	return uint8(x)
}
