#define OLC_PGE_APPLICATION
#include "pixelGameEngine.h"
#include "data.h"

using namespace std;

enum GAMESTATE{
	CUTSCENE_1,
	CUTSCENE_2,
	CUTSCENE_3,
	GAMEWORLD,
	WAITING_FOR_CUTSCENE_3,
};

enum TILES{
	DIRT,
	GRASS,
	WATER,
	PLANTS
};

enum CUTSCENE{
	PAN_DOME,
	PAUSE_TO_CUTSCENE_3,
	CUTSCENE_4,
};

#define WIDTH 256
#define HEIGHT 224
#define ALPHA_SCREEN1 128
#define ALPHA_SCREEN2 20
#define FADE_SPD 6
#define MOVE_SPD 0.2
#define MESSAGE_SCROLL_WAIT_SPD 2 //Number of frames to wait.

class Object{
	public:
		float x,y;
		olc::Decal*spr;
};

class SeasonsOfLoneliness : public olc::PixelGameEngine
{
public:
	SeasonsOfLoneliness()
	{
		sAppName = "Seasons of Loneliness";
	}

public:
	GAMESTATE GAME_STATE=GAMEWORLD;
	int textInd=0;
	int cursorX=0;
	int transitionTime=0;
	bool fade=false;
	int transparency=0;
	int frameCount=0;
	float elapsedTime=0;
	const float TARGET_RATE = 1/60.0;
	std::string CUTSCENE_CONSOLE_TEXT = "";
	int**MAP=NULL;
	int MAP_WIDTH=-1;
	int MAP_HEIGHT=-1;
	olc::Decal*TILES;
	float PLAYER_COORDS[2] = {};
	std::vector<Object> OBJECTS;
	bool CUTSCENE_ACTIVE=false;
	CUTSCENE CURRENT_CUTSCENE=PAN_DOME;
	int CUTSCENE_TIMER=0;
	bool CUTSCENE_FLAGS[8];
	bool messageBoxVisible=false;
	int messageBoxCursor;
	std::string messageBoxSpeaker;
	std::string messageBoxText;
	std::string messageBoxRefText;
	bool firstNewline=false;
	bool secondNewline=false;
	bool foodMeterVisible=false;
	int foodCount=3;
	bool oxygenMeterVisible=false;
	int oxygenQualityLevel=34;

	olc::Decal*DOME_DECAL,*FOOD_METER_DECAL,*OXYGEN_METER_DECAL;
	std::map<std::string,olc::Decal*> BASE_OBJECTS;

	void LoadMap(char*mapName) {
		std::ifstream f(mapName);
		std::string data;
		MAP_WIDTH=MAP_HEIGHT=-1;
		if (MAP!=NULL) {
			for (int y=0;y<MAP_HEIGHT;y++) {
				free(MAP[y]);
			}
			free(MAP);
			MAP=NULL;
		}
		OBJECTS.clear();

		int y=0;
		if (f.is_open()) {
			while (f.good()) {
				f>>data;
				if (MAP_WIDTH==-1) {
					std::stringstream stream(data);
					stream>>MAP_WIDTH;
				} else
				if (MAP_HEIGHT==-1) {
					std::stringstream stream(data);
					stream>>MAP_HEIGHT;
				} else 
				if (y<MAP_HEIGHT) {
					if (MAP==NULL) {
						MAP=(int**)malloc(sizeof(int**)*MAP_HEIGHT);
					}
					MAP[y]=(int*)malloc(sizeof(int*)*MAP_WIDTH);
					for (int i=0;i<data.length();i++) {
						MAP[y][i]=data[i]-'0';
					}
					y++;
				} else {
					Object obj;
					std::stringstream split1(data.substr(0,data.find(';')));
					split1>>obj.x;
					std::stringstream split2(data.substr(data.find(';')+1,data.find(';',data.find(";")+1)-(data.find(';')+1)));
					split2>>obj.y;
					std::string split3 = data.substr(data.find(';',data.find(";")+1)+1,data.length()-(data.find(';',data.find(";")+1)+1));
					obj.spr=BASE_OBJECTS[split3];
					printf("Loaded object %s: (%f,%f)\n",split3.c_str(),obj.x,obj.y);
					OBJECTS.push_back(obj);
				}
			}
		}
		printf("Loaded %dx%d map:\n",MAP_WIDTH,MAP_HEIGHT);
		for (int y=0;y<MAP_HEIGHT;y++) {
			for (int x=0;x<MAP_WIDTH;x++) {
				printf("%d",MAP[y][x]);
			}
			printf("\n");
		}
	}

