// Copyright 2015 Google Inc. 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.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 events

import (
	"testing"
	"time"

	info "github.com/google/cadvisor/info/v1"
	"github.com/stretchr/testify/assert"
)

func createOldTime(t *testing.T) time.Time {
	const longForm = "Jan 2, 2006 at 3:04pm (MST)"
	linetime, err := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
	if err != nil {
		t.Fatalf("could not format time.Time object")
	} else {
		return linetime
	}
	return time.Now()
}

// used to convert an OomInstance to an Event object
func makeEvent(inTime time.Time, containerName string) *info.Event {
	return &info.Event{
		ContainerName: containerName,
		Timestamp:     inTime,
		EventType:     info.EventOom,
	}
}

// returns EventManager and Request to use in tests
func initializeScenario(t *testing.T) (*events, *Request, *info.Event, *info.Event) {
	fakeEvent := makeEvent(createOldTime(t), "/")
	fakeEvent2 := makeEvent(time.Now(), "/")

	return NewEventManager(), NewRequest(), fakeEvent, fakeEvent2
}

func checkNumberOfEvents(t *testing.T, numEventsExpected int, numEventsReceived int) {
	if numEventsReceived != numEventsExpected {
		t.Fatalf("Expected to return %v events but received %v",
			numEventsExpected, numEventsReceived)
	}
}

func ensureProperEventReturned(t *testing.T, expectedEvent *info.Event, eventObjectFound *info.Event) {
	if eventObjectFound != expectedEvent {
		t.Errorf("Expected to find test object %v but found a different object: %v",
			expectedEvent, eventObjectFound)
	}
}

func TestCheckIfIsSubcontainer(t *testing.T) {
	myRequest := NewRequest()
	myRequest.ContainerName = "/root"

	sameContainerEvent := &info.Event{
		ContainerName: "/root",
	}
	subContainerEvent := &info.Event{
		ContainerName: "/root/subdir",
	}
	differentContainerEvent := &info.Event{
		ContainerName: "/root-completely-different-container",
	}

	if !checkIfIsSubcontainer(myRequest, sameContainerEvent) {
		t.Errorf("should have found %v and %v had the same container name",
			myRequest, sameContainerEvent)
	}
	if checkIfIsSubcontainer(myRequest, subContainerEvent) {
		t.Errorf("should have found %v and %v had different containers",
			myRequest, subContainerEvent)
	}

	myRequest.IncludeSubcontainers = true

	if !checkIfIsSubcontainer(myRequest, sameContainerEvent) {
		t.Errorf("should have found %v and %v had the same container",
			myRequest.ContainerName, sameContainerEvent.ContainerName)
	}
	if !checkIfIsSubcontainer(myRequest, subContainerEvent) {
		t.Errorf("should have found %v was a subcontainer of %v",
			subContainerEvent.ContainerName, myRequest.ContainerName)
	}
	if checkIfIsSubcontainer(myRequest, differentContainerEvent) {
		t.Errorf("should have found %v and %v had different containers",
			myRequest.ContainerName, differentContainerEvent.ContainerName)
	}
}

func TestWatchEventsDetectsNewEvents(t *testing.T) {
	myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t)
	myRequest.EventType[info.EventOom] = true
	returnEventChannel, err := myEventHolder.WatchEvents(myRequest)
	assert.Nil(t, err)

	myEventHolder.AddEvent(fakeEvent)
	myEventHolder.AddEvent(fakeEvent2)

	startTime := time.Now()
	go func() {
		time.Sleep(5 * time.Second)
		if time.Since(startTime) > (5 * time.Second) {
			t.Errorf("Took too long to receive all the events")
		}
	}()

	eventsFound := 0
	go func() {
		for event := range returnEventChannel.GetChannel() {
			eventsFound += 1
			if eventsFound == 1 {
				ensureProperEventReturned(t, fakeEvent, event)
			} else if eventsFound == 2 {
				ensureProperEventReturned(t, fakeEvent2, event)
				break
			}
		}
	}()
}

func TestAddEventAddsEventsToEventManager(t *testing.T) {
	myEventHolder, _, fakeEvent, _ := initializeScenario(t)

	myEventHolder.AddEvent(fakeEvent)

	checkNumberOfEvents(t, 1, myEventHolder.eventList.Len())
	ensureProperEventReturned(t, fakeEvent, myEventHolder.eventList[0])
}

func TestGetEventsForOneEvent(t *testing.T) {
	myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t)
	myRequest.MaxEventsReturned = 1
	myRequest.EventType[info.EventOom] = true

	myEventHolder.AddEvent(fakeEvent)
	myEventHolder.AddEvent(fakeEvent2)

	receivedEvents, err := myEventHolder.GetEvents(myRequest)
	assert.Nil(t, err)
	checkNumberOfEvents(t, 1, receivedEvents.Len())
	ensureProperEventReturned(t, fakeEvent2, receivedEvents[0])
}

func TestGetEventsForTimePeriod(t *testing.T) {
	myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t)
	myRequest.StartTime = createOldTime(t).Add(-1 * time.Second * 10)
	myRequest.EndTime = createOldTime(t).Add(time.Second * 10)
	myRequest.EventType[info.EventOom] = true

	myEventHolder.AddEvent(fakeEvent)
	myEventHolder.AddEvent(fakeEvent2)

	receivedEvents, err := myEventHolder.GetEvents(myRequest)
	assert.Nil(t, err)

	checkNumberOfEvents(t, 1, receivedEvents.Len())
	ensureProperEventReturned(t, fakeEvent, receivedEvents[0])
}

func TestGetEventsForNoTypeRequested(t *testing.T) {
	myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t)

	myEventHolder.AddEvent(fakeEvent)
	myEventHolder.AddEvent(fakeEvent2)

	receivedEvents, err := myEventHolder.GetEvents(myRequest)
	assert.Nil(t, err)
	checkNumberOfEvents(t, 0, receivedEvents.Len())
}
