// ==============================================================
//	This file is part of Glest (www.glest.org)
//
//	Copyright (C) 2001-2008 Martiño Figueroa
//
//	You can redistribute this code and/or modify it under
//	the terms of the GNU General Public License as published
//	by the Free Software Foundation; either version 2 of the
//	License, or (at your option) any later version
// ==============================================================


#include "program.h"
#include "util.h"
#include <iostream>
#include "platform_util.h"

using namespace Shared::Util;

namespace MapEditor {

////////////////////////////
// class UndoPoint
////////////////////////////
int UndoPoint::w = 0;
int UndoPoint::h = 0;

UndoPoint::UndoPoint()
		: change(ctNone)
		, surface(0)
		, object(0)
		, resource(0)
		, height(0) {
	w = Program::map->getW();
	h = Program::map->getH();
}
/*
UndoPoint::UndoPoint(ChangeType change) {
	w = Program::map->getW();
	h = Program::map->getH();

	init(change);
}*/

void UndoPoint::init(ChangeType change) {
	this->change = change;
	switch (change) {
		// Back up everything
		case ctAll:
		// Back up heights
		case ctHeight:
		case ctGradient:
			// Build an array of heights from the map
			//std::cout << "Building an array of heights to use for our UndoPoint" << std::endl;
			height = new float[w * h];
			for (int i = 0; i < w; ++i) {
				for (int j = 0; j < h; ++j) {
					 height[j * w + i] = Program::map->getHeight(i, j);
				}
			}
			//std::cout << "Built the array" << std::endl;
			if (change != ctAll) break;
		// Back up surfaces
		case ctSurface:
			surface = new int[w * h];
			for (int i = 0; i < w; ++i) {
				for (int j = 0; j < h; ++j) {
					 surface[j * w + i] = Program::map->getSurface(i, j);
				}
			}
			if (change != ctAll) break;
		// Back up both objects and resources if either changes
		case ctObject:
		case ctResource:
			object = new int[w * h];
			for (int i = 0; i < w; ++i) {
				for (int j = 0; j < h; ++j) {
					 object[j * w + i] = Program::map->getObject(i, j);
				}
			}
			resource = new int[w * h];
			for (int i = 0; i < w; ++i) {
				for (int j = 0; j < h; ++j) {
					 resource[j * w + i] = Program::map->getResource(i, j);
				}
			}
			break;
	}
}

UndoPoint::~UndoPoint() {
	delete [] height;
	delete [] resource;
	delete [] object;
	delete [] surface;
}

void UndoPoint::revert() {
	//std::cout << "attempting to revert last changes to " << undoID << std::endl;
	switch (change) {
		// Revert Everything
		case ctAll:
		// Revert Heights
		case ctHeight:
		case ctGradient:
			// Restore the array of heights to the map
			//std::cout << "attempting to restore the height array" << std::endl;
			for (int i = 0; i < w; i++) {
				for (int j = 0; j < h; j++) {
					 Program::map->setHeight(i, j, height[j * w + i]);
				}
			}
			if (change != ctAll) break;
		// Revert surfaces
		case ctSurface:
			//std::cout << "attempting to restore the surface array" << std::endl;
			for (int i = 0; i < w; i++) {
				for (int j = 0; j < h; j++) {
					 Program::map->setSurface(i, j, static_cast<MapSurfaceType>(surface[j * w + i]));
				}
			}
			if (change != ctAll) break;
		// Revert objects and resources
		case ctObject:
		case ctResource:
			for (int i = 0; i < w; i++) {
				for (int j = 0; j < h; j++) {
					 Program::map->setObject(i, j, object[j * w + i]);
				}
			}
			for (int i = 0; i < w; i++) {
				for (int j = 0; j < h; j++) {
					 Program::map->setResource(i, j, resource[j * w + i]);
				}
			}
			break;
	}
	//std::cout << "reverted changes (we hope)" << std::endl;
}

// ===============================================
//	class Program
// ===============================================

MapPreview *Program::map = NULL;

Program::Program(int w, int h, string playerName) {
	cellSize = 5;
	grid=false;
	heightmap=false;
	hideWater=false;
	ofsetX = 0;
	ofsetY = 0;

	map = new MapPreview();
	resetFactions(8);
	renderer.initMapSurface(w, h);
	map->setAuthor(playerName);
}

void Program::init() {
	undoStack = ChangeStack();
	redoStack = ChangeStack();
	cellSize = 5;
	grid=false;
	heightmap=false;
	hideWater=false;
	ofsetX = 0;
	ofsetY = 0;
	map = NULL;
}

Program::~Program() {
	delete map;
	map = NULL;
}

int Program::getObject(int x, int y) {
	int i=(x - ofsetX) / cellSize;
	int j= (y + ofsetY) / cellSize;
	if (map && map->inside(i, j)) {
		return map->getObject(i,j);
	}
	else {
		return 0;
	}
}

int Program::getCellX(int x) {
	return (x - ofsetX) / cellSize;
}

int Program::getCellY(int y) {
	return (y + ofsetY) / cellSize;
}

int Program::getResource(int x, int y) {
	int i=(x - ofsetX) / cellSize;
	int j= (y + ofsetY) / cellSize;
	if (map && map->inside(i, j)) {
		return map->getResource(i,j);
	}
	else {
		return 0;
	}
}

void Program::glestChangeMapHeight(int x, int y, int Height, int radius) {
	if(map) map->glestChangeHeight((x - ofsetX) / cellSize, (y + ofsetY) / cellSize, Height, radius);
}

void Program::pirateChangeMapHeight(int x, int y, int Height, int radius) {
	if(map) map->pirateChangeHeight((x - ofsetX) / cellSize, (y + ofsetY) / cellSize, Height, radius);
}

void Program::changeMapSurface(int x, int y, int surface, int radius) {
	if(map) map->changeSurface((x - ofsetX) / cellSize, (y + ofsetY) / cellSize, static_cast<MapSurfaceType>(surface), radius);
}

void Program::changeMapObject(int x, int y, int object, int radius) {
	if(map) map->changeObject((x - ofsetX) / cellSize, (y + ofsetY) / cellSize, object, radius);
}

void Program::changeMapResource(int x, int y, int resource, int radius) {
	if(map) map->changeResource((x - ofsetX) / cellSize, (y + ofsetY) / cellSize, resource, radius);
}

void Program::changeStartLocation(int x, int y, int player) {
	if(map) map->changeStartLocation((x - ofsetX) / cellSize, (y + ofsetY) / cellSize, player);
}

void Program::setUndoPoint(ChangeType change) {
	if (change == ctLocation) return;

	undoStack.push(UndoPoint());
	undoStack.top().init(change);

	redoStack.clear();
}

bool Program::undo() {
	if (undoStack.empty()) {
		return false;
	}
	// push current state onto redo stack
	redoStack.push(UndoPoint());
	redoStack.top().init(undoStack.top().getChange());

	undoStack.top().revert();
	undoStack.pop();
	return true;
}

bool Program::redo() {
	if (redoStack.empty()) {
		return false;
	}
	// push current state onto undo stack
	undoStack.push(UndoPoint());
	undoStack.top().init(redoStack.top().getChange());

	redoStack.top().revert();
	redoStack.pop();
	return true;
}

void Program::renderMap(int w, int h) {
	//printf("Rendering map\n");
	if(map) renderer.renderMap(map, ofsetX, ofsetY, w, h, cellSize, grid,heightmap,hideWater);
}

void Program::setRefAlt(int x, int y) {
	if(map) map->setRefAlt((x - ofsetX) / cellSize, (y + ofsetY) / cellSize);
}

void Program::flipX() {
	if(map) map->flipX();
}

void Program::flipY() {
	if(map) map->flipY();
}

void Program::mirrorX() { // copy left to right
	if(map) {
		int w=map->getW();
		int h=map->getH();
		for (int i = 0; i < w/2; i++) {
			for (int j = 0; j < h; j++) {
				map->copyXY(w-i-1,j  ,  i,j);
			}
		}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationX(i) >= w/2) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if((map->getStartLocationX(i) < w/2) && (map->getStartLocationX(i)!=0 || map->getStartLocationY(i)!=0)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii)==0) { // first free one
						map->changeStartLocation(w-map->getStartLocationX(i)-1, map->getStartLocationY(i), ii);
						break;
					}
		}
	}
}

