1// Copyright 2016 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	"sort"
9	"time"
10
11	"golang.org/x/text/language"
12)
13
14// QueryIter represents a set of Units. The default set includes all Units that
15// are currently in use as legal tender in any Region.
16type QueryIter interface {
17	// Next returns true if there is a next element available.
18	// It must be called before any of the other methods are called.
19	Next() bool
20
21	// Unit returns the unit of the current iteration.
22	Unit() Unit
23
24	// Region returns the Region for the current iteration.
25	Region() language.Region
26
27	// From returns the date from which the unit was used in the region.
28	// It returns false if this date is unknown.
29	From() (time.Time, bool)
30
31	// To returns the date up till which the unit was used in the region.
32	// It returns false if this date is unknown or if the unit is still in use.
33	To() (time.Time, bool)
34
35	// IsTender reports whether the unit is a legal tender in the region during
36	// the specified date range.
37	IsTender() bool
38}
39
40// Query represents a set of Units. The default set includes all Units that are
41// currently in use as legal tender in any Region.
42func Query(options ...QueryOption) QueryIter {
43	it := &iter{
44		end:  len(regionData),
45		date: 0xFFFFFFFF,
46	}
47	for _, fn := range options {
48		fn(it)
49	}
50	return it
51}
52
53// NonTender returns a new query that also includes matching Units that are not
54// legal tender.
55var NonTender QueryOption = nonTender
56
57func nonTender(i *iter) {
58	i.nonTender = true
59}
60
61// Historical selects the units for all dates.
62var Historical QueryOption = historical
63
64func historical(i *iter) {
65	i.date = hist
66}
67
68// A QueryOption can be used to change the set of unit information returned by
69// a query.
70type QueryOption func(*iter)
71
72// Date queries the units that were in use at the given point in history.
73func Date(t time.Time) QueryOption {
74	d := toDate(t)
75	return func(i *iter) {
76		i.date = d
77	}
78}
79
80// Region limits the query to only return entries for the given region.
81func Region(r language.Region) QueryOption {
82	p, end := len(regionData), len(regionData)
83	x := regionToCode(r)
84	i := sort.Search(len(regionData), func(i int) bool {
85		return regionData[i].region >= x
86	})
87	if i < len(regionData) && regionData[i].region == x {
88		p = i
89		for i++; i < len(regionData) && regionData[i].region == x; i++ {
90		}
91		end = i
92	}
93	return func(i *iter) {
94		i.p, i.end = p, end
95	}
96}
97
98const (
99	hist = 0x00
100	now  = 0xFFFFFFFF
101)
102
103type iter struct {
104	*regionInfo
105	p, end    int
106	date      uint32
107	nonTender bool
108}
109
110func (i *iter) Next() bool {
111	for ; i.p < i.end; i.p++ {
112		i.regionInfo = &regionData[i.p]
113		if !i.nonTender && !i.IsTender() {
114			continue
115		}
116		if i.date == hist || (i.from <= i.date && (i.to == 0 || i.date <= i.to)) {
117			i.p++
118			return true
119		}
120	}
121	return false
122}
123
124func (r *regionInfo) Region() language.Region {
125	// TODO: this could be much faster.
126	var buf [2]byte
127	buf[0] = uint8(r.region >> 8)
128	buf[1] = uint8(r.region)
129	return language.MustParseRegion(string(buf[:]))
130}
131
132func (r *regionInfo) Unit() Unit {
133	return Unit{r.code &^ nonTenderBit}
134}
135
136func (r *regionInfo) IsTender() bool {
137	return r.code&nonTenderBit == 0
138}
139
140func (r *regionInfo) From() (time.Time, bool) {
141	if r.from == 0 {
142		return time.Time{}, false
143	}
144	return fromDate(r.from), true
145}
146
147func (r *regionInfo) To() (time.Time, bool) {
148	if r.to == 0 {
149		return time.Time{}, false
150	}
151	return fromDate(r.to), true
152}
153