// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CTextSceneNode.h"
#include "ISceneManager.h"
#include "IVideoDriver.h"
#include "ICameraSceneNode.h"
#include "IGUISpriteBank.h"
#include "SMeshBuffer.h"
#include "os.h"


namespace irr
{
namespace scene
{


//! constructor
CTextSceneNode::CTextSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id,
			gui::IGUIFont* font, scene::ISceneCollisionManager* coll,
			const core::vector3df& position, const wchar_t* text,
			video::SColor color)
	: ITextSceneNode(parent, mgr, id, position), Text(text), Color(color),
		Font(font), Coll(coll)

{
	#ifdef _DEBUG
	setDebugName("CTextSceneNode");
	#endif

	if (Font)
		Font->grab();

	setAutomaticCulling(scene::EAC_OFF);
}

//! destructor
CTextSceneNode::~CTextSceneNode()
{
	if (Font)
		Font->drop();
}

void CTextSceneNode::OnRegisterSceneNode()
{
	if (IsVisible)
		SceneManager->registerNodeForRendering(this, ESNRP_TRANSPARENT);

	ISceneNode::OnRegisterSceneNode();
}

//! renders the node.
void CTextSceneNode::render()
{
	if (!Font || !Coll)
		return;

	core::position2d<s32> pos = Coll->getScreenCoordinatesFrom3DPosition(getAbsolutePosition(),
		SceneManager->getActiveCamera());

	core::rect<s32> r(pos, core::dimension2d<s32>(1,1));
	Font->draw(Text, r, Color, true, true);
}


//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32>& CTextSceneNode::getBoundingBox() const
{
	return Box;
}

//! sets the text string
void CTextSceneNode::setText(const wchar_t* text)
{
	Text = text;
}

//! get the text string
const wchar_t* CTextSceneNode::getText() const
{
	return Text.c_str();
}

//! sets the color of the text
void CTextSceneNode::setTextColor(video::SColor color)
{
	Color = color;
}

//! get the color of the text
video::SColor CTextSceneNode::getTextColor() const
{
	return Color;
}

void CTextSceneNode::setFont(gui::IGUIFont* font)
{
	if ( font != Font )
	{
		if ( font )
			font->grab();
		if ( Font )
			Font->drop();
		Font = font;
	}
}

//! Get the font used to draw the text
gui::IGUIFont* CTextSceneNode::getFont() const
{
	return Font;
}


//!--------------------------------- CBillboardTextSceneNode ----------------------------------------------


//! constructor
CBillboardTextSceneNode::CBillboardTextSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id,
	gui::IGUIFont* font,const wchar_t* text,
	const core::vector3df& position, const core::dimension2d<f32>& size,
	video::SColor colorTop,video::SColor shade_bottom )
: IBillboardTextSceneNode(parent, mgr, id, position),
	Font(0), ColorTop(colorTop), ColorBottom(shade_bottom), Mesh(0)
{
	#ifdef _DEBUG
	setDebugName("CBillboardTextSceneNode");
	#endif

	Material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
	Material.MaterialTypeParam = 1.f / 255.f;
	Material.BackfaceCulling = false;
	Material.Lighting = false;
	Material.ZBuffer = video::ECFN_LESSEQUAL;
	Material.ZWriteEnable = video::EZW_OFF;

	if (font)
	{
		// doesn't support other font types
		if (font->getType() == gui::EGFT_BITMAP)
		{
			Font = (gui::IGUIFontBitmap*)font;
			Font->grab();

			// mesh with one buffer per texture
			Mesh = new SMesh();
			for (u32 i=0; i<Font->getSpriteBank()->getTextureCount(); ++i)
			{
				SMeshBuffer *mb = new SMeshBuffer();
				mb->Material = Material;
				mb->Material.setTexture(0, Font->getSpriteBank()->getTexture(i));
				Mesh->addMeshBuffer(mb);
				mb->drop();
			}
		}
		else
		{
			os::Printer::log("Sorry, CBillboardTextSceneNode does not support this font type", ELL_INFORMATION);
		}
	}

	setText(text);
	setSize(size);

	setAutomaticCulling ( scene::EAC_BOX );
}



