1package pgtype
2
3import (
4	"database/sql/driver"
5	"encoding/binary"
6	"fmt"
7	"math"
8	"strconv"
9	"strings"
10
11	"github.com/jackc/pgx/pgio"
12	"github.com/pkg/errors"
13)
14
15type Polygon struct {
16	P      []Vec2
17	Status Status
18}
19
20func (dst *Polygon) Set(src interface{}) error {
21	return errors.Errorf("cannot convert %v to Polygon", src)
22}
23
24func (dst *Polygon) Get() interface{} {
25	switch dst.Status {
26	case Present:
27		return dst
28	case Null:
29		return nil
30	default:
31		return dst.Status
32	}
33}
34
35func (src *Polygon) AssignTo(dst interface{}) error {
36	return errors.Errorf("cannot assign %v to %T", src, dst)
37}
38
39func (dst *Polygon) DecodeText(ci *ConnInfo, src []byte) error {
40	if src == nil {
41		*dst = Polygon{Status: Null}
42		return nil
43	}
44
45	if len(src) < 7 {
46		return errors.Errorf("invalid length for Polygon: %v", len(src))
47	}
48
49	points := make([]Vec2, 0)
50
51	str := string(src[2:])
52
53	for {
54		end := strings.IndexByte(str, ',')
55		x, err := strconv.ParseFloat(str[:end], 64)
56		if err != nil {
57			return err
58		}
59
60		str = str[end+1:]
61		end = strings.IndexByte(str, ')')
62
63		y, err := strconv.ParseFloat(str[:end], 64)
64		if err != nil {
65			return err
66		}
67
68		points = append(points, Vec2{x, y})
69
70		if end+3 < len(str) {
71			str = str[end+3:]
72		} else {
73			break
74		}
75	}
76
77	*dst = Polygon{P: points, Status: Present}
78	return nil
79}
80
81func (dst *Polygon) DecodeBinary(ci *ConnInfo, src []byte) error {
82	if src == nil {
83		*dst = Polygon{Status: Null}
84		return nil
85	}
86
87	if len(src) < 5 {
88		return errors.Errorf("invalid length for Polygon: %v", len(src))
89	}
90
91	pointCount := int(binary.BigEndian.Uint32(src))
92	rp := 4
93
94	if 4+pointCount*16 != len(src) {
95		return errors.Errorf("invalid length for Polygon with %d points: %v", pointCount, len(src))
96	}
97
98	points := make([]Vec2, pointCount)
99	for i := 0; i < len(points); i++ {
100		x := binary.BigEndian.Uint64(src[rp:])
101		rp += 8
102		y := binary.BigEndian.Uint64(src[rp:])
103		rp += 8
104		points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)}
105	}
106
107	*dst = Polygon{
108		P:      points,
109		Status: Present,
110	}
111	return nil
112}
113
114func (src *Polygon) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
115	switch src.Status {
116	case Null:
117		return nil, nil
118	case Undefined:
119		return nil, errUndefined
120	}
121
122	buf = append(buf, '(')
123
124	for i, p := range src.P {
125		if i > 0 {
126			buf = append(buf, ',')
127		}
128		buf = append(buf, fmt.Sprintf(`(%s,%s)`,
129			strconv.FormatFloat(p.X, 'f', -1, 64),
130			strconv.FormatFloat(p.Y, 'f', -1, 64),
131		)...)
132	}
133
134	return append(buf, ')'), nil
135}
136
137func (src *Polygon) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
138	switch src.Status {
139	case Null:
140		return nil, nil
141	case Undefined:
142		return nil, errUndefined
143	}
144
145	buf = pgio.AppendInt32(buf, int32(len(src.P)))
146
147	for _, p := range src.P {
148		buf = pgio.AppendUint64(buf, math.Float64bits(p.X))
149		buf = pgio.AppendUint64(buf, math.Float64bits(p.Y))
150	}
151
152	return buf, nil
153}
154
155// Scan implements the database/sql Scanner interface.
156func (dst *Polygon) Scan(src interface{}) error {
157	if src == nil {
158		*dst = Polygon{Status: Null}
159		return nil
160	}
161
162	switch src := src.(type) {
163	case string:
164		return dst.DecodeText(nil, []byte(src))
165	case []byte:
166		srcCopy := make([]byte, len(src))
167		copy(srcCopy, src)
168		return dst.DecodeText(nil, srcCopy)
169	}
170
171	return errors.Errorf("cannot scan %T", src)
172}
173
174// Value implements the database/sql/driver Valuer interface.
175func (src *Polygon) Value() (driver.Value, error) {
176	return EncodeValueText(src)
177}
178