#include "ref.h"

#include "dk_gce_main.h"
#include "dk_gce_spline.h"
#include "dk_gce_point.h"
#include "dk_gce_interface.h"
#include "dk_cin_world_interface.h"
#include "dk_cin_playback.h"
#include "dk_gce_script.h"

#include "dk_shared.h"
#include "qcommon.h"
#include "client.h"

extern "C" __declspec(dllimport) int __stdcall GetTickCount(void);

//our private export functions
int GCE_Version();
void GCE_Import(cin_gce_import_t &import);
void LastCameraPos(point &pos, point &angles);
void UpdateScript(CCinematicScript &script);
void DrawString(int x, int y, const char *str);
refexport_t *GetRefExport();
const CCinematicScript *LoadScript(const char *name);
void DeleteScript(const CCinematicScript *script);
void GCE_AngleVectors(point &angles, point &forward, point &right, point &up);

void CIN_Toggle();
void cin_play_shot();

cvar_t *gce_set_position;

//our private interface to gce.dll
cin_gce_export_t gce_export;
cin_gce_import_t gce_import;

//our private interface to world.dll
cin_world_import_t world_import;
bool got_world_import = false;

void GCE_Init() {
    gce_export.GCE_Version = GCE_Version;
    gce_export.GCE_Import = GCE_Import;
    gce_export.LastCameraPos = LastCameraPos;
    gce_export.UpdateScript = UpdateScript;
    gce_export.DrawString = DrawString;
    gce_export.V_AddEntity = V_AddEntity;
    gce_export.GetRefExport = GetRefExport;
    gce_export.LoadScript = LoadScript;
    gce_export.DeleteScript = DeleteScript;
    gce_export.AngleVectors = GCE_AngleVectors;
    gce_export.EntityPosition = EntityPosition;
    gce_export.TeleportEntity = TeleportEntity;

    //register our commands for script playback.
    Cmd_AddCommand("cin_play", CIN_Toggle);
    Cmd_AddCommand("cin_play_shot", cin_play_shot);
}

cin_gce_import_t::cin_gce_import_t() {
    GCE_HUD = NULL;
    AddEntities = NULL;
    InputActive = NULL;
    InputKeyEvent = NULL;
    InputMouseMove = NULL;
    SetCamera = NULL;
}

refexport_t *GetRefExport() {
    return &re;
}

void *CIN_GCE_Interface() {
    return &gce_export;
}

void CIN_WorldPlayback(void *funcs) {
    //the pointer passed in is type cin_world_import_t
    world_import = *(cin_world_import_t *)funcs;

    got_world_import = true;
}

void GCE_Import(cin_gce_import_t &import) {
    gce_import = import;
}

void CIN_GCE_HUD() {
    if (gce_import.GCE_HUD != NULL) {
        try {
            gce_import.GCE_HUD();
        }
        catch (...) {
            gce_import.GCE_HUD = NULL;
        }
    }
}

void CIN_GCE_AddEntities() {
    if (gce_import.AddEntities != NULL) {
        try {
            gce_import.AddEntities();
        }
        catch (...) {
            gce_import.AddEntities = NULL;
        }
    }
}

//our script
CCinematicScript script;

static point gce_last_camera_pos;
static point gce_last_camera_angle;

//true if we are playing a script
bool playback_running = false;


void CIN_Toggle() {
    if (CIN_Running() == 1) {
        CIN_Stop(NULL);
    }
    else {
        CIN_Start(NULL, 0, script.NumShots() - 1);
    }
}

void cin_play_shot() {
    if (GetArgc() != 2) return;

    //get the shot number passed in.
    int shot = atoi(GetArgv(1));
    if (shot < 0) return;

    if (CIN_Running() == 1) {
        CIN_Stop(NULL);
    }
    else {
        //start the script.
        CIN_Start(NULL, shot, shot);
    }
}

void GCE_ClientFrame() {
    //save the position/angles of the camera in case we need it.
    gce_last_camera_pos = cl.refdef.vieworg;
    gce_last_camera_angle = cl.refdef.viewangles;

    CIN_Play();
}

