/// 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.

/// @file Logic.cpp
/// This source file is part of CUDA Ray Casting test.
/// The file holds the Logic class implementation.
/// @author Ruben Penalva (ruben.penalva@rpenalva.com)

#include "Logic.h"

#include "Matrix4x4.h"
#include <assert.h>
#include <fstream>

using namespace CUDARayCasting;

const int Logic::m_colorElementsCount = 4;
const int Logic::m_tableColorsCount = 256;
const int Logic::m_tableSize = Logic::m_colorElementsCount * Logic::m_tableColorsCount;
const Vector3 Logic::m_startColor(1.0f, 0.0f, 0.0f);
const Vector3 Logic::m_endColor(0.0f, 1.0f, 0.0f);
const float Logic::m_stepFixedAmount = 0.0001f;
const float Logic::m_maxStep = 1.0f;
const float Logic::m_minStep = 0.0f;
const int Logic::m_stepNumberFixedAmount = 25;
const int Logic::m_maxStepNumber = 1000;
const int Logic::m_minStepNumber = 1;
const float Logic::m_animationTimeFixedAmount = 0.1f;
const float Logic::m_maxAnimationTime = 50.0f;
const float Logic::m_minAnimationTime = 1.0f;

Logic::Logic(void)	:	m_totalElapsedTime(15.0f), m_dir(1.0f), 
						m_step(0.0015f), m_stepNumber(1000), 
						m_animationTime(15.0f), m_paused(false),
						m_cameraPos(0.0f, 0.0f, 2.0f),
						m_cameraTarget(0.0f, 0.0f, 0.0f), 
						m_cameraUp(0.0f, 1.0f, 0.0f),
						m_densityWinWidth(206),
						m_densityWinBegin(50),
						m_densityWinEnd(255),
						m_bboxMin(-0.5f, -0.5f, -0.5f),
						m_bboxMax(0.5f, 0.5f, 0.5f)
{
	Matrix4x4 rotationY;
	rotationY.BuildRotationY(180.0f);

	Matrix4x4 rotationX;
	rotationX.BuildRotationX(-90.0f);

	Matrix4x4 rotation = rotationX * rotationY;

	m_volumeMatrix = rotation;

	m_viewMatrix.BuildView(m_cameraPos, m_cameraTarget, m_cameraUp);
}

Logic::~Logic(void)
{
}

boost::shared_array<unsigned char> Logic::LoadVolumeData(void) const
{
	std::ifstream file("data\\VisMale.raw", std::ios::in | std::ios::binary | std::ios::ate);
	if (!file)
	{
		throw "Volume data file not found: data\\VisMale.raw";
	}

	int size = file.tellg();
	boost::shared_array<unsigned char> dataPtr(new unsigned char[size]);
	file.seekg (0, std::ios::beg);
	file.read ((char*)dataPtr.get(), size);
	file.close();
	
	return dataPtr;
}

boost::shared_array<float> Logic::LoadTransferFunction(void)
{
	m_transferFunctionPtr.reset(new float[m_tableSize]);

	UpdateTransferFunction(m_totalElapsedTime);

	return m_transferFunctionPtr;
}	

void Logic::UpdateTransferFunction(float elapsedTime)
{
	if (IsPaused())
	{
		return;
	}

	m_totalElapsedTime += elapsedTime * m_dir;

	if (m_totalElapsedTime > m_animationTime)
	{
		m_dir = -1.0f;
		m_totalElapsedTime = m_animationTime;
	}
	else if (m_totalElapsedTime < 0.0f)
	{
		m_dir = 1.0f;
		m_totalElapsedTime = 0.0f;
	}

	float interpolationValue = m_totalElapsedTime / m_animationTime;

	Vector3 color = Interpolate(m_startColor, m_endColor, interpolationValue);

	float* data = m_transferFunctionPtr.get();
	for (int i = 0; i < m_tableColorsCount; ++i)
	{
		data[i * m_colorElementsCount] = color.m_x;	
		data[i * m_colorElementsCount + 1] = color.m_y;
		data[i * m_colorElementsCount + 2] = color.m_z;
	}

	int index = (int)(m_densityWinBegin * (1.0f - interpolationValue) + m_densityWinEnd * interpolationValue);

	for (int i = m_densityWinBegin; i < index; ++i)
	{
		data[i * m_colorElementsCount + 3] = i * 1.0f / 255.0f;
	}

	for (int i = 0; i < m_densityWinBegin + 1; ++i)
	{
		data[i * m_colorElementsCount + 3] = 0.0f;
	}

	for (int i = index; i < m_densityWinWidth; ++i)
	{
		data[i * m_colorElementsCount + 3] = 0.0f;
	}
}	

void Logic::IncreaseStep(void)
{
	if (IsStepIncreseable())
	{
		m_step += m_stepFixedAmount;
	}
}

void Logic::DecreaseStep(void)
{
	if (IsStepDecreseable())
	{
		m_step -= m_stepFixedAmount;
	}
}

