/// 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 "Geometry.h"

#include <fstream>
#include <boost\scoped_array.hpp>
#include <boost\shared_array.hpp>

#include <sstream>
#include "Exception.h"

using namespace TestFrameWork;

Geometry::Geometry(const std::wstring& fileName)
{
	LoadOBJFile(fileName);

	if (!m_normals.size())
	{
		ComputeNormals();
	}
}

Geometry::Geometry(const Geometry& geometry) : 	m_positions(geometry.m_positions.begin(), geometry.m_positions.end()),
												m_normals(geometry.m_normals.begin(), geometry.m_normals.end()),
												m_indices(geometry.m_indices.begin(), geometry.m_indices.end())

{
	assert(geometry.m_positions.size() > 0);
	assert(geometry.m_normals.size() > 0);
	assert(geometry.m_indices.size() > 0);
}

Geometry::~Geometry(void)
{

}

Geometry& Geometry::operator=(const Geometry& geometry)
{
	assert(geometry.m_positions.size() > 0);
	assert(geometry.m_normals.size() > 0);
	assert(geometry.m_indices.size() > 0);

	m_positions.assign(geometry.m_positions.begin(), geometry.m_positions.end());
	m_normals.assign(geometry.m_normals.begin(), geometry.m_normals.end());
	m_indices.assign(geometry.m_indices.begin(), geometry.m_indices.end());

	return *this;
}

const Positions& Geometry::GetPositions(void) const
{
	return m_positions;
}

const Normals& Geometry::GetNormals(void) const
{
	return m_normals;
}

const Indices& Geometry::GetIndices(void) const
{
	return m_indices;
}

void Geometry::LoadOBJFile(const std::wstring& fileName)
{
	std::wifstream file(fileName.c_str(), std::ios::binary | std::ios::in);
	if (!file)
	{
		throw Exception(L"No such a file");
	}

	file.seekg(0, std::ios::end);
	size_t fileSize = file.tellg();
	file.seekg(0, std::ios::beg);

	// allocate memory:
	boost::scoped_array<wchar_t> fileBuffer(new wchar_t[fileSize]);


	file.read(fileBuffer.get(), fileSize);
	file.close();

	wchar_t* commands = fileBuffer.get();
	size_t currentFileSize = fileSize;
	while (currentFileSize != 0)
	{
		std::wstringstream commandStream(commands);
		std::wstring command;
		commandStream >> command;

		/// Process command
		if (command[0] == 'v' && command[1] == 'n')
		{
			float x;
			float y;
			float z;
			commandStream >> x >> y >> z;
			m_normals.push_back(Vector3(x, y, z));
		}
		else if (command[0] == 'v')
		{
			float x;
			float y;
			float z;
			commandStream >> x >> y >> z;
			m_positions.push_back(Vector3(x, y, z));
		}
		else if (command[0] == 'f')
		{
			int i0;
			int i1;
			int i2;
			commandStream >> i0 >> i1 >> i2;
			m_indices.push_back(i0 - 1);
			m_indices.push_back(i1 - 1);
			m_indices.push_back(i2 - 1);
		}
		else
		{
			/// Not supported - Comments - Do nothing
		}

		/// Skip command
		while (*commands != '\n' && *commands != '\t' && currentFileSize != 0)
		{
			++commands;
			currentFileSize--;
		}
		
		/// Skip line
		while ((*commands == '\n' || *commands == '\t' || *commands == '\r' || *commands == ' ') && currentFileSize != 0)
		{
			++commands;
			currentFileSize--;
		}
	}
}

void Geometry::ComputeNormals(void)
{
	size_t normalsCount = m_positions.size();
	m_normals.resize(normalsCount);

	size_t facesCount = m_indices.size() / 3;
	boost::shared_array<Vector3> faceNormals(new Vector3[facesCount]);
	for (size_t i = 0; i < facesCount; ++i)
	{
		size_t i0 = m_indices[i * 3];
		size_t i1 = m_indices[i * 3 + 1];
		size_t i2 = m_indices[i * 3 + 2];

		Vector3& v0(m_positions[i0]);
		Vector3& v1(m_positions[i1]);
		Vector3& v2(m_positions[i2]);

		Vector3 v01(v0 - v1);
		Vector3 v02(v0 - v2);
		Vector3 faceNormal = v01.Cross(v02);
		faceNormal.Normalize();
		faceNormals[i] = faceNormal;

		m_normals[i0] += faceNormal;
		m_normals[i1] += faceNormal;
		m_normals[i2] += faceNormal;
	}


	for (size_t i = 0; i < normalsCount; ++i)
	{
		m_normals[i].Normalize();
	}
}