/*
===========================================================================
blockattack - Block Attack - Rise of the Blocks
Copyright (C) 2005-2012 Poul Sander

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 2 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 http://www.gnu.org/licenses/

Source information and contacts persons can be found at
http://www.blockattack.net
===========================================================================
*/

#ifndef BLOCKGAME_HPP
#define BLOCKGAME_HPP 1

#include "nlohmann/json.hpp"
#include "stats.h"
#include "common.h"
#include <deque>

#define NUMBEROFCHAINS 100
#define BLOCKWAIT 100000
#define BLOCKHANG 1000

enum stageButton {SBdontShow, SBstageClear, SBpuzzleMode};

extern stageButton stageButtonStatus;

//This is the size of the blocks. They are always 50. The internal logic calculates it that way
const int bsize = 50;

/**
 * This struct defines the start conditions of the game
 */
struct BlockGameStartInfo {
	unsigned int ticks = 0;
	bool timeTrial = false;
	///True means a stage clear game will be started. level must be set too.
	bool stageClear = false;
	///True if puzzle mode. level must be set too.
	bool puzzleMode = false;
	///Single puzzle is used for the editor only.
	bool singlePuzzle = false;
	int level = 0;
	bool AI = false;
	/**
	 * True means that stats will be recorded.
	 * If AI is true then this will be overruled to false
	 */
	bool recordStats = true;
	bool vsMode = false;
	/**
	 * Set to true if we are fighting an AI. level should be the AI level we are fighting
	 */
	bool vsAI = false;
	int startBlocks = -1;
	int handicap = 0;
	int gameSpeed = 0;
	int basicBlockVariants = 6;
	NLOHMANN_DEFINE_TYPE_INTRUSIVE(BlockGameStartInfo, ticks, timeTrial, stageClear, puzzleMode, singlePuzzle, level, AI, recordStats, vsMode, vsAI, startBlocks, handicap, gameSpeed, basicBlockVariants);
};

struct GarbageStruct {
	bool greyGarbage = false;
	int width = 0;
	int height = 0;
	void setGarbage(int w, int h, bool g = false) {
		greyGarbage = g;
		width = w;
		height = h;
	}
	NLOHMANN_DEFINE_TYPE_INTRUSIVE(GarbageStruct, greyGarbage, width, height);
};

struct BlockGameAction {
	enum class Action {NONE, UPDATE, SET_DRAW, SET_WON, SET_GAME_OVER, MOVE, SWITCH, PUSH, PUSH_GARBAGE, MOUSE_DOWN, MOUSE_UP, MOUSE_MOVE};
	Action action = Action::NONE;
	unsigned int tick = 0;  //< Used for update
	char way = '\0';  //< The direction to move the cursor: 'N', 'E', 'S' or 'W'
	int x = 0;
	int y = 0;
	std::vector<GarbageStruct> garbage;
};

static void to_json(nlohmann::json& j, const BlockGameAction& p) {
	j = nlohmann::json{ {"action", p.action}, {"tick", p.tick}, {"way", p.way}, {"x", p.x}, {"y", p.y}, {"garbage", p.garbage} };
}

static void from_json(const nlohmann::json& j, BlockGameAction& p) {
	j.at("action").get_to(p.action);
	j.at("tick").get_to(p.tick);
	j.at("way").get_to(p.way);
	j.at("x").get_to(p.x);
	j.at("y").get_to(p.y);
	j.at("garbage").get_to(p.garbage);
}


struct BlockGameInfoExtra {
	std::string name;
	int score = 0;
	int seconds = 0;
	NLOHMANN_DEFINE_TYPE_INTRUSIVE(BlockGameInfoExtra, name, score, seconds);
};

struct BlockGameInfo {
	BlockGameStartInfo startInfo;
	std::deque<BlockGameAction> actions;
	BlockGameInfoExtra extra;
	NLOHMANN_DEFINE_TYPE_INTRUSIVE(BlockGameInfo, startInfo, actions, extra);
};

////////////////////////////////////////////////////////////////////////////////
//The BloackGame class represents a board, score, time etc. for a single player/
////////////////////////////////////////////////////////////////////////////////
class BlockGame {
private:
	int prevTowerHeight = 0;
	bool bGarbageFallLeft = false;
	bool singlePuzzle = false;

