/* Copyright 2015, Michele Santullo
 * This file is part of DoorKeeper.
 *
 * DoorKeeper 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.
 *
 * DoorKeeper 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 DoorKeeper.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace dkh {
	template <uint32_t D, typename C>
	dk::Tyler<D> map_load (C& parFileOpener, const std::string& parOpen, const PushLayerMapType<D>& parPusher) {
		dk::BaseMapSource<D>* reader = parFileOpener(parOpen);
		dk::Tyler<D> tyler(reader->tileSize());
		for (int z = 0; z < reader->layersCount(); ++z) {
			implem::call_push_layer<D>(tyler, parPusher, reader, z);
		}
		std::vector<std::string> submaps;
		reader->chainedMaps(submaps);
		for (const auto& name : submaps) {
			map_load<D, C>(tyler, parFileOpener, name, parPusher);
		}
		return std::move(tyler);
	}

	template <uint32_t D, typename C>
	dk::Tyler<D>& map_load (dk::Tyler<D>& parTyler, C& parFileOpener, const std::string& parOpen, const PushLayerMapType<D>& parPusher) {
		std::stack<std::string, std::vector<std::string>> name_stack;
		std::vector<std::string> submaps;
		name_stack.push(parOpen);

		do {
			dk::BaseMapSource<D>* reader = parFileOpener(name_stack.top());
			name_stack.pop();

			submaps.clear();
			reader->chainedMaps(submaps);
			for (auto&& curr_name : submaps) {
				name_stack.emplace(std::move(curr_name));
			}

			for (int z = 0; z < reader->layersCount(); ++z) {
				implem::call_push_layer<D>(parTyler, parPusher, reader, z);
			}
		} while (not name_stack.empty());
		return parTyler;
	}

	template <uint32_t D, typename C>
	dk::BaseMapSource<D>* MapLoaderPool<D, C>::operator() (const std::string& parName) {
		auto it_found = pool.find(parName);
		if (pool.end() != it_found) {
			return it_found->second.get();
		}
		else {
			std::pair<typename PoolMapType::iterator, bool> new_item = pool.insert(std::make_pair(parName, BaseMapSourceUPtr(nullptr)));
			DK_ASSERT(new_item.second);
			new_item.first->second.reset(opener(parName));
			return new_item.first->second.get();
		}
	}

	template <typename M>
	dk::Tyler<M::dimensions> call_map_load (M& parFileOpener, const std::string& parOpen, const PushLayerMapType<M::dimensions>& parPusher) {
		return map_load<M::dimensions, M>(parFileOpener, parOpen, parPusher);
	}

	template <typename M>
	dk::Tyler<M::dimensions> call_map_load (dk::Tyler<M::dimensions>& parTyler, M& parFileOpener, const std::string& parOpen, const PushLayerMapType<M::dimensions>& parPusher) {
		return map_load<M::dimensions, M>(parTyler, parFileOpener, parOpen, parPusher);
	}

	namespace implem {
		template <uint32_t D>
		void call_push_layer (dk::Tyler<D>& parTyler, const PushLayerMapType<D>& parPusher, dk::BaseMapSource<D>* parReader, int parLayerIndex) {
			auto it_found = parPusher.find(parReader->layerTypeHash(parLayerIndex));
			if (parPusher.end() == it_found)
				throw UnknownLayerTemplateException();

			(parTyler.*(it_found->second))(parReader, parLayerIndex);
		}
	} //namespace implem
} //namespace dkh
