#ifndef CUTSCENE_H
#define CUTSCENE_H
#include "pixelGameEngine.h"
#include "defines.h"
#include "flags.h"
#include "object.h"

#define CAMERA_MOVESPD 5
#define BATTLE_CAMERA_MOVESPD 2.5

using namespace olc;

namespace CutsceneName{
    enum Cutscene{
        TEST_CUTSCENE,
        TRANSFER_MAP_CUTSCENE,
        LOAD_FILE_CUTSCENE,
        INTRO_CUTSCENE,
    };
}

enum class ActionType{
    NONE,
    PAN_CAMERA,
    CREATE_OBJECTS,
    CLEANUP,
    SET_FLAG_WHEN_CUTSCENE_ENDS,
    FADE,
    DIALOG,
    MOVE_CUTSCENE_OBJ,
    PAN_CAMERA_ASYNC,
    FADE_ASYNC,
    DIALOG_ASYNC,
    MOVE_CUTSCENE_OBJ_ASYNC,
    MODIFY_OBJECT,
    LOAD_MAP,
    MOVE_PLAYER_OBJS,
    LOAD_FILE,
    SET_FLAG,
    SET_CUTSCENE_DISPLAY_TEXT,
};

class CutsceneAction{
    bool queued=false; //Set to true when added to action asynchronous queue.
    public:
        virtual ActionType GetActionType()=0;
        bool InQueue() {
            return queued;
        }
        void SetQueued(bool queued=true) {
            this->queued=queued;
        }
};

class Cleanup:public CutsceneAction{
    public:
        ActionType GetActionType() override{return ActionType::CLEANUP;}
};

class Object;
class Animation;
class Map;

class CreateObjects:public CutsceneAction{
    protected:
        std::vector<Object*>objs;
    public:
        template <class T>
        CreateObjects(std::initializer_list<T> objs) {
            for( auto elem : objs )
            {
                this->objs.push_back(elem);
            }
        }
        ActionType GetActionType() override{return ActionType::CREATE_OBJECTS;}
        std::vector<Object*> GetObjects() {
            return objs;
        };
 };

class PanCamera:public CutsceneAction{
    protected:
        vd2d targetPos;
        PriorityDirection dir;
        double cameraSpd;
    public:
        PanCamera(vd2d targetPos,PriorityDirection dir,double cameraSpd=CAMERA_MOVESPD) {
            this->targetPos=targetPos;
            this->dir=dir;
            this->cameraSpd=cameraSpd;
        }
        ActionType GetActionType() override{return ActionType::PAN_CAMERA;}
        vd2d GetCameraTargetPos() {
            return targetPos;
        }
        PriorityDirection GetPriorityDirection() {
            return dir;
        }
        double GetCameraSpeed() {
            return cameraSpd;
        }
 };

class PanCameraAsync:public PanCamera{
    public:
        PanCameraAsync(vd2d targetPos,PriorityDirection dir,double cameraSpd=CAMERA_MOVESPD)
        :PanCamera(targetPos,dir,cameraSpd){}
        ActionType GetActionType() override{return ActionType::PAN_CAMERA_ASYNC;}
 };

class MoveCutsceneObject:public CutsceneAction{
    protected:
        int objID;
        vd2d targetPos;
        double moveSpd;
        PriorityDirection dir;
    public:
        MoveCutsceneObject(int objID,vd2d targetPos,double moveSpd=CAMERA_MOVESPD,PriorityDirection dir=BOTH) {
            this->objID=objID;
            this->targetPos=targetPos;
            this->moveSpd=moveSpd;
            this->dir=dir;
        }
        ActionType GetActionType() override{return ActionType::MOVE_CUTSCENE_OBJ;}
        //Based on how they were created in the cutscene, an object ID starting from 0 indicating which object you want to manipulate.
        int GetObjectID() {
            return objID;
        }
        double GetMoveSpd() {
            return moveSpd;
        }
        vd2d GetTargetPos() {
            return targetPos;
        }
        PriorityDirection GetMovement() {
            return dir;
        }
};

class MoveCutsceneObjectAsync:public MoveCutsceneObject{
    public:
        MoveCutsceneObjectAsync(int objID,vd2d targetPos,double moveSpd=CAMERA_MOVESPD,PriorityDirection dir=BOTH)
        :MoveCutsceneObject(objID,targetPos,moveSpd){}
        ActionType GetActionType() override{return ActionType::MOVE_CUTSCENE_OBJ_ASYNC;}
 };