void Logic::IncreaseStepNumber(void)
{
	if (IsStepNumberIncreseable())
	{
		m_stepNumber += m_stepNumberFixedAmount;
	}
}

void Logic::DecreaseStepNumber(void)
{
	if (IsStepNumberDecreseable())
	{
		m_stepNumber -= m_stepNumberFixedAmount;
	}
}

void Logic::IncreaseAnimationTime(void)
{
	if (IsAnimationTimeIncreseable())
	{
		m_animationTime += m_animationTimeFixedAmount;
	}
}

void Logic::DecreaseAnimationTime(void)
{
	if (IsAnimationTimeDecreseable())
	{
		m_animationTime -= m_animationTimeFixedAmount;
	}
}

void Logic::Pause(void)
{
	m_paused = true;
}

void Logic::Play(void)
{
	m_paused = false;
}

float Logic::GetStep(void) const
{
	return m_step;
}

int Logic::GetStepNumber(void) const
{
	return m_stepNumber;
}

float Logic::GetAnimationTime(void) const
{
	return m_animationTime;
}

bool Logic::IsStepIncreseable(void) const
{
	return m_step < m_maxStep;
}

bool Logic::IsStepDecreseable(void) const
{
	return m_step > m_minStep;
}

bool Logic::IsStepNumberIncreseable(void) const
{
	return m_stepNumber < m_maxStepNumber;
}

bool Logic::IsStepNumberDecreseable(void) const
{
	return m_stepNumber > m_minStepNumber;
}

bool Logic::IsAnimationTimeIncreseable(void) const
{
	return m_animationTime < m_maxAnimationTime;
}

bool Logic::IsAnimationTimeDecreseable(void) const
{
	return m_animationTime > m_minAnimationTime;
}

bool Logic::IsPaused(void) const
{
	return m_paused;
}

void Logic::ProcessMouse(const GlutWrapper::MouseState& mouseState, float elapsedTime)
{
	if (mouseState.m_button == GlutWrapper::Left && mouseState.m_buttonState == GlutWrapper::Down)
	{
		int diffX = mouseState.m_x - mouseState.m_oldX;
		if (diffX == 0)
		{
			return;
		}

		if (diffX > 0)
		{
			diffX = 1;
		}
		else if (diffX < 0)
		{
			diffX = -1;
		}

		Matrix4x4 rotation;
		rotation.BuildRotationY(90.0f * elapsedTime * diffX);

		m_cameraPos = rotation * m_cameraPos;

		m_viewMatrix.BuildView(m_cameraPos, m_cameraTarget, m_cameraUp);
	}
	else if (mouseState.m_button == GlutWrapper::Middle && mouseState.m_buttonState == GlutWrapper::Down)
	{
		int diffX = mouseState.m_x - mouseState.m_oldX;
		int diffY = mouseState.m_y - mouseState.m_oldY;
		
		if ((diffY > -5 || diffY < 5) && (diffX > 3.0f || diffX < -3.0f))
		{
			if (diffX > 0.0f && m_densityWinEnd < 253)
			{
				m_densityWinBegin += 3;
				m_densityWinEnd += 3;
			}
			else if (diffX < 0.0f && m_densityWinBegin > 2)
			{
				m_densityWinBegin -= 3;
				m_densityWinEnd -= 3;
			}
		} 
		
		if ((diffX > -5 || diffX < 5) && (diffY > 3.0f || diffY < -3.0f))
		{
			int leftWeight = 0;
			int rightWeight = 0;

			if (diffY < 0.0f && m_densityWinWidth < 251)
			{
				m_densityWinWidth += 6;
				leftWeight = -3;
				rightWeight = 3;
				if (m_densityWinEnd > 252)
				{
					int offset = m_densityWinEnd - 252;
					rightWeight -= offset;

					if (m_densityWinBegin < 3)
					{
						leftWeight -= offset + (3 - m_densityWinBegin);
					}
				}
			}
			else if (diffY > 0.0f && m_densityWinWidth > 4)
			{
				m_densityWinWidth -= 6;
				leftWeight = 3;
				rightWeight = -3;
			}

			m_densityWinEnd += rightWeight;
			m_densityWinBegin += leftWeight;
		}
	}
}

const Matrix4x4& Logic::GetViewMatrix(void) const
{
	return m_viewMatrix;
}	

const Matrix4x4& Logic::GetVolumeMatrix(void) const
{
	return m_volumeMatrix;
}

int Logic::GetDensityWindowWidth(void) const
{
	return m_densityWinWidth;
}

int Logic::GetDensityWindowBegin(void) const
{
	return m_densityWinBegin;
}

int Logic::GetDensityWindowEnd(void) const
{
	return m_densityWinEnd;
}

const Vector3& Logic::GetBboxMin(void) const
{
	return m_bboxMin;
}

const Vector3& Logic::GetBboxMax(void) const
{
	return m_bboxMax;
}