// Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

package sampling

import (
	"math"
	"testing"

	"github.com/aws/aws-xray-sdk-go/v2/utils"
	"github.com/stretchr/testify/assert"
)

const Interval = 100

func takeOverTime(r *Reservoir, millis int) int {
	taken := 0
	for i := 0; i < millis/Interval; i++ {
		if r.Take() {
			taken++
		}
		r.clock.Increment(0, 1e6*Interval)
	}
	return taken
}

const TestDuration = 1500

// Asserts consumption from reservoir once per second
func TestOnePerSecond(t *testing.T) {
	clock := &utils.MockClock{}
	cap := 1
	res := &Reservoir{
		clock: clock,
		reservoir: &reservoir{
			capacity: int64(cap),
		},
	}
	taken := takeOverTime(res, TestDuration)
	assert.True(t, int(math.Ceil(TestDuration/1000.0)) == taken)
}

// Asserts consumption from reservoir ten times per second
func TestTenPerSecond(t *testing.T) {
	clock := &utils.MockClock{}
	cap := 10
	res := &Reservoir{
		clock: clock,
		reservoir: &reservoir{
			capacity: int64(cap),
		},
	}
	taken := takeOverTime(res, TestDuration)
	assert.True(t, int(math.Ceil(float64(TestDuration*cap)/1000.0)) == taken)
}

func TestTakeQuotaAvailable(t *testing.T) {
	capacity := int64(100)
	used := int64(0)
	quota := int64(9)

	clock := &utils.MockClock{
		NowTime: 1500000000,
	}

	r := &CentralizedReservoir{
		quota: quota,
		reservoir: &reservoir{
			capacity:     capacity,
			used:         used,
			currentEpoch: clock.Now().Unix(),
		},
	}

	s := r.Take(clock.Now().Unix())
	assert.Equal(t, true, s)
	assert.Equal(t, int64(1), r.used)
}

func TestTakeQuotaUnavailable(t *testing.T) {
	capacity := int64(100)
	used := int64(100)
	quota := int64(9)

	clock := &utils.MockClock{
		NowTime: 1500000000,
	}

	r := &CentralizedReservoir{
		quota: quota,
		reservoir: &reservoir{
			capacity:     capacity,
			used:         used,
			currentEpoch: clock.Now().Unix(),
		},
	}

	s := r.Take(clock.Now().Unix())
	assert.Equal(t, false, s)
	assert.Equal(t, int64(100), r.used)
}

func TestExpiredReservoir(t *testing.T) {
	clock := &utils.MockClock{
		NowTime: 1500000001,
	}

	r := &CentralizedReservoir{
		expiresAt: 1500000000,
	}

	expired := r.expired(clock.Now().Unix())

	assert.Equal(t, true, expired)
}

// Assert that the borrow flag is reset every second
func TestBorrowFlagReset(t *testing.T) {
	clock := &utils.MockClock{
		NowTime: 1500000000,
	}

	r := &CentralizedReservoir{
		reservoir: &reservoir{
			capacity: 10,
		},
	}

	s := r.borrow(clock.Now().Unix())
	assert.True(t, s)

	s = r.borrow(clock.Now().Unix())
	assert.False(t, s)

	// Increment clock by 1
	clock = &utils.MockClock{
		NowTime: 1500000001,
	}

	// Reset borrow flag
	r.Take(clock.Now().Unix())

	s = r.borrow(clock.Now().Unix())
	assert.True(t, s)
}

// Assert that the reservoir does not allow borrowing if the reservoir capacity
// is zero.
func TestBorrowZeroCapacity(t *testing.T) {
	clock := &utils.MockClock{
		NowTime: 1500000000,
	}

	r := &CentralizedReservoir{
		reservoir: &reservoir{
			capacity: 0,
		},
	}

	s := r.borrow(clock.Now().Unix())
	assert.False(t, s)
}

func TestResetQuotaUsageRotation(t *testing.T) {
	capacity := int64(100)
	used := int64(0)
	quota := int64(5)

	clock := &utils.MockClock{
		NowTime: 1500000000,
	}

	r := &CentralizedReservoir{
		quota: quota,
		reservoir: &reservoir{
			capacity:     capacity,
			used:         used,
			currentEpoch: clock.Now().Unix(),
		},
	}

	// Consume quota for second
	for i := 0; i < 5; i++ {
		taken := r.Take(clock.Now().Unix())
		assert.Equal(t, true, taken)
		assert.Equal(t, int64(i+1), r.used)
	}

	// Take() should be false since no unused quota left
	taken := r.Take(clock.Now().Unix())
	assert.Equal(t, false, taken)
	assert.Equal(t, int64(5), r.used)

	// Increment epoch to reset unused quota
	clock = &utils.MockClock{
		NowTime: 1500000001,
	}

	// Take() should be true since ununsed quota is available
	taken = r.Take(clock.Now().Unix())
	assert.Equal(t, int64(1500000001), r.currentEpoch)
	assert.Equal(t, true, taken)
	assert.Equal(t, int64(1), r.used)
}

func TestResetReservoirUsageRotation(t *testing.T) {
	capacity := int64(5)
	used := int64(0)

	clock := &utils.MockClock{
		NowTime: 1500000000,
	}

	r := &Reservoir{
		clock: clock,
		reservoir: &reservoir{
			capacity:     capacity,
			used:         used,
			currentEpoch: clock.Now().Unix(),
		},
	}

	// Consume reservoir for second
	for i := 0; i < 5; i++ {
		taken := r.Take()
		assert.Equal(t, true, taken)
		assert.Equal(t, int64(i+1), r.used)
	}

	// Take() should be false since no reservoir left
	taken := r.Take()
	assert.Equal(t, false, taken)
	assert.Equal(t, int64(5), r.used)

	// Increment epoch to reset reservoir
	clock = &utils.MockClock{
		NowTime: 1500000001,
	}
	r.clock = clock

	// Take() should be true since reservoir is available
	taken = r.Take()
	assert.Equal(t, int64(1500000001), r.currentEpoch)
	assert.Equal(t, true, taken)
	assert.Equal(t, int64(1), r.used)
}
