/*
** tileatlas.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
**
** mkxp is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 2 of the License, or
** (at your option) any later version.
**
** mkxp is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with mkxp.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "tileatlas.h"

namespace TileAtlas
{

/* A Column represents a Rect
 * with undefined width */
struct Column
{
	int x, y, h;

	Column(int x, int y, int h)
	    : x(x), y(y), h(h)
	{}
};

typedef std::vector<Column> ColumnVec;

/* Autotile area width */
static const int atAreaW = 96*4;
/* Autotile area height */
static const int atAreaH = 128*7;
/* Autotile area */
static const int atArea = atAreaW * atAreaH;

static const int tilesetW = 256;
static const int tsLaneW = tilesetW / 2;

static int freeArea(int width, int height)
{
	return width * height - atArea;
}

Vec2i minSize(int tilesetH, int maxAtlasSize)
{
	int width = atAreaW;
	int height = atAreaH;

	const int tsArea = tilesetW * tilesetH;

	/* Expand vertically */
	while (freeArea(width, height) < tsArea && height < maxAtlasSize)
		height += 32;

	if (freeArea(width, height) >= tsArea && height <= maxAtlasSize)
		return Vec2i(width, height);

	/* Expand horizontally */
	while (freeArea(width, height) < tsArea && width < maxAtlasSize)
		width += tsLaneW;

	if (freeArea(width, height) >= tsArea && width <= maxAtlasSize)
		return Vec2i(width, height);

	return Vec2i(-1, -1);
}

static ColumnVec calcSrcCols(int tilesetH)
{
	ColumnVec cols;
	cols.reserve(2);

	cols.push_back(Column(0, 0, tilesetH));
	cols.push_back(Column(tsLaneW, 0, tilesetH));

	return cols;
}

static ColumnVec calcDstCols(int atlasW, int atlasH)
{
	ColumnVec cols;
	cols.reserve(3);

	/* Columns below the autotile area */
	const int underAt = atlasH - atAreaH;

	for (int i = 0; i < 3; ++i)
		cols.push_back(Column(i*tsLaneW, atAreaH, underAt));

	if (atlasW <= atAreaW)
		return cols;

	const int remCols = (atlasW - atAreaW) / tsLaneW;

	for (int i = 0; i < remCols; ++i)
		cols.push_back(Column(i*tsLaneW + atAreaW, 0, atlasH));

	return cols;
}

static BlitVec calcBlitsInt(ColumnVec &srcCols, ColumnVec &dstCols)
{
	BlitVec blits;

	/* Using signed indices here is safer, as we
	 * might decrement dstI while it is zero. */
	int dstI = 0;

	for (size_t srcI = 0; srcI < srcCols.size(); ++srcI)
	{
		Column &srcCol = srcCols[srcI];

		for (; dstI < (int) dstCols.size() && srcCol.h > 0; ++dstI)
		{
			Column &dstCol = dstCols[dstI];

			if (srcCol.h > dstCol.h)
			{
				/* srcCol doesn't fully fit into dstCol */
				blits.push_back(Blit(srcCol.x, srcCol.y,
				                     dstCol.x, dstCol.y, dstCol.h));

				srcCol.y += dstCol.h;
				srcCol.h -= dstCol.h;
			}
			else if (srcCol.h < dstCol.h)
			{
				/* srcCol fits into dstCol with space remaining */
				blits.push_back(Blit(srcCol.x, srcCol.y,
				                     dstCol.x, dstCol.y, srcCol.h));

				dstCol.y += srcCol.h;
				dstCol.h -= srcCol.h;

				/* Queue this column up again for processing */
				--dstI;

				srcCol.h = 0;
			}
			else
			{
				/* srcCol fits perfectly into dstCol */
				blits.push_back(Blit(srcCol.x, srcCol.y,
				                     dstCol.x, dstCol.y, dstCol.h));
			}
		}
	}

	return blits;
}

BlitVec calcBlits(int tilesetH, const Vec2i &atlasSize)
{
	ColumnVec srcCols = calcSrcCols(tilesetH);
	ColumnVec dstCols = calcDstCols(atlasSize.x, atlasSize.y);

	return calcBlitsInt(srcCols, dstCols);
}

Vec2i tileToAtlasCoor(int tileX, int tileY, int tilesetH, int atlasH)
{
	int laneX = tileX*32;
	int laneY = tileY*32;

	int longlaneH = atlasH;
	int shortlaneH = longlaneH - atAreaH;

	int longlaneOffset = shortlaneH * 3;

	int laneIdx = 0;
	int atlasY = 0;

	/* Check if we're inside the 2nd lane */
	if (laneX >= tsLaneW)
	{
		laneY += tilesetH;
		laneX -= tsLaneW;
	}

	if (laneY < longlaneOffset)
	{
		/* Below autotile area */
		laneIdx = laneY / shortlaneH;
		atlasY  = laneY % shortlaneH + atAreaH;
	}
	else
	{
		/* Right of autotile area */
		int _y = laneY - longlaneOffset;
		laneIdx = 3 + _y / longlaneH;
		atlasY = _y % longlaneH;
	}

	int atlasX = laneIdx * tsLaneW + laneX;

	return Vec2i(atlasX, atlasY);
}

}