// This file is part of ZetaGlest <https://github.com/ZetaGlest>
//
// Copyright (C) 2018  The ZetaGlest team
//
// ZetaGlest is a fork of MegaGlest <https://megaglest.org>
//
// This program 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.

// This program 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 this program. If not, see <https://www.gnu.org/licenses/>

#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
