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