package statsd

import (
    "bytes"
    "math"
    "math/rand"
    "testing"
    "time"
)

var testConf = StatsConfig{
    Rate:   1.0,
    Prefix: "",
}

var unsampledTestConf = StatsConfig{
    Rate:   0.0,
    Prefix: "",
}

func assertEquals(t *testing.T, got, expected interface{}) {
    if expected != got {
        t.Errorf("expected '%v', got '%v'", expected, got)
    }

}

func setUp(t *testing.T, conf StatsConfig) (*bytes.Buffer, Stats) {
    b := &bytes.Buffer{}
    s := New(b, conf)

    return b, s
}

func TestSafeDial(t *testing.T) {
    s := SafeDial("there is no spoon", "", testConf)

    assertEquals(t, s, noopStats)
}

func TestMustDial(t *testing.T) {
    defer func() { recover() }()

    _ = MustDial("there is no spoon", "", testConf)

    t.Error("Expected panic()")
}

func TestIncrBy(t *testing.T) {
    b, s := setUp(t, testConf)

    s.IncrBy("gorets", 13)

    assertEquals(t, "gorets:13|c", b.String())
}

func TestIncr(t *testing.T) {
    b, s := setUp(t, testConf)

    s.Incr("goreta")

    assertEquals(t, "goreta:1|c", b.String())
}

func TestIncrByUnsampled(t *testing.T) {
    b, s := setUp(t, testConf)

    s.IncrBy("gorets", 987)
    assertEquals(t, "gorets:987|c", b.String())

    b, s = setUp(t, unsampledTestConf)

    s.IncrBy("gorets", 987)
    assertEquals(t, "", b.String())
    s.IncrByUnsampled("gorets", 987)
    assertEquals(t, "gorets:987|c", b.String())
}

func TestIncrUnsampled(t *testing.T) {
    b, s := setUp(t, testConf)

    s.Incr("goreta")
    assertEquals(t, "goreta:1|c", b.String())

    b, s = setUp(t, unsampledTestConf)

    s.Incr("goreta")
    assertEquals(t, "", b.String())
    s.IncrUnsampled("goreta")
    assertEquals(t, "goreta:1|c", b.String())
}

func TestTiming(t *testing.T) {
    b, s := setUp(t, testConf)

    s.Timing("glork", 320*time.Millisecond)

    assertEquals(t, "glork:320|ms", b.String())
}

func TestTimingUnsampled(t *testing.T) {
    b, s := setUp(t, unsampledTestConf)

    s.Timing("glork", 320*time.Millisecond)
    assertEquals(t, "", b.String())
    s.TimingUnsampled("glork", 320*time.Millisecond)
    assertEquals(t, "glork:320|ms", b.String())
}

func TestIntegerTiming(t *testing.T) {
    b, s := setUp(t, testConf)

    s.IntegerTiming("xylophone", 387563)
    assertEquals(t, "xylophone:387563|ms", b.String())
}

func TestIntegerTimingUnsampled(t *testing.T) {
    b, s := setUp(t, unsampledTestConf)

    s.IntegerTiming("xylophone", 387563)
    assertEquals(t, "", b.String())
    s.IntegerTimingUnsampled("xylophone", 387563)
    assertEquals(t, "xylophone:387563|ms", b.String())
}

func TestGaugeSet(t *testing.T) {
    b, s := setUp(t, testConf)

    s.GaugeSet("gaugor", 333)

    assertEquals(t, "gaugor:333|g", b.String())
}

func TestGaugeSetUnsampled(t *testing.T) {
    b, s := setUp(t, testConf)

    s.GaugeSetUnsampled("gaugor", 333)
    assertEquals(t, "gaugor:333|g", b.String())

    b, s = setUp(t, unsampledTestConf)

    s.GaugeSet("gaugor", 333)
    assertEquals(t, "", b.String())
    s.GaugeSetUnsampled("gaugor", 333)
    assertEquals(t, "gaugor:333|g", b.String())
}

func TestGaugeChangeUnsampled(t *testing.T) {
    b, s := setUp(t, testConf)

    s.GaugeChangeUnsampled("gaugor", 333)
    assertEquals(t, "gaugor:+333|g", b.String())

    b, s = setUp(t, unsampledTestConf)

    s.GaugeChange("gaugor", 333)
    assertEquals(t, "", b.String())
    s.GaugeChangeUnsampled("gaugor", 333)
    assertEquals(t, "gaugor:+333|g", b.String())
}

func TestGaugeChange(t *testing.T) {
    b, s := setUp(t, testConf)

    s.GaugeChange("gaugor", 333)

    assertEquals(t, "gaugor:+333|g", b.String())
}

func TestUnique(t *testing.T) {
    b, s := setUp(t, testConf)

    s.Unique("gaugor", "case")

    assertEquals(t, "gaugor:case|s", b.String())
}

func TestSamplingZero(t *testing.T) {
    b, s := setUp(t, StatsConfig{Rate: 0, Prefix: ""})

    s.Incr("nobucket") // should never succeed

    assertEquals(t, "", b.String())
}

func TestPrefix(t *testing.T) {
    b, s := setUp(t, StatsConfig{Rate: 1.0, Prefix: "root"})

    s.Incr("gorets")

    assertEquals(t, "root.gorets:1|c", b.String())
}

func checkIfHit(s Stats, b *bytes.Buffer) bool {
    b.Reset()
    s.Incr("hit")
    return b.Len() != 0
}

func TestRate(t *testing.T) {
    for i := 0; i < 10; i++ {
        rand.Seed(int64(i))
        doTestRate(t)
    }
}

func doTestRate(t *testing.T) {
    const GRANULITY = 50
    const TRIES = GRANULITY * 100

    rate := float32(rand.Int31n(GRANULITY)) / GRANULITY
    b, s := setUp(t, StatsConfig{Rate: rate, Prefix: ""})

    hitCount := 0
    for count := 0; count < TRIES; count++ {
        if checkIfHit(s, b) {
            hitCount++
        }
    }

    realRate := float32(hitCount) / TRIES

    if math.Abs(float64(realRate-rate)) > (1.0 / GRANULITY) {
        t.Errorf("Expected rate %v, got %v", rate, realRate)
    }
}

func TestSampledTimer(t *testing.T) {
    b, s := setUp(t, StatsConfig{Rate: 0.9, Prefix: ""})
    for b.Len() == 0 {
        s.Timing("sampled", 30*time.Millisecond)
    }
    if got, exp := b.String(), "sampled:30|ms|@0.900000"; got != exp {
        t.Errorf("incorrect format for sampled stats: have %#v, want %#v", got, exp)
    }
}
