/*
	Copyright 2016, 2017 Michele "King_DuckZ" Santullo

	This file is part of MyCurry.

	MyCurry 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.

	MyCurry 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 MyCurry.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "worldgrid.hpp"
#include "worldsizenotifiable.hpp"
#include <cassert>
#include <utility>
#include <algorithm>

namespace curry {
	WorldGrid::WorldGrid (vec2us parTileSize) :
		m_tile_size(parTileSize),
		m_world_size(0),
		m_layer_count(0)
	{
	}

	const vec2us& WorldGrid::tile_size() const {
		return m_tile_size;
	}

	const vec2us& WorldGrid::world_size() const {
		return m_world_size;
	}

	const TileIndex* WorldGrid::tile (const vec2us& parIndex) const {
		const std::size_t index = parIndex.x() + parIndex.y() * m_world_size.x();
		assert(m_layer_count);
		assert(not m_layers.empty());
		assert(index < tile_count());
		assert(index * m_layer_count + m_layer_count <= m_layers.size());
		return m_layers.data() + index * m_layer_count;
	}

	void WorldGrid::add_layer (vec2us parWorldSize, const std::vector<TileIndex>& parLayer) {
		assert(parLayer.size() == tile_count());

		std::vector<TileIndex> new_vec;
		new_vec.reserve((m_layer_count + 1) * tile_count());
		auto curr_tiles = m_layers.cbegin();
		for (auto tile : parLayer) {
			std::copy_n(curr_tiles, m_layer_count, std::back_inserter(new_vec));
			std::advance(curr_tiles, m_layer_count);
			new_vec.push_back(tile);
		}
		++m_layer_count;
		set_world_size(parWorldSize);
		std::swap(m_layers, new_vec);
	}

	void WorldGrid::set_layers (vec2us parWorldSize, const std::vector<std::vector<TileIndex>>& parLayers) {
		if (parLayers.empty()) {
			unload_layers();
			return;
		}

		assert(not parLayers.empty());
		std::vector<TileIndex> new_vec;
		new_vec.reserve(parLayers.size() * parLayers.front().size());
		for (std::size_t z = 0; z < parLayers.front().size(); ++z) {
			for (std::size_t t = 0; t < parLayers.size(); ++t) {
				assert(parLayers[t].size() == parLayers.front().size());
				new_vec.push_back(parLayers[t][z]);
			}
		}

		m_layer_count = static_cast<uint16_t>(parLayers.size());
		std::swap(m_layers, new_vec);
		set_world_size(parWorldSize);
	}

	uint16_t WorldGrid::layer_count() const {
		return m_layer_count;
	}

	std::size_t WorldGrid::tile_count() const {
		assert((layer_count() and not m_layers.empty()) or (m_layers.size() % layer_count() == 0));
		return (layer_count() ? m_layers.size() / layer_count() : 0);
	}

	void WorldGrid::unload_layers() {
		m_layers.clear();
		m_layer_count = 0;
	}

	void WorldGrid::set_world_size (const vec2us& parSize) {
		if (parSize != m_world_size) {
			m_world_size = parSize;

			for (auto& observer : m_world_size_watchers) {
				observer->world_size_changed(m_world_size, world_size_pixel(*this));
			}
		}
	}

	auto WorldGrid::register_observer (WorldSizeNotifiable* parWatcher) -> WorldSizeWatcherTicket {
		assert(parWatcher);
		return m_world_size_watchers.Add(parWatcher);
	}

	void WorldGrid::unregister_observer (WorldSizeWatcherTicket parTicket) {
		m_world_size_watchers.Remove(parTicket);
	}

	void WorldGrid::set_tile_properties (const std::vector<TileProperty>& parProperties) {
		m_tile_properties.resize(parProperties.size());
		std::copy(parProperties.begin(), parProperties.end(), m_tile_properties.begin());
	}

	const TileProperty& WorldGrid::tile_property (const TileIndex* parIndex) {
		assert(parIndex);
		assert(static_cast<size_t>(*parIndex) < m_tile_properties.size());
		return m_tile_properties[static_cast<size_t>(*parIndex)];
	}

	vec2i world_size_pixel (const WorldGrid& parWorld) {
		return parWorld.tile_size() * parWorld.world_size();
	}

	vec2us pixel_to_world_tile (const WorldGrid& parWorld, const vec2f& parPos) {
		assert(position_is_on_map(parWorld, parPos));
		return vector_cast<vec2us>(
			parPos / vector_cast<vec2f>(parWorld.tile_size())
		);
	}

	bool position_is_on_map (const WorldGrid& parWorld, const vec2i& parPos) {
		assert(parWorld.tile_size() > vec2us(0));

		return parPos >= vec2i(0) and
			parPos / vector_cast<vec2i>(parWorld.tile_size()) < parWorld.world_size();
	}
} //namespace curry
