// Copyright (c) 2012-2016 Eli Janssen
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package statsd

import (
	"bytes"
	"log"
	"reflect"
	"strings"
	"testing"
	"time"
)

var statsdSubStatterPacketTests = []struct {
	Prefix    string
	SubPrefix string
	Method    string
	Stat      string
	Value     interface{}
	Rate      float32
	Expected  string
}{
	{"test", "sub", "Gauge", "gauge", int64(1), 1.0, "test.sub.gauge:1|g"},
	{"test", "sub", "Inc", "count", int64(1), 0.999999, "test.sub.count:1|c|@0.999999"},
	{"test", "sub", "Inc", "count", int64(1), 1.0, "test.sub.count:1|c"},
	{"test", "sub", "Dec", "count", int64(1), 1.0, "test.sub.count:-1|c"},
	{"test", "sub", "Timing", "timing", int64(1), 1.0, "test.sub.timing:1|ms"},
	{"test", "sub", "TimingDuration", "timing", 1500 * time.Microsecond, 1.0, "test.sub.timing:1.5|ms"},
	{"test", "sub", "TimingDuration", "timing", 3 * time.Microsecond, 1.0, "test.sub.timing:0.003|ms"},
	{"test", "sub", "Set", "strset", "pickle", 1.0, "test.sub.strset:pickle|s"},
	{"test", "sub", "SetInt", "intset", int64(1), 1.0, "test.sub.intset:1|s"},
	{"test", "sub", "GaugeDelta", "gauge", int64(1), 1.0, "test.sub.gauge:+1|g"},
	{"test", "sub", "GaugeDelta", "gauge", int64(-1), 1.0, "test.sub.gauge:-1|g"},
	// empty sub prefix -- note: not used in subsub tests
	{"test", "", "Inc", "count", int64(1), 1.0, "test.count:1|c"},
	// empty base prefix
	{"", "sub", "Inc", "count", int64(1), 1.0, "sub.count:1|c"},
}

func TestSubStatterClient(t *testing.T) {
	l, err := newUDPListener("127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	for _, tt := range statsdSubStatterPacketTests {
		c, err := NewClient(l.LocalAddr().String(), tt.Prefix)
		if err != nil {
			t.Fatal(err)
		}
		s := c.NewSubStatter(tt.SubPrefix)
		method := reflect.ValueOf(s).MethodByName(tt.Method)
		e := method.Call([]reflect.Value{
			reflect.ValueOf(tt.Stat),
			reflect.ValueOf(tt.Value),
			reflect.ValueOf(tt.Rate)})[0]
		errInter := e.Interface()
		if errInter != nil {
			t.Fatal(errInter.(error))
		}

		data := make([]byte, 128)
		_, _, err = l.ReadFrom(data)
		if err != nil {
			c.Close()
			t.Fatal(err)
		}

		data = bytes.TrimRight(data, "\x00")
		if !bytes.Equal(data, []byte(tt.Expected)) {
			c.Close()
			t.Fatalf("%s got '%s' expected '%s'", tt.Method, data, tt.Expected)
		}
		c.Close()
	}
}

func TestMultSubStatterClient(t *testing.T) {
	l, err := newUDPListener("127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	for _, tt := range statsdSubStatterPacketTests {
		// ignore empty sub test for this, as there is nothing to regex sub
		if tt.SubPrefix == "" {
			continue
		}
		c, err := NewClient(l.LocalAddr().String(), tt.Prefix)
		if err != nil {
			t.Fatal(err)
		}
		s1 := c.NewSubStatter("sub1")
		s2 := c.NewSubStatter("sub2")

		responses := [][]byte{}
		for _, s := range []SubStatter{s1, s2} {
			method := reflect.ValueOf(s).MethodByName(tt.Method)
			e := method.Call([]reflect.Value{
				reflect.ValueOf(tt.Stat),
				reflect.ValueOf(tt.Value),
				reflect.ValueOf(tt.Rate)})[0]
			errInter := e.Interface()
			if errInter != nil {
				t.Fatal(errInter.(error))
			}

			data := make([]byte, 128)
			_, _, err = l.ReadFrom(data)
			if err != nil {
				c.Close()
				t.Fatal(err)
			}

			data = bytes.TrimRight(data, "\x00")
			responses = append(responses, data)
		}

		expected := strings.Replace(tt.Expected, "sub.", "sub1.", -1)
		if !bytes.Equal(responses[0], []byte(expected)) {
			c.Close()
			t.Fatalf("%s got '%s' expected '%s'",
				tt.Method, responses[0], tt.Expected)
		}

		expected = strings.Replace(tt.Expected, "sub.", "sub2.", -1)
		if !bytes.Equal(responses[1], []byte(expected)) {
			c.Close()
			t.Fatalf("%s got '%s' expected '%s'",
				tt.Method, responses[1], tt.Expected)
		}
		c.Close()
	}
}

