/*!
 * @file Field.cpp
 * @brief ゲーム中のフロアを定義する
 * @date 2013/12/11
 * @author Deskull
 * 2013 Sikabane Works.
 */

#include "stdafx.h"
#include "Field.h"

namespace Deeangband
{

	Field::Field(std::map<TAG, boost::shared_ptr<Dungeon>>::iterator dungeonIt, DEPTH depth) : GameInstance()
	{
		Dungeon *dungeonPtr = &(*dungeonIt->second);
		int x, y;
		this->dungeonTag = dungeonIt->first;
		this->width = dungeonPtr->GetBaseSize().GetX();
		this->height = dungeonPtr->GetBaseSize().GetY();
		this->generated = false;
		this->dungeonPtr = dungeonIt->second.get();

		if(depth > dungeonPtr->GetMaxDepth() || depth > dungeonPtr->GetMinDepth()) return;

		squares.resize(this->height);
		for(y = 0; y < height; y++)
		{
			for(x = 0; x < width; x++)
			{
				squares[y].push_back(boost::shared_ptr<Square>(new Square()));
				if(Dice::Cast(1, 4) == 3)
				{
					squares[y][x]->SetFloor(dungeonPtr->GetInnerWallFloorTag());
				}
				else
				{
					squares[y][x]->SetFloor(dungeonPtr->GetFloorFloorTag());
				}
			}
		}

		this->UpdateSight();

		this->createRectRoom(13, 13, 20, 33);

		creatures.resize(0);
		doors.resize(0);
		traps.resize(0);
		items.resize(0);

		this->generated = true;
	}

	Field::Field()
	{
		WipeData();
	}

	Field::Field(DEPTH depth)
	{

	}

	Field::~Field()
	{
		WipeData();
	}

	void Field::WipeData(void)
	{
		this->width = 0;
		this->height = 0;
		this->dungeonTag = "";
		creatures.resize(0);
		doors.resize(0);
		traps.resize(0);
		items.resize(0);
	}

	MAP_LENGTH Field::GetWidth(void)
	{
		return width;
	}

	MAP_LENGTH Field::GetHeight(void)
	{
		return height;
	}

	bool Field::SetSize(MAP_LENGTH width, MAP_LENGTH height)
	{
		this->width = width;
		this->height = height;
		return true;
	}

	Square *Field::GetSquare(MAP_LENGTH x, MAP_LENGTH y)
	{
		//! @note フィールドの範囲外の座標の場合falseを返す。
		if(x < 0 || y < 0 || x >= this->width || y >= this->height) return NULL;
		return squares[y][x].get();
	}

	Square *Field::GetSquare(Coordinates &coord)
	{
		return this->GetSquare(coord.GetX(), coord.GetY());
	}

	bool Field::GenerateTrap(std::map<TAG, boost::shared_ptr<TrapBase>>::iterator trapBaseIt, Coordinates *position)
	{
		traps.emplace(traps.end(), boost::make_shared<Trap>(trapBaseIt, position));
		return true;
	}

