#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
 * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
 *
 * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
 * For more information, visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 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 3 of the License, or
 * (at your option) any later version.
 *
 * A full copy of the GNU General Public License can be found in licence.txt
 *****************************************************************************/
#pragma endregion

#include "../addresses.h"
#include "../game.h"
#include "../ride/ride.h"
#include "../ride/ride_data.h"
#include "../ride/track.h"
#include "../interface/viewport.h"
#include "map_animation.h"
#include "map.h"
#include "scenery.h"
#include "sprite.h"

typedef bool (*map_animation_invalidate_event_handler)(int x, int y, int baseZ);

static bool map_animation_invalidate(rct_map_animation *obj);

static const map_animation_invalidate_event_handler _animatedObjectEventHandlers[MAP_ANIMATION_TYPE_COUNT];

rct_map_animation *gAnimatedObjects = (rct_map_animation*)0x013886A0;

/**
 *
 *  rct2: 0x0068AF67
 *
 * @param type (dh)
 * @param x (ax)
 * @param y (cx)
 * @param z (dl)
 */
void map_animation_create(int type, int x, int y, int z)
{
	rct_map_animation *aobj = &gAnimatedObjects[0];
	int numAnimatedObjects = gNumMapAnimations;
	if (numAnimatedObjects >= 2000) {
		log_error("Exceeded the maximum number of animations");
		return;
	}
	for (int i = 0; i < numAnimatedObjects; i++, aobj++) {
		if (aobj->x != x)
			continue;
		if (aobj->y != y)
			continue;
		if (aobj->baseZ != z)
			continue;
		if (aobj->type != type)
			continue;
		// Animation already exists
		return;
	}

	// Create new animation
	gNumMapAnimations++;
	aobj->type = type;
	aobj->x = x;
	aobj->y = y;
	aobj->baseZ = z;
}

/**
 *
 *  rct2: 0x0068AFAD
 */
void map_animation_invalidate_all()
{
	rct_map_animation *aobj = &gAnimatedObjects[0];
	int numAnimatedObjects = gNumMapAnimations;
	while (numAnimatedObjects > 0) {
		if (map_animation_invalidate(aobj)) {
			// Remove animated object
			gNumMapAnimations--;
			numAnimatedObjects--;
			if (numAnimatedObjects > 0)
				memmove(aobj, aobj + 1, numAnimatedObjects * sizeof(rct_map_animation));
		} else {
			numAnimatedObjects--;
			aobj++;
		}
	}
}

/**
 * @returns true if the animation should be removed.
 */
static bool map_animation_invalidate(rct_map_animation *obj)
{
	assert(obj->type < MAP_ANIMATION_TYPE_COUNT);

	return _animatedObjectEventHandlers[obj->type](obj->x, obj->y, obj->baseZ);
}

/**
 *
 *  rct2: 0x00666670
 */
static bool map_animation_invalidate_ride_entrance(int x, int y, int baseZ)
{
	rct_map_element *mapElement;
	rct_ride *ride;
	const rct_ride_entrance_definition *entranceDefinition;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_ENTRANCE)
			continue;
		if (mapElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_ENTRANCE)
			continue;

		ride = get_ride(mapElement->properties.entrance.ride_index);
		entranceDefinition = &RideEntranceDefinitions[ride->entrance_style];

		int height = (mapElement->base_height * 8) + entranceDefinition->height + 8;
		map_invalidate_tile_zoom1(x, y, height, height + 16);
		return false;
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006A7BD4
 */
static bool map_animation_invalidate_queue_banner(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_PATH)
			continue;
		if (!(mapElement->flags & 1))
			continue;
		if (!(mapElement->properties.path.type & PATH_FLAG_QUEUE_BANNER))
			continue;

		int direction = ((mapElement->type >> 6) + get_current_rotation()) & 3;
		if (direction == MAP_ELEMENT_DIRECTION_NORTH || direction == MAP_ELEMENT_DIRECTION_EAST) {
			baseZ = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, baseZ + 16, baseZ + 30);
		}
		return false;
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006E32C9
 */
