#ifndef URASTER_HPP
#define URASTER_HPP

#include<iostream>
#include<Eigen/Dense>
#include<Eigen/LU>	//for .inverse().  Probably not needed
#include<vector>
#include<array>
#include<memory>
#include<functional>

namespace uraster
{


//This is the framebuffer class.  It's a part of namespace uraster because the uraster needs to have a well-defined image class to render to.
//It is templated because the output type need not be only colors, could contain anything (like a stencil buffer or depth buffer or gbuffer for deferred rendering)
template<class PixelType>
class Framebuffer
{
protected:
	std::vector<PixelType> data;
public:
	const std::size_t width;
	const std::size_t height;
	//constructor initializes the array
	Framebuffer(std::size_t w,std::size_t h,const PixelType& pt=PixelType()):
		data(w*h,pt),
		width(w),height(h)
	{}
	//2D pixel access
	PixelType& operator()(std::size_t x,std::size_t y)
	{
		return data[y*width+x];
	}
	//const version
	const PixelType& operator()(std::size_t x,std::size_t y) const
	{
		return data[y*width+x];
	}
	void clear(const PixelType& pt=PixelType())
	{
		std::fill(data.begin(),data.end(),pt);
	}
};

//This function runs the vertex shader on all the vertices, producing the varyings that will be interpolated by the rasterizer.
//VertexVsIn can be anything, VertexVsOut MUST have a position() method that returns a 4D vector, and it must have an overloaded *= and += operator for the interpolation
//The right way to think of VertexVsOut is that it is the class you write containing the varying outputs from the vertex shader.
template<class VertexVsIn,class VertexVsOut,class VertShader>
void run_vertex_shader(const VertexVsIn* b,const VertexVsIn* e,VertexVsOut* o,
	VertShader vertex_shader)
{
	std::size_t n=e-b;
	#pragma omp parallel for
	for(std::size_t i=0;i<n;i++)
	{
		o[i]=vertex_shader(b[i]);
	}
}
struct BarycentricTransform
{
private:
	Eigen::Vector2d offset;
	Eigen::Matrix2d Ti;
public:
	BarycentricTransform(const Eigen::Vector2d& s1,const Eigen::Vector2d& s2,const Eigen::Vector2d& s3):
		offset(s3)
	{
		Eigen::Matrix2d T;
		T << (s1-s3),(s2-s3);
        if (T.determinant()==0)
            Ti << Eigen::Vector2d(0,0),Eigen::Vector2d(0,0);
        else
		    Ti=T.inverse();
	}
	Eigen::Vector3d operator()(const Eigen::Vector2d& v) const
	{
		Eigen::Vector2d b;
		b=Ti*(v-offset);
		return Eigen::Vector3d(b[0],b[1],1.0f-b[0]-b[1]);
	}
};
inline auto bary_coords(int x, int y, const BarycentricTransform &bt, const Eigen::Array2i &isz) {
	Eigen::Vector2d ssc(x,y);
	ssc.array()/=isz.cast<double>();	//move pixel to relative coordinates
	ssc.array()-=0.5f;
	ssc.array()*=2.0f;

	//Compute barycentric coordinates of the pixel center
	return bt(ssc);
}
template<class VertexVsOut>
inline auto bary_vs(const Eigen::Vector3d &bary,const std::array<VertexVsOut,3>& verts) {
	//interpolate varying parameters
	VertexVsOut v=verts[0];
	v*=bary[0];
	VertexVsOut vt=verts[1];
	vt*=bary[1];
	v+=vt;
	vt=verts[2];
	vt*=bary[2];
	v+=vt;

	return v;
}
//This function takes in 3 varyings vertices from the fragment shader that make up a triangle,
//rasterizes the triangle and runs the fragment shader on each resulting pixel.
template<class PixelOut,class VertexVsOut,class FragShader>
void rasterize_triangle(Framebuffer<PixelOut>& fb,const std::array<VertexVsOut,3>& verts,FragShader fragment_shader)
{
	std::array<Eigen::Vector4d,3> points{{verts[0].position(),verts[1].position(),verts[2].position()}};
	//Do the perspective divide by w to get screen space coordinates.
	std::array<Eigen::Vector4d,3> epoints{{points[0]/points[0][3],points[1]/points[1][3],points[2]/points[2][3]}};
	auto ss1=epoints[0].head<2>().array(),ss2=epoints[1].head<2>().array(),ss3=epoints[2].head<2>().array();

	//calculate the bounding box of the triangle in screen space floating point.
	Eigen::Array2d bb_ul=ss1.min(ss2).min(ss3);
	Eigen::Array2d bb_lr=ss1.max(ss2).max(ss3);
	Eigen::Array2i isz(fb.width,fb.height);	

	//convert bounding box to fixed point.
	//move bounding box from (-1.0,1.0)->(0,imgdim)
	Eigen::Array2i ibb_ul=((bb_ul*0.5+0.5)*isz.cast<double>()).cast<int>();	
	Eigen::Array2i ibb_lr=((bb_lr*0.5+0.5)*isz.cast<double>()).cast<int>();
	ibb_lr+=1;	//add one pixel of coverage

	//clamp the bounding box to the framebuffer size if necessary (this is clipping.  Not quite how the GPU actually does it but same effect sorta).
	ibb_ul=ibb_ul.max(Eigen::Array2i(0,0));
	ibb_lr=ibb_lr.min(isz);
	
	const BarycentricTransform bt(ss1.matrix(),ss2.matrix(),ss3.matrix());

	//for all the pixels in the bounding box
	for(int y=ibb_ul[1];y<ibb_lr[1];y++)
	for(int x=ibb_ul[0];x<ibb_lr[0];x++) {
		//Compute barycentric coordinates of the pixel center
		auto bary=bary_coords(x,y,bt,isz);
		auto baryx=bary_coords(x+1,y,bt,isz);
		auto baryy=bary_coords(x,y+1,bt,isz);
		
		//if the pixel has valid barycentric coordinates, the pixel is in the triangle
		if((bary.array() < 1.0f).all() && (bary.array() > 0.0f).all())
		{
			auto d=bary[0]*epoints[0][2]+bary[1]*epoints[1][2]+bary[2]*epoints[2][2];
			//Reference the current pixel at that coordinate
			PixelOut& po=fb(x,y);
			// if the interpolated depth passes the depth test
			if(po.depth() < d && d < 1.0)
			{
				const auto v=bary_vs(bary,verts);
				auto dVdx=bary_vs(baryx,verts);
				auto dVdy=bary_vs(baryy,verts);
				dVdx -= v;
				dVdy -= v;
				
				//call the fragment shader
				po=fragment_shader(v,dVdx,dVdy,x+y*fb.width,x,y);
				po.depth()=d; //write the depth buffer
			}
		}
	}
}


//This function rasterizes a set of triangles determined by an index buffer and a buffer of output verts.
template<class PixelOut,class VertexVsOut,class Idx,class FragShader>
void rasterize(Framebuffer<PixelOut>& fb,const Idx* ib,const Idx* ie,const VertexVsOut* verts,
	FragShader fragment_shader)
{
	const auto n=ie-ib;
	#pragma omp parallel for
	for(std::size_t i=0;i<n;i+=3)
	{
		const auto* ti=ib+i;
		std::array<VertexVsOut,3> tri{{verts[ti[0]],verts[ti[1]],verts[ti[2]]}};
		rasterize_triangle(fb,tri,fragment_shader);
	}
}

//This function does a draw call from an indexed buffer
template<class PixelOut,class VertexVsOut,class VertexVsIn,class Idx,class VertShader, class FragShader>
void draw(	Framebuffer<PixelOut>& fb,
		const VertexVsIn* vertexbuffer_b,const VertexVsIn* vertexbuffer_e,
		const Idx* indexbuffer_b,const Idx* indexbuffer_e,
		VertexVsOut* vcache_b,VertexVsOut* vcache_e,
		VertShader vertex_shader,
		FragShader fragment_shader)
{
	std::unique_ptr<VertexVsOut[]> vc;
	if(vcache_b==NULL || (vcache_e-vcache_b) != (vertexbuffer_e-vertexbuffer_b))
	{
		vcache_b=new VertexVsOut[(vertexbuffer_e-vertexbuffer_b)];
		vc.reset(vcache_b);
	}
	run_vertex_shader(vertexbuffer_b,vertexbuffer_e,vcache_b,vertex_shader);
	rasterize(fb,indexbuffer_b,indexbuffer_e,vcache_b,fragment_shader);
}

}

#endif