CBillboardTextSceneNode::~CBillboardTextSceneNode()
{
	if (Font)
		Font->drop();

	if (Mesh)
		Mesh->drop();

}


//! sets the text string
void CBillboardTextSceneNode::setText(const wchar_t* text)
{
	if ( !Mesh )
		return;

	Text = text;

	Symbol.clear();

	// clear mesh
	for (u32 j=0; j < Mesh->getMeshBufferCount(); ++j)
	{
		((SMeshBuffer*)Mesh->getMeshBuffer(j))->Indices.clear();
		((SMeshBuffer*)Mesh->getMeshBuffer(j))->Vertices.clear();
	}

	if (!Font)
		return;

	const core::array< core::rect<s32> > &sourceRects = Font->getSpriteBank()->getPositions();
	const core::array< gui::SGUISprite > &sprites = Font->getSpriteBank()->getSprites();

	f32 dim[2];
	f32 tex[4];

	u32 i;
	for ( i = 0; i != Text.size (); ++i )
	{
		SSymbolInfo info;

		u32 spriteno = Font->getSpriteNoFromChar( &text[i] );
		u32 rectno = sprites[spriteno].Frames[0].rectNumber;
		u32 texno = sprites[spriteno].Frames[0].textureNumber;

		video::ITexture* texture = Font->getSpriteBank()->getTexture(texno);
		if (texture)
		{
			const core::dimension2d<u32>& texSize = texture->getOriginalSize();
			dim[0] = core::reciprocal((f32)texSize.Width);
			dim[1] = core::reciprocal((f32)texSize.Height);
		}
		else
		{
			dim[0] = 0;
			dim[1] = 0;
		}

		const core::rect<s32>& s = sourceRects[rectno];

		// add space for letter to buffer
		SMeshBuffer* buf = (SMeshBuffer*)Mesh->getMeshBuffer(texno);
		u32 firstInd = buf->Indices.size();
		u32 firstVert = buf->Vertices.size();
		buf->Indices.set_used(firstInd + 6);
		buf->Vertices.set_used(firstVert + 4);

		tex[0] = (s.LowerRightCorner.X * dim[0]) + 0.5f*dim[0]; // half pixel
		tex[1] = (s.LowerRightCorner.Y * dim[1]) + 0.5f*dim[1];
		tex[2] = (s.UpperLeftCorner.Y  * dim[1]) - 0.5f*dim[1];
		tex[3] = (s.UpperLeftCorner.X  * dim[0]) - 0.5f*dim[0];

		buf->Vertices[firstVert+0].TCoords.set(tex[0], tex[1]);
		buf->Vertices[firstVert+1].TCoords.set(tex[0], tex[2]);
		buf->Vertices[firstVert+2].TCoords.set(tex[3], tex[2]);
		buf->Vertices[firstVert+3].TCoords.set(tex[3], tex[1]);

		buf->Vertices[firstVert+0].Color = ColorBottom;
		buf->Vertices[firstVert+3].Color = ColorBottom;
		buf->Vertices[firstVert+1].Color = ColorTop;
		buf->Vertices[firstVert+2].Color = ColorTop;

		buf->Indices[firstInd+0] = (u16)firstVert+0;
		buf->Indices[firstInd+1] = (u16)firstVert+2;
		buf->Indices[firstInd+2] = (u16)firstVert+1;
		buf->Indices[firstInd+3] = (u16)firstVert+0;
		buf->Indices[firstInd+4] = (u16)firstVert+3;
		buf->Indices[firstInd+5] = (u16)firstVert+2;

		wchar_t *tp = 0;
		if (i>0)
			tp = &Text[i-1];

		info.Width = (f32)s.getWidth();
		info.bufNo = texno;
		info.Kerning = (f32)Font->getKerningWidth(&Text[i], tp);
		info.firstInd = firstInd;
		info.firstVert = firstVert;

		Symbol.push_back(info);
	}
}

//! get the text string
const wchar_t* CBillboardTextSceneNode::getText() const
{
	return Text.c_str();
}