func TestSubSubStatterClient(t *testing.T) {
	l, err := newUDPListener("127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	for _, tt := range statsdSubStatterPacketTests {
		// ignore empty sub test for this, as there is nothing to regex sub
		if tt.SubPrefix == "" {
			continue
		}
		c, err := NewClient(l.LocalAddr().String(), tt.Prefix)
		if err != nil {
			t.Fatal(err)
		}
		s := c.NewSubStatter(tt.SubPrefix).NewSubStatter("sub2")

		method := reflect.ValueOf(s).MethodByName(tt.Method)
		e := method.Call([]reflect.Value{
			reflect.ValueOf(tt.Stat),
			reflect.ValueOf(tt.Value),
			reflect.ValueOf(tt.Rate)})[0]
		errInter := e.Interface()
		if errInter != nil {
			t.Fatal(errInter.(error))
		}

		data := make([]byte, 128)
		_, _, err = l.ReadFrom(data)
		if err != nil {
			c.Close()
			t.Fatal(err)
		}

		data = bytes.TrimRight(data, "\x00")
		expected := strings.Replace(tt.Expected, "sub.", "sub.sub2.", -1)
		if !bytes.Equal(data, []byte(expected)) {
			c.Close()
			t.Fatalf("%s got '%s' expected '%s'", tt.Method, data, tt.Expected)
		}
		c.Close()
	}
}

func TestSubStatterClosedClient(t *testing.T) {
	l, err := newUDPListener("127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	for _, tt := range statsdSubStatterPacketTests {
		c, err := NewClient(l.LocalAddr().String(), tt.Prefix)
		if err != nil {
			t.Fatal(err)
		}
		c.Close()
		s := c.NewSubStatter(tt.SubPrefix)
		method := reflect.ValueOf(s).MethodByName(tt.Method)
		e := method.Call([]reflect.Value{
			reflect.ValueOf(tt.Stat),
			reflect.ValueOf(tt.Value),
			reflect.ValueOf(tt.Rate)})[0]
		errInter := e.Interface()
		if errInter == nil {
			t.Fatal("Expected error but got none")
		}
	}
}

func TestNilSubStatterClient(t *testing.T) {
	l, err := newUDPListener("127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	for _, tt := range statsdSubStatterPacketTests {
		var c *Client
		s := c.NewSubStatter(tt.SubPrefix)

		method := reflect.ValueOf(s).MethodByName(tt.Method)
		e := method.Call([]reflect.Value{
			reflect.ValueOf(tt.Stat),
			reflect.ValueOf(tt.Value),
			reflect.ValueOf(tt.Rate)})[0]
		errInter := e.Interface()
		if errInter != nil {
			t.Fatal(errInter.(error))
		}

		data := make([]byte, 128)
		n, _, err := l.ReadFrom(data)
		// this is expected to error, since there should
		// be no udp data sent, so the read will time out
		if err == nil || n != 0 {
			c.Close()
			t.Fatal(err)
		}
		c.Close()
	}
}

func TestNoopSubStatterClient(t *testing.T) {
	l, err := newUDPListener("127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()
	for _, tt := range statsdSubStatterPacketTests {
		c, err := NewNoopClient(l.LocalAddr().String(), tt.Prefix)
		if err != nil {
			t.Fatal(err)
		}
		s := c.NewSubStatter(tt.SubPrefix)
		method := reflect.ValueOf(s).MethodByName(tt.Method)
		e := method.Call([]reflect.Value{
			reflect.ValueOf(tt.Stat),
			reflect.ValueOf(tt.Value),
			reflect.ValueOf(tt.Rate)})[0]
		errInter := e.Interface()
		if errInter != nil {
			t.Fatal(errInter.(error))
		}

		data := make([]byte, 128)
		n, _, err := l.ReadFrom(data)
		// this is expected to error, since there should
		// be no udp data sent, so the read will time out
		if err == nil || n != 0 {
			c.Close()
			t.Fatal(err)
		}
		c.Close()
	}
}

func ExampleClient_substatter() {
	// First create a client config. Here is a simple config that sends one
	// stat per packet (for compatibility).
	config := &ClientConfig{
		Address: "127.0.0.1:8125",
		Prefix:  "test-client",
	}

	// Now create the client
	client, err := NewClientWithConfig(config)
	// handle any errors
	if err != nil {
		log.Fatal(err)
	}
	// make sure to close to clean up when done, to avoid leaks.
	defer client.Close()

	// create a substatter
	subclient := client.NewSubStatter("sub")
	// send a stat
	err = subclient.Inc("stat1", 42, 1.0)
	// handle any errors
	if err != nil {
		log.Printf("Error sending metric: %+v", err)
	}
}
