/*
 * This source file is part of MyGUI. For the latest info, see http://mygui.info/
 * Distributed under the MIT License
 * (See accompanying file COPYING.MIT or copy at http://opensource.org/licenses/MIT)
 */

#include "MyGUI_Precompiled.h"
#include "MyGUI_TileRect.h"
#include "MyGUI_RenderItem.h"
#include "MyGUI_SkinManager.h"
#include "MyGUI_LanguageManager.h"
#include "MyGUI_LayerNode.h"
#include "MyGUI_CommonStateInfo.h"
#include "MyGUI_RenderManager.h"
#include "MyGUI_TextureUtility.h"

namespace MyGUI
{

	const size_t TILERECT_COUNT_VERTEX = 16 * VertexQuad::VertexCount;

	TileRect::TileRect() :
		mEmptyView(false),
		mCurrentColour(0xFFFFFFFF),
		mNode(nullptr),
		mRenderItem(nullptr),
		mCountVertex(TILERECT_COUNT_VERTEX),
		mRealTileWidth(0),
		mRealTileHeight(0),
		mTextureHeightOne(0),
		mTextureWidthOne(0),
		mTileH(true),
		mTileV(true)
	{
		mVertexFormat = RenderManager::getInstance().getVertexFormat();
	}

	void TileRect::setVisible(bool _visible)
	{
		if (mVisible == _visible)
			return;
		mVisible = _visible;

		if (nullptr != mNode)
			mNode->outOfDate(mRenderItem);
	}

	void TileRect::setAlpha(float _alpha)
	{
		uint32 alpha = ((uint8)(_alpha * 255) << 24);
		mCurrentColour = (mCurrentColour & 0x00FFFFFF) | (alpha & 0xFF000000);

		if (nullptr != mNode)
			mNode->outOfDate(mRenderItem);
	}

	void TileRect::_correctView()
	{
		if (nullptr != mNode)
			mNode->outOfDate(mRenderItem);
	}

	void TileRect::_setAlign(const IntSize& _oldsize)
	{
		// первоначальное выравнивание
		if (mAlign.isHStretch())
		{
			// растягиваем
			mCoord.width = mCoord.width + (mCroppedParent->getWidth() - _oldsize.width);
			mIsMargin = true; // при изменении размеров все пересчитывать
		}
		else if (mAlign.isRight())
		{
			// двигаем по правому краю
			mCoord.left = mCoord.left + (mCroppedParent->getWidth() - _oldsize.width);
		}
		else if (mAlign.isHCenter())
		{
			// выравнивание по горизонтали без растяжения
			mCoord.left = (mCroppedParent->getWidth() - mCoord.width) / 2;
		}

		if (mAlign.isVStretch())
		{
			// растягиваем
			mCoord.height = mCoord.height + (mCroppedParent->getHeight() - _oldsize.height);
			mIsMargin = true; // при изменении размеров все пересчитывать
		}
		else if (mAlign.isBottom())
		{
			// двигаем по нижнему краю
			mCoord.top = mCoord.top + (mCroppedParent->getHeight() - _oldsize.height);
		}
		else if (mAlign.isVCenter())
		{
			// выравнивание по вертикали без растяжения
			mCoord.top = (mCroppedParent->getHeight() - mCoord.height) / 2;
		}

        mCurrentCoord = mCoord;
        if (!mTileH) mTileSize.width = mCoord.width;
        if (!mTileV) mTileSize.height = mCoord.height;
        _updateView();
	}

	void TileRect::_updateView()
	{
		bool margin = _checkMargin();

		mEmptyView = ((0 >= _getViewWidth()) || (0 >= _getViewHeight()));

		mCurrentCoord.left = mCoord.left + mMargin.left;
		mCurrentCoord.top = mCoord.top + mMargin.top;
		mCurrentCoord.width = _getViewWidth();
		mCurrentCoord.height = _getViewHeight();

		// подсчитываем необходимое колличество тайлов
		if (!mEmptyView)
		{
			size_t count = 0;
			if (!mTileSize.empty())
			{
				size_t count_x = mCoord.width / mTileSize.width;
				if ((mCoord.width % mTileSize.width) > 0)
					count_x ++;

				size_t count_y = mCoord.height / mTileSize.height;
				if ((mCoord.height % mTileSize.height) > 0)
					count_y ++;

				count = count_y * count_x * VertexQuad::VertexCount;
			}

			// нужно больше вершин
			if (count > mCountVertex)
			{
				mCountVertex = count + TILERECT_COUNT_VERTEX;
				if (nullptr != mRenderItem)
					mRenderItem->reallockDrawItem(this, mCountVertex);
			}
		}

		// вьюпорт стал битым
		if (margin)
		{
			// проверка на полный выход за границу
			if (_checkOutside())
			{
				// запоминаем текущее состояние
				mIsMargin = margin;

				// обновить перед выходом
				if (nullptr != mNode)
					mNode->outOfDate(mRenderItem);
				return;
			}
		}

		// запоминаем текущее состояние
		mIsMargin = margin;

		if (nullptr != mNode)
			mNode->outOfDate(mRenderItem);
	}

	void TileRect::_setUVSet(const FloatRect& _rect)
	{
		mCurrentTexture = _rect;
		if (nullptr != mNode)
			mNode->outOfDate(mRenderItem);
	}