//! pre render event
void CBillboardTextSceneNode::OnAnimate(u32 timeMs)
{
	ISceneNode::OnAnimate(timeMs);

	if (!IsVisible || !Font || !Mesh)
		return;

	ICameraSceneNode* camera = SceneManager->getActiveCamera();
	if (!camera)
		return;

	// TODO: Risky - if camera is later in the scene-graph then it's not yet updated here
	//       CBillBoardSceneNode does it different, but maybe real solution would be to enforce cameras to update earlier?
	//       Maybe we can also unify the code by using a common base-class or having updateMesh functionality in an animator instead.
	updateMesh(camera);

	// mesh uses vertices with absolute coordinates so to get a bbox for culling we have to get back to local ones.
	BBox = Mesh->getBoundingBox();
	core::matrix4 mat( getAbsoluteTransformation(), core::matrix4::EM4CONST_INVERSE );
	mat.transformBoxEx(BBox);
}

const core::aabbox3d<f32>& CBillboardTextSceneNode::getTransformedBillboardBoundingBox(const irr::scene::ICameraSceneNode* camera)
{
	updateMesh(camera);
	return Mesh->getBoundingBox();
}

void CBillboardTextSceneNode::updateMesh(const irr::scene::ICameraSceneNode* camera)
{
	// get text width
	f32 textLength = 0.f;
	u32 i;
	for(i=0; i!=Symbol.size(); ++i)
	{
		SSymbolInfo &info = Symbol[i];
		textLength += info.Kerning + info.Width;
	}
	if (textLength<0.0f)
		textLength=1.0f;

	//const core::matrix4 &m = camera->getViewFrustum()->Matrices[ video::ETS_VIEW ];

	// make billboard look to camera
	core::vector3df pos = getAbsolutePosition();

	core::vector3df campos = camera->getAbsolutePosition();
	core::vector3df target = camera->getTarget();
	core::vector3df up = camera->getUpVector();
	core::vector3df view = target - campos;
	view.normalize();

	core::vector3df horizontal = up.crossProduct(view);
	if ( horizontal.getLength() == 0 )
	{
		horizontal.set(up.Y,up.X,up.Z);
	}

	horizontal.normalize();
	core::vector3df space = horizontal;

	horizontal *= 0.5f * Size.Width;

	core::vector3df vertical = horizontal.crossProduct(view);
	vertical.normalize();
	vertical *= 0.5f * Size.Height;

	view *= -1.0f;

	// center text
	pos += space * (Size.Width * -0.5f);

	for ( i = 0; i!= Symbol.size(); ++i )
	{
		SSymbolInfo &info = Symbol[i];
		f32 infw = info.Width / textLength;
		f32 infk = info.Kerning / textLength;
		f32 w = (Size.Width * infw * 0.5f);
		pos += space * w;

		SMeshBuffer* buf = (SMeshBuffer*)Mesh->getMeshBuffer(info.bufNo);

		buf->Vertices[info.firstVert+0].Normal = view;
		buf->Vertices[info.firstVert+1].Normal = view;
		buf->Vertices[info.firstVert+2].Normal = view;
		buf->Vertices[info.firstVert+3].Normal = view;

		buf->Vertices[info.firstVert+0].Pos = pos + (space * w) + vertical;
		buf->Vertices[info.firstVert+1].Pos = pos + (space * w) - vertical;
		buf->Vertices[info.firstVert+2].Pos = pos - (space * w) - vertical;
		buf->Vertices[info.firstVert+3].Pos = pos - (space * w) + vertical;

		pos += space * (Size.Width*infk + w);
	}

	// make bounding box
	for (i=0; i< Mesh->getMeshBufferCount() ; ++i)
		Mesh->getMeshBuffer(i)->recalculateBoundingBox();
	Mesh->recalculateBoundingBox();
}

void CBillboardTextSceneNode::OnRegisterSceneNode()
{
	if (IsVisible && Font && Mesh)
	{
		SceneManager->registerNodeForRendering(this, ESNRP_TRANSPARENT);
		ISceneNode::OnRegisterSceneNode();
	}
}


