#ifndef OBJECT_H
#define OBJECT_H
#include "pixelGameEngine.h"
#include "flags.h"
#include "animation.h"
#include "defines.h"
#include "layers.h"
#include "item.h"
#include "states.h"
#include "entity.h"
#include "SeasonI.h"

using namespace olc;

extern std::vector<Object*> OBJECTS;

struct Interaction{
	std::vector<std::string> messages={};
	Item*item=nullptr;
	Flag flag=Flag::NONE;
};

struct ObjectExtraData{
	int moveTime,moveFreq;
	double moveSpd;
};

class Object{
	private:
	vd2d scale={1,1};
	vd2d pos;
	vd2d startPos; //Where the object was first located. The object will be put back here if we enter the editor.
	public:
	int id;
	Animation*spr;	
	int animationSubIndex=0; //Which animation within the animation we are on.
	int frameIndex=0;
	int frameCount=0;
	int animationSpd=12; //How many frames to wait between each frame. Setting to 0 pauses the animation.
	std::string name;
	bool animated=true;
	Pixel color=WHITE;
	vd2d originPoint={0,0};
	bool drawn=false;
	Flag disableFlag=Flag::NONE;
	Flag enableFlag=Flag::NONE;
	ObjectExtraData extraData;
	int objArrElement; //Which element in the object array this object is located in. For sorting purposes.
	bool temp=false; //If set to true, it's marked for deletion after cutscene handling.
	bool enc=false; //If set to true, it's not included in the main list of entities for map saving because it's from an encounter.
	bool dead=false; //If set to true, this object was properly part of an Entity and got declared as dead.
	int blinkFrames=0; //Frame count of how much time is left for the image to be blinking. Used when enemies take damage.
	//animationSpd is how long to wait before switching frames.
	bool highlighted=false; //Whether or not this object has been declared as highlighted by a target range selector.
	bool Collision(vd2d pos) {
		GAME->SetDrawTarget(layer::COLLISION);
		Pixel collisionData = GAME->GetDrawTarget()->GetPixel((int)pos.x-cameraPos.x,(int)pos.y-cameraPos.y);
		if (collisionData!=MAGENTA) {
			return true;
		} else {
			for (int i=0;i<OBJECTS.size();i++) {
				if (OBJECTS[i]!=this) {
					if (std::abs(OBJECTS[i]->GetPosWithOrigin().x-pos.x)+std::abs(OBJECTS[i]->GetPosWithOrigin().y-pos.y)<4) {
						return true;
					}
				}
			}
		}
		return false;
	}
	//A grid version of the constructor. used ONLY for battle setups.
	Object(int id,std::string name,int gridx,int gridy,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})
	:Object(id,name,{gridx*32-(spr->sprSize.x*0.5)*(scale.x-1),gridy*32-(spr->sprSize.y-4)*(scale.y-1)},spr,scale,color,animationSpd,temp,data) {}
	Object(int id,std::string name,vd2d pos,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})
	:spr(spr),pos(pos),id(id),name(name),color(color),animationSpd(animationSpd),temp(temp),extraData(data),startPos(pos) {
		SetScale(scale);
	}
	virtual Object* CreateType(int id,std::string name,vd2d pos,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})=0;
	//When the player tries to interact with this object.
	virtual Interaction Interact()=0;
	virtual void ChoiceMade(int choice)=0;
	virtual void DialogClosed()=0;
	virtual void ShoppingCompleted()=0;
	virtual void Update()=0;
	void SetScale(vd2d scale) {
		this->scale=scale;
		if (spr!=nullptr) {
			this->originPoint={(double)spr->sprSize.x/2*scale.x,(spr->sprSize.y-4)*scale.y};
		}
	}
	vd2d GetScale() {
		return scale;
	}
	vd2d GetPos() {
		return pos;
	}
	//Get where the object was first located. The object will be put back here if we enter the editor.
	vd2d GetStartPos() {
		return startPos;
	}
	void Move(vd2d move);
	void SetPos(vd2d pos) {
		Move(pos-this->pos);
	}
	vd2d GetPosWithOrigin() {
		return GetPos()+originPoint;
	}
	bool SmoothMove(vd2d move) {
		const int wiggleRoom=5;
		vd2d originPos = {pos.x+originPoint.x,pos.y-1+originPoint.y};
		if (!Collision(originPos+move)) {
			Move(move);
			return true;
		} else
		if (move.x!=0&&!Collision({originPos.x+move.x,originPos.y})) {
			Move({move.x,0});
			return true;
		} else
		if (move.y!=0&&!Collision({originPos.x,originPos.y+move.y})) {
			Move({0,move.y});
			return true;
		}
		
		if (move.x!=0) {
			for (int i=0;i<wiggleRoom;i++) { //Search Up.
				if (!Collision({originPos.x+move.x,originPos.y-i})) {
					//There is potentially to move up-right here, so we will do so.
					Move({0,-1});
					originPos.y+=-1;
					return true;
				}
			}
			for (int i=0;i<wiggleRoom;i++) { //Search Down.
				if (!Collision({originPos.x+move.x,originPos.y+i})) {
					//There is potentially to move down-right here, so we will do so.
					Move({0,1});
					return true;
				}

				if (!Collision({originPos.x+move.x,originPos.y-i})) {
					//There is potentially to move up-left here, so we will do so.
					Move({0,-1});
					return true;
				}
			}
			for (int i=0;i<wiggleRoom;i++) { //Search Down.
				if (!Collision({originPos.x+move.x,originPos.y+i})) {
					//There is potentially to move down-left here, so we will do so.
					Move({0,1});
					return true;
				}
			}
		}
		if (move.y!=0) {
			for (int i=0;i<wiggleRoom;i++) { //Search Left.
				if (!Collision({originPos.x-i,originPos.y+move.y})) {
					//There is potentially to move down-left here, so we will do so.
					Move({-1,0});
					return true;
				}
			}
			for (int i=0;i<wiggleRoom;i++) { //Search Right.
				if (!Collision({originPos.x+i,originPos.y+move.y})) {
					//There is potentially to move down-right here, so we will do so.
					Move({1,0});
					return true;
				}
			}
		}
		return false;
	}
};

