#include "dk_misc.h"

#include "ref.h"

#include "dk_buffer.h"
#include "dk_gce_spline.h"
#include "dk_gce_script.h"
#include "dk_gce_entities.h"
#include "dk_cin_world_interface.h"

#include "sound.h"
#include "client.h"

//
// system playback entry points.
//
void CinPlaybackSound(const char *name) {
    if (name == NULL || name[0] == '\0') return;

    buffer128 buffer("%s.wav", name);
    S_StartLocalSound(buffer);
}

extern cin_world_import_t world_import;
extern bool got_world_import;

void CinPlaybackEntityTask(const char *entity_name, const CScriptEntityTask *task) {
    if (got_world_import == false) return;

    //check the task type.
    switch (task->Type()) {
        case TT_MOVE_TO:
            world_import.QueueMoveTo(entity_name, task->Destination());
            break;
        case TT_TURN:
            world_import.QueueTurn(entity_name, task->Direction());
            break;
        case TT_MOVE_AND_TURN:
            world_import.QueueMoveAndTurn(entity_name, task->Destination(), task->Direction());
            break;
    }
}

bool EntityPosition(const char *entity_name, point &pos, point &angles) {
    if (got_world_import == false) return false;

    //get the entity's index from the server.
    int index;
    if (world_import.EntityIndex(entity_name, index) == false) return false;
    if (index < 0 || index >= MAX_EDICTS) return false;

    //find the client entity at that index.
    centity_t *entity = &cl_entities[index];

    //get the lerp'd info of the entity.
    pos = entity->lerp_origin;
    angles = entity->lerp_angles;

    return true;
}

bool EntityDone(const char *entity_name) {
    if (got_world_import == false) return true;

    //ask world.dll if the entity's queue is empty.
    return world_import.DoneWithTasks(entity_name);
}

void TeleportEntity(const char *entity_name, const point &pos, const point &angles, bool use_angles) {
    if (entity_name == NULL) return;
    if (got_world_import == false) return;

    //get the entity's index from the server.
    int index;
    if (world_import.EntityIndex(entity_name, index) == false) return;
    if (index < 0 || index >= MAX_EDICTS) return;

    //find the client entity at that index.
    centity_t *entity = &cl_entities[index];

    //set the position (current, previous, and lerped)
    pos.copyto(entity->current.origin);
    pos.copyto(entity->current.old_origin);
    pos.copyto(entity->prev.origin);
    pos.copyto(entity->prev.old_origin);
    pos.copyto(entity->lerp_origin);

    if (use_angles == true) {
        //set the angles (current, previous, and lerped)
        angles.copyto(entity->current.angles);
        angles.copyto(entity->prev.angles);
        angles.copyto(entity->lerp_angles);
    }

    //move the entity in world.dll.
    world_import.TeleportEntity(entity_name, pos, angles, use_angles);
}

//
// class playback functions
//
void CScriptEntity::PlaybackReset() {
    next_task = 0;
}

void CScriptShot::PlaybackReset() {
    next_sound = 0;
    wait_entity_done = false;
    wait_entity_done_time = 0;

    //reset all of our entities.
    for (int i = 0; i < entities.Num(); i++) {
        entities.Get(i)->PlaybackReset();
    }
}

void CScriptEntity::Playback(int end_time) {
    bound_min(next_task, 0);

    //go through the list of tasks and queue up each one
    for (;;) {
        //check if we are done with all the tasks.
        if (next_task >= tasks.Num()) break;

        //get the task.
        const CScriptEntityTask *task = tasks.Item(next_task);
        if (task == NULL) return;

        //check the time
        if (task->Time() * 1000 >= end_time) {
            //not time for this task.
            break;
        }

        //queue this task.
        CinPlaybackEntityTask(name, task);

        //go to our next task.
        next_task++;
    }
}

bool CScriptEntity::Done() const {
    //check if we have queued all of our tasks yet.
    if (next_task < tasks.Num()) {
        //haven't even queued the tasks yet.
        return false;
    }

    //all the tasks are queued, find out if the entity has completed them.
    return EntityDone(name);
}