void Program::mirrorY() { // copy top to bottom
	if(map) {
		int w=map->getW();
		int h=map->getH();
		for (int i = 0; i < w; i++) {
			for (int j = 0; j < h/2; j++) {
				map->copyXY(i,h-j-1  ,  i,j);
			}
		}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationY(i) >= h/2) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if((map->getStartLocationY(i) < h/2) && (map->getStartLocationX(i)!=0 || map->getStartLocationY(i)!=0)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii)==0) { // first free one
						map->changeStartLocation(map->getStartLocationX(i), h-map->getStartLocationY(i)-1, ii);
						break;
					}
		}
	}
}

void Program::mirrorXY() { // copy leftbottom to topright, can handle non-sqaure maps
	if(map) {
		int w=map->getW();
		int h=map->getH();
		if (h==w) {
			for (int i = 0; i < w-1; i++) {
				for (int j = i+1; j < h; j++) {
					map->copyXY(j,i  ,  i,j);
				}
			}
		}
		// Non-sqaure maps:
		else if (h < w) { // copy horizontal strips
			int s=w/h; // 2 if twice as wide as heigh
			for (int i = 0; i < w/s-1; i++) {
				for (int j = i+1; j < h; j++) {
					for (int p = 0; p < s; p++) map->copyXY(j*s+p,i  ,  i*s+p,j);
				}
			}
		}
		else { // copy vertical strips
			int s=h/w; // 2 if twice as heigh as wide
			for (int i = 0; i < w-1; i++) {
				for (int j = i+1; j < h/s; j++) {
					for (int p = 0; p < s; p++) map->copyXY(j,i*s+p  ,  i,j*s+p);
				}
			}
		}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationX(i) > map->getStartLocationY(i)) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if(map->getStartLocationX(i) < map->getStartLocationY(i)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii) == 0) { // first free one
						map->changeStartLocation(map->getStartLocationY(i)*w/h, map->getStartLocationX(i)*h/w, ii);
						break;
					}
		}
	}
}