	void Field::UpdateSight(void)
	{
		int x, y;
		int updateSizeX = this->width * 2 + 1;
		int updateSizeY = this->height * 2 + 1;
		this->SightPass.resize(updateSizeX * updateSizeY);
		this->PhysicalPass.resize(updateSizeX * updateSizeY);

		for(x = 0; x < updateSizeX * updateSizeY; x++)
		{
			this->SightPass[x] = false;
			this->PhysicalPass[x] = false;
		}

		for(y = 1; y < updateSizeY; y += 2)
		{
			for(x = 1; x < updateSizeX; x += 2)
			{
				this->SightPass[y * updateSizeX + x] = !this->GetSquare(x / 2, y / 2)->IsWall();
				this->PhysicalPass[y * updateSizeX + x] = !this->GetSquare(x / 2, y / 2)->IsWall();
			}
		}

		for(y = 0; y < this->height * 2 + 1; y += 2)
		{
			for(x = 0; x < this->width * 2 + 1; x += 2)
			{
				if(!this->GetSquare(x / 2 - 1, y / 2 - 1) || !this->GetSquare(x / 2 - 1, y / 2) || !this->GetSquare(x / 2, y / 2 - 1) || !this->GetSquare(x / 2, y / 2))
				{
					this->SightPass[y * updateSizeX + x] = false;
					this->PhysicalPass[y * updateSizeX + x] = false;
				}
				else if((!this->GetSquare(x / 2 - 1, y / 2 - 1)->IsWall() && !this->GetSquare(x / 2, y / 2 )->IsWall()) || (!this->GetSquare(x / 2, y / 2 - 1)->IsWall() && !this->GetSquare(x / 2 - 1, y / 2)->IsWall()))
				{
					this->SightPass[y * updateSizeX + x] = true;
					this->PhysicalPass[y * updateSizeX + x] = true;
				}
				else
				{
					this->SightPass[y * updateSizeX + x] = false;
					this->PhysicalPass[y * updateSizeX + x] = false;
				}
			}
		}

		for(y = 0; y < this->height * 2 + 1; y += 2)
		{
			for(x = 1; x < this->width * 2 + 1; x += 2)
			{
				if(!this->GetSquare(x / 2, y / 2 - 1) || !this->GetSquare(x / 2, y / 2))
				{
					this->SightPass[y * updateSizeX + x] = false;
					this->PhysicalPass[y * updateSizeX + x] = false;
				}
				else if(this->GetSquare(x / 2, y / 2 - 1)->IsWall() || this->GetSquare(x / 2, y / 2)->IsWall())
				{
					this->SightPass[y * updateSizeX + x] = false;
					this->PhysicalPass[y * updateSizeX + x] = false;
				}
				else
				{
					this->SightPass[y * updateSizeX + x] = true;
					this->PhysicalPass[y * updateSizeX + x] = true;
				}
			}
		}

		for(y = 1; y < updateSizeY; y += 2)
		{
			for(x = 0; x < updateSizeX; x += 2)
			{
				if(!this->GetSquare(x / 2 - 1, y / 2) || !this->GetSquare(x / 2, y / 2))
				{
					this->SightPass[y * updateSizeX + x] = false;
					this->PhysicalPass[y * updateSizeX + x] = false;
				}
				else if(this->GetSquare(x / 2 - 1, y / 2)->IsWall() || this->GetSquare(x / 2, y / 2)->IsWall())
				{
					this->SightPass[y * updateSizeX + x] = false;
					this->PhysicalPass[y * updateSizeX+ x] = false;
				}
				else
				{
					this->SightPass[y * updateSizeX + x] = true;
					this->PhysicalPass[y * updateSizeX + x] = true;
				}
			}
		}

	}

	void Field::createRectRoom(MAP_LENGTH sx, MAP_LENGTH sy,  MAP_LENGTH ex, MAP_LENGTH ey)
	{
		Square *squarePtr;
		std::vector<boost::shared_ptr<Coordinates>> wallVec;

		MAP_LENGTH x, y;
		wallVec.clear();

		for(y = sy + 1; y < ey; y++)
		{
			for(x = sx + 1; x < ex; x++)
			{
				squarePtr = this->GetSquare(x, y);
				if(squarePtr)
				{
					squarePtr->SetFloor(dungeonPtr->GetFloorFloorTag());
				}
			}
		}
		
		for(y = sy; y <= ey; y++)
		{
			squarePtr = this->GetSquare(sx, y);
			squarePtr->SetFloor(dungeonPtr->GetInnerWallFloorTag());
			squarePtr = this->GetSquare(ex, y);
			squarePtr->SetFloor(dungeonPtr->GetInnerWallFloorTag());
			wallVec.push_back(boost::make_shared<Coordinates>(sx, y));
			wallVec.push_back(boost::make_shared<Coordinates>(ex, y));
		}

		for(x = sx + 1; x < ex; x++)
		{
			squarePtr = this->GetSquare(x, sy);
			squarePtr->SetFloor(dungeonPtr->GetInnerWallFloorTag());
			squarePtr = this->GetSquare(x, ey);
			squarePtr->SetFloor(dungeonPtr->GetInnerWallFloorTag());
			wallVec.push_back(boost::make_shared<Coordinates>(x, sy));
			wallVec.push_back(boost::make_shared<Coordinates>(x, ey));
		}

		this->GetSquare(*wallVec[Dice::Rand0(wallVec.size())].get())->SetFloor(dungeonPtr->GetFloorFloorTag());
	}

	void Field::createRectRoom(Coordinates& leftTop, Coordinates& rightBottom)
	{
		this->createRectRoom(leftTop.GetX(), leftTop.GetY(), rightBottom.GetX(), rightBottom.GetY());
	}

