1package gopter
2
3import (
4	"fmt"
5	"reflect"
6)
7
8// Shrink is a stream of shrinked down values.
9// Once the result of a shrink is false, it is considered to be exhausted.
10// Important notes for implementors:
11//   * Ensure that the returned stream is finite, even though shrinking will
12//     eventually be aborted, infinite streams may result in very slow running
13//     test.
14//   * Ensure that modifications to the returned value will not affect the
15//     internal state of your Shrink. If in doubt return by value not by reference
16type Shrink func() (interface{}, bool)
17
18// Filter creates a shrink filtered by a condition
19func (s Shrink) Filter(condition func(interface{}) bool) Shrink {
20	if condition == nil {
21		return s
22	}
23	return func() (interface{}, bool) {
24		value, ok := s()
25		for ok && !condition(value) {
26			value, ok = s()
27		}
28		return value, ok
29	}
30}
31
32// Map creates a shrink by applying a converter to each element of a shrink.
33// f: has to be a function with one parameter (matching the generated value) and a single return.
34func (s Shrink) Map(f interface{}) Shrink {
35	mapperVal := reflect.ValueOf(f)
36	mapperType := mapperVal.Type()
37
38	if mapperVal.Kind() != reflect.Func {
39		panic(fmt.Sprintf("Param of Map has to be a func, but is %v", mapperType.Kind()))
40	}
41	if mapperType.NumIn() != 1 {
42		panic(fmt.Sprintf("Param of Map has to be a func with one param, but is %v", mapperType.NumIn()))
43	}
44	if mapperType.NumOut() != 1 {
45		panic(fmt.Sprintf("Param of Map has to be a func with one return value, but is %v", mapperType.NumOut()))
46	}
47
48	return func() (interface{}, bool) {
49		value, ok := s()
50		if ok {
51			return mapperVal.Call([]reflect.Value{reflect.ValueOf(value)})[0].Interface(), ok
52		}
53		return nil, false
54	}
55}
56
57// All collects all shrinks as a slice. Use with care as this might create
58// large results depending on the complexity of the shrink
59func (s Shrink) All() []interface{} {
60	result := []interface{}{}
61	value, ok := s()
62	for ok {
63		result = append(result, value)
64		value, ok = s()
65	}
66	return result
67}
68
69type concatedShrink struct {
70	index   int
71	shrinks []Shrink
72}
73
74func (c *concatedShrink) Next() (interface{}, bool) {
75	for c.index < len(c.shrinks) {
76		value, ok := c.shrinks[c.index]()
77		if ok {
78			return value, ok
79		}
80		c.index++
81	}
82	return nil, false
83}
84
85// ConcatShrinks concats an array of shrinks to a single shrinks
86func ConcatShrinks(shrinks ...Shrink) Shrink {
87	concated := &concatedShrink{
88		index:   0,
89		shrinks: shrinks,
90	}
91	return concated.Next
92}
93
94type interleaved struct {
95	first          Shrink
96	second         Shrink
97	firstExhausted bool
98	secondExhaused bool
99	state          bool
100}
101
102func (i *interleaved) Next() (interface{}, bool) {
103	for !i.firstExhausted || !i.secondExhaused {
104		i.state = !i.state
105		if i.state && !i.firstExhausted {
106			value, ok := i.first()
107			if ok {
108				return value, true
109			}
110			i.firstExhausted = true
111		} else if !i.state && !i.secondExhaused {
112			value, ok := i.second()
113			if ok {
114				return value, true
115			}
116			i.secondExhaused = true
117		}
118	}
119	return nil, false
120}
121
122// Interleave this shrink with another
123// Both shrinks are expected to produce the same result
124func (s Shrink) Interleave(other Shrink) Shrink {
125	interleaved := &interleaved{
126		first:  s,
127		second: other,
128	}
129	return interleaved.Next
130}
131
132// Shrinker creates a shrink for a given value
133type Shrinker func(value interface{}) Shrink
134
135type elementShrink struct {
136	original      []interface{}
137	index         int
138	elementShrink Shrink
139}
140
141func (e *elementShrink) Next() (interface{}, bool) {
142	element, ok := e.elementShrink()
143	if !ok {
144		return nil, false
145	}
146	shrinked := make([]interface{}, len(e.original))
147	copy(shrinked, e.original)
148	shrinked[e.index] = element
149
150	return shrinked, true
151}
152
153// CombineShrinker create a shrinker by combining a list of shrinkers.
154// The resulting shrinker will shrink an []interface{} where each element will be shrinked by
155// the corresonding shrinker in 'shrinkers'.
156// This method is implicitly used by CombineGens.
157func CombineShrinker(shrinkers ...Shrinker) Shrinker {
158	return func(v interface{}) Shrink {
159		values := v.([]interface{})
160		shrinks := make([]Shrink, 0, len(values))
161		for i, shrinker := range shrinkers {
162			if i >= len(values) {
163				break
164			}
165			shrink := &elementShrink{
166				original:      values,
167				index:         i,
168				elementShrink: shrinker(values[i]),
169			}
170			shrinks = append(shrinks, shrink.Next)
171		}
172		return ConcatShrinks(shrinks...)
173	}
174}
175
176// NoShrink is an empty shrink.
177var NoShrink = Shrink(func() (interface{}, bool) {
178	return nil, false
179})
180
181// NoShrinker is a shrinker for NoShrink, i.e. a Shrinker that will not shrink any values.
182// This is the default Shrinker if none is provided.
183var NoShrinker = Shrinker(func(value interface{}) Shrink {
184	return NoShrink
185})
186