/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "RTS.h"
#include "Globals.h"
#include "Group.h"
#include "PreBattle.h"
#include "TerrainTile.h"
#include "Projectile.h"

#include <vector>
#include <list>

using std::vector;
using std::list;

namespace RTS {

RTS_State::RTS_State() {
	radarDragging = false;
	lastAITime = now;
	lastScrollTime = now;
}

RTS_State::~RTS_State() {
	projectiles.clear();
	worldUpdateInterval = standardInterval;

	if (gsTo != GST_PreBattle && gsTo != GST_Score)
		PreBattle::Unload();
	else if (gsTo == GST_PreBattle)
		RestartPreBattle();
}

void RTS_State::Main() {
	ScrollAndDrag();

	if (now - lastAITime > worldUpdateInterval && !(paused)) {
		RunGroupAI();
		RunMoveCommands();
		RunFireCommands();
		Upkeep();

		lastAITime = now;

		++frameCounter;
	}
	
	if (skipDisplayFrame || globalSettings.batch)
		return;
		
	DrawWorld();
	DrawAllWindows();
}

void RTS_State::MouseD(Uint8 button, Uint16 x, Uint16 y) {
	RTSMouseD(button, x, y);
}

void RTS_State::MouseU(Uint8 button, Uint16 x, Uint16 y) {
	RTSMouseU(button, x, y);
}

void RTS_State::MouseM(Uint8 state, Uint16 x, Uint16 y) {
	RTSMouseM(state, x, y);
}

void RTS_State::Keyboard(SDL_keysym& keysym) {
	RTSKeyboard(keysym);
}

void RTSMouseD(Uint8 button, Uint16 x, Uint16 y) {
	if (button == SDL_BUTTON_RIGHT)
		myWindows.push_back(GenWindow(x, y, RTS_BasePU, 0, 0, 0));
	
	//radar before groups
	if (button == SDL_BUTTON_LEFT
	&& x > radarRect.x
	&& x < radarRect.x + radarRect.w
	&& y > radarRect.y
	&& y < radarRect.y + radarRect.h) {
		radarDragging = true;
		RTSMouseM(0, x, y);
	} else {
		for (int i = 0; i != sides.size(); ++i) {
			for (int j = 0; j != sides[i].groups.size(); ++j)
				sides[i].groups[j].MouseD(button, x, y);
		}
	}
}

void RTSMouseU(Uint8 button, Uint16 x, Uint16 y) {
	if (button == SDL_BUTTON_LEFT)
		radarDragging = false;

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].MouseU(button, x, y);
	}
}

void RTSMouseM(Uint8 state, Uint16 x, Uint16 y) {
	//radar
	if (radarDragging
		&& x > radarRect.x
		&& x < radarRect.x + radarRect.w
		&& y > radarRect.y
		&& y < radarRect.y + radarRect.h) {
			int tempCenterx = (x - radarRect.x) * worldWidth / radarRect.w;
			int tempCentery = (y - radarRect.y) * worldHeight / radarRect.h;

			viewx = tempCenterx - (globalSettings.screenWidth >> 1);
			viewy = tempCentery - (globalSettings.screenHeight >> 1);

			viewSide = -1;
	}
}

void RTSKeyboard(SDL_keysym& keysym) {
	switch(keysym.sym) {
	case SDLK_ESCAPE:
		if (gsCurrent == GST_PreBattle && PreBattle::pbState == PBS_Position)
			gsTo = GST_ForceSelect;
		else
			gsTo = GST_MainMenu;
		return;
		break;

	case SDLK_F2:
		for (int i = 0; i != sides.size(); ++i) {
			for (int j = 0; j != sides[i].groups.size(); ++j)
				sides[i].groups[j].ToggleDrawNumber();
		}
		break;

	case SDLK_F3:
		for (int i = 0; i != sides.size(); ++i) {
			for (int j = 0; j != sides[i].groups.size(); ++j)
				sides[i].groups[j].ToggleDrawBound();
		}
		break;

	case SDLK_F11:
		myWindows.push_back(GenWindow(0, 0, RTS_RestartQ, 0, 0, 0));
		break;
	
	case SDLK_SPACE: {
		static bool speedSlider = false;
		static int sliderWinID = 0;		
		if (speedSlider) {
			try {
				Slider* pSlider = dynamic_cast<Slider*>(LocateWindow(sliderWinID));
				pSlider->WinMessage(WC_YouClose, 0, 0, sliderWinID, none_constant);
				speedSlider = false;
				break;
			} catch (runtime_error e) {
			}
		}
		//if we fell through due to a runtime error because window was already closed, create a new one
		myWindows.push_back(GenWindow(topRightBoxRect.x, topRightBoxRect.y + topRightBoxRect.h, RTS_SetGameSpeed, 0, 0, 0));
		sliderWinID = windowIDs;
		speedSlider = true;
		}
		break;
	
	case SDLK_KP_PLUS:
		SetWorldUpdateInterval(worldUpdateInterval - 10);
		break;
		
	case SDLK_KP_MINUS:
		SetWorldUpdateInterval(worldUpdateInterval + 10);
		break;
	
	case SDLK_p:
		if (paused == false)
			paused = true;
		else if (worldUpdateInterval < maxWorldUpdateInterval)
			paused = false;
		break;
		
	case SDLK_i:
		SetWorldUpdateInterval(standardInterval);
		break;
		
	case SDLK_o:
		SetWorldUpdateInterval(0);
		break;
	}
}

