//-----------------------------------------------------------------------------
// TmxMap.cpp
//
// Copyright (c) 2010-2012, Tamir Atias
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Author: Tamir Atias
//-----------------------------------------------------------------------------
#include <cstdio>
#include <tinyxml.h>

#include "TmxMap.h"
#include "TmxTileset.h"
#include "TmxLayer.h"
#include "TmxObjectGroup.h"

using std::vector;
using std::string;

namespace Tmx 
{
	Map::Map() 
		: file_name()
		, file_path()
		, version(0.0)
		, orientation(TMX_MO_ORTHOGONAL)
		, width(0)
		, height(0)
		, tile_width(0)
		, tile_height(0)
		, layers()
		, object_groups()
		, tilesets() 
		, has_error(false)
		, error_code(0)
		, error_text()
	{}

	Map::~Map() 
	{
		// Iterate through all of the object groups and delete each of them.
		vector< ObjectGroup* >::iterator ogIter;
		for (ogIter = object_groups.begin(); ogIter != object_groups.end(); ++ogIter) 
		{
			ObjectGroup *objectGroup = (*ogIter);
			
			if (objectGroup)
			{
				delete objectGroup;
				objectGroup = NULL;
			}
		}

		// Iterate through all of the layers and delete each of them.
		vector< Layer* >::iterator lIter;
		for (lIter = layers.begin(); lIter != layers.end(); ++lIter) 
		{
			Layer *layer = (*lIter);

			if (layer) 
			{
				delete layer;
				layer = NULL;
			}
		}

		// Iterate through all of the tilesets and delete each of them.
		vector< Tileset* >::iterator tsIter;
		for (tsIter = tilesets.begin(); tsIter != tilesets.end(); ++tsIter) 
		{
			Tileset *tileset = (*tsIter);
			
			if (tileset) 
			{
				delete tileset;
				tileset = NULL;
			}
		}
	}

	void Map::ParseFile(const string &fileName) 
	{
		file_name = fileName;

		int lastSlash = fileName.find_last_of("/");

		// Get the directory of the file using substring.
		if (lastSlash > 0) 
		{
			file_path = fileName.substr(0, lastSlash + 1);
		} 
		else 
		{
			file_path = "";
		}

		char* fileText;
		int fileSize;

		// Open the file for reading.
		FILE *file = fopen(fileName.c_str(), "rb");

		// Check if the file could not be opened.
		if (!file) 
		{
			has_error = true;
			error_code = TMX_COULDNT_OPEN;
			error_text = "Could not open the file.";
			return;
		}
		
		// Find out the file size.
		fseek(file, 0, SEEK_END);
		fileSize = ftell(file);
		fseek(file, 0, SEEK_SET);

		// Allocate memory for the file and read it into the memory.
		fileText = new char[fileSize];
		fread(fileText, 1, fileSize, file);

		fclose(file);

		// Copy the contents into a C++ string and delete it from memory.
		std::string text(fileText, fileText+fileSize);
		delete [] fileText;

		ParseText(text);		
	}

	void Map::ParseText(const string &text) 
	{
		// Create a tiny xml document and use it to parse the text.
		TiXmlDocument doc;
		doc.Parse(text.c_str());
	
		// Check for parsing errors.
		if (doc.Error()) 
		{
			has_error = true;
			error_code = TMX_PARSING_ERROR;
			error_text = doc.ErrorDesc();
			return;
		}

		TiXmlNode *mapNode = doc.FirstChild("map");
		TiXmlElement* mapElem = mapNode->ToElement();

		// Read the map attributes.
		mapElem->Attribute("version", &version);
		mapElem->Attribute("width", &width);
		mapElem->Attribute("height", &height);
		mapElem->Attribute("tilewidth", &tile_width);
		mapElem->Attribute("tileheight", &tile_height);

		// Read the orientation
		std::string orientationStr = mapElem->Attribute("orientation");

		if (!orientationStr.compare("orthogonal")) 
		{
			orientation = TMX_MO_ORTHOGONAL;
		} 
		else if (!orientationStr.compare("isometric")) 
		{
			orientation = TMX_MO_ISOMETRIC;
		}

		// Read the map properties.
		const TiXmlNode *propertiesNode = mapElem->FirstChild("properties");
		if (propertiesNode) 
		{
			properties.Parse(propertiesNode);
		}

		// Iterate through all of the tileset elements.
		const TiXmlNode *tilesetNode = mapNode->FirstChild("tileset");
		while (tilesetNode) 
		{
			// Allocate a new tileset and parse it.
			Tileset *tileset = new Tileset();
			tileset->Parse(tilesetNode->ToElement());

			// Add the tileset to the list.
			tilesets.push_back(tileset);

			tilesetNode = mapNode->IterateChildren("tileset", tilesetNode);
		}

		// Iterate through all of the layer elements.
		TiXmlNode *layerNode = mapNode->FirstChild("layer");
		while (layerNode) 
		{
			// Allocate a new layer and parse it.
			Layer *layer = new Layer();
			layer->Parse(layerNode);

			// Add the layer to the list.
			layers.push_back(layer);

			layerNode = mapNode->IterateChildren("layer", layerNode);
		}

		// Iterate through all of the objectgroup elements.
		TiXmlNode *objectGroupNode = mapNode->FirstChild("objectgroup");
		while (objectGroupNode) 
		{
			// Allocate a new object group and parse it.
			ObjectGroup *objectGroup = new ObjectGroup();
			objectGroup->Parse(objectGroupNode);
		
			// Add the object group to the list.
			object_groups.push_back(objectGroup);

			objectGroupNode = mapNode->IterateChildren("objectgroup", objectGroupNode);
		}
	}

	const Tileset *Map::FindTileset(int gid) const 
	{
		for (int i = tilesets.size() - 1; i > -1; --i) 
		{
			// If the gid beyond the tileset gid return it.
			if (gid >= tilesets[i]->GetFirstGid()) 
			{
				return tilesets[i];
			}
		}
		
		return NULL;
	}
};