	void Field::GetSight(std::vector<bool> &coordVec, MAP_LENGTH size, MAP_LENGTH baseX, MAP_LENGTH baseY)
	{
		MAP_LENGTH x, y, s;
		int sideWidth = size * 2 + 1;
		int allocSize = sideWidth * sideWidth;
		std::vector<Coordinates> lineVec;
		std::vector<Coordinates>::iterator lineIt;
		std::vector<Coordinates>::reverse_iterator lineRIt;

		for(s = 0 ; s < allocSize; s++) coordVec[s] = false;
		coordVec[size * sideWidth + size] = true;

		for(s = size; s >= 1; s--)
		{
			for(x = baseX - s; x <= baseX + s; x++)
			{
				int tx = x;
				int ty1 = baseY - s;
				int ty2 = baseY + s;

				if(Coordinates::Distance(baseX, baseY, tx, ty1) >= size) continue;

				if(tx >= 0 && ty1 >= 0 && tx < this->width && ty1 < this->height)
				{
					if(!coordVec[(ty1 - baseY + size) * sideWidth + tx - baseX + size])
					{
						Coordinates::LineOfSight(lineVec, baseX, baseY, tx, ty1);
						if(lineVec[0].GetX() == baseX && lineVec[0].GetY() == baseY)
						{
							for(lineIt = ++lineVec.begin(); lineIt < lineVec.end(); lineIt++)
							{
								int lsx = lineIt->GetX() - baseX + size;
								int lsy = lineIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineIt->GetX(), lineIt->GetY()) || this->GetSquare(lineIt->GetX(), lineIt->GetY())->IsWall()) break;
							}
						}
						else
						{
							for(lineRIt = ++lineVec.rbegin(); lineRIt < lineVec.rend(); lineRIt++)
							{
								int lsx = lineRIt->GetX() - baseX + size;
								int lsy = lineRIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineRIt->GetX(), lineRIt->GetY()) || this->GetSquare(lineRIt->GetX(), lineRIt->GetY())->IsWall()) break;
							}
						}
					}
				}

				lineVec.clear();

				if(tx >= 0 && ty2 >= 0 && tx < this->width && ty2 < this->height)
				{
					if(!coordVec[(ty2 - baseY + size) * sideWidth + tx - baseX + size])
					{
						Coordinates::LineOfSight(lineVec, baseX, baseY, tx, ty2);
						if(lineVec[0].GetX() == baseX && lineVec[0].GetY() == baseY)
						{
							for(lineIt = ++lineVec.begin()++; lineIt < lineVec.end(); lineIt++)
							{
								int lsx = lineIt->GetX() - baseX + size;
								int lsy = lineIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineIt->GetX(), lineIt->GetY()) || this->GetSquare(lineIt->GetX(), lineIt->GetY())->IsWall()) break;
							}
						}
						else
						{
							for(lineRIt = ++lineVec.rbegin()++; lineRIt < lineVec.rend(); lineRIt++)
							{
								int lsx = lineRIt->GetX() - baseX + size;
								int lsy = lineRIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineRIt->GetX(), lineRIt->GetY()) || this->GetSquare(lineRIt->GetX(), lineRIt->GetY())->IsWall()) break;
							}
						}
					}
				}

				lineVec.clear();

			}

			for(y = baseY - s; y <= baseY + s; y++)
			{
				int tx1 = baseX - s;
				int tx2 = baseX + s;
				int ty = y;

				if(Coordinates::Distance(baseX, baseY, tx1, ty) >= size) continue;


				if(tx1 >= 0 && ty >= 0 && tx1 < this->width && ty < this->height)
				{
					if(!coordVec[(ty - baseY + size) * sideWidth + tx1 - baseX + size])
					{
						Coordinates::LineOfSight(lineVec, baseX, baseY, tx1, ty);
						if(lineVec[0].GetX() == baseX && lineVec[0].GetY() == baseY)
						{
							for(lineIt = ++lineVec.begin(); lineIt < lineVec.end(); lineIt++)
							{
								int lsx = lineIt->GetX() - baseX + size;
								int lsy = lineIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineIt->GetX(), lineIt->GetY()) || this->GetSquare(lineIt->GetX(), lineIt->GetY())->IsWall()) break;
							}
						}
						else
						{
							for(lineRIt = ++lineVec.rbegin(); lineRIt < lineVec.rend(); lineRIt++)
							{
								int lsx = lineRIt->GetX() - baseX + size;
								int lsy = lineRIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineRIt->GetX(), lineRIt->GetY()) || this->GetSquare(lineRIt->GetX(), lineRIt->GetY())->IsWall()) break;
							}	
						}
					}
				}

				lineVec.clear();

				if(tx2 >= 0 && ty >= 0 && tx2 < this->width && ty < this->height)
				{
					if(!coordVec[(ty - baseY + size) * sideWidth + tx2 - baseX + size])
					{
						Coordinates::LineOfSight(lineVec, baseX, baseY, tx2, ty);
						if(lineVec[0].GetX() == baseX && lineVec[0].GetY() == baseY)
						{
							for(lineIt = ++lineVec.begin(); lineIt < lineVec.end(); lineIt++)
							{
								int lsx = lineIt->GetX() - baseX + size;
								int lsy = lineIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineIt->GetX(), lineIt->GetY()) || this->GetSquare(lineIt->GetX(), lineIt->GetY())->IsWall()) break;
							}
						}
						else
						{
							for(lineRIt = ++lineVec.rbegin(); lineRIt < lineVec.rend(); lineRIt++)
							{
								int lsx = lineRIt->GetX() - baseX + size;
								int lsy = lineRIt->GetY() - baseY + size;
								coordVec[lsy * sideWidth + lsx] = true;
								if(!this->GetSquare(lineRIt->GetX(), lineRIt->GetY()) || this->GetSquare(lineRIt->GetX(), lineRIt->GetY())->IsWall()) break;
							}	
						}
					}
				}

				lineVec.clear();

			}

		}

		for(y = -size + 1; y < size; y++)
		{
			for(x = -size + 1; x < size; x++)
			{
				if(Coordinates::Distance(0, 0, x, y) <= size)
				{
					if(coordVec[(y+size) * sideWidth + (x+size)] == true && !this->GetSquare(baseX + x, baseY + y)->IsWall())
					{
						if(this->GetSquare(baseX + x - 1, baseY + y - 1) && this->GetSquare(baseX + x - 1, baseY + y - 1)->IsWall()) coordVec[(y+size-1) * sideWidth + (x+size-1)] = true;
						if(this->GetSquare(baseX + x - 1, baseY + y + 1) && this->GetSquare(baseX + x - 1, baseY + y + 1)->IsWall()) coordVec[(y+size+1) * sideWidth + (x+size-1)] = true;
						if(this->GetSquare(baseX + x + 1, baseY + y - 1) && this->GetSquare(baseX + x + 1, baseY + y - 1)->IsWall()) coordVec[(y+size-1) * sideWidth + (x+size+1)] = true;
						if(this->GetSquare(baseX + x + 1, baseY + y + 1) && this->GetSquare(baseX + x + 1, baseY + y + 1)->IsWall()) coordVec[(y+size+1) * sideWidth + (x+size+1)] = true;
						if(this->GetSquare(baseX + x + 1, baseY + y) && this->GetSquare(baseX + x + 1, baseY + y)->IsWall()) coordVec[(y+size) * sideWidth + (x+size+1)] = true;
						if(this->GetSquare(baseX + x, baseY + y + 1) && this->GetSquare(baseX + x, baseY + y + 1)->IsWall()) coordVec[(y+size+1) * sideWidth + (x+size)] = true;
						if(this->GetSquare(baseX + x - 1, baseY + y) && this->GetSquare(baseX + x - 1, baseY + y)->IsWall()) coordVec[(y+size) * sideWidth + (x+size-1)] = true;
						if(this->GetSquare(baseX + x, baseY + y - 1) && this->GetSquare(baseX + x, baseY + y - 1)->IsWall()) coordVec[(y+size-1) * sideWidth + (x+size)] = true;
					}
				}
			}
		}

	}

	bool Field::HaveSight(MAP_LENGTH bx, MAP_LENGTH by, MAP_LENGTH tx, MAP_LENGTH ty)
	{
		std::vector<Coordinates> coordVec;
		std::vector<Coordinates>::iterator coordIt;

		if(bx < 0 || by < 0 || tx < 0 || ty < 0) return false;
		if(bx >= this->width || by >= this->height || tx >= this->width || ty >= this->height) return false;

		Coordinates::LineOfSight(coordVec, bx, by, tx, ty);
		for(coordIt = coordVec.begin(); coordIt != coordVec.end(); coordIt++)
		{
			if(bx == coordIt->GetX() && by == coordIt->GetY()) continue;
			if(tx == coordIt->GetX() && ty == coordIt->GetY()) continue;
			if(this->GetSquare(coordIt->GetX(), coordIt->GetY())->GetFloorTag() == "VANILLA_PERMANENT_WALL") return false;
		}
		return true;
	}

	bool Field::GetSightPass(MAP_LENGTH x, MAP_LENGTH y)
	{
		return SightPass[y * (this->width * 2 + 1) + x];
	}

	Coordinates* Field::Scatter(MAP_LENGTH y, MAP_LENGTH x, MAP_LENGTH distance)
	{
		// TODO
		return NULL;
	}

	bool Field::AddRiver(Floor *floor1_ptr, Floor *floor2_ptr)
	{
		// TODO
		return NULL;
	}

}