#define DynamicObject(objName) public:\
	objName(int id,std::string name,int gridx,int gridy,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})\
	:Object(id,name,gridx,gridy,spr,scale,color,animationSpd,temp,data){};\
	objName(int id,std::string name,vd2d pos,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})\
	:Object(id,name,pos,spr,scale,color,animationSpd,temp,data){};\
	Object* CreateType(int id,std::string name,vd2d pos,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})override{\
		return new objName(id,name,pos,spr,scale,color,animationSpd,temp,data);\
	}

class Standard_Obj : public Object{
	DynamicObject(Standard_Obj)
	Interaction Interact()override{return {};}
	void DialogClosed()override{};
	void ChoiceMade(int choice)override{};
	void ShoppingCompleted()override{};
	void Update()override{};
};

class NPC_Obj : public Standard_Obj{
	float remainingMoveTime=0;
	Direction moveDir=Direction::SOUTH;
	public:
	NPC_Obj(int id,std::string name,vd2d pos,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})
	:Standard_Obj(id,name,pos,spr,scale,color,animationSpd,temp,data){
		animated=false;
		frameIndex=0;
	}
	Object* CreateType(int id,std::string name,vd2d pos,Animation*spr,vd2d scale={1,1},Pixel color=WHITE,int animationSpd=1,bool temp=false,ObjectExtraData data={moveTime:0,moveFreq:0,moveSpd:0})override{
		return new NPC_Obj(id,name,pos,spr,scale,color,animationSpd,temp,data);
	}
	void Update()override{
		if (extraData.moveFreq>0) {
			if (remainingMoveTime>0) {
				vd2d moveVector;
				animationSubIndex=(int)moveDir;
				switch (moveDir) {
					case Direction::SOUTH:{
						moveVector={0,1};
					}break;
					case Direction::EAST:{
						moveVector={1,0};
					}break;
					case Direction::WEST:{
						moveVector={-1,0};
					}break;
					case Direction::NORTH:{
						moveVector={0,-1};
					}break;
				}
				animated=SmoothMove(moveVector*extraData.moveSpd);
				remainingMoveTime--;
			} else {
				if (rand()%extraData.moveFreq==0) {
					remainingMoveTime=extraData.moveTime;
					moveDir=(Direction)(rand()%4);
				}
				animated=false;
			}
		}
	}
};

