#include "CGridSceneNode.h"

using namespace irr;

CGridSceneNode::SGrid::SGrid()
    : IsVisible(true)
	, Alignment(CENTER)
	, Spacing(8.f)
	, Size(2048.f, 2048.f)
	, Offset(0.f, 0.f, 0.f)
	, GridColor( irr::video::SColor(255,128,128,128))
	, MaxRenderDist(FLT_MAX)
	, GridDirty(true)
	, BoundingBoxDirty(true)
	, MeshBuffer(0)
{
    // Set the material
	MeshBuffer = new irr::scene::CDynamicMeshBuffer(irr::video::EVT_STANDARD, irr::video::EIT_16BIT);
	setDefaultMaterial();
}

CGridSceneNode::SGrid::~SGrid()
{
	if ( MeshBuffer )
		MeshBuffer->drop();
}

CGridSceneNode::SGrid::SGrid(const SGrid& other)
: MeshBuffer(0)
{
	MeshBuffer = new irr::scene::CDynamicMeshBuffer(irr::video::EVT_STANDARD, irr::video::EIT_16BIT);
	*this = other;
}

CGridSceneNode::SGrid& CGridSceneNode::SGrid::operator=(const CGridSceneNode::SGrid& other)
{
	if (&other == this )
		return *this;

	IsVisible = other.IsVisible;
	Alignment = other.Alignment;
	Spacing = other.Spacing;
	Size = other.Size;
	Offset = other.Offset;
	GridColor = other.GridColor;
	MaxRenderDist = other.MaxRenderDist;
	setMaterial(other.getMaterial());
	// note, each object keeps it's own MeshBuffer for the lifetime and just changes the content
	GridDirty = true;

	return *this;
}

void CGridSceneNode::SGrid::setDefaultMaterial()
{
	if( !MeshBuffer )
		return;
    MeshBuffer->getMaterial().Wireframe = false;
    MeshBuffer->getMaterial().Lighting = false;
    MeshBuffer->getMaterial().Thickness = 1;
    MeshBuffer->getMaterial().FogEnable = false;
    MeshBuffer->getMaterial().ZWriteEnable = true;
	MeshBuffer->getMaterial().ZBuffer = true;
    MeshBuffer->getMaterial().BackfaceCulling = true;
    MeshBuffer->getMaterial().AntiAliasing = false;
}

bool CGridSceneNode::SGrid::canUseGridLine(irr::f32 pos, bool axisX, const CGridSceneNode* const gridNode)
{
	if ( !gridNode )
		return true;

	const irr::f32 epsilonHalf = 0.1f;
	for ( irr::u32 i=0; i<gridNode->getNumberOfGrids(); ++i )
	{
		const SGrid & otherGrid = gridNode->getGrid(i);
		if ( &otherGrid == this )
			continue;
		if ( otherGrid.getSpacing() <= getSpacing() )
			continue;	// we want the biggest spacing to show up most
		irr::core::vector3df otherStart = otherGrid.calcGridStart();
		irr::f32 dist = axisX ? irr::core::abs_(pos - otherStart.X) : irr::core::abs_(pos - otherStart.Z);
		irr::f32 mod = fmodf(dist+epsilonHalf, otherGrid.getSpacing());
		if ( irr::core::equals( mod, 0.f, 2*epsilonHalf ) )
			return false;
	}
	return true;
}

irr::core::vector3df CGridSceneNode::SGrid::calcGridStart() const
{
    core::vector3df start(0,0,0);

    //Set our corners
	if ( Alignment == CENTER )
	{
		irr::u32 tilesX = (irr::u32)(0.5f*Size.Width/Spacing);
		irr::u32 tilesY = (irr::u32)(0.5f*Size.Height/Spacing);
		start.X -= tilesX*Spacing;
		start.Z -= tilesY*Spacing;
	}
	else
	{
		start.X -= Size.Width/2.f;
		start.Z -= Size.Height/2.f;
	}
	start += Offset;
	return start;
}

