1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package currency
6
7import (
8	"fmt"
9	"io"
10	"sort"
11
12	"golang.org/x/text/internal/format"
13	"golang.org/x/text/internal/language/compact"
14)
15
16// Amount is an amount-currency unit pair.
17type Amount struct {
18	amount   interface{} // Change to decimal(64|128).
19	currency Unit
20}
21
22// Currency reports the currency unit of this amount.
23func (a Amount) Currency() Unit { return a.currency }
24
25// TODO: based on decimal type, but may make sense to customize a bit.
26// func (a Amount) Decimal()
27// func (a Amount) Int() (int64, error)
28// func (a Amount) Fraction() (int64, error)
29// func (a Amount) Rat() *big.Rat
30// func (a Amount) Float() (float64, error)
31// func (a Amount) Scale() uint
32// func (a Amount) Precision() uint
33// func (a Amount) Sign() int
34//
35// Add/Sub/Div/Mul/Round.
36
37var space = []byte(" ")
38
39// Format implements fmt.Formatter. It accepts format.State for
40// language-specific rendering.
41func (a Amount) Format(s fmt.State, verb rune) {
42	v := formattedValue{
43		currency: a.currency,
44		amount:   a.amount,
45		format:   defaultFormat,
46	}
47	v.Format(s, verb)
48}
49
50// formattedValue is currency amount or unit that implements language-sensitive
51// formatting.
52type formattedValue struct {
53	currency Unit
54	amount   interface{} // Amount, Unit, or number.
55	format   *options
56}
57
58// Format implements fmt.Formatter. It accepts format.State for
59// language-specific rendering.
60func (v formattedValue) Format(s fmt.State, verb rune) {
61	var lang compact.ID
62	if state, ok := s.(format.State); ok {
63		lang, _ = compact.RegionalID(compact.Tag(state.Language()))
64	}
65
66	// Get the options. Use DefaultFormat if not present.
67	opt := v.format
68	if opt == nil {
69		opt = defaultFormat
70	}
71	cur := v.currency
72	if cur.index == 0 {
73		cur = opt.currency
74	}
75
76	// TODO: use pattern.
77	io.WriteString(s, opt.symbol(lang, cur))
78	if v.amount != nil {
79		s.Write(space)
80
81		// TODO: apply currency-specific rounding
82		scale, _ := opt.kind.Rounding(cur)
83		if _, ok := s.Precision(); !ok {
84			fmt.Fprintf(s, "%.*f", scale, v.amount)
85		} else {
86			fmt.Fprint(s, v.amount)
87		}
88	}
89}
90
91// Formatter decorates a given number, Unit or Amount with formatting options.
92type Formatter func(amount interface{}) formattedValue
93
94// func (f Formatter) Options(opts ...Option) Formatter
95
96// TODO: call this a Formatter or FormatFunc?
97
98var dummy = USD.Amount(0)
99
100// adjust creates a new Formatter based on the adjustments of fn on f.
101func (f Formatter) adjust(fn func(*options)) Formatter {
102	var o options = *(f(dummy).format)
103	fn(&o)
104	return o.format
105}
106
107// Default creates a new Formatter that defaults to currency unit c if a numeric
108// value is passed that is not associated with a currency.
109func (f Formatter) Default(currency Unit) Formatter {
110	return f.adjust(func(o *options) { o.currency = currency })
111}
112
113// Kind sets the kind of the underlying currency unit.
114func (f Formatter) Kind(k Kind) Formatter {
115	return f.adjust(func(o *options) { o.kind = k })
116}
117
118var defaultFormat *options = ISO(dummy).format
119
120var (
121	// Uses Narrow symbols. Overrides Symbol, if present.
122	NarrowSymbol Formatter = Formatter(formNarrow)
123
124	// Use Symbols instead of ISO codes, when available.
125	Symbol Formatter = Formatter(formSymbol)
126
127	// Use ISO code as symbol.
128	ISO Formatter = Formatter(formISO)
129
130	// TODO:
131	// // Use full name as symbol.
132	// Name Formatter
133)
134
135// options configures rendering and rounding options for an Amount.
136type options struct {
137	currency Unit
138	kind     Kind
139
140	symbol func(compactIndex compact.ID, c Unit) string
141}
142
143func (o *options) format(amount interface{}) formattedValue {
144	v := formattedValue{format: o}
145	switch x := amount.(type) {
146	case Amount:
147		v.amount = x.amount
148		v.currency = x.currency
149	case *Amount:
150		v.amount = x.amount
151		v.currency = x.currency
152	case Unit:
153		v.currency = x
154	case *Unit:
155		v.currency = *x
156	default:
157		if o.currency.index == 0 {
158			panic("cannot format number without a currency being set")
159		}
160		// TODO: Must be a number.
161		v.amount = x
162		v.currency = o.currency
163	}
164	return v
165}
166
167var (
168	optISO    = options{symbol: lookupISO}
169	optSymbol = options{symbol: lookupSymbol}
170	optNarrow = options{symbol: lookupNarrow}
171)
172
173// These need to be functions, rather than curried methods, as curried methods
174// are evaluated at init time, causing tables to be included unconditionally.
175func formISO(x interface{}) formattedValue    { return optISO.format(x) }
176func formSymbol(x interface{}) formattedValue { return optSymbol.format(x) }
177func formNarrow(x interface{}) formattedValue { return optNarrow.format(x) }
178
179func lookupISO(x compact.ID, c Unit) string    { return c.String() }
180func lookupSymbol(x compact.ID, c Unit) string { return normalSymbol.lookup(x, c) }
181func lookupNarrow(x compact.ID, c Unit) string { return narrowSymbol.lookup(x, c) }
182
183type symbolIndex struct {
184	index []uint16 // position corresponds with compact index of language.
185	data  []curToIndex
186}
187
188var (
189	normalSymbol = symbolIndex{normalLangIndex, normalSymIndex}
190	narrowSymbol = symbolIndex{narrowLangIndex, narrowSymIndex}
191)
192
193func (x *symbolIndex) lookup(lang compact.ID, c Unit) string {
194	for {
195		index := x.data[x.index[lang]:x.index[lang+1]]
196		i := sort.Search(len(index), func(i int) bool {
197			return index[i].cur >= c.index
198		})
199		if i < len(index) && index[i].cur == c.index {
200			x := index[i].idx
201			start := x + 1
202			end := start + uint16(symbols[x])
203			if start == end {
204				return c.String()
205			}
206			return symbols[start:end]
207		}
208		if lang == 0 {
209			break
210		}
211		lang = lang.Parent()
212	}
213	return c.String()
214}
215