int CScriptShot::Playback(int end_time) {
    //look for events that should trigger in this time period.

    //play the sounds.
    for (;;) {
        //get the next sound we are going to play.
        const CScriptSound *sound = sounds.Item(next_sound);
        if (sound == NULL) break;

        //see if this sound is played after our end time.
        if (int(sound->Time() * 1000) >= end_time) {
            //this sound is not up yet.
            break;
        }

        //we should play this sound.
        CinPlaybackSound(sound->Name());

        //advance our sound number
        next_sound++;
    }

    //go through each of our entities.
    for (int ent = 0; ent < entities.Num(); ent++) {
        entities.Get(ent)->Playback(end_time);
    }

    //check if the script is over.
    if (end_flag == SE_ENTITY) {
        //find the entity.
        const CScriptEntity *ent = entities.Find(entity_shot_end, CScriptEntity::Find);

        if (ent != NULL) {
            //the shot ends when the entity whose name we have is done with all of it's goals.
            //check if we already found out that the entity is done and are now waiting for the end wait time.
            if (wait_entity_done == true) {
                //the entity is done with it's tasks, check if the end wait time is done.
                if (end_time > wait_entity_done_time + extra_time_end * 1000) {
                    //the shot is over.
                    return 0;
                }

                //the shot is not over.
                return -1;
            }
            else {
                //check if the entity is done with it's tasks
                if (ent->Done() == true) {
                    //the entity finished.  
                    wait_entity_done = true;
                    wait_entity_done_time = end_time;

                    //check if we have extra wait time for the end.
                    if (extra_time_end <= 0.0f) {
                        //no extra time.
                        return 0;
                    }
                }

                return -1;
            }
        }
    }

    //either the shot ends when the time is up, or we dont have a valid entity to wait for.
    //get the ending time of the script based on the sequence time.
    int shot_time = (extra_time_start + sequence->TotalTime() + extra_time_end) * 1000;

    if (end_time > shot_time) {
        //its over.
        return end_time - shot_time;
    }

    //the shot is not over.
    return -1;
}

void YawPitch(point &dir, float &yaw, float &pitch) {
    //get the shadow of the vector.
    point shadow(dir.x, dir.y, 0.0f);

    //normalize the shadow.
    float len = shadow.normalize();

    if (len < 0.001f) {
        //the vector is pointing almost straight up, or it is a zero vector.
        yaw = 0.0f;
        pitch = 90.0f;
        return;
    }

    //compute the yaw based on the normalized shadow vector.
    yaw = RAD2DEG * float(atan2(shadow.y, shadow.x));

    //compute the pitch based on the shadow length and the z of the dir.
    pitch = -RAD2DEG * float(atan2(dir.z, len));
}

void CScriptShot::PlaybackCamera(point &pos, point &dir, float time) {
    //if our sequence has 
    //compute the time we will give to the sequence
    float seq_time;

    if (time < extra_time_start) {
        //get the initial position of the sequence.
        seq_time = 0.0f;
    }
    else if (time < extra_time_start + sequence->TotalTime()) {
        seq_time = time - extra_time_start;
    }
    else {
        //get the ending position of the sequence.
        seq_time = sequence->TotalTime();
    }

    sequence->Pos(seq_time, pos);
    sequence->Dir(seq_time, dir);

    if (got_world_import == false) return;

    //check if we are supposed to look at an entity.
    if (target_flag == CT_ENTITY) {
        //find the entity's position.
        point entity, angles;
        if (EntityPosition(entity_camera_target, entity, angles) == false) return;

        //get the vector from the camera to the entity's location.
        point to_entity;
        to_entity.sub(entity, pos);
        to_entity.normalize();

        //get a yaw/pitch from the vector.
        float yaw, pitch;
        YawPitch(to_entity, yaw, pitch);

        //put the yaw/pitch and a roll of 0 into dir.
        dir.x = pitch;
        dir.y = yaw;
        dir.z = 0.0f;
    }
}


void CCinematicScript::PlaybackReset(int start_shot, int end_shot) {
    cur_shot = start_shot;
    this->end_shot = end_shot;
    shot_time = 0;

    bound_min(cur_shot, 0);
    bound_max(cur_shot, NumShots() - 1);
    bound_min(this->end_shot, cur_shot);
    bound_max(this->end_shot, NumShots() - 1);

    //initialize the shot's playback variables.
    if (shots.Get(cur_shot) != NULL) {
        shots.Get(cur_shot)->PlaybackReset();
    }
}

void CCinematicScript::PlaybackTimeElapsed(int ms) {
    if (cur_shot < 0 || cur_shot > end_shot) return;

    //add the time to our total for the shot so far.
    shot_time += ms;

    //tell the current shot to advance.
    ms = shots.Get(cur_shot)->Playback(shot_time);

    //check if we have any time remaining.
    if (ms >= 0) {
        //advance to the next shot.
        cur_shot++;

        shot_time = 0;

        //initialize the shot's playback variables.
        if (shots.Get(cur_shot) != NULL) {
            shots.Get(cur_shot)->PlaybackReset();
        }

        PlaybackTimeElapsed(ms);
    }
}

bool CCinematicScript::PlaybackDone() {
    if (cur_shot > end_shot) return true;

    return false;
}

void CCinematicScript::PlaybackCamera(point &pos, point &dir) {
    if (cur_shot < 0 || cur_shot > end_shot) return;

    float time = shot_time * 0.001f;

    //ask the current shot for the camera info.
    shots.Get(cur_shot)->PlaybackCamera(pos, dir, time);
}

bool CCinematicScript::HaveCameraInfo() const {
    return have_camera_info;    
}