/// Copyright (c) 2010 Ruben Penalva. All rights reserved.
/// Redistribution and use in source and binary forms, with or without modification, 
/// are permitted provided that the following conditions are met:
///    1. Redistributions of source code must retain the above copyright notice, this 
///    list of conditions and the following disclaimer.
///    2. Redistributions in binary form must reproduce the above copyright notice,
///    this list of conditions and the following disclaimer in the documentation and/or
///    other materials provided with the distribution.
///    3. The name of the author may not be used to endorse or promote products derived
///    from this software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
/// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 
/// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
/// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
/// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "TestLogic.h"

#include "TaskContext.h"
#include "FlockTask.h"
#include <boost\math\constants\constants.hpp>
#include <boost\math\special_functions\sign.hpp>
#include "SettingsTab.hxx"

using namespace TaskBasedFlockSim;

TestLogic::TestLogic(TestFrameWork::ScenePtr scene)	:	m_scene(boost::shared_polymorphic_downcast<TestScene>(scene)), 
														m_totalTime(0.0f)
{
}

TestLogic::~TestLogic(void)
{
}

void TestLogic::Run(float elapsedTime)
{	
	m_totalTime += elapsedTime;

	m_scene->SetLogicElapsedTime(elapsedTime);

	Flock& flock = m_scene->GetFlock();
	size_t boidsCount = flock.GetBoidsCount();
	TaskContext context;
	
	const size_t tasksCount = m_scene->GetTasksCount();
	for (size_t i = 0; i < tasksCount - 1; ++i)
	{
		FlockTaskPtr task(new FlockTask(flock, m_flockBuffer, i * boidsCount / tasksCount, 
										(i + 1) * boidsCount / tasksCount, elapsedTime));
		context.AddTask(task);
	}		
	
	FlockTaskPtr task(new FlockTask(flock, m_flockBuffer, (tasksCount - 1) * boidsCount / tasksCount,
									boidsCount, elapsedTime));
	context.AddTask(task);

	m_pool.SubmitTasks(&context);

	context.WaitOnTasksCompletion();

	flock = m_flockBuffer;
}

void TestLogic::SetupScene(void)
{
	TestFrameWork::Camera& camera = m_scene->GetCamera();
	camera = TestFrameWork::Camera(TestFrameWork::Vector3(0.0f, 0.0f, 80.0f), 
									TestFrameWork::Vector3(0.0f, 0.0f, 0.0f), 
									TestFrameWork::Vector3(0.0f, 1.0f, 0.0f));
	
	SetupFlock(m_scene->GetFlock(), 1);

	m_flockBuffer = m_scene->GetFlock();

	m_scene->SetTasksCount(1);
}

void TestLogic::OnMouseEvent(float elapsedTime, TestFrameWork::MouseEventPtr event)
{
	if (event->m_state.m_button == TestFrameWork::Left && event->m_state.m_pressed && event->m_state.m_dx != 0)
	{
		TestFrameWork::Matrix4x4 rotY;
		if (event->m_state.m_dx != 0)
		{
			rotY.BuildRotationY(-20.0f * boost::math::sign<float>(event->m_state.m_dx) * elapsedTime);
		}		

		TestFrameWork::Vector3 position(m_scene->GetCamera().GetPosition());
		position = rotY * position;

		TestFrameWork::Camera& camera = m_scene->GetCamera();
		camera = TestFrameWork::Camera(position, TestFrameWork::Vector3(), TestFrameWork::Vector3(0.0f, 1.0f, 0.0f));
	}
	else if (event->m_state.m_button == TestFrameWork::Right && event->m_state.m_pressed && event->m_state.m_dy != 0)
	{		
		TestFrameWork::Vector3 position(m_scene->GetCamera().GetPosition());
		TestFrameWork::Vector3 forward(-1.0f * Normalize(position));

		position += -1.0f * boost::math::sign<float>(event->m_state.m_dy) * forward * 20.0f * elapsedTime;

		TestFrameWork::Camera& camera = m_scene->GetCamera();
		camera = TestFrameWork::Camera(position, TestFrameWork::Vector3(), TestFrameWork::Vector3(0.0f, 1.0f, 0.0f));
	}
}

void TestLogic::OnCustomEvent(float /*elapsedTime*/, TestFrameWork::ICustomEventPtr event)
{
	ValueChangedEventPtr valueChangedEventPtr(boost::shared_polymorphic_downcast<ValueChangedEvent>(event));
	float value = valueChangedEventPtr->m_value;

	switch(valueChangedEventPtr->m_settingsTabValueId)
	{
		case TasksCount:
			m_scene->SetTasksCount(static_cast<size_t>(value));
			break;

		case FlockSize:
			{
				ResizeFlock(m_flockBuffer, static_cast<size_t>(value));
				Flock& flock = m_scene->GetFlock();
				flock = m_flockBuffer;
			}
			break;

		case AngleIncrement:
			m_flockBuffer.SetAngleIncrement(value);
			break;

		case SeparationWeight:
			m_flockBuffer.SetSeparationWeight(value);
			break;

		case CohesionWeight:
			m_flockBuffer.SetCohesionWeight(value);
			break;

		case AlignmentWeight:
			m_flockBuffer.SetAlignmentWeight(value);
			break;

		case TrefoilKnotPathWeight:
			m_flockBuffer.SetTrefoilKnotPathWeight(value);
			break;

		case MaxSpeed:
			m_flockBuffer.SetMaxSpeed(TestFrameWork::Vector3(value, value, value));
			break;

		case SeparationDistance:
			m_flockBuffer.SetSeparationDistance(value);
			break;

		case GroupDistance:
			m_flockBuffer.SetGroupDistance(value);
			break;

		default:
			assert(false);
			break;
	}
}

void TestLogic::SetupFlock(Flock& flock, size_t boidsCount)
{
	ResizeFlock(flock, boidsCount);

	flock.SetMaxSpeed(TestFrameWork::Vector3(5.0f, 5.0f, 5.0f));
	flock.SetSeparationDistance(0.5f);
	flock.SetGroupDistance(3.0f);

	flock.SetSeparationWeight(2.5f);
	flock.SetCohesionWeight(0.1f);
	flock.SetAlignmentWeight(0.1f);
	flock.SetTrefoilKnotPathWeight(0.1f);
	flock.SetAngleIncrement(0.1f);
}

void TestLogic::ResizeFlock(Flock& flock, size_t boidsCount)
{
	flock.Resize(boidsCount);

	Positions& positions = flock.GetPositions();
	Velocities& velocities = flock.GetVelocities();
	Angles& angles = flock.GetAngles();

	float angleDiff = (720.0f / boidsCount) * boost::math::constants::pi<float>() / 180.0f;

	for (size_t i = 0; i < boidsCount; ++i)
	{
		float phi = (i * angleDiff);

		angles[i] = phi;

		positions[i] = 	TestFrameWork::Vector3((2 + cosf(phi * 3.0f / 2.0f)) * cosf(phi) * 10.0f,
												(2 + cosf(phi * 3.0f / 2.0f)) * sinf(phi) * 10.0f,
												sinf(phi * 3.0f / 2.0f) * 10.0f);

		phi += boost::math::constants::pi<float>() / 180.0f;
		velocities[i] = TestFrameWork::Vector3((2 + cosf(phi * 3.0f / 2.0f)) * cosf(phi) * 10.0f,
												(2 + cosf(phi * 3.0f / 2.0f)) * sinf(phi) * 10.0f,
												sinf(phi * 3.0f / 2.0f) * 10.0f) - 
												positions[i];
	}
}