class SetFlagWhenCutsceneEnds:public CutsceneAction{
    private:
        Flag flag;
        bool val;
    public:
        SetFlagWhenCutsceneEnds(Flag flag,bool val=true) {
            this->flag=flag;
            this->val=val;
        }
        ActionType GetActionType() override{return ActionType::SET_FLAG_WHEN_CUTSCENE_ENDS;}
        Flag GetCutsceneEndingFlag() {
            return flag;
        }
        bool GetCutsceneEndingVal() {
            return val;
        }
 };

class Fade:public CutsceneAction{
    protected:
        bool fadeIn=true; //If false, it fades out instead.
        double fadeSpd=0;
    public:
        //If false, it fades out instead.
        Fade(bool fadeIn=false,double fadeSpd=4) {
            this->fadeIn=fadeIn;
            this->fadeSpd=fadeSpd;
        }
        ActionType GetActionType() override{return ActionType::FADE;}
        //If false, then it means we need to fade out instead.
        bool FadeIn() {
            return fadeIn;
        }
        double GetFadeSpd() {
            return fadeSpd;
        }
 };

class FadeAsync:public Fade{
    public:
        //If false, it fades out instead.
        FadeAsync(bool fadeIn=false,double fadeSpd=4)
        :Fade(fadeIn,fadeSpd){}
        ActionType GetActionType() override{return ActionType::FADE_ASYNC;}
 };

class DialogBox:public CutsceneAction{
    protected:
        std::string message;
        bool messageBoxVisible=false;
    public:
        //If false, it fades out instead.
        DialogBox(std::string message) {
            this->message=message;
        }
        ActionType GetActionType() override{return ActionType::DIALOG;}
        //If false, then it means we need to fade out instead.
        std::string GetMessage() {
            return message;
        }
        void SetMessageBoxVisible() {
            this->messageBoxVisible=true;
        }
        bool MessageHasBeenShown() {
            return messageBoxVisible;
        }
 };

class DialogBoxAsync:public DialogBox{
    public:
        //If false, it fades out instead.
        DialogBoxAsync(std::string message)
        :DialogBox(message){}
        ActionType GetActionType() override{return ActionType::DIALOG_ASYNC;}
 };

class ModifyObject:public CutsceneAction{
    protected:
        int objID;
        vd2d scale;
        Pixel col;
        Animation*spr;
        int animationFrame; //Defaults to -1, meaning don't touch the animation frame at all.
        int animationSpd; //How many frames to wait between each animation frame. Setting to 0 pauses the animation.
    public:
        ModifyObject(int objID,Animation*spr,vd2d scale={1,1},Pixel col=WHITE,int animationFrame=-1,int animationSpd=12) {
            this->objID=objID;
            this->scale=scale;
            this->col=col;
            this->spr=spr;
            this->animationFrame=animationFrame;
            this->animationSpd=animationSpd;
        }
        ActionType GetActionType() override{return ActionType::MODIFY_OBJECT;}
        int GetCutsceneObjID(){
            return objID;
        }
        vd2d GetObjScale() {
            return scale;
        }
        Pixel GetObjCol() {
            return col;
        }
        Animation*GetObjSpr() {
            return spr;
        }
        //Defaults to -1, meaning don't touch the animation frame at all.
        int GetFrameIndex() {
            return animationFrame;
        }
        //How many frames to wait between each animation frame. Setting to 0 pauses the animation.
        int GetAnimationSpeed() {
            return animationSpd;
        }
};

class LoadMap:public CutsceneAction{
    protected:
        Map*map;
    public:
        LoadMap(Map*newMap)
        :map(newMap) {}
        ActionType GetActionType() override{return ActionType::LOAD_MAP;}
        Map*GetTargetMap(){
            return map;
        }
        void SetTargetMap(Map*map){
            this->map=map;
        }
};

class MovePlayerObjects:public CutsceneAction{
    protected:
        vd2d newPos;
    public:
        MovePlayerObjects(vd2d newPos)
        :newPos(newPos) {}
        ActionType GetActionType() override{return ActionType::MOVE_PLAYER_OBJS;}
        vd2d GetTargetPos(){
            return newPos;
        }
        void SetTargetPos(vd2d newPos){
            this->newPos=newPos;
        }
};