void Program::rotatecopyX() {
	if(map) {
		int w=map->getW();
		int h=map->getH();
		for (int i = 0; i < w/2; i++) {
			for (int j = 0; j < h; j++) {
				map->copyXY(w-i-1,h-j-1  ,  i,j);
			}
		}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationX(i) >= w/2) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if((map->getStartLocationX(i) < w/2) && (map->getStartLocationX(i)!=0 || map->getStartLocationY(i)!=0)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii)==0) { // first free one
						map->changeStartLocation(w-map->getStartLocationX(i)-1, h-map->getStartLocationY(i)-1, ii);
						break;
					}
		}
	}
}

void Program::rotatecopyY() {
	if(map) {
		int w=map->getW();
		int h=map->getH();
		for (int i = 0; i < w; i++) {
			for (int j = 0; j < h/2; j++) {
				map->copyXY(w-i-1,h-j-1  ,  i,j);
			}
		}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationY(i) >= h/2) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if((map->getStartLocationY(i) < h/2) && (map->getStartLocationX(i)!=0 || map->getStartLocationY(i)!=0)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii)==0) { // first free one
						map->changeStartLocation(w-map->getStartLocationX(i)-1, h-map->getStartLocationY(i)-1, ii);
						break;
					}
		}
	}
}