//! render
void CBillboardTextSceneNode::render()
{
	if ( !Mesh )
		return;

	video::IVideoDriver* driver = SceneManager->getVideoDriver();

	// draw
	core::matrix4 mat;
	driver->setTransform(video::ETS_WORLD, mat);

	for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i)
	{
		driver->setMaterial(Mesh->getMeshBuffer(i)->getMaterial());
		driver->drawMeshBuffer(Mesh->getMeshBuffer(i));
	}

	if ( DebugDataVisible & scene::EDS_BBOX )
	{
		driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
		video::SMaterial m;
		m.Lighting = false;
		driver->setMaterial(m);
		driver->draw3DBox(BBox, video::SColor(0,208,195,152));
	}
}


//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32>& CBillboardTextSceneNode::getBoundingBox() const
{
	return BBox;
}


//! sets the size of the billboard
void CBillboardTextSceneNode::setSize(const core::dimension2d<f32>& size)
{
	Size = size;

	if (Size.Width == 0.0f)
		Size.Width = 1.0f;

	if (Size.Height == 0.0f )
		Size.Height = 1.0f;

	//f32 avg = (size.Width + size.Height)/6;
	//BBox.MinEdge.set(-avg,-avg,-avg);
	//BBox.MaxEdge.set(avg,avg,avg);
}


video::SMaterial& CBillboardTextSceneNode::getMaterial(u32 i)
{
	if (Mesh && Mesh->getMeshBufferCount() > i )
		return Mesh->getMeshBuffer(i)->getMaterial();
	else
		return Material;
}


//! returns amount of materials used by this scene node.
u32 CBillboardTextSceneNode::getMaterialCount() const
{
	if (Mesh)
		return Mesh->getMeshBufferCount();
	else
		return 0;
}


//! gets the size of the billboard
const core::dimension2d<f32>& CBillboardTextSceneNode::getSize() const
{
	return Size;
}

//! Get the font used to draw the text
gui::IGUIFont* CBillboardTextSceneNode::getFont() const
{
	return Font;
}

//! Set the color of all vertices of the billboard
//! \param overallColor: the color to set
void CBillboardTextSceneNode::setColor(const video::SColor & overallColor)
{
	if ( !Mesh )
		return;

	for ( u32 i = 0; i != Text.size (); ++i )
	{
		const SSymbolInfo &info = Symbol[i];
		SMeshBuffer* buf = (SMeshBuffer*)Mesh->getMeshBuffer(info.bufNo);
		buf->Vertices[info.firstVert+0].Color = overallColor;
		buf->Vertices[info.firstVert+1].Color = overallColor;
		buf->Vertices[info.firstVert+2].Color = overallColor;
		buf->Vertices[info.firstVert+3].Color = overallColor;
	}
}


//! Set the color of the top and bottom vertices of the billboard
//! \param topColor: the color to set the top vertices
//! \param bottomColor: the color to set the bottom vertices
void CBillboardTextSceneNode::setColor(const video::SColor & topColor, const video::SColor & bottomColor)
{
	if ( !Mesh )
		return;

	ColorBottom = bottomColor;
	ColorTop = topColor;
	for ( u32 i = 0; i != Text.size (); ++i )
	{
		const SSymbolInfo &info = Symbol[i];
		SMeshBuffer* buf = (SMeshBuffer*)Mesh->getMeshBuffer(info.bufNo);
		buf->Vertices[info.firstVert+0].Color = ColorBottom;
		buf->Vertices[info.firstVert+3].Color = ColorBottom;
		buf->Vertices[info.firstVert+1].Color = ColorTop;
		buf->Vertices[info.firstVert+2].Color = ColorTop;
	}
}


//! Gets the color of the top and bottom vertices of the billboard
//! \param topColor: stores the color of the top vertices
//! \param bottomColor: stores the color of the bottom vertices
void CBillboardTextSceneNode::getColor(video::SColor & topColor, video::SColor & bottomColor) const
{
	topColor = ColorTop;
	bottomColor = ColorBottom;
}


} // end namespace scene
} // end namespace irr