void CGridSceneNode::SGrid::regenerateMesh(const CGridSceneNode* const gridNode)
{
	GridDirty = false;

    //Clean up memory
	MeshBuffer->getIndexBuffer().set_used(0);
	MeshBuffer->getVertexBuffer().set_used(0);
 
	u32 numVertices = ((u32)(Size.Width / Spacing) + 1) * 2 + ((u32)(Size.Height / Spacing) + 1) * 2;
    if ( numVertices > 65535)
    {
        //Too many vertices on 16 bit for for 16bit indices of SMeshBuffer
        //Returning with a blank buffer to avoid segfaulting the entire application
        return;
    }
 
    core::vector3df leftMost( calcGridStart() );
    core::vector3df rightMost(Size.Width/2.f,0,Size.Height/2.f);
	rightMost += Offset;
 
    u32 indexIndex = 0;
 
    //X-axis lines
	for (f32 x = 0.f; x <= Size.Width; x+= Spacing)
    {
        core::vector3df start = leftMost;
        start.X += x ;

		if ( !canUseGridLine(start.X, true, gridNode) )
			continue;
 
        core::vector3df end = rightMost;
        end.X = start.X;
 
        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(start, core::vector3df(0,1,0), GridColor, core::vector2df(0.0f, 0.0f)));
        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(end, core::vector3df(0,1,0), GridColor, core::vector2df(0.0f, 0.0f)));
 
		MeshBuffer->getIndexBuffer().push_back(indexIndex++);
        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
    }
 
    //Z-axis lines
	for (f32 z = 0.f; z <= Size.Height; z+= Spacing)
    {
        core::vector3df start = leftMost;
        start.Z += z ;

		if ( !canUseGridLine(start.Z, false, gridNode) )
			continue;
 
        core::vector3df end = rightMost;
        end.Z = start.Z;
 
        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(start, core::vector3df(0,1,0), GridColor, core::vector2df(0.0f, 0.0f)));
        MeshBuffer->getVertexBuffer().push_back(video::S3DVertex(end, core::vector3df(0,1,0), GridColor, core::vector2df(0.0f, 0.0f)));
 
        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
        MeshBuffer->getIndexBuffer().push_back(indexIndex++);
    }
 
    // Create our box, it is the size of the grid exactly, plus 1 in the Y axis
	irr::core::aabbox3df bbox(-(f32)Size.Width/2.f,-0.5f,-(f32)Size.Height/2.f,(f32)Size.Width/2.f,0.5f,(f32)Size.Height/2.f);
	bbox.MinEdge += Offset;
	bbox.MaxEdge += Offset;
	MeshBuffer->setBoundingBox(bbox);

	BoundingBoxDirty = true;
}

void CGridSceneNode::SGrid::setAlignment(CGridSceneNode::EAlign align)
{
	Alignment = align;
	GridDirty = true;
}

CGridSceneNode::EAlign CGridSceneNode::SGrid::getAlignment() const
{
	return Alignment;
}

void CGridSceneNode::SGrid::setSpacing(f32 newspacing)
{
    Spacing = newspacing;
    GridDirty = true;
}

f32 CGridSceneNode::SGrid::getSpacing() const
{
    return Spacing;
}

void CGridSceneNode::SGrid::setSize(const irr::core::dimension2df& newsize)
{
	Size = newsize;
    GridDirty = true;
}
 
const irr::core::dimension2df& CGridSceneNode::SGrid::getSize() const
{
    return Size;
}

void CGridSceneNode::SGrid::setOffset(const irr::core::vector3df& offset)
{
	Offset = offset;
	GridDirty = true;
}

const irr::core::vector3df& CGridSceneNode::SGrid::getOffset() const
{
	return Offset;
}

void CGridSceneNode::SGrid::setGridColor(video::SColor newcolor)
{
    GridColor = newcolor;
    GridDirty = true;
}

video::SColor CGridSceneNode::SGrid::getGridColor() const
{
    return GridColor;
}

void CGridSceneNode::SGrid::setMaxRenderDistance(irr::f32 dist)
{
	MaxRenderDist = dist;
}

irr::f32 CGridSceneNode::SGrid::getMaxRenderDistance() const
{
	return MaxRenderDist;
}

void CGridSceneNode::SGrid::setMaterial(const video::SMaterial& newMaterial)
{
    MeshBuffer->getMaterial() = newMaterial;
}

const irr::video::SMaterial& CGridSceneNode::SGrid::getMaterial() const
{
	return MeshBuffer->getMaterial();
}

irr::video::SMaterial& CGridSceneNode::SGrid::getMaterial()
{
	return MeshBuffer->getMaterial();
}

const irr::core::aabbox3d<irr::f32>& CGridSceneNode::SGrid::getBoundingBox() const
{
	return MeshBuffer->getBoundingBox();
}


void CGridSceneNode::SGrid::render(irr::video::IVideoDriver * driver )
{
	if ( !isVisible() )
		return;

	driver->setMaterial(MeshBuffer->getMaterial());
 
	driver->drawVertexPrimitiveList(MeshBuffer->getVertexBuffer().getData(),	// const void* vertices
									MeshBuffer->getVertexBuffer().size(),		// u32 vertexCount
									MeshBuffer->getIndexBuffer().getData(),		// const void* indexList
									MeshBuffer->getVertexBuffer().size()/2,		// u32 primCount
									MeshBuffer->getVertexType(),				// E_VERTEX_TYPE vType
									scene::EPT_LINES,							// scene::E_PRIMITIVE_TYPE pType
									MeshBuffer->getIndexType() );				// E_INDEX_TYPE iType
}