void GCE_SetCamera() {
    //check if we have a cinematic running.
    if (CIN_Running() != 0) {
        CIN_CamLocation(cl.refdef.vieworg, cl.refdef.viewangles);
        return;
    }

    //check if the dll wants to modify the camera for some reason.
    if (gce_import.SetCamera != NULL) {
        try {
            gce_import.SetCamera(cl.refdef.vieworg, cl.refdef.viewangles);
        }
        catch (...) {
            gce_import.SetCamera = NULL;
        }
    }
}

static int cin_last_tick;

void CIN_Start(const char *name, int start_shot, int end_shot) {
    cin_last_tick = GetTickCount();

    playback_running = true;

    script.PlaybackReset(start_shot, end_shot);

    //check if the script has camera info.
    if (script.HaveCameraInfo() == true) {
        //turn on our special camera mode.
        Cbuf_AddText("cam_toggle cin_on\n");
    }
}

void CIN_Stop(const char *name) {
    playback_running = false;

    //check if the script has camera info.
    if (script.HaveCameraInfo() == true) {
        //turn off our special camera mode.
        Cbuf_AddText("cam_toggle cin_off\n");
    }
}

int CIN_Running() {
    if (playback_running == true) { 
        return 1;
    }
    else {
        return 0;
    }
}

//advances the positions of all active cinematic seqences.
void CIN_Play() {
    if (CIN_Running() == 0) return;

    int cin_cur_tick = GetTickCount();

    //get the elapsed time.
    int time = cin_cur_tick - cin_last_tick;
    bound_max(time, 100);

    //send the time to the script.
    script.PlaybackTimeElapsed(time);

    cin_last_tick = cin_cur_tick;

    //check if the cinematic is over.
    if (script.PlaybackDone() == true) {
        CIN_Stop(NULL);
    }
}

void CIN_CamLocation(float *fpos, float *angles) {
    //check if the current script has any camera info.
    if (script.HaveCameraInfo() == false) {
        //there is no camera info available in the script.
        return;
    }

    //we make these static so that in case we try to get the location after the cinematic is
    //over we can return whatever was returned previously.
    static point pos, dir;

    if (CIN_Running() == 1) {
        //get the current camera position and direction.
        script.PlaybackCamera(pos, dir);
    }

    pos.copyto(fpos);
    dir.copyto(angles);
}

int GCE_Version() {
    return sizeof(CSplineSequence) + sizeof(CCinematicScript);
}

void LastCameraPos(point &pos, point &angles) {
    pos = gce_last_camera_pos;
    angles = gce_last_camera_angle;
}

void UpdateScript(CCinematicScript &script) {
    //copy the data over to our sequence.
    ::script = script;
}

//we use the menu's draw string function... no need to rewrite our own.
void DKM_DrawString(int x, int y, const char *str, bool center = false);

void DrawString(int x, int y, const char *str) {
    DKM_DrawString(x, y, str);
}

int CIN_GCE_CurrentlyEditing() {
    if (gce_import.InputActive != NULL) {
        try {
            return gce_import.InputActive();
        }
        catch (...) {
            gce_import.InputActive = NULL;
        }
    }

    return 0;
}

int CIN_GCE_KeyEvent(int key) {
    if (gce_import.InputKeyEvent != NULL) {
        try {
            return gce_import.InputKeyEvent(key);
        }
        catch (...) {
            gce_import.InputKeyEvent = NULL;
        }
    }

    return 0;
}

int CIN_GCE_MouseMove(int dx, int dy) {
    if (gce_import.InputMouseMove != NULL) {
        try {
            return gce_import.InputMouseMove(dx, dy);
        }
        catch (...) {
            gce_import.InputMouseMove = NULL;
        }
    }

    return 0;
}

void GCE_AngleVectors(point &angles, point &forward, point &right, point &up) {
    AngleVectors((float *)&angles, (float *)&forward, (float *)&right, (float *)&up);
}