class LoadGame:public CutsceneAction{
    protected:
        int saveslot;
    public:
        LoadGame(int saveslot)
        :saveslot(saveslot) {}
        ActionType GetActionType() override{return ActionType::LOAD_FILE;}
        int GetSaveFileSlot(){
            return saveslot;
        }
};

class SetFlag:public CutsceneAction{
    protected:
        Flag flag;
        bool value;
    public:
        SetFlag(Flag flag,bool value)
        :flag(flag),value(value) {}
        ActionType GetActionType() override{return ActionType::SET_FLAG;}
        Flag GetFlag(){
            return flag;
        }
        bool GetValue(){
            return value;
        }
};

class SetCutsceneDisplayText:public CutsceneAction{
    protected:
        std::string text;
        int delay;
    public:
        //delay in frames
        SetCutsceneDisplayText(std::string text,int delay)
        :text(text),delay(delay) {}
        ActionType GetActionType() override{return ActionType::SET_CUTSCENE_DISPLAY_TEXT;}
        std::string GetCutsceneText(){
            return text;
        }
        int GetDelay() {
            return delay;
        }
};

/*
To use this class, specify multiple actions back-to-back, filling their appropriate arguments.

In responsive events, poll what CurrentAction() is, and if it's suitable, then use LockAction()
to lock the event and perform whatever actions are required. When the cutscene action is complete,
you can advance the action using AdvanceAction().
*/
class Cutscene{
    private:
        int actionMarker=0;
        bool actionIsActive=false;
        std::vector<Object*>cutsceneObjs;
        Flag storedFlag=Flag::NONE;
        bool storedVal=true;
    protected:
        std::vector<CutsceneAction*> actions;
    public:
        template <class T>
        Cutscene(std::initializer_list<T> actions) {
            AddAction(actions);
        };
        template <class T>
        void AddAction(std::initializer_list<T> actions )
        {
            for( auto elem : actions )
            {
                this->actions.push_back(elem);
            }
            this->actions.push_back(new Cleanup());
        }
        CutsceneAction*GetAction(){
            return actions[actionMarker];
        }
        ActionType CurrentAction(){
            if (!actionIsActive&&actionMarker<actions.size()) {
                return actions[actionMarker]->GetActionType();
            } else {
                return ActionType::NONE;
            }
        }
        void LockAction() {
            actionIsActive=true;
        }
        void AdvanceAction(){
            actionIsActive=false;
            actionMarker++;
        }
        void ResetCutscene(){
            actionMarker=0;
            actionIsActive=false;
            for (auto&action:actions) {
                action->SetQueued(false);
            }
        }
        Object*AddCutsceneObject(Object*obj) {
            this->cutsceneObjs.push_back(obj);
            return obj;
        }
        std::vector<Object*> GetCutsceneObjects() {
            return cutsceneObjs;
        }
        //Does things like reset cutscene flags and removing objects.
        //
        //MAKE SURE TO DELETE ALL OBJECTS IN THE OBJECTS WORLD ARRAY BEFORE CALLING THIS!
        //THIS FUNCTION WILL DELETE THE POINTERS FOR YOU!
        void CleanupCutscene() {
            cutsceneObjs.clear();
        }
        void SetupEndingCutsceneFlag(Flag flag,bool val=true) {
            storedFlag=flag;
            storedVal=val;
        }
        Flag GetEndingCutsceneFlag() {
            return storedFlag;
        }
        bool GetEndingCutsceneVal() {
            return storedVal;
        }
};

class MapTransitionCutscene:public Cutscene{
    public:
    MapTransitionCutscene()
    :Cutscene({
        액션 Fade(),
        액션 LoadMap(nullptr),
        액션 MovePlayerObjects({0,0}),
        액션 Fade(true),
    }){}
    std::vector<CutsceneAction*>GetActions(){
        return actions;
    }
};

extern std::array<NPC_Obj*,4> PARTY_MEMBER_OBJ;

class LoadFileCutscene:public Cutscene{
    public:
    LoadFileCutscene()
    :Cutscene({
        액션 Fade(),
        액션 LoadGame(0),
        액션 Fade(true),
    }){}
    std::vector<CutsceneAction*>GetActions(){
        return actions;
    }
};
#endif