	int nextGarbageNumber = 0;
	int pushedPixelAt = 0;
	int nrPushedPixel = 0;
	int nrFellDown = 0;
	unsigned int nrStops = 0;
	bool garbageToBeCleared[7][30] = {};
	unsigned int lastAImove = 0;

	int AI_LineOffset = 0; //how many lines have changed since command
	int hangTicks = 0;    //How many times have hang been decreased?
	//int the two following index 0 may NOT be used (what the fuck did I meen?)
	int chainSize[NUMBEROFCHAINS] {}; //Contains the chains
	bool chainUsed[NUMBEROFCHAINS] {};  //True if the chain is used

	unsigned int nextRandomNumber = 0;
	int Level = 0; //Only used in stageClear and puzzle (not implemented)

	BlockGameInfo replayInfo;

	int rand2();
	int firstUnusedChain();

protected:
	int lastCounter = 0;
	std::string strHolder;
	bool bDraw = false;
	unsigned int ticks = 0;
	unsigned int gameStartedAt = 0;
	unsigned int gameEndedAfter = 0;        //How long did the game last?
	int linesCleared = 0;
	int TowerHeight = 0;
	int board[7][30];
	int stop = 0;
	int speedLevel = 0;
	int pixels = 0;
	int MovesLeft = 0;
	bool timetrial = false;
	bool stageClear = false;
	bool vsMode = false;
	bool puzzleMode = false;
	int stageClearLimit = 0; //stores number of lines user must clear to win
	int combo = 0;
	int chain = 0;
	int cursorx = 2; //stores cursor position
	int cursory = 3; // -||-
	int mouse_cursorx = -1;  //Stores the mouse hold cursor. -1 if nothing is selected.
	int mouse_cursory = -1;
	double speed = 0.0;
	double baseSpeed = 0.0; //factor for speed. Lower value = faster gameplay
	int score = 0;
	bool bGameOver = false;
	bool hasWonTheGame = false;
	int AI_MoveSpeed = 0;   //How often will the computer move? milliseconds
	bool AI_Enabled = false;
	bool recordStats = true;
	bool vsAI = false;  //Set to true for single player vs

	int handicap = 0;

	// Number of different basic blocks. Default is 6. Easy mde is limited t 5.
	int basicBlockVariants = 6;

	std::vector<GarbageStruct> garbageSendQueue;

	int AIlineToClear = 0;

	short AIstatus = 0;   //Status flags:
	//0: nothing, 2: clear tower, 3: clear horisontal, 4: clear vertical
	//1: make more lines, 5: make 2 lines, 6: make 1 line

public:

	std::string name;

public:
	BlockGame();
	virtual ~BlockGame() = default;

	int getAIlevel()  const;

	virtual void AddText(int, int, unsigned int, int) const  {}
	virtual void AddBall(int, int, bool, int) const  {}
	virtual void AddExplosion(int, int) const  {}
	virtual void PlayerWonEvent() const  {}
	virtual void DrawEvent() const {}
	virtual void BlockPopEvent() const  {}
	virtual void LongChainDoneEvent() const  {}
	virtual void TimeTrialEndEvent() const  {}
	virtual void EndlessHighscoreEvent() const  {}

	void NewGame(const BlockGameStartInfo& s);
	void DoAction (const BlockGameAction& action);
	/**
	 * This function returns all the garbage.
	 * This is actual const in the way that it does not change the games state
	 * Technically it is not const because it empties the queue that are stored inside the object even if not part of the game state.
	 * @param poppedData
	 */
	void PopSendGarbage(std::vector<GarbageStruct>& poppedData);