void ScrollAndDrag() {
	int state, x, y;
	state = SDL_GetMouseState(&x, &y);

	//scrolling via mouse, also used to break out of follow view
	if (now - lastScrollTime > scrollInterval) {
		if (x > globalSettings.screenWidth - 8 || JSDL.keyboard[SDLK_RIGHT]) {
			viewx += screenMoveSpeed;
			viewSide = -1;
		}
		if (x < 8  || JSDL.keyboard[SDLK_LEFT]) {
			viewx -= screenMoveSpeed;
			viewSide = -1;
		}
		if (y > globalSettings.screenHeight - 8 || JSDL.keyboard[SDLK_DOWN]) {
			viewy += screenMoveSpeed;
			viewSide = -1;
		}
		if (y < 8 || JSDL.keyboard[SDLK_UP]) {
			viewy -= screenMoveSpeed;
			viewSide = -1;
		}

		lastScrollTime = now;
	}

	if (gsCurrent == GST_PreBattle) {
		for (int i = 0; i != sides.size(); ++i) {
			for (int j = 0; j != sides[i].groups.size(); ++j)
				sides[i].groups[j].Drag(state, x, y);
		}
	}
}

void RunGroupAI() {
	for (int i  = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].RunGroupAI();
	}
}

void RunMoveCommands() {
	for (int i  = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].Move();
	}

	for (list<Projectile>::iterator iter = projectiles.begin(); iter != projectiles.end();) {
		if (!iter->Move())
			iter = projectiles.erase(iter);
		else
			++iter;
	}
}

void RunFireCommands() {
	for (int i  = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].RunFireCommands();
	}
}

void Upkeep() {
	for (int i  = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].Upkeep();
	}

	int sidesLeft = 0;

	for (int i  = 0; i != sides.size(); ++i) {
		int sideTotalCS = sides[i].GetTotalCapShips();
		if (sideTotalCS)
			++sidesLeft;
	}

	if (sidesLeft < 2)
		gsTo = GST_Score;
	else if (globalSettings.batch && frameCounter > globalSettings.maxFrames)
		gsTo = GST_Score;
}

void DrawWorld() {
	FollowViewCenter();
	CheckViewPos();

	JSDL.BltFill(screenRect, 0);

	DrawTerrain();

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].SetUSRect();
	}

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].DrawSelfBackBack();
	}

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].DrawSelfBack();
	}

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].DrawSelfMiddle();
	}

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].DrawSelfFront();
	}

	JSDL.LockBack();
	for (list<Projectile>::iterator iter = projectiles.begin(); iter != projectiles.end(); ++iter)
		iter->DrawSelfPixels();

	for (int i = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j)
			sides[i].groups[j].DrawSelfPixels();
	}
	JSDL.UnlockBack();

	for (list<Projectile>::iterator iter = projectiles.begin(); iter != projectiles.end(); ++iter)
		iter->DrawSelfBitmap();

	if (gsCurrent == GST_PreBattle) {
		for (int i = 0; i != sides.size(); ++i) {
			SDL_Rect tempRect;
			tempRect.x = sides[i].startingRect.x - viewx;
			tempRect.y = sides[i].startingRect.y - viewy;
			tempRect.w = sides[i].startingRect.w;
			tempRect.h = sides[i].startingRect.h;
			DrawRectBorder(tempRect, sides[i].color);
		}
	}

	DrawRadar();
}

void DrawTerrain() {
	for (int i = 0; i != terrainTree.size(); ++i) {
		if (viewx + globalSettings.screenWidth > terrainTree[i].rect.x
		&& viewx < terrainTree[i].rect.x + terrainTree[i].rect.w
		&& viewy + globalSettings.screenHeight > terrainTree[i].rect.y
		&& viewy < terrainTree[i].rect.y + terrainTree[i].rect.h)
			for (int j = 0; j != terrainTree[i].tiles.size(); ++j)
				terrainTree[i].tiles[j].DrawSelf();
	}
}