	bool OnUserCreate() override
	{
		SetPixelMode(olc::Pixel::ALPHA);
		//ConsoleCaptureStdOut(true);
		// Called once at the start, so create things here
		TILES=new olc::Decal(new olc::Sprite("assets/tiles.png"));
		DOME_DECAL=new olc::Decal(new olc::Sprite("assets/dome.png"));
		FOOD_METER_DECAL=new olc::Decal(new olc::Sprite("assets/corn.png"));
		OXYGEN_METER_DECAL=new olc::Decal(new olc::Sprite("assets/co2.png"));
		BASE_OBJECTS["DOME"]=DOME_DECAL;
		LoadMap("assets/maps/map1");
		return true;
	}

	void GetAnyKeyPress() override { 
		if (messageBoxVisible) {
			if (messageBoxCursor!=messageBoxRefText.length()) {
				while (messageBoxCursor<messageBoxRefText.length()) {
					advanceMessageBox();
				}
			} else {
				messageBoxVisible=false;
			}
		}
		switch (GAME_STATE) {
			case CUTSCENE_1:{
				if (textInd>=STORY_TEXT1.length()) {
					fadeOut();
				}
			}break;
		}
	}

	bool OnUserUpdate(float fElapsedTime) override
	{
		elapsedTime+=fElapsedTime;
		while (elapsedTime>TARGET_RATE) {
			elapsedTime-=TARGET_RATE;
			updateGame();
		}

		drawGame();
		// called once per frame
		return true;
	}

	void fadeOutCompleted() {
		switch (GAME_STATE) {
			case CUTSCENE_1:{
				GAME_STATE=CUTSCENE_2;
				PlayCutscene(PAN_DOME);
				fadeIn();
			}break;
			case CUTSCENE_2:{
				fadeIn();
				PlayCutscene(CUTSCENE_4);
				GAME_STATE=GAMEWORLD;
			}break;
		}
	}

	void fadeInCompleted() {

	}

	void PlayCutscene(CUTSCENE scene) {
		CURRENT_CUTSCENE=scene;
		switch (scene) {
			case PAN_DOME:{
				PLAYER_COORDS[0]=14;
				PLAYER_COORDS[1]=18+(64/2/32);
			}break;
			case PAUSE_TO_CUTSCENE_3:{
				CUTSCENE_CONSOLE_TEXT.clear();
				textInd=0;
			}break;
			case CUTSCENE_4:{
				LoadMap("assets/maps/map2");
				PLAYER_COORDS[0]=16;
				PLAYER_COORDS[1]=6;
			}break;
		}
		for (int i=0;i<8;i++) {
			CUTSCENE_FLAGS[i]=false;
		}
		CUTSCENE_ACTIVE=true;
		CUTSCENE_TIMER=0;
	}

