#pragma once
#include "Utils.h"
#include "Isaac.h"
#include "Curves.h"
#define CURVE_POINTS 5
/// 
/// Curves class.
/// 
namespace EmberNs
{
/// 
/// The b-spline curves used to adjust the colors during final accumulation.
/// This functionality was gotten inferred from Chaotica.
/// Note this is now incompatible with Apophysis, which uses Bezier curves instead.
/// 
template 
class EMBER_API Curves
{
public:
	/// 
	/// Constructor which sets the curve and weight values to their defaults.
	/// 
	Curves(bool init = false)
	{
		if (init)
			Init();
		else
			Clear();
	}
	/// 
	/// Default copy constructor.
	/// 
	/// The Curves object to copy
	Curves(const Curves& curves)
	{
		Curves::operator=(curves);
	}
	/// 
	/// Copy constructor to copy a Curves object of type U.
	/// Special case that must be here in the header because it has
	/// a second template parameter.
	/// 
	/// The Curves object to copy
	template 
	Curves(const Curves& curves)
	{
		Curves::operator=(curves);
	}
	/// 
	/// Default assignment operator.
	/// 
	/// The Curves object to copy
	Curves& operator = (const Curves& curves)
	{
		if (this != &curves)
			Curves::operator=(curves);
		return *this;
	}
	/// 
	/// Assignment operator to assign a Curves object of type U.
	/// 
	/// The Curves object to copy
	/// Reference to updated self
	template 
	Curves& operator = (const Curves& curves)
	{
		int i = 0;
		for (auto& pp : curves.m_Points)
		{
			int j = 0;
			m_Points[i].clear();
			for (auto& p : pp)
			{
				m_Points[i].push_back(p);
				j++;
			}
			i++;
		}
		i = 0;
		return *this;
	}
	/// 
	/// Unary addition operator to add a Curves object to this one.
	/// 
	/// The Curves object to add
	/// Reference to updated self
	template 
	Curves& operator += (const Curves& curves)
	{
		int i = 0;
		for (auto& pp : m_Points)
		{
			int j = 0;
			for (auto& p : pp)
			{
				if (j < curves.m_Points[i].size())
					p += curves.m_Points[i][j];
				else
					break;
				j++;
			}
			i++;
		}
		return *this;
	}
	/// 
	/// Unary multiplication operator to multiply this object by another Curves object.
	/// 
	/// The Curves object to multiply this one by
	/// Reference to updated self
	template 
	Curves& operator *= (const Curves& curves)
	{
		int i = 0;
		for (auto& pp : m_Points)
		{
			int j = 0;
			for (auto& p : pp)
			{
				if (j < curves.m_Points[i].size())
					p *= curves.m_Points[i][j];
				else
					break;
				j++;
			}
			i++;
		}
		return *this;
	}
	/// 
	/// Unary multiplication operator to multiply this object by a scalar of type T.
	/// 
	/// The scalar to multiply this object by
	/// Reference to updated self
	template 
	Curves& operator *= (const U& t)
	{
		for (auto& pp : m_Points)
			for (auto& p : pp)
				p *= T(t);
		return *this;
	}
	/// 
	/// Set the curve and weight values to their default state.
	/// 
	void Init()
	{
		for (size_t i = 0; i < 4; i++)
		{
			m_Points[i].resize(5);
			m_Points[i][0] = v2T(0);
			m_Points[i][1] = v2T(T(0.25));
			m_Points[i][2] = v2T(T(0.50));
			m_Points[i][3] = v2T(T(0.75));
			m_Points[i][4] = v2T(1);
		}
	}
	/// 
	/// Set the a specific curve and its weight value to their default state.
	/// 
	void Init(size_t i)
	{
		if (i < 4)
		{
			m_Points[i].resize(5);
			m_Points[i][0] = v2T(0);
			m_Points[i][1] = v2T(T(0.25));
			m_Points[i][2] = v2T(T(0.50));
			m_Points[i][3] = v2T(T(0.75));
			m_Points[i][4] = v2T(1);
		}
	}
	/// 
	/// Set the curve and weight values to an empty state.
	/// 
	void Clear()
	{
		for (auto& p : m_Points)
			p.clear();
	}
	/// 
	/// Whether any points are not the default.
	/// 
	/// True if any point has been set to a value other than the default, else false.
	bool CurvesSet()
	{
		bool set = false;
		for (size_t i = 0; i < 4; i++)
		{
			if (m_Points[i].size() != CURVE_POINTS)
			{
				set = true;
				break;
			}
			if ((m_Points[i][0] != v2T(0)) ||
					(m_Points[i][1] != v2T(T(0.25))) ||
					(m_Points[i][2] != v2T(T(0.50))) ||
					(m_Points[i][3] != v2T(T(0.75))) ||
					(m_Points[i][4] != v2T(1))
			   )
			{
				set = true;
				break;
			}
		}
		return set;
	}
public:
	std::array, 4> m_Points;
};
//Must declare this outside of the class to provide for both orders of parameters.
/// 
/// Multiplication operator to multiply a Curves object by a scalar of type U.
/// 
/// The curves object to multiply
/// The scalar to multiply curves by by
/// Copy of new Curves
template 
Curves operator * (const Curves& curves, const U& t)
{
	T tt = T(t);
	Curves c(curves);
	for (auto& pp : c.m_Points)
		for (auto& p : pp)
			p *= tt;
	return c;
}
/// 
/// Multiplication operator for reverse order.
/// 
/// The scalar to multiply curves by by
/// The curves object to multiply
/// Copy of new Curves
template 
Curves operator * (const U& t, const Curves& curves)
{
	return curves * t;
}
}