static bool map_animation_invalidate_small_scenery(int x, int y, int baseZ)
{
	rct_map_element *mapElement;
	rct_scenery_entry *sceneryEntry;
	rct_sprite *sprite;
	rct_peep *peep;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_SCENERY)
			continue;
		if (mapElement->flags & (1 << 4))
			continue;

		sceneryEntry = get_small_scenery_entry(mapElement->properties.scenery.type);
		if (sceneryEntry->small_scenery.flags & 0xD800) {
			map_invalidate_tile_zoom1(x, y, mapElement->base_height * 8, mapElement->clearance_height * 8);
			return false;
		}

		if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_IS_CLOCK) {
			// Peep, looking at scenery
			if (!(gCurrentTicks & 0x3FF) && game_is_not_paused()) {
				int direction = mapElement->type & 3;
				int x2 = x - TileDirectionDelta[direction].x;
				int y2 = y - TileDirectionDelta[direction].y;

				uint16 spriteIdx = RCT2_ADDRESS(0x00F1EF60, uint16)[((x2 & 0x1FE0) << 3) | (y2 >> 5)];
				for (; spriteIdx != 0xFFFF; spriteIdx = sprite->unknown.next_in_quadrant) {
					sprite = &g_sprite_list[spriteIdx];
					if (sprite->unknown.linked_list_type_offset != SPRITE_LIST_PEEP * 2)
						continue;

					peep = &sprite->peep;
					if (peep->state != PEEP_STATE_WALKING)
						continue;
					if (peep->z != mapElement->base_height * 8)
						continue;
					if (peep->action < PEEP_ACTION_NONE_1)
						continue;

					peep->action = PEEP_ACTION_CHECK_TIME;
					peep->action_frame = 0;
					peep->action_sprite_image_offset = 0;
					sub_693B58(peep);
					invalidate_sprite_1((rct_sprite*)peep);
					break;
				}
			}
			map_invalidate_tile_zoom1(x, y, mapElement->base_height * 8, mapElement->clearance_height * 8);
			return false;
		}
	} while (!map_element_is_last_for_tile(mapElement++));
	return true;
}

/**
 *
 *  rct2: 0x00666C63
 */
static bool map_animation_invalidate_park_entrance(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_ENTRANCE)
			continue;
		if (mapElement->properties.entrance.type != ENTRANCE_TYPE_PARK_ENTRANCE)
			continue;
		if (mapElement->properties.entrance.index & 0x0F)
			continue;

		baseZ = mapElement->base_height * 8;
		map_invalidate_tile_zoom1(x, y, baseZ + 32, baseZ + 64);
		return false;
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006CE29E
 */
static bool map_animation_invalidate_track_waterfall(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK)
			continue;

		if (mapElement->properties.track.type == TRACK_ELEM_WATERFALL) {
			int z = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, z + 14, z + 46);
			return false;
		}
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006CE2F3
 */
static bool map_animation_invalidate_track_rapids(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK)
			continue;

		if (mapElement->properties.track.type == TRACK_ELEM_RAPIDS) {
			int z = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, z + 14, z + 18);
			return false;
		}
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006CE39D
 */
static bool map_animation_invalidate_track_onridephoto(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK)
			continue;

		if (mapElement->properties.track.type == TRACK_ELEM_ON_RIDE_PHOTO) {
			map_invalidate_tile_zoom1(x, y, mapElement->base_height * 8, mapElement->clearance_height * 8);
			if (game_is_paused()) {
				return false;
			}
			if (mapElement->properties.track.sequence & 0xF0) {
				mapElement->properties.track.sequence -= 0x10;
				return false;
			} else {
				return true;
			}
		}
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006CE348
 */
static bool map_animation_invalidate_track_whirlpool(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK)
			continue;

		if (mapElement->properties.track.type == TRACK_ELEM_WHIRLPOOL) {
			int z = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, z + 14, z + 18);
			return false;
		}
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006CE3FA
 */
static bool map_animation_invalidate_track_spinningtunnel(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK)
			continue;

		if (mapElement->properties.track.type == TRACK_ELEM_SPINNING_TUNNEL) {
			int z = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, z + 14, z + 32);
			return false;
		}
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x0068DF8F
 */
static bool map_animation_invalidate_remove(int x, int y, int baseZ)
{
	return true;
}

/**
 *
 *  rct2: 0x006BA2BB
 */
