1// Copyright (c) 2020, Peter Ohler, All rights reserved.
2
3package alt
4
5import (
6	"fmt"
7
8	"github.com/ohler55/ojg/gen"
9)
10
11var emptySlice = []interface{}{}
12
13// Builder is a basic type builder. It uses a stack model to build where maps
14// (objects) and slices (arrays) add pushed on the stack and closed with a
15// pop.
16type Builder struct {
17	stack  []interface{}
18	starts []int
19}
20
21// Reset the builder.
22func (b *Builder) Reset() {
23	if 0 < cap(b.stack) && 0 < len(b.stack) {
24		b.stack = b.stack[:0]
25		b.starts = b.starts[:0]
26	} else {
27		b.stack = make([]interface{}, 0, 64)
28		b.starts = make([]int, 0, 16)
29	}
30}
31
32// Object pushs a map[string]interface{} onto the stack. A key must be
33// provided if the top of the stack is an object (map) and must not be
34// provided if the op of the stack is an array or slice.
35func (b *Builder) Object(key ...string) error {
36	newObj := map[string]interface{}{}
37	if 0 < len(key) {
38		if len(b.starts) == 0 || 0 <= b.starts[len(b.starts)-1] {
39			return fmt.Errorf("can not use a key when pushing to an array")
40		}
41		if obj, _ := b.stack[len(b.stack)-1].(map[string]interface{}); obj != nil {
42			obj[key[0]] = newObj
43		}
44	} else if 0 < len(b.starts) && b.starts[len(b.starts)-1] < 0 {
45		return fmt.Errorf("must have a key when pushing to an object")
46	}
47	b.starts = append(b.starts, -1)
48	b.stack = append(b.stack, newObj)
49
50	return nil
51}
52
53// Array pushs a []interface{} onto the stack. A key must be provided if the
54// top of the stack is an object (map) and must not be provided if the op of
55// the stack is an array or slice.
56func (b *Builder) Array(key ...string) error {
57	if 0 < len(key) {
58		if len(b.starts) == 0 || 0 <= b.starts[len(b.starts)-1] {
59			return fmt.Errorf("can not use a key when pushing to an array")
60		}
61		b.stack = append(b.stack, gen.Key(key[0]))
62	} else if 0 < len(b.starts) && b.starts[len(b.starts)-1] < 0 {
63		return fmt.Errorf("must have a key when pushing to an object")
64	}
65	b.starts = append(b.starts, len(b.stack))
66	b.stack = append(b.stack, emptySlice)
67
68	return nil
69}
70
71// Value pushs a value onto the stack. A key must be provided if the top of
72// the stack is an object (map) and must not be provided if the op of the
73// stack is an array or slice.
74func (b *Builder) Value(value interface{}, key ...string) error {
75	if 0 < len(key) {
76		if len(b.starts) == 0 || 0 <= b.starts[len(b.starts)-1] {
77			return fmt.Errorf("can not use a key when pushing to an array")
78		}
79		if obj, _ := b.stack[len(b.stack)-1].(map[string]interface{}); obj != nil {
80			obj[key[0]] = value
81		}
82	} else if 0 < len(b.starts) && b.starts[len(b.starts)-1] < 0 {
83		return fmt.Errorf("must have a key when pushing to an object")
84	} else {
85		b.stack = append(b.stack, value)
86	}
87	return nil
88}
89
90// Pop the stack, closing an array or object.
91func (b *Builder) Pop() {
92	if 0 < len(b.starts) {
93		start := b.starts[len(b.starts)-1]
94		if 0 <= start { // array
95			start++
96			size := len(b.stack) - start
97			a := make([]interface{}, size)
98			copy(a, b.stack[start:len(b.stack)])
99			b.stack = b.stack[:start]
100			b.stack[start-1] = a
101			if 2 < len(b.stack) {
102				if k, ok := b.stack[len(b.stack)-2].(gen.Key); ok {
103					if obj, _ := b.stack[len(b.stack)-3].(map[string]interface{}); obj != nil {
104						obj[string(k)] = a
105						b.stack = b.stack[:len(b.stack)-2]
106					}
107				}
108			}
109		}
110		b.starts = b.starts[:len(b.starts)-1]
111	}
112}
113
114// PopAll repeats Pop until all open arrays or objects are closed.
115func (b *Builder) PopAll() {
116	for 0 < len(b.starts) {
117		b.Pop()
118	}
119}
120
121// Result of the builder is returned. This is the first item pushed on to the
122// stack.
123func (b *Builder) Result() (result interface{}) {
124	if 0 < len(b.stack) {
125		result = b.stack[0]
126	}
127	return
128}
129