class TrashCan_Obj : public Object{
	DynamicObject(TrashCan_Obj)
	Interaction Interact()override{
		frameIndex=1;
		return {{"You dig around the trash can.","Nope! Just looks like plain ol' trash."}};}
	void DialogClosed()override{};
	void ChoiceMade(int choice)override{};
	void ShoppingCompleted()override{};
	void Update()override{};
};

extern int MESSAGE_BOX_DIALOG_ANSWER;
extern std::array<bool,512> GAME_FLAGS;
extern int GAME_STATE;
extern void SetupShop(Object*shopkeeper,std::vector<std::pair<Item*,int>> shopItems);
extern std::map<ItemName,Item*>ITEMLIST;
extern void DisplayMessageBox(std::string targetT);
extern int MESSAGE_BOX_DIALOG_ANSWER;
extern std::vector<Item*> PARTY_INVENTORY;
extern int ITEM_SELECTION_CURSOR;
extern int MONEY;
extern std::array<Entity*,7> PARTY_MEMBER_STATS;
extern std::array<int,4> PARTY_MEMBER_ID;
extern std::string A_An(std::string str);
extern int GAME_STATE;
extern SeasonI*GAME;
extern Object*SHOPKEEPER_INTERACTING_WITH;

class Shopkeeper_Obj : public Object{
	DynamicObject(Shopkeeper_Obj)
	std::vector<std::pair<Item*,int>> itemList;
	std::string welcomeMessage="Welcome! Please choose an option: [BUY,SELL,FDSAJIVDSAJ]\
		>0:Please take a look through our wares.\
		>1:What would you like to sell?\
		>2:Are you okay?<";
	void Update()override{};
	Interaction Interact()override{
		if (GAME->GetGameFlag(Flag::SHOPKEER_BRANCH1)) {
			GAME_FLAGS[(int)Flag::SHOPKEER_BRANCH1]=false;
			switch (MESSAGE_BOX_DIALOG_ANSWER) {
				case 2:{
					return {{"No! Stay away! [Okay,Ignore]\
					>0:Yeah I thought so.\
					>1:...<"}};
				}break;
			}
		} else {
			return {{welcomeMessage},flag:Flag::SHOPKEER_BRANCH1};
		}
	}
	void ChoiceMade(int choice)override{
		if (GAME->GetGameFlag(Flag::SHOPKEER_BRANCH1)) {
			if (choice!=2) {
				GAME->SetGameFlag(Flag::SHOPKEER_BRANCH1,false);
			}
		}
	}
	void DialogClosed()override{
		switch (MESSAGE_BOX_DIALOG_ANSWER) {
			case 0:{
				GAME_STATE = GameState::SHOPKEEPER_MENU;
				SetupShop(
					this,
					{
						{ITEMLIST[ItemName::EGG],8},
						{ITEMLIST[ItemName::COOKIE],4},
						{ITEMLIST[ItemName::PIZZA],36},
						{ITEMLIST[ItemName::SOME_STUPIDLY_LONG_FEATHER],46},
					}
				);
			}break;
			case 1:{
				GAME_STATE = GameState::SHOPKEEPER_SELL_MENU;
				SHOPKEEPER_INTERACTING_WITH=this;
			}break;
			default:{}
		}
	}
	void ShoppingCompleted()override{
		DisplayMessageBox("Thanks for shopping with us! Have a great day.");
	};
};
#endif