	void updateGame(){
		frameCount++;
		if (CUTSCENE_ACTIVE) {
			CUTSCENE_TIMER++;
		}
		if (fade&&transparency<255) {
			transparency=clamp(transparency+FADE_SPD,0,255);
			if (transparency==255) {
				fadeOutCompleted();
			}
		} else
		if (!fade&&transparency>0) {
			transparency=clamp(transparency-FADE_SPD,0,255);
			if (transparency==0) {
				fadeInCompleted();
			}
		}
		if (messageBoxVisible) {
			if (frameCount%MESSAGE_SCROLL_WAIT_SPD==0) {
				if (messageBoxCursor<messageBoxRefText.length()) {
					advanceMessageBox();
				}
			}
		}
		if (GetKey(olc::F1).bPressed) {
		}
		
		if (!CUTSCENE_ACTIVE&&!messageBoxVisible) {
			if (GetKey(olc::D).bHeld||GetKey(olc::RIGHT).bHeld) {
				PLAYER_COORDS[0]=clamp(PLAYER_COORDS[0]+MOVE_SPD,0.1,(double)MAP_WIDTH);
			}
			if (GetKey(olc::A).bHeld||GetKey(olc::LEFT).bHeld) {
				PLAYER_COORDS[0]=clamp(PLAYER_COORDS[0]-MOVE_SPD,0.1,(double)MAP_WIDTH);
			}
			if (GetKey(olc::W).bHeld||GetKey(olc::UP).bHeld) {
				PLAYER_COORDS[1]=clamp(PLAYER_COORDS[1]-MOVE_SPD,0.1,(double)MAP_HEIGHT);
			}
			if (GetKey(olc::S).bHeld||GetKey(olc::DOWN).bHeld) {
				PLAYER_COORDS[1]=clamp(PLAYER_COORDS[1]+MOVE_SPD,0.1,(double)MAP_HEIGHT);
			}
		}

		switch (CURRENT_CUTSCENE) {
			case CUTSCENE_4:{
				if (!messageBoxVisible) {
					if (!CUTSCENE_FLAGS[0]) {
						CUTSCENE_FLAGS[0]=true;
						DisplayMessageBox(0);
					} else
					if (!CUTSCENE_FLAGS[1]) {
						CUTSCENE_FLAGS[1]=true;
						DisplayMessageBox(1);
					} else
					if (!CUTSCENE_FLAGS[2]) {
						CUTSCENE_FLAGS[2]=true;
						DisplayMessageBox(2);
						foodMeterVisible=true;
					} else
					if (!CUTSCENE_FLAGS[3]) {
						CUTSCENE_FLAGS[3]=true;
						DisplayMessageBox(3);
						oxygenMeterVisible=true;
					}
				}
			}break;
		}

		switch (GAME_STATE) {
			case CUTSCENE_1:
			case CUTSCENE_3:{
				std::string refText;
				switch (GAME_STATE) {
					case CUTSCENE_1:{
						refText=STORY_TEXT1;
					}break;
					case CUTSCENE_3:{
						refText=STORY_TEXT2;
					}break;
				}
				if (GAME_STATE==CUTSCENE_3&&frameCount%4!=0) {break;}
				if (textInd<refText.length()) {
					char c = refText[textInd++];
					CUTSCENE_CONSOLE_TEXT+=c;
					if (c!='\n') {
						cursorX++;
					} else {
						cursorX=0;
					}
					if (GetTextSize(CUTSCENE_CONSOLE_TEXT).x>WIDTH-32) {
						int tempIndex=textInd;
						while (CUTSCENE_CONSOLE_TEXT[--tempIndex]!=' ') {
							CUTSCENE_CONSOLE_TEXT.erase(tempIndex);
							cursorX--;
						}
						CUTSCENE_CONSOLE_TEXT.erase(tempIndex++);
						CUTSCENE_CONSOLE_TEXT+='\n';
						cursorX=0;
						while (tempIndex<textInd) {
							CUTSCENE_CONSOLE_TEXT+=refText[tempIndex++];
							cursorX++;
						}
					}
				}
			}break;
			case CUTSCENE_2:{
				//FLAG 0 - When flipped on, camera stops.
				if (!CUTSCENE_FLAGS[0]) {
					PLAYER_COORDS[0]+=0.06;
				} else {
					if (CUTSCENE_TIMER>120) {
						fadeOut();
					}
				}
				if (PLAYER_COORDS[0]>38+(128/2/32)) {
					PLAYER_COORDS[0]=38+(128/2/32);
					CUTSCENE_FLAGS[0]=true;
					CUTSCENE_TIMER=0;
				}
			}break;
			case WAITING_FOR_CUTSCENE_3:{
				if (transparency>200) {
					GAME_STATE=CUTSCENE_3;
				}
			}break;
		}
	}

	void advanceMessageBox() {
		char c = messageBoxRefText[messageBoxCursor++];
		printf("%c",c);
		if (c=='\n') {
			if (!firstNewline) {
				firstNewline=true;
				return;
			} else if (!secondNewline) {
				secondNewline=true;
				return;
			}
		}
		if (firstNewline&&!secondNewline) {
			messageBoxSpeaker+=c;
			return;
		}
		messageBoxText+=c;
		if (GetTextSizeProp(messageBoxText).x>WIDTH-16) {
			int tempIndex=messageBoxCursor;
			while (messageBoxText[--tempIndex]!=' ') {
				messageBoxText.erase(tempIndex);
			}
			messageBoxText.erase(tempIndex++);
			messageBoxText+='\n';
			while (tempIndex<messageBoxCursor) {
				messageBoxText+=messageBoxRefText[tempIndex++];
			}
		}
	}