CGridSceneNode::CGridSceneNode(ISceneNode* parent, scene::ISceneManager* smgr, s32 id, irr::u32 numGrids)
        : ISceneNode(parent, smgr, id)
{
    // Set the default culling state to Frustum Box
    AutomaticCullingState = scene::EAC_FRUSTUM_BOX;

	for ( irr::u32 i=0; i<numGrids;++i )
	{
		Grids.push_back(SGrid());
	}
}
 
CGridSceneNode* CGridSceneNode::clone(scene::ISceneNode *newParent, scene::ISceneManager *newSceneManager)
{
    if (!newParent) newParent = Parent;
    if (!newSceneManager) newSceneManager = SceneManager;
 
    CGridSceneNode* clone = new CGridSceneNode(
        Parent,
        SceneManager,
        ID,
		Grids.size() );
    if (!clone)
        return 0;
 
	for ( irr::u32 i=0; i< Grids.size(); ++i )
	{
		SGrid & grid = clone->getGrid(i);
		grid = getGrid(i);
	}

    clone->drop();
    return clone;
}
 
void CGridSceneNode::OnRegisterSceneNode()
{
	if ( hasDirtyGrid() )
		regenerateMeshes();
	if( hasDirtyBoundingBox() )
		rebuildBoundingBox();

    if (IsVisible)
        SceneManager->registerNodeForRendering(this);
 
    ISceneNode::OnRegisterSceneNode();
}
 

void CGridSceneNode::render()
{
    video::IVideoDriver* driver = SceneManager->getVideoDriver();
 
	if ( !driver )
		return;

	driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
	for ( irr::u32 i=0; i<Grids.size(); ++i )
	{
		if ( !SceneManager->getActiveCamera()->isOrthogonal() )
		{
			irr::core::plane3df gridPlane(0,Grids[i].getOffset().Y,0,0,1,0);
			AbsoluteTransformation.transformPlane(gridPlane);
			irr::f32 distToCam = gridPlane.getDistanceTo( SceneManager->getActiveCamera()->getAbsolutePosition() );
			if ( !SceneManager->getActiveCamera()->isOrthogonal() && distToCam > Grids[i].getMaxRenderDistance() )
				continue;
		}

		Grids[i].render(driver);
	}
}

bool CGridSceneNode::hasDirtyBoundingBox() const
{
	bool hasDirty = false;
	for ( irr::u32 i=0; i<Grids.size(); ++i )
	{
		if ( Grids[i].BoundingBoxDirty )
		{
			hasDirty = true;
			break;
		}
	}
	return hasDirty;
}

bool CGridSceneNode::hasDirtyGrid() const
{
	bool hasDirty = false;
	for ( irr::u32 i=0; i<Grids.size(); ++i )
	{
		if ( Grids[i].GridDirty )
		{
			hasDirty = true;
			break;
		}
	}
	return hasDirty;
}

void CGridSceneNode::regenerateMeshes()
{
	for ( irr::u32 i=0; i<Grids.size(); ++i )
	{
		Grids[i].regenerateMesh(this);
	}
}

void CGridSceneNode::rebuildBoundingBox()
{
	if ( Grids.empty() )
		BoundingBox.reset( irr::core::vector3df(FLT_MAX,FLT_MAX,FLT_MAX) );
	else
	{
		// recalculate when one boundingbox has changed
		bool hasDirty = false;
		for ( irr::u32 i=0; i<Grids.size(); ++i )
		{
			if ( Grids[i].BoundingBoxDirty )
			{
				hasDirty = true;
				break;
			}
		}

		if ( hasDirty )
		{
			BoundingBox = Grids[0].getBoundingBox();
			Grids[0].BoundingBoxDirty = false;
			for ( irr::u32 i=1; i<Grids.size(); ++i )
			{
				BoundingBox.addInternalBox( Grids[i].getBoundingBox() );
				Grids[i].BoundingBoxDirty = false;
			}
		}
	}
}

const core::aabbox3d<f32>& CGridSceneNode::getBoundingBox() const
{
	return BoundingBox;
}
 
u32 CGridSceneNode::getMaterialCount() const
{
	return Grids.size();
}
 
video::SMaterial& CGridSceneNode::getMaterial(u32 i)
{
	return Grids[i].getMaterial();
}

irr::u32 CGridSceneNode::getNumberOfGrids() const
{
	return Grids.size();
}

irr::u32 CGridSceneNode::addGrid()
{
	Grids.push_back(SGrid());
	return Grids.size()-1;
}

void CGridSceneNode::removeGrid(irr::u32 index)
{
	Grids.erase(index, 1);
}

CGridSceneNode::SGrid& CGridSceneNode::getGrid(irr::u32 index)
{
	return Grids[index];
}

const CGridSceneNode::SGrid& CGridSceneNode::getGrid(irr::u32 index) const
{
	return Grids[index];
}

void CGridSceneNode::setGridsSize(const irr::core::dimension2df& newsize)
{
	for ( irr::u32 i=0; i < Grids.size(); ++i )
	{
		Grids[i].setSize(newsize);
	}
}
