// Copyright 2014 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.

//go:generate go run maketables.go -output tables.go

// Package display provides display names for languages, scripts and regions in
// a requested language.
//
// The data is based on CLDR's localeDisplayNames. It includes the names of the
// draft level "contributed" or "approved". The resulting tables are quite
// large. The display package is designed so that users can reduce the linked-in
// table sizes by cherry picking the languages one wishes to support. There is a
// Dictionary defined for a selected set of common languages for this purpose.
package display

import (
	"strings"

	"gx/QmVgdgtv5rUcWWcHvHN1vdGkXi8UNztJ7JuT72YUJgG2ic/go-text/language"
)

/*
TODO:
All fairly low priority at the moment:
  - Include alternative and variants as an option (using func options).
  - Option for returning the empty string for undefined values.
  - Support variants, currencies, time zones, option names and other data
    provided in CLDR.
  - Do various optimizations:
    - Reduce size of offset tables.
    - Consider compressing infrequently used languages and decompress on demand.
*/

// A Namer is used to get the name for a given value, such as a Tag, Language,
// Script or Region.
type Namer interface {
	// Name returns a display string for the given value. A Namer returns an
	// empty string for values it does not support. A Namer may support naming
	// an unspecified value. For example, when getting the name for a region for
	// a tag that does not have a defined Region, it may return the name for an
	// unknown region. It is up to the user to filter calls to Name for values
	// for which one does not want to have a name string.
	Name(x interface{}) string
}

var (
	// Supported lists the languages for which names are defined.
	Supported language.Coverage

	// The set of all possible values for which names are defined. Note that not
	// all Namer implementations will cover all the values of a given type.
	// A Namer will return the empty string for unsupported values.
	Values language.Coverage

	matcher language.Matcher
)

func init() {
	tags := make([]language.Tag, numSupported)
	s := supported
	for i := range tags {
		p := strings.IndexByte(s, '|')
		tags[i] = language.Raw.Make(s[:p])
		s = s[p+1:]
	}
	matcher = language.NewMatcher(tags)
	Supported = language.NewCoverage(tags)

	Values = language.NewCoverage(langTagSet.Tags, supportedScripts, supportedRegions)
}

// Languages returns a Namer for naming languages. It returns nil if there is no
// data for the given tag. The type passed to Name must be either language.Base
// or language.Tag. Note that the result may differ between passing a tag or its
// base language. For example, for English, passing "nl-BE" would return Flemish
// whereas passing "nl" returns "Dutch".
func Languages(t language.Tag) Namer {
	if _, index, conf := matcher.Match(t); conf != language.No {
		return languageNamer(index)
	}
	return nil
}

type languageNamer int

func (n languageNamer) name(i int) string {
	return lookup(langHeaders[:], int(n), i)
}

// Name implements the Namer interface for language names.
func (n languageNamer) Name(x interface{}) string {
	return nameLanguage(n, x)
}

// nonEmptyIndex walks up the parent chain until a non-empty header is found.
// It returns -1 if no index could be found.
func nonEmptyIndex(h []header, index int) int {
	for ; index != -1 && h[index].data == ""; index = int(parents[index]) {
	}
	return index
}

// Scripts returns a Namer for naming scripts. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Script or a language.Tag. It will not attempt to infer a script for
// tags with an unspecified script.
func Scripts(t language.Tag) Namer {
	if _, index, conf := matcher.Match(t); conf != language.No {
		if index = nonEmptyIndex(scriptHeaders[:], index); index != -1 {
			return scriptNamer(index)
		}
	}
	return nil
}

type scriptNamer int

func (n scriptNamer) name(i int) string {
	return lookup(scriptHeaders[:], int(n), i)
}

// Name implements the Namer interface for script names.
func (n scriptNamer) Name(x interface{}) string {
	return nameScript(n, x)
}

// Regions returns a Namer for naming regions. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Region or a language.Tag. It will not attempt to infer a region for
// tags with an unspecified region.
func Regions(t language.Tag) Namer {
	if _, index, conf := matcher.Match(t); conf != language.No {
		if index = nonEmptyIndex(regionHeaders[:], index); index != -1 {
			return regionNamer(index)
		}
	}
	return nil
}

type regionNamer int

func (n regionNamer) name(i int) string {
	return lookup(regionHeaders[:], int(n), i)
}

// Name implements the Namer interface for region names.
func (n regionNamer) Name(x interface{}) string {
	return nameRegion(n, x)
}

// Tags returns a Namer for giving a full description of a tag. The names of
// scripts and regions that are not already implied by the language name will
// in appended within parentheses. It returns nil if there is not data for the
// given tag. The type passed to Name must be a tag.
func Tags(t language.Tag) Namer {
	if _, index, conf := matcher.Match(t); conf != language.No {
		return tagNamer(index)
	}
	return nil
}

type tagNamer int

