1package annotate
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7	"time"
8
9	glob "github.com/ryanuber/go-glob"
10)
11
12type RFC3339 struct {
13	time.Time
14}
15
16func (t RFC3339) MarshalJSON() ([]byte, error) {
17	return []byte(`"` + t.Format(time.RFC3339) + `"`), nil
18}
19
20func (t *RFC3339) UnmarshalJSON(b []byte) (err error) {
21	if b[0] == '"' && b[len(b)-1] == '"' {
22		b = b[1 : len(b)-1]
23	}
24	if len(b) == 0 {
25		t.Time = time.Time{}
26		return
27	}
28	t.Time, err = time.Parse(time.RFC3339, string(b))
29	return
30}
31
32type Epoch struct {
33	time.Time
34}
35
36func (t Epoch) MarshalJSON() ([]byte, error) {
37	return []byte(fmt.Sprintf("%v", t.UTC().Unix())), nil
38}
39
40func (t *Epoch) UnmarshalJSON(b []byte) (err error) {
41	if len(b) == 0 {
42		t.Time = time.Time{}
43		return
44	}
45	epoch, err := strconv.ParseInt(string(b), 10, 64)
46	if err != nil {
47		return err
48	}
49	t.Time = time.Unix(epoch, 0)
50	return
51}
52
53func NewAnnotation(id string, start, end time.Time, user, owner, source, host, category, url, message string) (a Annotation) {
54	a.Id = id
55	a.StartDate.Time = start
56	a.EndDate.Time = end
57	a.CreationUser = user
58	a.Owner = owner
59	a.Source = source
60	a.Category = category
61	a.Url = url
62	a.Message = message
63	a.Host = host
64	return
65}
66
67type Annotation struct {
68	AnnotationFields
69	StartDate RFC3339
70	EndDate   RFC3339
71}
72
73type EpochAnnotation struct {
74	AnnotationFields
75	StartDate Epoch
76	EndDate   Epoch
77}
78
79// AnnotationsByStartID is a Type to sort by start time then by id
80type AnnotationsByStartID Annotations
81
82func (b AnnotationsByStartID) Len() int      { return len(b) }
83func (b AnnotationsByStartID) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
84func (b AnnotationsByStartID) Less(i, j int) bool {
85	if b[i].StartDate.Time.Before(b[j].StartDate.Time) {
86		return true
87	}
88	if b[i].StartDate.Time.After(b[j].StartDate.Time) {
89		return false
90	}
91	return b[i].Id < b[j].Id
92}
93
94func emptyOrGlob(userVal, fieldVal string) bool {
95	if userVal == "empty" && fieldVal == "" {
96		return true
97	}
98
99	return glob.Glob(strings.ToLower(userVal), strings.ToLower(fieldVal))
100}
101
102// Ask makes it so annotations can be filtered in memory using
103// github.com/kylebrandt/boolq
104func (a Annotation) Ask(filter string) (bool, error) {
105	sp := strings.SplitN(filter, ":", 2)
106	if len(sp) != 2 {
107		return false, fmt.Errorf("bad filter, filter must be in k:v format, got %v", filter)
108	}
109	key := sp[0]
110	value := sp[1]
111	switch key {
112	case "owner":
113		return emptyOrGlob(value, a.Owner), nil
114	case "user":
115		return emptyOrGlob(value, a.CreationUser), nil
116	case "host":
117		return emptyOrGlob(value, a.Host), nil
118	case "category":
119		return emptyOrGlob(value, a.Category), nil
120	case "url":
121		return emptyOrGlob(value, a.Url), nil
122	case "message":
123		return emptyOrGlob(value, a.Message), nil
124	default:
125		return false, fmt.Errorf("invalid keyword: %s", key)
126	}
127}
128
129func (ea *EpochAnnotation) AsAnnotation() (a Annotation) {
130	a.AnnotationFields = ea.AnnotationFields
131	a.StartDate.Time = ea.StartDate.Time
132	a.EndDate.Time = ea.EndDate.Time
133	return
134}
135
136func (a *Annotation) AsEpochAnnotation() (ea EpochAnnotation) {
137	ea.AnnotationFields = a.AnnotationFields
138	ea.StartDate.Time = a.StartDate.Time
139	ea.EndDate.Time = a.EndDate.Time
140	return
141}
142
143type AnnotationFields struct {
144	Id           string
145	Message      string
146	CreationUser string
147	Url          string
148	Source       string
149	Host         string
150	Owner        string
151	Category     string
152}
153
154const (
155	Message      = "Message"
156	StartDate    = "StartDate"
157	EndDate      = "EndDate"
158	Source       = "Source"
159	Host         = "Host"
160	CreationUser = "CreationUser"
161	Owner        = "Owner"
162	Category     = "Category"
163	Url          = "Url"
164)
165
166type Annotations []Annotation
167type EpochAnnotations []EpochAnnotation
168
169func (as Annotations) AsEpochAnnotations() EpochAnnotations {
170	eas := make(EpochAnnotations, len(as))
171	for i, a := range as {
172		eas[i] = a.AsEpochAnnotation()
173	}
174	return eas
175}
176
177func (a *Annotation) SetNow() {
178	a.StartDate.Time = time.Now()
179	a.EndDate = a.StartDate
180}
181
182func (a *Annotation) IsTimeNotSet() bool {
183	t := time.Time{}
184	return a.StartDate.Equal(t) || a.EndDate.Equal(t)
185}
186
187func (a *Annotation) IsOneTimeSet() bool {
188	t := time.Time{}
189	return (a.StartDate.Equal(t) && !a.EndDate.Equal(t)) || (!a.StartDate.Equal(t) && a.EndDate.Equal(t))
190}
191
192// Match Times Sets Both times to the greater of the two times
193func (a *Annotation) MatchTimes() {
194	if a.StartDate.After(a.EndDate.Time) {
195		a.EndDate = a.StartDate
196		return
197	}
198	a.StartDate = a.EndDate
199}
200
201func (a *Annotation) ValidateTime() error {
202	t := time.Time{}
203	if a.StartDate.Equal(t) {
204		return fmt.Errorf("StartDate is not set")
205	}
206	if a.EndDate.Equal(t) {
207		return fmt.Errorf("StartDate is not set")
208	}
209	if a.EndDate.Before(a.StartDate.Time) {
210		return fmt.Errorf("EndDate is before StartDate")
211	}
212	return nil
213}
214