void DrawRadar() {
	JSDL.BltFill(topRightBoxRect, gold);

	SDL_FillRect(radarSurface, 0, black);

	//Uint16 strided lpitch
	static const Uint16 lPitch = radarSurface->pitch >> 1;

	SDL_LockSurface(radarSurface);

	Uint16* videoBuffer = reinterpret_cast<Uint16*>(radarSurface->pixels);

	for (int i  = 0; i != sides.size(); ++i) {
		for (int j = 0; j != sides[i].groups.size(); ++j) {
			if (sides[i].groups[j].GetAlive()) {
				CoordsInt center = sides[i].groups[j].GetCenter();

				int radarx = center.x * radarRect.w / worldWidth;
				int radary = center.y * radarRect.h / worldHeight;

				videoBuffer[radary*lPitch + radarx] = sides[i].radarColor;
			}
		}
	}
	
	int radarx = viewx * radarRect.w / worldWidth;
	int radary = viewy * radarRect.h / worldHeight;
	
	int lineWidth = radarRect.w * globalSettings.screenWidth / worldWidth;
	int lineHeight = radarRect.h * globalSettings.screenHeight / worldHeight;
	
	for (int x = radarx; x != radarx + lineWidth + 1; ++x)
		videoBuffer[radary*lPitch + x] = white;
		
	for (int x = radarx; x != radarx + lineWidth + 1; ++x)
		videoBuffer[(radary + lineHeight)*lPitch + x] = white;
		
	for (int y = radary; y != radary + lineHeight + 1; ++y)
		videoBuffer[y*lPitch + radarx] = white;
		
	for (int y = radary; y != radary + lineHeight + 1; ++y)
		videoBuffer[y*lPitch + radarx + lineWidth] = white;

	SDL_UnlockSurface(radarSurface);

	JSDL.Blt(radarSurface, radarRect);
}

void SetWorldUpdateInterval(int newValue) {
	worldUpdateInterval = newValue;
	
	if (worldUpdateInterval < 0)
		worldUpdateInterval = 0;
		
	if (worldUpdateInterval == maxWorldUpdateInterval)
		paused = true;
	else
		paused = false;
		
	if (worldUpdateInterval > maxWorldUpdateInterval)
		worldUpdateInterval = maxWorldUpdateInterval;

	if (worldUpdateInterval > standardInterval - 10 && worldUpdateInterval < standardInterval + 10)
		worldUpdateInterval = standardInterval;
}

//////

string MoveComToString(const AICommands& theCommands) {
	string ret;
	char output[32];

	if (!theCommands.bInverse)
		ret = "Moving towards ";
	else
		ret = "Moving away from ";

	switch (theCommands.moveCommand) {
	case MC_NoMove:
		ret = "Not moving";
		break;

	case MC_MoveCompass:
		switch (theCommands.moveTarget.y) {
		case 0:
			ret += "North";
			break;

		case 1:
			ret += "North-East";
			break;

		case 2:
			ret += "East";
			break;

		case 3:
			ret += "South-East";
			break;

		case 4:
			ret += "South";
			break;

		case 5:
			ret += "South-West";
			break;

		case 6:
			ret += "West";
			break;

		case 7:
			ret += "North-West";
			break;
		}
		break;

	case MC_MoveGroup:
		//+1 so side 1/group is 1
		sprintf(output, "%s Group %d", sides[theCommands.moveTarget.x].name.c_str(), theCommands.moveTarget.y + 1);

		ret += output;
		break;

	case MC_Patrol:
		ret = "Patrolling ";
		sprintf(output, "%s Group %d", sides[theCommands.moveTarget.x].name.c_str(), theCommands.moveTarget.y + 1);

		ret += output;
		break;
	}

	return ret;
}

string FireComToString(const AICommands& theCommands) {
	string ret;
	char output[32];

	ret = "Fire target: ";

	if (theCommands.bFire) {
		//+1 so side 1/group is 1
		sprintf(output, "%s Group %d", sides[theCommands.fireTarget.x].name.c_str(), theCommands.fireTarget.y + 1);
		ret += output;
	} else
		ret += "No target";

	return ret;
}

void CheckViewPos() {
	if (viewx > worldWidth - globalSettings.screenWidth)
		viewx = worldWidth - globalSettings.screenWidth;
	if (viewx < 0)
		viewx = 0;
	if (viewy > worldHeight - globalSettings.screenHeight)
		viewy = worldHeight - globalSettings.screenHeight;
	if (viewy < 0)
		viewy = 0;
}

void RestartPreBattle() {
	projectiles.clear();
	PreBattle::UnloadGraphics();

	for (int i = 0; i != sides.size(); ++i)
		sides[i].Reset();
}

} //end namespace
