// Copyright 2018-2018 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 ringbuffer

import (
	"math/rand"
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/aws/aws-xray-daemon/pkg/bufferpool"
	"github.com/aws/aws-xray-daemon/pkg/telemetry"
	"github.com/aws/aws-xray-daemon/pkg/tracesegment"
	"github.com/aws/aws-xray-daemon/pkg/util/test"
	"github.com/stretchr/testify/assert"
)

func init() {
	telemetry.T = telemetry.GetTestTelemetry()
}

func TestRingBufferNewWithZeroCapacity(t *testing.T) {
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)
	// Only run the failing part when a specific env variable is set
	if os.Getenv("Test_New") == "1" {
		New(0, bufferPool)
		return
	}
	// Start the actual test in a different subprocess
	cmd := exec.Command(os.Args[0], "-test.run=TestRingBufferNewWithZeroCapacity")
	cmd.Env = append(os.Environ(), "Test_New=1")
	if err := cmd.Start(); err != nil {
		t.Fatal(err)
	}
	// Check that the program exited
	err := cmd.Wait()
	if e, ok := err.(*exec.ExitError); !ok || e.Success() {
		t.Fatalf("Process ran with err %v, want exit status 1", err)
	}
}

func TestRingBufferNewWithDefaultCapacity(t *testing.T) {
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)

	randomFlag := rand.Intn(defaultCapacity - 1)
	ringBuffer := New(randomFlag, bufferPool) // Ring buffer initialized with less than default capacity value

	assert.Equal(t, defaultCapacity, cap(ringBuffer.c), "The size of buffered channel should be equal to default capacity")
	assert.Equal(t, defaultCapacity, cap(ringBuffer.Channel), "The size of buffered channel should be equal to default capacity")
	assert.Equal(t, false, ringBuffer.Empty, "The ringBuffer is not empty")
	assert.Equal(t, uint64(0), ringBuffer.count, "The truncated count should be 0")
	assert.Equal(t, bufferPool, ringBuffer.pool, "The value of bufferpool should be same with the given value")

}

func TestRingBufferNew(t *testing.T) { // RingBuffer size greater than defaultCapacity
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)

	randomFlag := getTestChannelSize()
	ringBuffer := New(randomFlag, bufferPool)

	assert.Equal(t, randomFlag, cap(ringBuffer.c), "The size of buffered channel should be same with the given number")
	assert.Equal(t, randomFlag, cap(ringBuffer.Channel), "The size of buffered channel should be same with the given number")
	assert.Equal(t, false, ringBuffer.Empty, "The ringBuffer is not empty")
	assert.Equal(t, uint64(0), ringBuffer.count, "The truncated count should be 0")
	assert.Equal(t, bufferPool, ringBuffer.pool, "The value of bufferpool should be same with the given value")

}

func TestRingBufferCloseChannel(t *testing.T) {
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)
	randomFlag := getTestChannelSize()
	ringBuffer := New(randomFlag, bufferPool)
	ringBuffer.Close()
	for i := 0; i < cap(ringBuffer.c); i++ {
		v, ok := <-ringBuffer.c

		assert.Equal(t, (*tracesegment.TraceSegment)(nil), v, "The value should be nil")
		assert.Equal(t, false, ok, "The value should be false if the channel is closed")
	}
}

func TestRingBufferSend(t *testing.T) {
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)
	randomFlag := getTestChannelSize()
	ringBuffer := New(randomFlag, bufferPool)
	segment := tracesegment.GetTestTraceSegment()
	for i := 0; i < randomFlag; i++ {
		ringBuffer.Send(&segment)
	}
	for i := 0; i < cap(ringBuffer.c); i++ {
		v, ok := <-ringBuffer.c

		assert.Equal(t, &segment, v, "The value should be same with the send segment")
		assert.Equal(t, true, ok, "The channel is open")
	}
}

func TestRingBufferTruncatedCount(t *testing.T) {
	log := test.LogSetup()
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)
	segment := tracesegment.GetTestTraceSegment()
	randomFlag := getTestChannelSize()
	ringBuffer := New(randomFlag, bufferPool)
	extraSegments := 100
	for i := 0; i < randomFlag+extraSegments; i++ {
		ringBuffer.Send(&segment)
	}
	num := ringBuffer.TruncatedCount()

	assert.Equal(t, num, uint64(extraSegments), "The truncated count should be same with the extra segments sent")
	for i := 0; i < extraSegments; i++ {
		assert.True(t, strings.Contains(log.Logs[i], "Segment buffer is full. Dropping oldest segment document."))
	}
}

func TestRingBufferSendTruncated(t *testing.T) {
	log := test.LogSetup()
	bufferLimit := 100
	bufferSize := 256 * 1024
	bufferPool := bufferpool.Init(bufferLimit, bufferSize)
	randomFlag := getTestChannelSize() + 2
	ringBuffer := New(randomFlag, bufferPool)
	var segment []tracesegment.TraceSegment
	for i := 0; i < randomFlag; i++ {
		segment = append(segment, tracesegment.GetTestTraceSegment())
		ringBuffer.Send(&segment[i])
	}
	s1 := tracesegment.GetTestTraceSegment()
	ringBuffer.Send(&s1)

	assert.Equal(t, &segment[1], <-ringBuffer.c, "Truncate the first segment in the original buffered channel")
	assert.Equal(t, randomFlag, cap(ringBuffer.c), "The buffered channel still full after truncating")
	assert.True(t, strings.Contains(log.Logs[0], "Segment buffer is full. Dropping oldest segment document."))

	s2 := tracesegment.GetTestTraceSegment()
	ringBuffer.Send(&s2)

	assert.Equal(t, &segment[2], <-ringBuffer.c, "Truncate the second segment that in the original buffered channel")
	assert.Equal(t, randomFlag, cap(ringBuffer.c), "The buffered channel still full after truncating")
	assert.True(t, strings.Contains(log.Logs[0], "Segment buffer is full. Dropping oldest segment document."))
}

// getTestChannelSize returns a random number greater than or equal to defaultCapacity
func getTestChannelSize() int {
	return rand.Intn(50) + defaultCapacity
}