	void drawGame(){
		switch (GAME_STATE) {
			case CUTSCENE_1:{
				DrawStringDecal({16,16},CUTSCENE_CONSOLE_TEXT,olc::GREEN,{1,1});
				if (textInd<STORY_TEXT1.length()) {
					FillRectDecal({(float)(16+(cursorX)*8%(WIDTH-32)),(float)(8+GetTextSize(CUTSCENE_CONSOLE_TEXT).y+((cursorX==28)?8:0))},{4,8},olc::GREEN);
				} else {
					FillRectDecal({(float)(16+(cursorX)*8%(WIDTH-32)),(float)(8+GetTextSize(CUTSCENE_CONSOLE_TEXT).y+((cursorX==28)?8:0))},{4,8},olc::Pixel(0,255,0,(0.5*sin(frameCount*4/60.0)+0.5)*256));
				}
				GradientFillRectDecal({0,0},{WIDTH/2,HEIGHT/2},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN2},{20, 28, 22,ALPHA_SCREEN1});
				GradientFillRectDecal({WIDTH/2,0},{WIDTH/2,HEIGHT/2},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN2},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN1});
				GradientFillRectDecal({0,HEIGHT/2},{WIDTH/2,HEIGHT/2},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN2});
				GradientFillRectDecal({WIDTH/2,HEIGHT/2},{WIDTH/2,HEIGHT/2},{20, 28, 22,ALPHA_SCREEN2},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN1},{20, 28, 22,ALPHA_SCREEN1});
			}break;
			case CUTSCENE_2:
			case GAMEWORLD:{
				DrawGameWorld();
			}break;
			case CUTSCENE_3:{
				DrawStringDecal({48,16},CUTSCENE_CONSOLE_TEXT,olc::Pixel(100, 10, 255),{2,2});
				if (textInd<STORY_TEXT2.length()) {
					FillRectDecal({(float)(48+(cursorX)*16%(WIDTH-32)),(float)(GetTextSize(CUTSCENE_CONSOLE_TEXT).y*2+((cursorX==28)?16:0))},{4*2,8*2},olc::Pixel(100, 10, 255));
				} else {
					FillRectDecal({(float)(48+(cursorX)*16%(WIDTH-32)),(float)(GetTextSize(CUTSCENE_CONSOLE_TEXT).y*2+((cursorX==28)?16:0))},{4*2,8*2},olc::Pixel(100, 10, 255,(0.5*sin(frameCount*4/60.0)+0.5)*256));
				}
				GradientFillRectDecal({0,0},{WIDTH/2,HEIGHT/2},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN2},{100, 10, 255,ALPHA_SCREEN1});
				GradientFillRectDecal({WIDTH/2,0},{WIDTH/2,HEIGHT/2},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN2},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN1});
				GradientFillRectDecal({0,HEIGHT/2},{WIDTH/2,HEIGHT/2},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN2});
				GradientFillRectDecal({WIDTH/2,HEIGHT/2},{WIDTH/2,HEIGHT/2},{100, 10, 255,ALPHA_SCREEN2},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN1},{100, 10, 255,ALPHA_SCREEN1});
			}break;
		}
		int meterYOffset=2;
		if (foodMeterVisible) {
			DrawStringDecal({WIDTH-36*0.4-GetTextSize(to_string(foodCount)).x*1-8,meterYOffset+2},to_string(foodCount),olc::BLUE,{1,1});
			DrawStringDecal({WIDTH-36*0.4-GetTextSize(to_string(foodCount)).x*1-7,meterYOffset+2},to_string(foodCount),olc::BLACK,{1,1});
			DrawDecal({WIDTH-52*0.4,meterYOffset},FOOD_METER_DECAL,{0.4,0.4});
			meterYOffset+=(2+48*0.4);
		}
		if (oxygenMeterVisible) {
			DrawStringDecal({WIDTH-36*0.4-GetTextSize(to_string(oxygenQualityLevel)+"%").x*1-8,meterYOffset+2},to_string(oxygenQualityLevel)+"%",olc::BLUE,{1,1});
			DrawStringDecal({WIDTH-36*0.4-GetTextSize(to_string(oxygenQualityLevel)+"%").x*1-7,meterYOffset+2},to_string(oxygenQualityLevel)+"%",olc::BLACK,{1,1});
			DrawDecal({WIDTH-52*0.4,meterYOffset},OXYGEN_METER_DECAL,{0.4,0.4});
			meterYOffset+=(2+48*0.4);
		}
		if (messageBoxVisible) {
			DrawDialogBox({4,HEIGHT-60},{WIDTH/2,16},olc::Pixel(18, 0, 33,180));
			DrawDialogBox({0,HEIGHT-48},{WIDTH,48},olc::Pixel(18, 0, 33,180));
			DrawStringPropDecal({8,HEIGHT-40},messageBoxText);
			DrawStringPropDecal({8,HEIGHT-57},messageBoxSpeaker);
			if (messageBoxCursor==messageBoxRefText.length()) {
				DrawStringPropDecal({WIDTH-16-(float)sin(frameCount*8/60.0)*3,HEIGHT-8+(float)(cos(frameCount*6/60.0)*0.6)},"v",olc::Pixel(173, 74, 255,(0.5*sin(frameCount*8/60.0)+0.5)*128+128),{(float)sin(frameCount*8/60.0),0.5});
			}
		}
		FillRectDecal({0,0},{WIDTH,HEIGHT},olc::Pixel(0,0,0,transparency));
	}

	void DrawGameWorld() {
		for (int y=-1;y<=HEIGHT/32+1;y++) {
			for (int x=-1;x<=WIDTH/32+1;x++) {
				float xOffset=PLAYER_COORDS[0]-(WIDTH/32/2);
				float yOffset=PLAYER_COORDS[1]-(HEIGHT/32/2);
				srand((int)(yOffset+y)*MAP_WIDTH+(int)(xOffset+x));
				int tileX=0;
				int tileRot=0;
				while (tileX<3&&rand()%4==0) {
					tileX++;
				}
				while (tileRot<3&&rand()%8<5) {
					tileRot++;
				}
				if ((int)(xOffset+x)>=0&&(int)(xOffset+x)<MAP_WIDTH&&(int)(yOffset+y)>=0&&(int)(yOffset+y)<MAP_HEIGHT) {
					DrawPartialRotatedDecal({(x-(PLAYER_COORDS[0]-(int)PLAYER_COORDS[0]))*32,(y-(PLAYER_COORDS[1]-(int)PLAYER_COORDS[1]))*32},TILES,tileRot*M_PI_2,{16,16},{tileX*32,0},{32,32},{1,1},TILE_COLORS[MAP[(int)(yOffset+y)][(int)(xOffset+x)]]);
				}
			}
		}
		for (auto&obj:OBJECTS) {
			DrawDecal({(obj.x-PLAYER_COORDS[0])*32+WIDTH/2,(obj.y-PLAYER_COORDS[1])*32+HEIGHT/2},obj.spr);
		}
	}

	void DrawDialogBox(const olc::vi2d &pos, const olc::vi2d &size, olc::Pixel p = olc::WHITE, olc::Pixel p2 = olc::DARK_GREY, olc::Pixel p3 = olc::VERY_DARK_GREY) {
		FillRectDecal({(float)pos.x,(float)pos.y},size,p2);
		FillRectDecal({(float)pos.x+1,(float)pos.y+1},{(float)size.x-2,(float)size.y-2},p);
		FillRectDecal({(float)pos.x+2,(float)pos.y+2},{(float)size.x-4,(float)size.y-4},p3);
		FillRectDecal({(float)pos.x+3,(float)pos.y+3},{(float)size.x-5,(float)size.y-5},p);
		Draw({pos.x,pos.y},olc::BLACK);
		Draw({pos.x+size.x,pos.y+size.y},olc::BLACK);
		Draw({pos.x+size.x,pos.y},olc::BLACK);
		Draw({pos.x,pos.y+size.y},olc::BLACK);
	}

	void fadeOut() {
		fade=true;
	}
	void fadeIn() {
		fade=false;
	}
	void DisplayMessageBox(int dialogNumber) {
		messageBoxVisible=true;
		messageBoxCursor=0;
		std::string split1=STORY_DIALOG[dialogNumber].substr(0,STORY_DIALOG[dialogNumber].find('\n')); //Unused for now.
		std::string split2=STORY_DIALOG[dialogNumber].substr(STORY_DIALOG[dialogNumber].find('\n')+1,STORY_DIALOG[dialogNumber].find('\n',STORY_DIALOG[dialogNumber].find('\n')+1)-(STORY_DIALOG[dialogNumber].find('\n')+1));
		std::string split3=STORY_DIALOG[dialogNumber].substr(STORY_DIALOG[dialogNumber].find('\n',STORY_DIALOG[dialogNumber].find('\n')+1)+1,STORY_DIALOG[dialogNumber].length()-(STORY_DIALOG[dialogNumber].find('\n',STORY_DIALOG[dialogNumber].find('\n')+1)+1));
		messageBoxSpeaker=split2;
		messageBoxText="";
		messageBoxRefText=split3;
	}
};


int main()
{
	SeasonsOfLoneliness demo;
	if (demo.Construct(256, 224, 4, 4))
		demo.Start();

	return 0;
}