void Program::rotatecopyXY() {
	if(map) {
		int w=map->getW();
		int h=map->getH();
		int sw=w/h; if(sw<1) sw=1; // x-squares per y
		int sh=h/w; if(sh<1) sh=1; // y-squares per x
		if (sh==1)
			for (int j = 0; j < h-1; j++) { // row by row!
				for (int i = j*sw; i < w; i++) {
					map->copyXY(i,j  ,  w-i-1,h-j-1);
				}
			}
		else
			for (int i = 0; i <w; i++) { // column for colum!
				for (int j = h-1-i*sh; j >= 0; j--) {
					map->copyXY(w-i-1,j  ,  i,h-j-1);
				}
			}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationX(i) > map->getStartLocationY(i)) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if(map->getStartLocationX(i) < map->getStartLocationY(i)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii) == 0) { // first free one
						map->changeStartLocation(w-map->getStartLocationX(i)-1, h-map->getStartLocationY(i)-1, ii);
						break;
					}
		}
	}
}

void Program::rotatecopyCorner() { // rotate top left 1/4 to top right 1/4
	if(map) {
		int w=map->getW();
		int h=map->getH();
		if (h==w) {
			for (int i = 0; i < w/2; i++) {
				for (int j = 0; j < h/2; j++) {
					map->copyXY(w-j-1,i  ,  i,j);
				}
			}
		}
		// Non-sqaure maps:
		else if (h < w) { // copy horizontal strips
			int s=w/h; // 2 if twice as wide as heigh
			for (int i = 0; i < w/s/2; i++) {
				for (int j = 0; j < h/2; j++) {
					for (int p = 0; p < s; p++) map->copyXY(w-j*s-1-p,i  ,  i*s+p,j);
				}
			}
		}
		else { // copy vertical strips
			int s=h/w; // 2 if twice as heigh as wide
			for (int i = 0; i < w/2; i++) {
				for (int j = 0; j < h/s/2; j++) {
					for (int p = 0; p < s; p++) map->copyXY(w-j-1,i*s+p  ,  i,j*s+p);
				}
			}
		}

		// move players
		for (int i = 0; i < map->getMaxFactions(); ++i)     // first remove players from target corner
			if (map->getStartLocationX(i) >= w/2 && map->getStartLocationY(i) < h/2) map->changeStartLocation(0,0,i);
		for (int i = 0; i < map->getMaxFactions(); ++i) {
			if((map->getStartLocationX(i) < w/2 && map->getStartLocationY(i) < h/2) && (map->getStartLocationX(i)!=0 || map->getStartLocationY(i)!=0)) // any startpositions to copy?
				for (int ii = 0; ii < map->getMaxFactions(); ++ii)
					if(map->getStartLocationX(ii)==0 && map->getStartLocationY(ii)==0) { // first free one
						map->changeStartLocation(w-map->getStartLocationY(i)*w/h-1, map->getStartLocationX(i)*h/w, ii);
						break;
					}
		}
	}
}


void Program::shiftLeft() {
	if(map) {
		int w=map->getW()-1;
		int h=map->getH();
		for (int i=0; i<w; i++)
			for (int j=0; j<h; j++)
				map->copyXY(i,j  ,  i+1,j);
		for (int i = 0; i < map->getMaxFactions(); ++i)     // move players
			map->changeStartLocation(map->getStartLocationX(i)-1, map->getStartLocationY(i), i); // it allready check limits
	}
}

void Program::flipDiagonal() {
	if(map) {
		int w=map->getW();
		int h=map->getH();
		for (int i=0; i<w; i++)
			for (int j=i; j<h; j++){
				map->swapXY(i,j ,  j,i);
			}
		for (int i = 0; i < map->getMaxFactions(); ++i)     // move players
			if (map->getStartLocationX(i) != 0 || map->getStartLocationY(i) != 0) // don't move the unset ones
				map->changeStartLocation(map->getStartLocationY(i), map->getStartLocationX(i), i); // it allready check limits
	}
}


