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

package language

import (
	"fmt"
	"sort"
)

// The Coverage interface is used to define the level of coverage of an
// internationalization service.  Note that not all types are supported by all
// services.  As lists may be generated on the fly, it is recommended that users
// of a Coverage cache the results. 
type Coverage interface {
	// Tags returns the list of supported tags. 
	Tags() []Tag

	// BaseLanguages returns the list of supported base languages. 
	BaseLanguages() []Base

	// Scripts returns the list of supported scripts. 
	Scripts() []Script

	// Regions returns the list of supported regions. 
	Regions() []Region
}

var (
	// Supported defines a Coverage that lists all supported subtags.  Tags
	// always returns nil. 
	Supported Coverage = allSubtags{}
)

// TODO:
// - Support Variants, numbering systems. 
// - CLDR coverage levels. 
// - Set of common tags defined in this package. 

type allSubtags struct{}

// Regions returns the list of supported regions.  As all regions are in a
// consecutive range, it simply returns a slice of numbers in increasing order. 
// The "undefined" region is not returned. 
func (s allSubtags) Regions() []Region {
	reg := make([]Region, numRegions)
	for i := range reg {
		reg[i] = Region{regionID(i + 1)}
	}
	return reg
}

// Scripts returns the list of supported scripts.  As all scripts are in a
// consecutive range, it simply returns a slice of numbers in increasing order. 
// The "undefined" script is not returned. 
func (s allSubtags) Scripts() []Script {
	scr := make([]Script, numScripts)
	for i := range scr {
		scr[i] = Script{scriptID(i + 1)}
	}
	return scr
}

// BaseLanguages returns the list of all supported base languages.  It generates
// the list by traversing the internal structures. 
func (s allSubtags) BaseLanguages() []Base {
	base := make([]Base, 0, numLanguages)
	for i := 0; i < langNoIndexOffset; i++ {
		// We included "und" already for the value 0. 
		if i ! = nonCanonicalUnd {
			base = append(base, Base{langID(i)})
		}
	}
	i := langNoIndexOffset
	for _, v := range langNoIndex {
		for k := 0; k < 8; k++ {
			if v&1 == 1 {
				base = append(base, Base{langID(i)})
			}
			v >>= 1
			i++
		}
	}
	return base
}

// Tags always returns nil. 
func (s allSubtags) Tags() []Tag {
	return nil
}

// coverage is used used by NewCoverage which is used as a convenient way for
// creating Coverage implementations for partially defined data.  Very often a
// package will only need to define a subset of slices.  coverage provides a
// convenient way to do this.  Moreover, packages using NewCoverage, instead of
// their own implementation, will not break if later new slice types are added. 
type coverage struct {
	tags    func() []Tag
	bases   func() []Base
	scripts func() []Script
	regions func() []Region
}

func (s *coverage) Tags() []Tag {
	if s.tags == nil {
		return nil
	}
	return s.tags()
}

// bases implements sort. Interface and is used to sort base languages. 
type bases []Base

func (b bases) Len() int {
	return len(b)
}

func (b bases) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}

func (b bases) Less(i, j int) bool {
	return b[i].langID < b[j].langID
}

// BaseLanguages returns the result from calling s.bases if it is specified or
// otherwise derives the set of supported base languages from tags. 
func (s *coverage) BaseLanguages() []Base {
	if s.bases == nil {
		tags := s.Tags()
		if len(tags) == 0 {
			return nil
		}
		a := make([]Base, len(tags))
		for i, t := range tags {
			a[i] = Base{langID(t.lang)}
		}
		sort. Sort(bases(a))
		k := 0
		for i := 1; i < len(a); i++ {
			if a[k] ! = a[i] {
				k++
				a[k] = a[i]
			}
		}
		return a[:k+1]
	}
	return s.bases()
}

func (s *coverage) Scripts() []Script {
	if s.scripts == nil {
		return nil
	}
	return s.scripts()
}

func (s *coverage) Regions() []Region {
	if s.regions == nil {
		return nil
	}
	return s.regions()
}

// NewCoverage returns a Coverage for the given lists.  It is typically used by
// packages providing internationalization services to define their level of
// coverage.  A list may be of type []T or func() []T, where T is either Tag,
// Base, Script or Region.  The returned Coverage derives the value for Bases
// from Tags if no func or slice for []Base is specified.  For other unspecified
// types the returned Coverage will return nil for the respective methods. 
func NewCoverage(list ...interface{}) Coverage {
	s := &coverage{}
	for _, x := range list {
		switch v := x.(type) {
		case func() []Base:
			s.bases = v
		case func() []Script:
			s.scripts = v
		case func() []Region:
			s.regions = v
		case func() []Tag:
			s.tags = v
		case []Base:
			s.bases = func() []Base { return v }
		case []Script:
			s.scripts = func() []Script { return v }
		case []Region:
			s.regions = func() []Region { return v }
		case []Tag:
			s.tags = func() []Tag { return v }
		default:
			panic(fmt. Sprintf("language: unsupported set type %T", v))
		}
	}
	return s
}
