/*
===========================================================================
Copyright (C) 2024 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
===========================================================================
*/

// mover.cpp: Base class for any object that needs to move to specific locations over a
// period of time.  This class is kept separate from most entities to keep
// class size down for objects that don't need such behavior.
//

#include "g_local.h"
#include "entity.h"
#include "trigger.h"
#include "mover.h"
#include "level.h"
#include "g_phys.h"

#define MOVE_ANGLES 1
#define MOVE_ORIGIN 2

CLASS_DECLARATION(Trigger, Mover, "mover") {
    {&EV_MoveDone, &Mover::MoveDone},
    {NULL,         NULL            }
};

Mover::Mover()
{
    endevent = NULL;
}

Mover::~Mover() {}

void Mover::MoveDone(Event *ev)
{
    Event *event;
    Vector move;
    Vector amove;

    // zero out the movement
    avelocity = vec_zero;
    if (moveflags & MOVE_ANGLES) {
        amove = angledest - localangles;
    } else {
        amove = vec_zero;
    }

    velocity = vec_zero;
    if (moveflags & MOVE_ORIGIN) {
        move = finaldest - localorigin;
    } else {
        move = vec_zero;
    }

    accel  = vec_zero;
    aaccel = vec_zero;

    if (!G_PushMove(this, move, amove)) {
        // Added in OPM
        //  Check to make sure the event isn't pending
        //  blocked events called by G_PushMove() might alter
        //  the mover
        if (!EventPending(EV_MoveDone)) {
            // Delay finish till we can move into the final position
            PostEvent(EV_MoveDone, FRAMETIME);
        }
        return;
    }

    //
    // After moving, set origin to exact final destination
    //
    if (moveflags & MOVE_ORIGIN) {
        setLocalOrigin(finaldest);
    }

    if (moveflags & MOVE_ANGLES) {
        localangles = angledest;

        if ((localangles.x >= 360) || (localangles.x < 0)) {
            localangles.x -= ((int)localangles.x / 360) * 360;
        }
        if ((localangles.y >= 360) || (localangles.y < 0)) {
            localangles.y -= ((int)localangles.y / 360) * 360;
        }
        if ((localangles.z >= 360) || (localangles.z < 0)) {
            localangles.z -= ((int)localangles.z / 360) * 360;
        }
    }

    event    = endevent;
    endevent = NULL;

    if (event) {
        ProcessEvent(event);
    }
}

/*
=============
MoveTo

calculate self.velocity and self.nextthink to reach dest from
self.origin traveling at speed
===============
*/
void Mover::MoveTo(Vector tdest, Vector angdest, float tspeed, Event& event)
{
    Vector vdestdelta;
    Vector angdestdelta;
    float  len;
    float  traveltime;

    assert(tspeed >= 0.0f);

    if (!tspeed) {
        error("MoveTo", "No speed is defined!");
    }

    if (tspeed < 0.0f) {
        error("MoveTo", "Speed is negative!");
    }

    // Cancel previous moves
    CancelEventsOfType(EV_MoveDone);

    moveflags = 0;

    if (endevent) {
        delete endevent;
    }

    endevent = new Event(event);

    finaldest = tdest;
    angledest = angdest;

    if (finaldest != localorigin) {
        moveflags |= MOVE_ORIGIN;
    }
    if (angledest != localangles) {
        moveflags |= MOVE_ANGLES;
    }

    if (!moveflags) {
        // stop the object from moving
        velocity  = vec_zero;
        avelocity = vec_zero;

        // post the event so we don't wait forever
        PostEvent(EV_MoveDone, FRAMETIME);
        return;
    }

    // set destdelta to the vector needed to move
    vdestdelta      = tdest - localorigin;
    angdestdelta[0] = angledist(angdest[0] - localangles[0]);
    angdestdelta[1] = angledist(angdest[1] - localangles[1]);
    angdestdelta[2] = angledist(angdest[2] - localangles[2]);

    if (tdest == localorigin) {
        // calculate length of vector based on angles
        len = angdestdelta.length();
    } else {
        // calculate length of vector based on distance
        len = vdestdelta.length();
    }

    // divide by speed to get time to reach dest
    traveltime = len / tspeed;

    if (traveltime < level.frametime) {
        traveltime   = level.frametime;
        vdestdelta   = vec_zero;
        angdestdelta = vec_zero;
    }

    // scale the destdelta vector by the time spent traveling to get velocity
    if (moveflags & MOVE_ORIGIN) {
        velocity = vdestdelta * (1.0f / traveltime);
    }

    if (moveflags & MOVE_ANGLES) {
        avelocity = angdestdelta * (1.0f / traveltime);
    }

    PostEvent(EV_MoveDone, traveltime);
}

/*
=============
LinearInterpolate
===============
*/
void Mover::LinearInterpolate(Vector tdest, Vector angdest, float time, Event& event)
{
    Vector vdestdelta;
    Vector angdestdelta;
    float  t;

    if (endevent) {
        delete endevent;
    }
    endevent  = new Event(event);
    finaldest = tdest;
    angledest = angdest;

    // Cancel previous moves
    CancelEventsOfType(EV_MoveDone);

    // Quantize to FRAMETIME
    if (time < FRAMETIME) {
        time = FRAMETIME;
    }

    moveflags = 0;
    t         = 1 / time;
    // scale the destdelta vector by the time spent traveling to get velocity
    if (finaldest != localorigin) {
        vdestdelta = tdest - localorigin;
        velocity   = vdestdelta * t;
        moveflags |= MOVE_ORIGIN;
    }

    if (angledest != localangles) {
        angdestdelta = angdest - localangles;
        avelocity    = angdestdelta * t;
        moveflags |= MOVE_ANGLES;
    }

    if (g_bBeforeThinks) {
        time -= FRAMETIME;
    }

    PostEvent(EV_MoveDone, time);
}