	int GetScore() const;
	int GetHandicap() const;
	bool isGameOver() const;
	int GetGameStartedAt() const;
	int GetGameEndedAt() const;
	bool isTimeTrial() const;
	bool isStageClear() const;
	bool isVsMode() const;
	bool isPuzzleMode() const;
	int GetLinesCleared() const;
	int GetStageClearLimit() const;
	int GetChains() const;
	int GetPixels() const;
	int GetSpeedLevel() const;
	int GetTowerHeight() const;
	int GetCursorX() const;
	int GetCursorY() const;
	void GetMouseCursor(bool& pressed, int& x, int& y) const;
	bool GetIsWinner() const;
	bool isSinglePuzzle() const;
	int getLevel() const;
	bool GetAIenabled() const;
	bool IsNearDeath() const;
	double GetBaseSpeed() const {
		return baseSpeed;
	}
	int GetBasicBlockVariants() const {
		return basicBlockVariants;
	}
	const BlockGameInfo& GetBlockGameInfo() {
		return replayInfo;
	}
	void putSampleBlocks();
private:
	void NewGameInternal(unsigned int ticks);
	//Test if LineNr is an empty line, returns false otherwise.
	bool LineEmpty(int lineNr) const;
	//Test if the entire board is empty (used for Puzzles)
	bool BoardEmpty() const;
	//Anything that the user can't move? In that case Game Over cannot occur
	bool hasStaticContent() const;
	void putStartBlocks();
	void putStartBlocks(int n);
	//decreases hang for all hanging blocks and wait for waiting blocks
	void ReduceStuff();
	void setGameSpeed(int globalSpeedLevel);
	void setHandicap(int globalHandicap);
	//Clears garbage, must take one the lower left corner!
	int GarbageClearer(int x, int y, int number, bool aLineToClear, int chain);
	//Marks garbage that must be cleared
	int GarbageMarker(int x, int y);
	int FirstGarbageMarker(int x, int y);
	//Clear Blocks if 3 or more is alligned (naive implemented)
	void ClearBlocks();
	//Moves all peaces a spot down if possible
	int FallBlock(int x, int y, int number);
	//Makes all Garbage fall one spot
	void GarbageFall();
	//Makes the blocks fall (it doesn't test time, this must be done before hand)
	void FallDown();
	//Pushes a single pixel, so it appears to scrool
	void PushPixels();
	//See how high the tower is, saved in integer TowerHeight
	void FindTowerHeight();
	//Generates a new line and moves the field one block up (restart puzzle mode)
	void PushLine();
	//prints "Game Over" and ends game
	void SetGameOver();
	//Moves the cursor, receaves N,S,E or W as a char an moves as desired
	void MoveCursor(char way);
	//switches the two blocks at the cursor position, unless game over
	void SwitchAtCursor();
	//Creates garbage using a given wide and height
	bool CreateGarbage(int wide, int height);
	//Creates garbage using a given wide and height
	bool CreateGreyGarbage();
	void MouseDown(int x, int y);  //Send then the mouse is pressed
	void MouseMove(int x);  //Send then the mouse moves
	void MouseUp();  //Send then the mouse goes up
	void MoveCursorTo(int x, int y);
	void FinalizeBlockGameInfo();
///////////////////////////////////////////////////////////////////////////
/////////////////////////// AI starts here! ///////////////////////////////
///////////////////////////////////////////////////////////////////////////
	//First the helpet functions:
	int nrOfType(int line, int type);
	int AIcolorToClear = 0;
	//See if a combo can be made in this line
	int horiInLine(int line);
	bool horiClearPossible();
	//the Line Has Unmoveable Objects witch might stall the AI
	bool lineHasGarbage(int line);
	//Types 0..6 in line
	int nrOfRealTypes(int line);
	//See if there is a tower
	bool ThereIsATower();
	double firstInLine1(int line);
	//returns the first coordinate of the block of type
	double firstInLine(int line, int type);
	//There in the line shall we move
	int closestTo(int line, int place);
	//The AI will remove a tower
	void AI_ClearTower();
	//The AI will try to clear block horisontally
	void AI_ClearHori();
	//Test if vertical clear is possible
	bool veriClearPossible();
	//There in the line shall we move
	int closestTo(int line, int type, int place);
	//The AI will try to clear blocks vertically
	void AI_ClearVertical();
	bool firstLineCreated = 0;
	void AI_Move();
//////////////////////////////////////////////////////////////////
///////////////////////////// AI ends here! //////////////////////
//////////////////////////////////////////////////////////////////
	//Set the move speed of the AI based on the aiLevel parameter
	void setAIlevel(int aiLevel);
	void PushLineInternal();
	//Prints "winner" and ends game
	void setPlayerWon();
	//Prints "draw" and ends the game
	void setDraw();
	//Updates evrything, if not called nothing happends
	void Update();
	void UpdateInternal(unsigned int newtick);
};

//Play the next level
void nextLevel(BlockGame& g, unsigned int ticks);
//Replay the current level
void retryLevel(BlockGame& g, unsigned int ticks);

#endif