// Name implements the Namer interface for tag names.
func (n tagNamer) Name(x interface{}) string {
	return nameTag(languageNamer(n), scriptNamer(n), regionNamer(n), x)
}

// lookup finds the name for an entry in a global table, traversing the
// inheritance hierarchy if needed.
func lookup(table []header, dict, want int) string {
	for dict != -1 {
		if s := table[dict].name(want); s != "" {
			return s
		}
		dict = int(parents[dict])
	}
	return ""
}

// A Dictionary holds a collection of Namers for a single language. One can
// reduce the amount of data linked in to a binary by only referencing
// Dictionaries for the languages one needs to support instead of using the
// generic Namer factories.
type Dictionary struct {
	parent *Dictionary
	lang   header
	script header
	region header
}

// Tags returns a Namer for giving a full description of a tag. The names of
// scripts and regions that are not already implied by the language name will
// in appended within parentheses. It returns nil if there is not data for the
// given tag. The type passed to Name must be a tag.
func (d *Dictionary) Tags() Namer {
	return dictTags{d}
}

type dictTags struct {
	d *Dictionary
}

// Name implements the Namer interface for tag names.
func (n dictTags) Name(x interface{}) string {
	return nameTag(dictLanguages{n.d}, dictScripts{n.d}, dictRegions{n.d}, x)
}

// Languages returns a Namer for naming languages. It returns nil if there is no
// data for the given tag. The type passed to Name must be either language.Base
// or language.Tag. Note that the result may differ between passing a tag or its
// base language. For example, for English, passing "nl-BE" would return Flemish
// whereas passing "nl" returns "Dutch".
func (d *Dictionary) Languages() Namer {
	return dictLanguages{d}
}

type dictLanguages struct {
	d *Dictionary
}

func (n dictLanguages) name(i int) string {
	for d := n.d; d != nil; d = d.parent {
		if s := d.lang.name(i); s != "" {
			return s
		}
	}
	return ""
}

// Name implements the Namer interface for language names.
func (n dictLanguages) Name(x interface{}) string {
	return nameLanguage(n, x)
}

// Scripts returns a Namer for naming scripts. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Script or a language.Tag. It will not attempt to infer a script for
// tags with an unspecified script.
func (d *Dictionary) Scripts() Namer {
	return dictScripts{d}
}

type dictScripts struct {
	d *Dictionary
}

func (n dictScripts) name(i int) string {
	for d := n.d; d != nil; d = d.parent {
		if s := d.script.name(i); s != "" {
			return s
		}
	}
	return ""
}

// Name implements the Namer interface for script names.
func (n dictScripts) Name(x interface{}) string {
	return nameScript(n, x)
}

// Regions returns a Namer for naming regions. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Region or a language.Tag. It will not attempt to infer a region for
// tags with an unspecified region.
func (d *Dictionary) Regions() Namer {
	return dictRegions{d}
}

type dictRegions struct {
	d *Dictionary
}

func (n dictRegions) name(i int) string {
	for d := n.d; d != nil; d = d.parent {
		if s := d.region.name(i); s != "" {
			return s
		}
	}
	return ""
}

// Name implements the Namer interface for region names.
func (n dictRegions) Name(x interface{}) string {
	return nameRegion(n, x)
}

// A SelfNamer implements a Namer that returns the name of language in this same
// language. It provides a very compact mechanism to provide a comprehensive
// list of languages to users in their native language.
type SelfNamer struct {
	// Supported defines the values supported by this Namer.
	Supported language.Coverage
}

var (
	// Self is a shared instance of a SelfNamer.
	Self *SelfNamer = &self

	self = SelfNamer{language.NewCoverage(selfTagSet.Tags)}
)

// Name returns the name of a given language tag in the language identified by
// this tag. It supports both the language.Base and language.Tag types.
func (n SelfNamer) Name(x interface{}) string {
	t, _ := language.All.Compose(x)
	base, scr, reg := t.Raw()
	baseScript := language.Script{}
	if (scr == language.Script{} && reg != language.Region{}) {
		// For looking up in the self dictionary, we need to select the
		// maximized script. This is even the case if the script isn't
		// specified.
		s1, _ := t.Script()
		if baseScript = getScript(base); baseScript != s1 {
			scr = s1
		}
	}

	i, scr, reg := selfTagSet.index(base, scr, reg)
	if i == -1 {
		return ""
	}

	// Only return the display name if the script matches the expected script.
	if (scr != language.Script{}) {
		if (baseScript == language.Script{}) {
			baseScript = getScript(base)
		}
		if baseScript != scr {
			return ""
		}
	}

	return selfHeaders[0].name(i)
}

// getScript returns the maximized script for a base language.
func getScript(b language.Base) language.Script {
	tag, _ := language.Raw.Compose(b)
	scr, _ := tag.Script()
	return scr
}
