#ifndef ENTITY_H
#define ENTITY_H

#include "pixelGameEngine.h"
#include "item.h"
#include <cassert>

extern std::vector<Item*>PARTY_INVENTORY;
extern int INVENTORY_CONSUMABLE_OFFSET[4];
extern int ITEM_SELECTION_CURSOR;
extern int ITEM_SELECTION_OFFSET;

using namespace olc;

class Object;

struct BoostData{
    int atk=0,hp=0,pp=0,spd=0;
};


class Entity{
    public:
    struct pstats_t{
        public:
        int HP=0;
        int maxHP=0;
        int PP=0;
        int maxPP=0;
        int targetHP=0;
        int targetPP=0;
        int baseAtk=0;
        int speed=0;
        std::array<int,4>resistances={0,0,0,0};
        int damageReduction=0; //A percentage of how much damage to reduce.
        bool smart=false;
        bool dumb=false;
    };
    struct stats_t{
        private:
        int HP=0;
        public:
        int maxHP=0;
        private:
        int PP=0;
        public:
        int maxPP=0;
        private:
        int targetHP=0;
        int targetPP=0;
        public:
        int baseAtk=0;
        int speed=0;
        std::array<int,4>resistances={0,0,0,0};
        int damageReduction=0; //A percentage of how much damage to reduce.
        bool smart=false;
        bool dumb=false;
        stats_t(pstats_t stats) {
            this->HP=stats.HP;
            this->maxHP=stats.maxHP;
            this->PP=stats.PP;
            this->maxPP=stats.maxPP;
            this->targetHP=stats.targetHP;
            this->targetPP=stats.targetPP;
            this->baseAtk=stats.baseAtk;
            this->speed=stats.speed;
            this->resistances=stats.resistances;
            this->damageReduction=stats.damageReduction;
            this->smart=stats.smart;
            this->dumb=stats.dumb;
        };
        bool operator==(const stats_t&t)const{
            return HP==t.HP&&
            maxHP==t.maxHP&&
            PP==t.PP&&
            maxPP==t.maxPP&&
            baseAtk==t.baseAtk&&
            speed==t.speed&&
            resistances==t.resistances&&
            damageReduction==t.damageReduction&&
            smart==t.smart&&
            dumb==t.dumb;
        }
        //Get the HP that the rolling counter is moving towards.
        int GetTargetHP(){
            return targetHP;
        }
        //Gets the current actual health of the target.
        int GetHP(){
            return HP;
        }
        //Get the PP that the rolling counter is moving towards.
        int GetTargetPP() {
            return targetPP;
        }
        //Gets the current actual pp of the target.
        int GetPP() {
            return PP;
        }
        //Sets the rolling counter target to this health value.
        void SetTargetHP(int hp) {
            targetHP=hp;
        }
        //Sets the rolling counter target to this pp value.
        void SetTargetPP(int pp) {
            targetPP=pp;
        }
        //Subtracts from the rolling counter target from this health value.
        void SubtractHP(int hp) {
            targetHP-=hp;
        }
        //Adds to the rolling counter target from this health value.
        void AddHP(int hp) {
            targetHP=std::clamp(targetHP+hp,0,maxHP);
        }
        //Subtracts from the rolling counter target from this pp value.
        void SubtractPP(int pp) {
            targetPP=std::clamp(targetPP-pp,0,maxPP);
        }
        //Adds to the rolling counter target from this pp value.
        void AddPP(int pp) {
            targetPP=std::clamp(targetPP+pp,0,maxPP);
        }
        //THIS IS FOR SPECIAL USE CASES ONLY! Normally you want to touch the rolling counter amount instead using SetTargetHP()!
        void _SetDirectHP(int hp) {
            HP=hp;
        }
        //THIS IS FOR SPECIAL USE CASES ONLY! Normally you want to touch the rolling counter amount instead using SetTargetPP()!
        void _SetDirectPP(int pp) {
            PP=pp;
        }
    };
    private:
    pstats_t pstats;
    int partyMemberID;
    public:
    stats_t stats;
    int atb=0; //When this value reaches 1000, it's this entity's turn.
    public:
    Object* obj;
    std::vector<Battle::Move*> moveSet;
    std::map<Property,int> statusEffects;
    int selectedTarget = NO_TARGET;
    Battle::Move*selectedMove = nullptr; //The index of the selected move.
    int channelTimeRemaining = 0; //The amount of channel time left until move can be performed.
    vd2d channelPos = DEFAULT_CHANNELPOS; //Where our channel is happening.
    std::vector<Item*> inventory; //Used mostly for enemy spoils.
    int money=0; //Amount of money this entity will drop when defeated.
    std::array<Item*,3> equipment = {nullptr,nullptr,nullptr}; //Equipment this character is using.
    bool isPlayer=false; //Whether or not this is a player entity.
    BoostData boosts = {0,0,0,0};
    //Used for initializing players.
    Entity(int partyMemberID,pstats_t stats,std::vector<Battle::Move*>moveSet,std::vector<Item*>items={},int money=0,std::array<Item*,3>equipment={})
    :Entity(nullptr,stats,moveSet,items,money,equipment){
        this->partyMemberID=partyMemberID;
        isPlayer=true;
    }
    //Use this for initializing enemies as it lets you specify an object.
    Entity(Object*obj,pstats_t stats,std::vector<Battle::Move*>moveSet,std::vector<Item*>items={},int money=0,std::array<Item*,3>equipment={})
    :obj(obj),stats({HP:stats.HP,maxHP:stats.maxHP,PP:stats.PP,maxPP:stats.maxPP,targetHP:stats.targetHP,targetPP:stats.targetPP,baseAtk:stats.baseAtk,speed:stats.speed,resistances:stats.resistances,damageReduction:stats.damageReduction,smart:stats.smart,dumb:stats.dumb}),equipment(equipment),moveSet(moveSet),inventory(items),money(money){
        for (int i=0;i<4;i++) {
            this->stats.resistances[i]=stats.resistances[i];
        }
        this->stats.SetTargetHP(stats.HP);
        this->stats.SetTargetPP(stats.PP);
    }
    //Get the HP that the rolling counter is moving towards.
    int GetTargetHP() {
        return stats.GetTargetHP();
    }
    //Gets the current actual health of the target.
    int GetHP() {
        return stats.GetHP();
    }
    //Get the PP that the rolling counter is moving towards.
    int GetTargetPP() {
        return stats.GetTargetPP();
    }
    //Gets the current actual pp of the target.
    int GetPP() {
        return stats.GetPP();
    }
    //Sets the rolling counter target to this health value.
    void SetTargetHP(int hp) {
        stats.SetTargetHP(hp);
    }
    //Sets the rolling counter target to this pp value.
    void SetTargetPP(int pp) {
        stats.SetTargetPP(pp);
    }
    //Subtracts from the rolling counter target from this health value.
    void SubtractHP(int hp) {
        stats.SubtractHP(hp);
    }
    //Adds to the rolling counter target from this health value.
    void AddHP(int hp) {
        stats.AddHP(hp);
    }
    //Subtracts from the rolling counter target from this pp value.
    void SubtractPP(int pp) {
        stats.SubtractPP(pp);
    }
    //Adds to the rolling counter target from this pp value.
    void AddPP(int pp) {
        stats.AddPP(pp);
    }
    //THIS IS FOR SPECIAL USE CASES ONLY! Normally you want to touch the rolling counter amount instead using SetTargetHP()!
    void _SetDirectHP(int hp) {
        stats._SetDirectHP(hp);
    }
    //THIS IS FOR SPECIAL USE CASES ONLY! Normally you want to touch the rolling counter amount instead using SetTargetPP()!
    void _SetDirectPP(int pp) {
        stats._SetDirectPP(pp);
    }