void Program::shiftRight() {
	if(map) {
		int w=map->getW()-1;
		int h=map->getH();
		for (int i=w; i>0; i--)
			for (int j=0; j<h; j++)
				map->copyXY(i,j  ,  i-1,j);
		for (int i = 0; i < map->getMaxFactions(); ++i)     // move players
			if (map->getStartLocationX(i) != 0 || map->getStartLocationY(i) != 0) // don't move the unset ones
				map->changeStartLocation(map->getStartLocationX(i)+1, map->getStartLocationY(i), i); // it allready check limits
	}
}
void Program::shiftUp() {
	if(map) {
		int w=map->getW();
		int h=map->getH()-1;
		for (int i=0; i<w; i++)
			for (int j=0; j<h; j++)
				map->copyXY(i,j  ,  i,j+1);
		for (int i = 0; i < map->getMaxFactions(); ++i)     // move players
			map->changeStartLocation(map->getStartLocationX(i), map->getStartLocationY(i)-1, i); // it allready check limits
	}
}
void Program::shiftDown() {
	if(map) {
		int w=map->getW();
		int h=map->getH()-1;
		for (int i=0; i<w; i++)
			for (int j=h; j>0; j--)
				map->copyXY(i,j  ,  i,j-1);
		for (int i = 0; i < map->getMaxFactions(); ++i)     // move players
			if (map->getStartLocationX(i) != 0 || map->getStartLocationY(i) != 0) // don't move the unset ones
				map->changeStartLocation(map->getStartLocationX(i), map->getStartLocationY(i)+1, i); // it allready check limits
	}
}


void Program::randomizeMapHeights(bool withReset,int minimumHeight, int maximumHeight, int chanceDivider, int smoothRecursions) {
	if(map) map->randomizeHeights(withReset, minimumHeight,  maximumHeight,  chanceDivider,  smoothRecursions);
}

void Program::randomizeFactions() {
	if(map) map->randomizeFactions();
}

void Program::switchMapSurfaces(int surf1, int surf2) {
	if(map) map->switchSurfaces(static_cast<MapSurfaceType>(surf1), static_cast<MapSurfaceType>(surf2));
}

void Program::reset(int w, int h, int alt, int surf) {
	undoStack.clear();
	redoStack.clear();
	if(map) map->reset(w, h, (float) alt, static_cast<MapSurfaceType>(surf));
}

void Program::resize(int w, int h, int alt, int surf) {
	if(map) map->resize(w, h, (float) alt, static_cast<MapSurfaceType>(surf));
}

void Program::resetFactions(int maxFactions) {
	if(map) map->resetFactions(maxFactions);
	randomizeFactions();
}

bool Program::setMapTitle(const string &title) {
	if(map) {
		if (map->getTitle() != title) {
			map->setTitle(title);
			return true;
		}
	}
	return false;
}

bool Program::setMapDesc(const string &desc) {
	if(map) {
		if (map->getDesc() != desc) {
			map->setDesc(desc);
			return true;
		}
	}
	return false;
}

bool Program::setMapAuthor(const string &author) {
	if(map) {
		if (map->getAuthor() != author) {
			map->setAuthor(author);
			return true;
		}
	}
	return false;
}

void Program::setOfset(int x, int y) {
	ofsetX += x;
	ofsetY -= y;
}

void Program::incCellSize(int i) {
	if(map) {
		int minInc = 2 - cellSize;

		if (i < minInc) {
			i = minInc;
		}
		cellSize += i;
		ofsetX -= (map->getW() * i) / 2;
		ofsetY += (map->getH() * i) / 2;
	}
}

void Program::resetOfset() {
	ofsetX = 0;
	ofsetY = 0;
	cellSize = 5;
}

bool Program::setGridOnOff() {
    grid=!grid;
    return grid;
}
bool Program::setHeightMapOnOff() {
    heightmap=!heightmap;
    return heightmap;
}
bool Program::setHideWaterOnOff() {
    hideWater=!hideWater;
    return hideWater;
}

void Program::setMapAdvanced(int altFactor, int waterLevel, int cliffLevel , int cameraHeight) {
	if(map) map->setAdvanced(altFactor, waterLevel, cliffLevel, cameraHeight);
}

void Program::loadMap(const string &path) {
	undoStack.clear();
	redoStack.clear();

	std::string encodedPath = path;
	map->loadFromFile(encodedPath);
}

void Program::saveMap(const string &path) {
	if(map) {
		std::string encodedPath = path;

		map->saveToFile(encodedPath);
	}
}

}// end namespace
