/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// viewthing.cpp: Actor code for the Viewthing.
//

#include "animate.h"
#include "viewthing.h"
#include "game.h"
#include "level.h"
#include "scriptexception.h"

Event EV_ViewThing_Think
(
    "viewthing_think",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called every frame to process the view thing.",
    EV_NORMAL
);
Event EV_ViewThing_ToggleAnimate
(
    "viewanimate",
    EV_CHEAT,
    NULL,
    NULL,
    "Cycle through the animations modes of the current viewthing\n"
    "No Animation\n"
    "Animation with no motion\n"
    "Animation with looping motion\n"
    "Animation with motion\n",
    EV_NORMAL
);
Event EV_ViewThing_SetModel
(
    "viewmodel",
    EV_CHEAT,
    "s",
    "viewthingModel",
    "Set the model of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_NextFrame
(
    "viewnext",
    EV_CHEAT,
    NULL,
    NULL,
    "Advance to the next frame of animation of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_PrevFrame
(
    "viewprev",
    EV_CHEAT,
    NULL,
    NULL,
    "Advance to the previous frame of animation of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_NextAnim
(
    "viewnextanim",
    EV_CHEAT,
    NULL,
    NULL,
    "Advance to the next animation of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_PrevAnim
(
    "viewprevanim",
    EV_CHEAT,
    NULL,
    NULL,
    "Advance to the previous animation of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_ScaleUp
(
    "viewscaleup",
    EV_CHEAT,
    NULL,
    NULL,
    "Increase the scale of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_ScaleDown
(
    "viewscaledown",
    EV_CHEAT,
    NULL,
    NULL,
    "Decrease the scale of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_SetScale
(
    "viewscale",
    EV_CHEAT,
    "f",
    "scale",
    "Set the scale of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_SetYaw
(
    "viewyaw",
    EV_CHEAT,
    "f",
    "yaw",
    "Set the yaw of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_SetPitch
(
    "viewpitch",
    EV_CHEAT,
    "f",
    "pitch",
    "Set the pitch of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_SetRoll
(
    "viewroll",
    EV_CHEAT,
    "f",
    "roll",
    "Set the roll of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_SetAngles
(
    "viewangles",
    EV_CHEAT,
    "f[0,360]f[0,360]f[0,360]",
    "pitch yaw roll",
    "Set the angles of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_Spawn
(
    "viewspawn",
    EV_CHEAT,
    "s",
    "model",
    "Create a viewthing with the specified model",
    EV_NORMAL
);
Event EV_ViewThing_Next
(
    "viewthingnext",
    EV_CHEAT,
    NULL,
    NULL,
    "Change the active viewthing to the next viewthing",
    EV_NORMAL
);
Event EV_ViewThing_Prev
(
    "viewthingprev",
    EV_CHEAT,
    NULL,
    NULL,
    "Change the active viewthing to the previous viewthing",
    EV_NORMAL
);
Event EV_ViewThing_Attach
(
    "viewattach",
    EV_CHEAT,
    "ss",
    "tagname model",
    "Attach a model the the specified tagname",
    EV_NORMAL
);
Event EV_ViewThing_Detach
(
    "viewdetach",
    EV_CHEAT,
    NULL,
    NULL,
    "Detach the current viewthing from its parent",
    EV_NORMAL
);
Event EV_ViewThing_DetachAll
(
    "viewdetachall",
    EV_CHEAT,
    NULL,
    NULL,
    "Detach all the models attached to the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_Delete
(
    "viewdelete",
    EV_CHEAT,
    NULL,
    NULL,
    "Delete the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_SetOrigin
(
    "vieworigin",
    EV_CHEAT,
    "fff",
    "x y z",
    "Set the origin of the current viewthing",
    EV_NORMAL
);
Event EV_ViewThing_DeleteAll
(
    "viewdeleteall",
    EV_CHEAT,
    NULL,
    NULL,
    "Delete all viewthings",
    EV_NORMAL
);
Event EV_ViewThing_LastFrame
(
    "viewlastframe",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the view things last animation frame is displayed.",
    EV_NORMAL
);
Event EV_ViewThing_SaveOffSurfaces
(
    "viewsavesurfaces",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called after the model is spawned to save off the models original surfaces.",
    EV_NORMAL
);
Event EV_ViewThing_PrintTime
(
    "_viewthing_printtime",
    EV_DEFAULT,
    NULL,
    NULL,
    "Prints out the current level.time.",
    EV_NORMAL
);
Event EV_ViewThing_SetAnim
(
    "viewsetanim",
    EV_CHEAT,
    "f",
    "animNum",
    "Set the animation absolutely based off a floating point value",
    EV_NORMAL
);

CLASS_DECLARATION(Animate, ViewMaster, NULL) {
    {&EV_ViewThing_Spawn,         &ViewMaster::Spawn        },
    {&EV_ViewThing_Next,          &ViewMaster::Next         },
    {&EV_ViewThing_Prev,          &ViewMaster::Prev         },
    {&EV_ViewThing_SetModel,      &ViewMaster::SetModelEvent},
    {&EV_ViewThing_DeleteAll,     &ViewMaster::DeleteAll    },
    {&EV_ViewThing_ToggleAnimate, &ViewMaster::PassEvent    },
    {&EV_ViewThing_NextFrame,     &ViewMaster::PassEvent    },
    {&EV_ViewThing_PrevFrame,     &ViewMaster::PassEvent    },
    {&EV_ViewThing_NextAnim,      &ViewMaster::PassEvent    },
    {&EV_ViewThing_PrevAnim,      &ViewMaster::PassEvent    },
    {&EV_ViewThing_ScaleUp,       &ViewMaster::PassEvent    },
    {&EV_ViewThing_ScaleDown,     &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetScale,      &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetYaw,        &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetPitch,      &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetRoll,       &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetAngles,     &ViewMaster::PassEvent    },
    {&EV_ViewThing_Attach,        &ViewMaster::PassEvent    },
    {&EV_ViewThing_Detach,        &ViewMaster::PassEvent    },
    {&EV_ViewThing_DetachAll,     &ViewMaster::PassEvent    },
    {&EV_ViewThing_Delete,        &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetOrigin,     &ViewMaster::PassEvent    },
    {&EV_ViewThing_SetAnim,       &ViewMaster::PassEvent    },
    {NULL,                        NULL                      }
};

ViewMaster Viewmodel;

ViewMaster::ViewMaster()
{
    current_viewthing = NULL;
}

void ViewMaster::Init(void)
{
    gi.AddCommand("viewanimate", NULL);
    gi.AddCommand("viewmodel", NULL);
    gi.AddCommand("viewnext", NULL);
    gi.AddCommand("viewprev", NULL);
    gi.AddCommand("viewnextanim", NULL);
    gi.AddCommand("viewprevanim", NULL);
    gi.AddCommand("viewscaleup", NULL);
    gi.AddCommand("viewscaledown", NULL);
    gi.AddCommand("viewscale", NULL);
    gi.AddCommand("viewyaw", NULL);
    gi.AddCommand("viewpitch", NULL);
    gi.AddCommand("viewroll", NULL);
    gi.AddCommand("viewangles", NULL);
    gi.AddCommand("viewspawn", NULL);
    gi.AddCommand("viewthingnext", NULL);
    gi.AddCommand("viewthingprev", NULL);
    gi.AddCommand("viewattach", NULL);
    gi.AddCommand("viewdetach", NULL);
    gi.AddCommand("viewdetachall", NULL);
    gi.AddCommand("viewdelete", NULL);
    gi.AddCommand("vieworigin", NULL);
    gi.AddCommand("viewdeleteall", NULL);
    gi.AddCommand("viewsetanim", NULL);
}

void ViewMaster::Next(Event *ev)
{
    Viewthing *viewthing;
    Entity    *ent;

    ent = (Entity *)G_FindClass(current_viewthing, "viewthing");
    if (ent) {
        current_viewthing = ent;

        viewthing = (Viewthing *)((Entity *)current_viewthing);
        gi.Printf("current viewthing model %s.\n", viewthing->model.c_str());
        viewthing->UpdateCvars();
    } else {
        gi.Printf("no more viewthings on map.\n");
    }
}

void ViewMaster::Prev(Event *ev)
{
    Viewthing *viewthing;
    Entity    *prev;
    Entity    *next;

    next = NULL;
    do {
        prev = next;
        next = (Entity *)G_FindClass(prev, "viewthing");
    } while (next != current_viewthing);

    if (prev) {
        current_viewthing = prev;

        viewthing = (Viewthing *)((Entity *)current_viewthing);
        gi.Printf("current viewthing model %s.\n", viewthing->model.c_str());
        viewthing->UpdateCvars();
    } else {
        gi.Printf("no more viewthings on map.\n");
    }
}

void ViewMaster::DeleteAll(Event *ev)
{
    Entity *next;

    for (next = (Entity *)G_FindClass(NULL, "viewthing"); next != NULL;
         next = (Entity *)G_FindClass(next, "viewthing")) {
        next->PostEvent(EV_Remove, 0);
    }

    current_viewthing = NULL;
}

void ViewMaster::Spawn(Event *ev)
{
    Viewthing *viewthing;
    str        mdl;
    Vector     forward;
    Vector     up;
    Vector     delta;
    Event     *event;
    Entity    *ent;

    mdl = ev->GetString(1);
    if (!mdl || !mdl[0]) {
        ScriptError("Must specify a model name");
        return;
    }

    // Check if we have a client
    ent = g_entities[0].entity;
    assert(ent);
    if (!ent) {
        return;
    }

    // create a new viewthing
    viewthing = new Viewthing;

    // set the current_viewthing
    current_viewthing = viewthing;

    //FIXME FIXME
    ent->angles.AngleVectors(&forward, NULL, &up);

    viewthing->baseorigin = ent->origin;
    viewthing->baseorigin += forward * 48;
    viewthing->baseorigin += up * 48;

    viewthing->setOrigin(viewthing->baseorigin);
    viewthing->droptofloor(256);

    viewthing->baseorigin = viewthing->origin;

    delta = ent->origin - viewthing->origin;
    viewthing->setAngles(delta.toAngles());

    event = new Event(EV_ViewThing_SetModel);
    event->AddString(mdl);
    viewthing->ProcessEvent(event);

    if (!gi.modeltiki(viewthing->model)) {
        ScriptError("model %s not found, viewmodel not spawned.", mdl.c_str());
        delete viewthing;
        current_viewthing = NULL;
        return;
    }
}

void ViewMaster::SetModelEvent(Event *ev)
{
    str        mdl;
    char       str[128];
    Event     *event;
    Viewthing *viewthing;

    mdl = ev->GetString(1);
    if (!mdl || !mdl[0]) {
        ScriptError("Must specify a model name");
        return;
    }

    if (!current_viewthing) {
        // try to find one on the map
        current_viewthing = (Entity *)G_FindClass(NULL, "viewthing");
        if (!current_viewthing) {
            ScriptError("No viewmodel");
            return;
        }
    }

    viewthing = (Viewthing *)((Entity *)current_viewthing);

    // Prepend 'models/' to make things easier
    str[0] = 0;
    if ((mdl[1] != ':') && Q_stricmpn(mdl, "models", 6)) {
        strcpy(str, "models/");
    }
    strcat(str, mdl);

    event = new Event(EV_ViewThing_SetModel);
    event->AddString(str);
    viewthing->ProcessEvent(event);
    viewthing->UpdateCvars();
}

void ViewMaster::PassEvent(Event *ev)
{
    Viewthing *viewthing;
    Event     *event;

    if (!current_viewthing) {
        ScriptError("No viewmodel");
        return;
    }

    viewthing = (Viewthing *)((Entity *)current_viewthing);
    if (viewthing) {
        viewthing->ProcessEvent(*ev);
    }
}

CLASS_DECLARATION(Animate, Viewthing, "viewthing") {
    {&EV_ViewThing_Think,           &Viewthing::ThinkEvent        },
    {&EV_ViewThing_LastFrame,       &Viewthing::LastFrameEvent    },
    {&EV_ViewThing_ToggleAnimate,   &Viewthing::ToggleAnimateEvent},
    {&EV_ViewThing_SetModel,        &Viewthing::SetModelEvent     },
    {&EV_ViewThing_NextFrame,       &Viewthing::NextFrameEvent    },
    {&EV_ViewThing_PrevFrame,       &Viewthing::PrevFrameEvent    },
    {&EV_ViewThing_NextAnim,        &Viewthing::NextAnimEvent     },
    {&EV_ViewThing_PrevAnim,        &Viewthing::PrevAnimEvent     },
    {&EV_ViewThing_ScaleUp,         &Viewthing::ScaleUpEvent      },
    {&EV_ViewThing_ScaleDown,       &Viewthing::ScaleDownEvent    },
    {&EV_ViewThing_SetScale,        &Viewthing::SetScaleEvent     },
    {&EV_ViewThing_SetYaw,          &Viewthing::SetYawEvent       },
    {&EV_ViewThing_SetPitch,        &Viewthing::SetPitchEvent     },
    {&EV_ViewThing_SetRoll,         &Viewthing::SetRollEvent      },
    {&EV_ViewThing_SetAngles,       &Viewthing::SetAnglesEvent    },
    {&EV_ViewThing_Attach,          &Viewthing::AttachModel       },
    {&EV_ViewThing_Detach,          &Viewthing::Delete            },
    {&EV_ViewThing_DetachAll,       &Viewthing::DetachAll         },
    {&EV_ViewThing_Delete,          &Viewthing::Delete            },
    {&EV_ViewThing_SetOrigin,       &Viewthing::ChangeOrigin      },
    {&EV_ViewThing_SaveOffSurfaces, &Viewthing::SaveSurfaces      },
    {&EV_ViewThing_PrintTime,       &Viewthing::PrintTime         },
    {&EV_ViewThing_SetAnim,         &Viewthing::SetAnim           },
    {NULL,                          NULL                          }
};

Viewthing::Viewthing(void)
{
    frame     = 0;
    animstate = 0;
    setSolidType(SOLID_NOT);
    baseorigin                  = origin;
    Viewmodel.current_viewthing = this;
    edict->s.eType              = ET_MODELANIM;
    edict->s.renderfx |= RF_SHADOW;
    edict->s.renderfx |= RF_SHADOW_PRECISE;
    edict->s.renderfx |= RF_EXTRALIGHT;

    // save off surfaces once the model is spawned
    PostEvent(EV_ViewThing_SaveOffSurfaces, FRAMETIME);

    PostEvent(EV_ViewThing_Think, FRAMETIME);
}

void Viewthing::PrintTime(Event *ev)
{
    gi.Printf("ev current frame %d leveltime %.2f\n", ev->GetInteger(1), level.time);
}

void Viewthing::UpdateCvars(qboolean quiet)
{
    gi.cvar_set("viewmodelentity", va("%d", edict->s.number));
}

void Viewthing::ThinkEvent(Event *ev)
{
    int f;
    if (animstate >= 2) {
        Vector forward;
        Vector left;
        Vector up;
        Vector realmove;

        angles.AngleVectors(&forward, &left, &up);
        realmove = left * accel[1] + up * accel[2] + forward * accel[0];
        setOrigin(baseorigin + realmove);
        gi.cvar_set(
            "viewthingorigin", va("%0.2f,%0.2f,%0.2f", edict->s.origin[0], edict->s.origin[1], edict->s.origin[2])
        );
    }
    PostEvent(EV_ViewThing_Think, FRAMETIME);
    if ((animstate > 0) && (Viewmodel.current_viewthing == this)) {
        f = GetTime() / gi.Anim_Frametime(edict->tiki, CurrentAnim());
        if (f != lastframe) {
            float time;
            lastframe = f;
            time      = f * AnimTime() / NumFrames();
            gi.Printf("current frame %d time %.2f\n", f, time);
            gi.cvar_set("viewmodeltime", va("%.2f", time));
            gi.cvar_set("viewmodelframe", va("%d", f));
            gi.cvar_set("viewmodelanim", AnimName());
        }
    }
}

void Viewthing::LastFrameEvent(Event *ev) {}

void Viewthing::ToggleAnimateEvent(Event *ev)
{
    animstate = (animstate + 1) % 4;
    setOrigin(baseorigin);
    // reset to a known state
    switch (animstate) {
    case 0:
        SetFrame();
        gi.Printf("Animation stopped.\n");
        gi.cvar_set("viewmodelanimmode", "Stopped");
        break;
    case 1:
        NewAnim(CurrentAnim());
        gi.Printf("Animation no motion.\n");
        gi.cvar_set("viewmodelanimmode", "No Motion");
        break;
    case 2:
        NewAnim(CurrentAnim(), EV_ViewThing_LastFrame);
        gi.Printf("Animation with motion and looping.\n");
        gi.cvar_set("viewmodelanimmode", "Motion and Looping");
        break;
    case 3:
        NewAnim(CurrentAnim(), EV_ViewThing_LastFrame);
        gi.Printf("Animation with motion no looping.\n");
        gi.cvar_set("viewmodelanimmode", "Motion and No Looping");
        break;
    }
    UpdateCvars(qtrue);
}

void Viewthing::SetModelEvent(Event *ev)
{
    str modelname;

    modelname = ev->GetString(1);

    setModel(modelname);

    if (gi.modeltiki(model)) {
        NewAnim(0);
        SetFrame();
    }
    UpdateCvars();
}

void Viewthing::NextFrameEvent(Event *ev)
{
    int numframes;

    numframes = NumFrames();
    if (numframes) {
        frame = (frame + 1) % numframes;
        SetFrame();
        animstate = 0;
        UpdateCvars();
    }
}

void Viewthing::PrevFrameEvent(Event *ev)
{
    int numframes;

    numframes = NumFrames();
    if (numframes) {
        frame = (frame - 1) % numframes;
        SetFrame();
        animstate = 0;
        UpdateCvars();
    }
}

void Viewthing::SetAnim(Event *ev)
{
    int numanims, anim;

    numanims = NumAnims();
    if (numanims) {
        // restore original surfaces
        memcpy(edict->s.surfaces, origSurfaces, sizeof(origSurfaces));

        anim = ev->GetFloat(1) * numanims;
        if (anim >= numanims) {
            anim = numanims - 1;
        }
        NewAnim(anim % numanims);
        frame = 0;
        SetFrame();
        animstate = 0;
        UpdateCvars();
    }
}

void Viewthing::NextAnimEvent(Event *ev)
{
    int numanims;

    numanims = NumAnims();
    if (numanims) {
        // restore original surfaces
        memcpy(edict->s.surfaces, origSurfaces, sizeof(origSurfaces));

        NewAnim((CurrentAnim() + 1) % numanims);
        frame = 0;
        SetFrame();
        animstate = 0;
        UpdateCvars();
    }
}

void Viewthing::PrevAnimEvent(Event *ev)
{
    int anim;
    int numanims;

    numanims = NumAnims();
    if (numanims) {
        // restore original surfaces
        memcpy(edict->s.surfaces, origSurfaces, sizeof(origSurfaces));

        anim = CurrentAnim() - 1;
        while (anim < 0) {
            anim += numanims;
        }
        NewAnim(anim);
        frame = 0;
        SetFrame();
        animstate = 0;
        UpdateCvars();
    }
}

void Viewthing::ScaleUpEvent(Event *ev)
{
    edict->s.scale += 0.01f;
    UpdateCvars();
}

void Viewthing::ScaleDownEvent(Event *ev)
{
    edict->s.scale -= 0.01f;
    UpdateCvars();
}

void Viewthing::SetScaleEvent(Event *ev)
{
    float s;

    if (ev->NumArgs()) {
        s              = ev->GetFloat(1);
        edict->s.scale = s;
        UpdateCvars();
    } else {
        gi.Printf("viewscale = %f\n", edict->s.scale);
    }
}

void Viewthing::SetYawEvent(Event *ev)
{
    if (ev->NumArgs() > 0) {
        angles.setYaw(ev->GetFloat(1));
        setAngles(angles);
    }
    gi.Printf("yaw = %f\n", angles.yaw());
}

void Viewthing::SetPitchEvent(Event *ev)
{
    if (ev->NumArgs() > 0) {
        angles.setPitch(ev->GetFloat(1));
        setAngles(angles);
    }
    gi.Printf("pitch = %f\n", angles.pitch());
}

void Viewthing::SetRollEvent(Event *ev)
{
    if (ev->NumArgs() > 0) {
        angles.setRoll(ev->GetFloat(1));
        setAngles(angles);
    }
    gi.Printf("roll = %f\n", angles.roll());
}

void Viewthing::SetAnglesEvent(Event *ev)
{
    if (ev->NumArgs() > 2) {
        angles.x = ev->GetFloat(1);
        angles.y = ev->GetFloat(2);
        angles.z = ev->GetFloat(3);
        setAngles(angles);
    }

    gi.Printf("angles = %f, %f, %f\n", angles.x, angles.y, angles.z);
}

void Viewthing::AttachModel(Event *ev)
{
    Event     *event;
    Viewthing *child;

    child = new Viewthing;
    child->setModel(ev->GetString(2));

    //
    // attach the child
    //
    event = new Event(EV_Attach);
    event->AddEntity(this);
    event->AddString(ev->GetString(1));
    child->ProcessEvent(event);
}

void Viewthing::Delete(Event *ev)
{
    Viewmodel.current_viewthing = NULL;
    PostEvent(EV_Remove, 0);
    Viewmodel.PostEvent(EV_ViewThing_Next, 0);
}

void Viewthing::DetachAll(Event *ev)
{
    int i;
    int num;

    num = numchildren;
    for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
        Entity *ent;
        if (!children[i]) {
            continue;
        }
        ent = (Entity *)G_GetEntity(children[i]);
        ent->PostEvent(EV_Remove, 0);
        num--;
        if (!num) {
            break;
        }
    }
}

void Viewthing::ChangeOrigin(Event *ev)
{
    if (ev->NumArgs()) {
        origin.x = ev->GetFloat(1);
        origin.y = ev->GetFloat(2);
        origin.z = ev->GetFloat(3);
        setOrigin(origin);
        baseorigin = origin;
        UpdateCvars();
    }
    gi.Printf("vieworigin = x%f y%f z%f\n", origin.x, origin.y, origin.z);
}

void Viewthing::SaveSurfaces(Event *ev)
{
    memcpy(origSurfaces, edict->s.surfaces, sizeof(origSurfaces));
}
