/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD 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 OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
 */

/** @file town_map.h Map accessors for towns. */

#ifndef TOWN_MAP_H
#define TOWN_MAP_H

#include "road_map.h"
#include "house.h"
#include "timer/timer_game_calendar.h"

/**
 * Get the index of which town this house/street is attached to.
 * @param t the tile
 * @pre IsTileType(t, TileType::House) or IsTileType(t, TileType::Road) but not a road depot
 * @return TownID
 */
inline TownID GetTownIndex(Tile t)
{
	assert(IsTileType(t, TileType::House) || (IsTileType(t, TileType::Road) && !IsRoadDepot(t)));
	return static_cast<TownID>(t.m2());
}

/**
 * Set the town index for a road or house tile.
 * @param t the tile
 * @param index the index of the town
 * @pre IsTileType(t, TileType::House) or IsTileType(t, TileType::Road) but not a road depot
 */
inline void SetTownIndex(Tile t, TownID index)
{
	assert(IsTileType(t, TileType::House) || (IsTileType(t, TileType::Road) && !IsRoadDepot(t)));
	t.m2() = index.base();
}

/**
 * Get the type of this house, which is an index into the house spec array
 * without doing any NewGRF related translations.
 * @param t the tile
 * @pre IsTileType(t, TileType::House)
 * @return house type
 */
inline HouseID GetCleanHouseType(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return GB(t.m8(), 0, 12);
}

/**
 * Get the type of this house, which is an index into the house spec array
 * @param t the tile
 * @pre IsTileType(t, TileType::House)
 * @return house type
 */
inline HouseID GetHouseType(Tile t)
{
	return GetTranslatedHouseID(GetCleanHouseType(t));
}

/**
 * Set the house type.
 * @param t the tile
 * @param house_id the new house type
 * @pre IsTileType(t, TileType::House)
 */
inline void SetHouseType(Tile t, HouseID house_id)
{
	assert(IsTileType(t, TileType::House));
	SB(t.m8(), 0, 12, house_id);
}

/**
 * Check if the house is protected from removal by towns.
 * @param t The tile.
 * @return If the house is protected from the town upgrading it.
 */
inline bool IsHouseProtected(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return HasBit(t.m3(), 5);
}

/**
 * Set a house as protected from removal by towns.
 * @param t The tile.
 * @param house_protected Whether the house is protected from the town upgrading it.
 */
inline void SetHouseProtected(Tile t, bool house_protected)
{
	assert(IsTileType(t, TileType::House));
	SB(t.m3(), 5, 1, house_protected ? 1 : 0);
}

/**
 * Check if the lift of this animated house has a destination
 * @param t the tile
 * @return has destination
 */
inline bool LiftHasDestination(Tile t)
{
	return HasBit(t.m7(), 0);
}

/**
 * Set the new destination of the lift for this animated house, and activate
 * the LiftHasDestination bit.
 * @param t the tile
 * @param dest new destination
 */
inline void SetLiftDestination(Tile t, uint8_t dest)
{
	SetBit(t.m7(), 0);
	SB(t.m7(), 1, 3, dest);
}

/**
 * Get the current destination for this lift
 * @param t the tile
 * @return destination
 */
inline uint8_t GetLiftDestination(Tile t)
{
	return GB(t.m7(), 1, 3);
}

/**
 * Stop the lift of this animated house from moving.
 * Clears the first 4 bits of m7 at once, clearing the LiftHasDestination bit
 * and the destination.
 * @param t the tile
 */
inline void HaltLift(Tile t)
{
	SB(t.m7(), 0, 4, 0);
}

/**
 * Get the position of the lift on this animated house
 * @param t the tile
 * @return position, from 0 to 36
 */
inline uint8_t GetLiftPosition(Tile t)
{
	return GB(t.m6(), 2, 6);
}

/**
 * Set the position of the lift on this animated house
 * @param t the tile
 * @param pos position, from 0 to 36
 */
inline void SetLiftPosition(Tile t, uint8_t pos)
{
	SB(t.m6(), 2, 6, pos);
}

/**
 * Get the completion of this house
 * @param t the tile
 * @return true if it is, false if it is not
 */
inline bool IsHouseCompleted(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return HasBit(t.m3(), 7);
}

/**
 * Mark this house as been completed
 * @param t the tile
 * @param status
 */
inline void SetHouseCompleted(Tile t, bool status)
{
	assert(IsTileType(t, TileType::House));
	SB(t.m3(), 7, 1, !!status);
}

/**
 * House Construction Scheme.
 *  Construction counter, for buildings under construction. Incremented on every
 *  periodic tile processing.
 *  On wraparound, the stage of building in is increased.
 *  GetHouseBuildingStage is taking care of the real stages,
 *  (as the sprite for the next phase of house building)
 *  (Get|Inc)HouseConstructionTick is simply a tick counter between the
 *  different stages
 */

/**
 * Gets the building stage of a house
 * Since the stage is used for determining what sprite to use,
 * if the house is complete (and that stage no longer is available),
 * fool the system by returning the TOWN_HOUSE_COMPLETE (3),
 * thus showing a beautiful complete house.
 * @param t the tile of the house to get the building stage of
 * @pre IsTileType(t, TileType::House)
 * @return the building stage of the house
 */
inline uint8_t GetHouseBuildingStage(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return IsHouseCompleted(t) ? (uint8_t)TOWN_HOUSE_COMPLETED : GB(t.m5(), 3, 2);
}