static bool map_animation_invalidate_banner(int x, int y, int baseZ)
{
	rct_map_element *mapElement;

	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_BANNER)
			continue;

		baseZ = mapElement->base_height * 8;
		map_invalidate_tile_zoom1(x, y, baseZ, baseZ + 16);
		return false;
	} while (!map_element_is_last_for_tile(mapElement++));

	return true;
}

/**
 *
 *  rct2: 0x006B94EB
 */
static bool map_animation_invalidate_large_scenery(int x, int y, int baseZ)
{
	rct_map_element *mapElement;
	rct_scenery_entry *sceneryEntry;

	bool wasInvalidated = false;
	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_SCENERY_MULTIPLE)
			continue;

		sceneryEntry = get_large_scenery_entry(mapElement->properties.scenery.type & 0x3FF);
		if (sceneryEntry->large_scenery.flags & (1 << 3)) {
			int z = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, z, z + 16);
			wasInvalidated = true;
		}
	} while (!map_element_is_last_for_tile(mapElement++));

	return !wasInvalidated;
}

/**
 *
 *  rct2: 0x006E5B50
 */
static bool map_animation_invalidate_wall_door(int x, int y, int baseZ)
{
	rct_map_element *mapElement;
	rct_scenery_entry *sceneryEntry;

	if (gCurrentTicks & 1)
		return false;

	bool wasInvalidated = false;
	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_FENCE)
			continue;

		sceneryEntry = get_wall_entry(mapElement->properties.scenery.type);
		if (!(sceneryEntry->wall.flags & (1 << 4)))
			continue;

		uint8 di = 0;
		uint8 bl = mapElement->properties.fence.item[2];
		uint8 bh = bl & 0x78;
		if (bh != 0) {
			if (bh == 0x78) {
				bl &= 0x87;
			} else {
				di |= 2;
				if (bh != 40) {
					bh += 8;
					if (bh == 104 && !(sceneryEntry->wall.flags & (1 << 5)))
						bh = 120;

					di |= 1;
					bl &= 135;
					bl |= bh;
				}
			}
		}
		if (game_is_paused()) {
			return false;
		}
		mapElement->properties.fence.item[2] = bl;
		if (di & 1) {
			int z = mapElement->base_height * 8;
			map_invalidate_tile_zoom1(x, y, z, z + 32);
		}
		if (di & 2)
			wasInvalidated = true;
	} while (!map_element_is_last_for_tile(mapElement++));

	return !wasInvalidated;
}

/**
 *
 *  rct2: 0x006E5EE4
 */
static bool map_animation_invalidate_wall(int x, int y, int baseZ)
{
	rct_map_element *mapElement;
	rct_scenery_entry *sceneryEntry;

	bool wasInvalidated = false;
	mapElement = map_get_first_element_at(x >> 5, y >> 5);
	do {
		if (mapElement->base_height != baseZ)
			continue;
		if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_FENCE)
			continue;

		sceneryEntry = get_wall_entry(mapElement->properties.scenery.type);
		if (!(sceneryEntry->wall.flags2 & (1 << 4)) && sceneryEntry->wall.var_0D == 255)
			continue;

		int z = mapElement->base_height * 8;
		map_invalidate_tile_zoom1(x, y, z, z + 16);
		wasInvalidated = true;
	} while (!map_element_is_last_for_tile(mapElement++));

	return !wasInvalidated;
}

/**
 *
 *  rct2: 0x009819DC
 */
static const map_animation_invalidate_event_handler _animatedObjectEventHandlers[MAP_ANIMATION_TYPE_COUNT] = {
	map_animation_invalidate_ride_entrance,
	map_animation_invalidate_queue_banner,
	map_animation_invalidate_small_scenery,
	map_animation_invalidate_park_entrance,
	map_animation_invalidate_track_waterfall,
	map_animation_invalidate_track_rapids,
	map_animation_invalidate_track_onridephoto,
	map_animation_invalidate_track_whirlpool,
	map_animation_invalidate_track_spinningtunnel,
	map_animation_invalidate_remove,
	map_animation_invalidate_banner,
	map_animation_invalidate_large_scenery,
	map_animation_invalidate_wall_door,
	map_animation_invalidate_wall
};
