package statsdtest

import (
	"bytes"
	"fmt"
	"strings"
)

// Stat contains the raw and extracted stat information from a stat that was
// sent by the RecordingSender. Raw will always have the content that was
// consumed for this specific stat and Parsed will be set if no errors were hit
// pulling information out of it.
type Stat struct {
	Raw    []byte
	Stat   string
	Value  string
	Tag    string
	Rate   string
	Parsed bool
}

// String fulfils the stringer interface
func (s *Stat) String() string {
	return fmt.Sprintf("%s %s %s", s.Stat, s.Value, s.Rate)
}

// ParseStats takes a sequence of bytes destined for a Statsd server and parses
// it out into one or more Stat structs. Each struct includes both the raw
// bytes (copied, so the src []byte may be reused if desired) as well as each
// component it was able to parse out. If parsing was incomplete Stat.Parsed
// will be set to false but no error is returned / kept.
func ParseStats(src []byte) Stats {
	d := make([]byte, len(src))
	copy(d, src)

	// standard protocol indicates one stat per line
	entries := bytes.Split(d, []byte{'\n'})

	result := make(Stats, len(entries))

	for i, e := range entries {
		result[i] = Stat{Raw: e}
		ss := &result[i]

		// : deliniates the stat name from the stat data
		marker := bytes.IndexByte(e, ':')
		if marker == -1 {
			continue
		}
		ss.Stat = string(e[0:marker])

		// stat data folows ':' with the form {value}|{type tag}[|@{sample rate}]
		e = e[marker+1:]
		marker = bytes.IndexByte(e, '|')
		if marker == -1 {
			continue
		}

		ss.Value = string(e[:marker])

		e = e[marker+1:]
		marker = bytes.IndexByte(e, '|')
		if marker == -1 {
			// no sample rate
			ss.Tag = string(e)
		} else {
			ss.Tag = string(e[:marker])
			e = e[marker+1:]
			if len(e) == 0 || e[0] != '@' {
				// sample rate should be prefixed with '@'; bail otherwise
				continue
			}
			ss.Rate = string(e[1:])
		}

		ss.Parsed = true
	}

	return result
}

// Stats is a slice of Stat
type Stats []Stat

// Unparsed returns any stats that were unable to be completely parsed.
func (s Stats) Unparsed() Stats {
	var r Stats
	for _, e := range s {
		if !e.Parsed {
			r = append(r, e)
		}
	}

	return r
}

// CollectNamed returns all data sent for a given stat name.
func (s Stats) CollectNamed(statName string) Stats {
	return s.Collect(func(e Stat) bool {
		return e.Stat == statName
	})
}

// Collect gathers all stats that make some predicate true.
func (s Stats) Collect(pred func(Stat) bool) Stats {
	var r Stats
	for _, e := range s {
		if pred(e) {
			r = append(r, e)
		}
	}
	return r
}

// Values returns the values associated with this Stats object.
func (s Stats) Values() []string {
	if len(s) == 0 {
		return nil
	}

	r := make([]string, len(s))
	for i, e := range s {
		r[i] = e.Value
	}
	return r
}

// String fulfils the stringer interface
func (s Stats) String() string {
	if len(s) == 0 {
		return ""
	}

	r := make([]string, len(s))
	for i, e := range s {
		r[i] = e.String()
	}
	return strings.Join(r, "\n")
}