/**
 * Gets the construction stage of a house
 * @param t the tile of the house to get the construction stage of
 * @pre IsTileType(t, TileType::House)
 * @return the construction stage of the house
 */
inline uint8_t GetHouseConstructionTick(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return IsHouseCompleted(t) ? 0 : GB(t.m5(), 0, 3);
}

/**
 * Sets the increment stage of a house
 * It is working with the whole counter + stage 5 bits, making it
 * easier to work:  the wraparound is automatic.
 * @param t the tile of the house to increment the construction stage of
 * @pre IsTileType(t, TileType::House)
 */
inline void IncHouseConstructionTick(Tile t)
{
	assert(IsTileType(t, TileType::House));
	AB(t.m5(), 0, 5, 1);

	if (GB(t.m5(), 3, 2) == TOWN_HOUSE_COMPLETED) {
		/* House is now completed.
		 * Store the year of construction as well, for newgrf house purpose */
		SetHouseCompleted(t, true);
	}
}

/**
 * Sets the age of the house to zero.
 * Needs to be called after the house is completed. During construction stages the map space is used otherwise.
 * @param t the tile of this house
 * @pre IsTileType(t, TileType::House) && IsHouseCompleted(t)
 */
inline void ResetHouseAge(Tile t)
{
	assert(IsTileType(t, TileType::House) && IsHouseCompleted(t));
	t.m5() = 0;
}

/**
 * Increments the age of the house.
 * @param t the tile of this house
 * @pre IsTileType(t, TileType::House)
 */
inline void IncrementHouseAge(Tile t)
{
	assert(IsTileType(t, TileType::House));
	if (IsHouseCompleted(t) && t.m5() < 0xFF) t.m5()++;
}

/**
 * Get the age of the house
 * @param t the tile of this house
 * @pre IsTileType(t, TileType::House)
 * @return year
 */
inline TimerGameCalendar::Year GetHouseAge(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return TimerGameCalendar::Year{IsHouseCompleted(t) ? t.m5() : 0};
}

/**
 * Set the random bits for this house.
 * This is required for newgrf house
 * @param t      the tile of this house
 * @param random the new random bits
 * @pre IsTileType(t, TileType::House)
 */
inline void SetHouseRandomBits(Tile t, uint8_t random)
{
	assert(IsTileType(t, TileType::House));
	t.m1() = random;
}

/**
 * Get the random bits for this house.
 * This is required for newgrf house
 * @param t the tile of this house
 * @pre IsTileType(t, TileType::House)
 * @return random bits
 */
inline uint8_t GetHouseRandomBits(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return t.m1();
}

/**
 * Set the activated triggers bits for this house.
 * This is required for newgrf house
 * @param t        the tile of this house
 * @param triggers the activated triggers
 * @pre IsTileType(t, TileType::House)
 */
inline void SetHouseRandomTriggers(Tile t, HouseRandomTriggers triggers)
{
	assert(IsTileType(t, TileType::House));
	SB(t.m3(), 0, 5, triggers.base());
}

/**
 * Get the already activated triggers bits for this house.
 * This is required for newgrf house
 * @param t the tile of this house
 * @pre IsTileType(t, TileType::House)
 * @return triggers
 */
inline HouseRandomTriggers GetHouseRandomTriggers(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return static_cast<HouseRandomTriggers>(GB(t.m3(), 0, 5));
}

/**
 * Get the amount of time remaining before the tile loop processes this tile.
 * @param t the house tile
 * @pre IsTileType(t, TileType::House)
 * @return time remaining
 */
inline uint8_t GetHouseProcessingTime(Tile t)
{
	assert(IsTileType(t, TileType::House));
	return GB(t.m6(), 2, 6);
}

/**
 * Set the amount of time remaining before the tile loop processes this tile.
 * @param t the house tile
 * @param time the time to be set
 * @pre IsTileType(t, TileType::House)
 */
inline void SetHouseProcessingTime(Tile t, uint8_t time)
{
	assert(IsTileType(t, TileType::House));
	SB(t.m6(), 2, 6, time);
}

/**
 * Decrease the amount of time remaining before the tile loop processes this tile.
 * @param t the house tile
 * @pre IsTileType(t, TileType::House)
 */
inline void DecHouseProcessingTime(Tile t)
{
	assert(IsTileType(t, TileType::House));
	t.m6() -= 1 << 2;
}

/**
 * Make the tile a house.
 * @param t tile index
 * @param tid Town index
 * @param counter of construction step
 * @param stage of construction (used for drawing)
 * @param type of house.  Index into house specs array
 * @param random_bits required for newgrf houses
 * @param house_protected Whether the house is protected from the town upgrading it.
 * @pre IsTileType(t, TileType::Clear)
 */
inline void MakeHouseTile(Tile t, TownID tid, uint8_t counter, uint8_t stage, HouseID type, uint8_t random_bits, bool house_protected)
{
	assert(IsTileType(t, TileType::Clear));

	SetTileType(t, TileType::House);
	t.m1() = random_bits;
	t.m2() = tid.base();
	t.m3() = 0;
	SetHouseType(t, type);
	SetHouseCompleted(t, stage == TOWN_HOUSE_COMPLETED);
	t.m5() = IsHouseCompleted(t) ? 0 : (stage << 3 | counter);
	SetHouseProtected(t, house_protected);
	SetAnimationFrame(t, 0);
	SetHouseProcessingTime(t, HouseSpec::Get(type)->processing_time);
	SB(t.m8(), 12, 4, 0);
}

#endif /* TOWN_MAP_H */