	void TileRect::doRender()
	{
		if (!mVisible || mEmptyView || mTileSize.empty())
			return;

		VertexQuad* quad = reinterpret_cast<VertexQuad*>(mRenderItem->getCurrentVertexBuffer());

		const RenderTargetInfo& info = mRenderItem->getRenderTarget()->getInfo();

		// размер одного тайла
		mRealTileWidth = info.pixScaleX * (float)(mTileSize.width) * 2;
		mRealTileHeight = info.pixScaleY * (float)(mTileSize.height) * 2;

		mTextureHeightOne = (mCurrentTexture.bottom - mCurrentTexture.top) / mRealTileHeight;
		mTextureWidthOne = (mCurrentTexture.right - mCurrentTexture.left) / mRealTileWidth;

		float vertex_z = mNode->getNodeDepth();

		// абсолютный размер окна
		float window_left = ((info.pixScaleX * (float)(mCoord.left + mCroppedParent->getAbsoluteLeft() - info.leftOffset) + info.hOffset) * 2) - 1;
		float window_top = -(((info.pixScaleY * (float)(mCoord.top + mCroppedParent->getAbsoluteTop() - info.topOffset) + info.vOffset) * 2) - 1);

		// размер вьюпорта
		float real_left = ((info.pixScaleX * (float)(mCurrentCoord.left + mCroppedParent->getAbsoluteLeft() - info.leftOffset) + info.hOffset) * 2) - 1;
		float real_right = real_left + (info.pixScaleX * (float)mCurrentCoord.width * 2);
		float real_top = -(((info.pixScaleY * (float)(mCurrentCoord.top + mCroppedParent->getAbsoluteTop() - info.topOffset) + info.vOffset) * 2) - 1);
		float real_bottom = real_top - (info.pixScaleY * (float)mCurrentCoord.height * 2);

		size_t count = 0;

		float left = window_left;
		float right = window_left;
		float top = window_top;
		float bottom = window_top;

		for (int y = 0; y < mCoord.height; y += mTileSize.height)
		{
			top = bottom;
			bottom -= mRealTileHeight;
			right = window_left;

			float vertex_top = top;
			float vertex_bottom = bottom;
			bool texture_crop_height  = false;

			if (vertex_top > real_top)
			{
				// проверка на полный выход
				if (vertex_bottom > real_top)
				{
					continue;
				}
				// обрезаем
				vertex_top = real_top;
				texture_crop_height = true;
			}
			if (vertex_bottom < real_bottom)
			{
				// вообще вниз ушли
				if (vertex_top < real_bottom)
				{
					continue;
				}
				// обрезаем
				vertex_bottom = real_bottom;
				texture_crop_height = true;
			}

			for (int x = 0; x < mCoord.width; x += mTileSize.width)
			{
				left = right;
				right += mRealTileWidth;

				float vertex_left = left;
				float vertex_right = right;
				bool texture_crop_width = false;


				if (vertex_left < real_left)
				{
					// проверка на полный выход
					if (vertex_right < real_left)
					{
						continue;
					}
					// обрезаем
					vertex_left = real_left;
					texture_crop_width = true;
				}

				if (vertex_right > real_right)
				{
					// вообще строку до конца не нуна
					if (vertex_left > real_right)
					{
						continue;
					}
					// обрезаем
					vertex_right = real_right;
					texture_crop_width = true;
				}

				// текущие текстурные координаты
				float texture_left = mCurrentTexture.left;
				float texture_right = mCurrentTexture.right;
				float texture_top = mCurrentTexture.top;
				float texture_bottom = mCurrentTexture.bottom;

				// смещение текстуры по вертикили
				if (texture_crop_height)
				{
					// прибавляем размер смещения в текстурных координатах
					texture_top += (top - vertex_top) * mTextureHeightOne;
					// отнимаем размер смещения в текстурных координатах
					texture_bottom -= (vertex_bottom - bottom) * mTextureHeightOne;
				}

				// смещение текстуры по горизонтали
				if (texture_crop_width)
				{
					// прибавляем размер смещения в текстурных координатах
					texture_left += (vertex_left - left) * mTextureWidthOne;
					// отнимаем размер смещения в текстурных координатах
					texture_right -= (right - vertex_right) * mTextureWidthOne;
				}

				quad[count].set(
					vertex_left,
					vertex_top,
					vertex_right,
					vertex_bottom,
					vertex_z,
					texture_left,
					texture_top,
					texture_right,
					texture_bottom,
					mCurrentColour);

				count ++;
			}
		}

		mRenderItem->setLastVertexCount(VertexQuad::VertexCount * count);
	}

	void TileRect::createDrawItem(ITexture* _texture, ILayerNode* _node)
	{
		MYGUI_ASSERT(!mRenderItem, "mRenderItem must be nullptr");

		mNode = _node;
		mRenderItem = mNode->addToRenderItem(_texture, true, false);
		mRenderItem->addDrawItem(this, mCountVertex);
	}

	void TileRect::destroyDrawItem()
	{
		MYGUI_ASSERT(mRenderItem, "mRenderItem must be not nullptr");

		mNode = nullptr;
		mRenderItem->removeDrawItem(this);
		mRenderItem = nullptr;
	}

	void TileRect::setStateData(IStateInfo* _data)
	{
		TileRectStateInfo* data = _data->castType<TileRectStateInfo>();

		mTileSize = data->getTileSize();
		mTileH = data->getTileH();
		mTileV = data->getTileV();
		_setUVSet(data->getRect());
	}

	void TileRect::_setColour(const Colour& _value)
	{
		uint32 colour = texture_utility::toColourARGB(_value);
		texture_utility::convertColour(colour, mVertexFormat);
		mCurrentColour = (colour & 0x00FFFFFF) | (mCurrentColour & 0xFF000000);

		if (nullptr != mNode)
			mNode->outOfDate(mRenderItem);
	}

} // namespace MyGUI