    friend std::ostream&operator<<(std::ostream &os,Entity&ent);
    
    Property GetPrimaryStatusEffect() {
        for (std::map<Property,int>::iterator it = statusEffects.begin();it!=statusEffects.end();++it) {
            if (it->second!=0) {
                return it->first;
            }
        }
        return Property::NONE;
    }

    void RemoveItem(int index) {
		int prevPartyInvenSize=PARTY_INVENTORY.size();
        if (isPlayer) {
            for (int i=0;i<4;i++) {
                if (index<INVENTORY_CONSUMABLE_OFFSET[i]) {
                    INVENTORY_CONSUMABLE_OFFSET[i]--; //Items 
                }
            }
            PARTY_INVENTORY.erase(PARTY_INVENTORY.begin()+index);
        } else {
            inventory.erase(inventory.begin()+index);
        }
        if (ITEM_SELECTION_CURSOR>=PARTY_INVENTORY.size()&&prevPartyInvenSize&1&&PARTY_INVENTORY.size()==prevPartyInvenSize-1) {
            ITEM_SELECTION_OFFSET-=2;
        }
        ITEM_SELECTION_CURSOR=std::clamp(ITEM_SELECTION_CURSOR,0,(int)PARTY_INVENTORY.size()-1);
    }

    //If index is -1, the item is inserted at the end of the list. Otherwise it's going to overwrite a certain slot. Be certain you swap the item before doing this!
    void RemoveEquip(EquipSlot::Equip slot,int index=-1) {
        Item*CurrentEquip=equipment[slot];
        if (CurrentEquip==nullptr) {
            return;
        }
        if (isPlayer) {
            if (index==-1) {
                PARTY_INVENTORY.push_back(CurrentEquip);
            } else {
                PARTY_INVENTORY[index]=CurrentEquip;
            }
            equipment[slot]=nullptr;
        } else {
            if (index==-1) {
                inventory.push_back(CurrentEquip);
            } else {
                inventory[index]=CurrentEquip;	
            }
            equipment[slot]=nullptr;
        }
    }

    //Will automatically swap items with current equipment so no need to call remove first.
    void EquipItem(int index) {
		int prevPartyInvenSize=PARTY_INVENTORY.size();
        Item*equip=nullptr;
        if (isPlayer) {
            equip=PARTY_INVENTORY[index];
        } else {
            equip=inventory[index];
        }
        if (equip==nullptr) {
            return;
        }
        if (equip->stats.equip==EquipSlot::NONE) {
            printf("Cannot equip %s! Does not have a valid Equip slot!\n",equip->name.c_str());
            return;
        }
        Item*CurrentEquip=equipment[equip->stats.equip];
        if (CurrentEquip==nullptr) {
            if (isPlayer) {
                PARTY_INVENTORY.erase(PARTY_INVENTORY.begin()+index);
            } else {
                inventory.erase(inventory.begin()+index);
            }
        } else {
            RemoveEquip(CurrentEquip->stats.equip,index);
        }
        equipment[equip->stats.equip]=equip;
        if (ITEM_SELECTION_CURSOR>=PARTY_INVENTORY.size()&&prevPartyInvenSize&1&&PARTY_INVENTORY.size()==prevPartyInvenSize-1) {
            ITEM_SELECTION_OFFSET-=2;
        }
        ITEM_SELECTION_CURSOR=std::clamp(ITEM_SELECTION_CURSOR,0,(int)PARTY_INVENTORY.size()-1);
    }
};
#endif