/*
===========================================================================
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
===========================================================================
*/

// bspline.cpp: Uniform non-rational bspline class.
//

#include "g_local.h"
#include "bspline.h"
#include "game.h"
#include "debuglines.h"
#include "g_phys.h"
#include "scriptexception.h"

void BSpline::Set(Vector *control_points_, int num_control_points_, splinetype_t type)
{
    int i;

    SetType(type);

    has_orientation = false;

    if (control_points) {
        delete[] control_points;
        control_points = NULL;
    }

    num_control_points = num_control_points_;
    if (num_control_points) {
        control_points = new BSplineControlPoint[num_control_points];
        assert(control_points);

        for (i = 0; i < num_control_points; i++) {
            control_points[i].Set(control_points_[i]);
        }
    }
}

void BSpline::Set(
    Vector      *control_points_,
    Vector      *control_orients_,
    float       *control_speeds_,
    int          num_control_points_,
    splinetype_t type
)
{
    int i;

    SetType(type);

    has_orientation = true;

    if (control_points) {
        delete[] control_points;
        control_points = NULL;
    }

    num_control_points = num_control_points_;
    if (num_control_points) {
        control_points = new BSplineControlPoint[num_control_points];
        assert(control_points);

        for (i = 0; i < num_control_points; i++) {
            control_points[i].Set(control_points_[i], control_orients_[i], control_speeds_[i]);
        }
    }
}

void BSpline::Clear(void)
{
    if (control_points) {
        delete[] control_points;
        control_points = NULL;
    }
    num_control_points = 0;
    has_orientation    = false;
}

inline float BSpline::EvalNormal(float u, Vector& pos, Vector& orient)
{
    int    segment_id;
    float  B[4];
    float  tmp;
    float  u_2;
    float  u_3;
    Vector ang;
    float  roll;
    float  speed;

    segment_id = (int)u;
    if (segment_id < 0) {
        segment_id = 0;
    }
    if (segment_id > num_control_points - 4) {
        segment_id = num_control_points - 4;
    }
    u -= (float)segment_id;

    u_2 = u * u;
    u_3 = u * u_2;

    tmp  = 1 - u;
    B[0] = (tmp * tmp * tmp) * (1.0f / 6.0f);
    B[1] = (3.0f * u_3 - 6.0f * u_2 + 4.0f) * (1.0f / 6.0f);
    B[2] = (-3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1) * (1.0f / 6.0f);
    B[3] = u_3 * (1.0f / 6.0f);

    pos = *control_points[0 + segment_id].GetPosition() * B[0] + *control_points[1 + segment_id].GetPosition() * B[1]
        + *control_points[2 + segment_id].GetPosition() * B[2] + *control_points[3 + segment_id].GetPosition() * B[3];

    ang = *control_points[0 + segment_id].GetOrientation() * B[0]
        + *control_points[1 + segment_id].GetOrientation() * B[1]
        + *control_points[2 + segment_id].GetOrientation() * B[2]
        + *control_points[3 + segment_id].GetOrientation() * B[3];

    roll = *control_points[0 + segment_id].GetRoll() * B[0] + *control_points[1 + segment_id].GetRoll() * B[1]
         + *control_points[2 + segment_id].GetRoll() * B[2] + *control_points[3 + segment_id].GetRoll() * B[3];

    speed = *control_points[0 + segment_id].GetSpeed() * B[0] + *control_points[1 + segment_id].GetSpeed() * B[1]
          + *control_points[2 + segment_id].GetSpeed() * B[2] + *control_points[3 + segment_id].GetSpeed() * B[3];

    orient       = ang.toAngles();
    orient[ROLL] = roll;

    return speed;
}

inline float BSpline::EvalLoop(float t, Vector& pos, Vector& orient)
{
    Vector retval;
    Vector ang;
    float  speed;
    float  roll;
    int    segment_id;
    int    next_id;
    float  B[4];
    float  tmp;
    float  u;
    float  u_2;
    float  u_3;
    int    i;
    int    j;

    segment_id = (int)floor(t);
    u          = t - floor(t);

    segment_id %= num_control_points;
    if (segment_id < 0) {
        segment_id += num_control_points;
    }

    u_2 = u * u;
    u_3 = u * u_2;

    tmp  = 1 - u;
    B[0] = (tmp * tmp * tmp) * (1.0f / 6.0f);
    B[1] = (3.0f * u_3 - 6.0f * u_2 + 4.0f) * (1.0f / 6.0f);
    B[2] = (-3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1) * (1.0f / 6.0f);
    B[3] = u_3 * (1.0f / 6.0f);

    speed = 0;
    roll  = 0;

    for (i = 0, j = segment_id; i < 4; i++, j++) {
        if (j >= num_control_points) {
            j -= (num_control_points - loop_control_point);
        }

        retval += *control_points[j].GetPosition() * B[i];
        ang += *control_points[j].GetOrientation() * B[i];
        speed += *control_points[j].GetSpeed() * B[i];
        roll += *control_points[j].GetRoll() * B[i];
    }

    pos = retval;

    next_id = segment_id + 1;
    if (next_id >= num_control_points) {
        next_id -= (num_control_points - loop_control_point);
    }
    orient       = ang.toAngles();
    orient[ROLL] = roll;

    return speed;
}

inline float BSpline::EvalClamp(float t, Vector& pos, Vector& orient)
{
    Vector retval;
    Vector ang;
    int    segment_id;
    int    next_id;
    float  B[4];
    float  tmp;
    float  u;
    float  u_2;
    float  u_3;
    int    i;
    int    j;
    float  speed;
    float  roll;

    segment_id = (int)floor(t);
    u          = t - floor(t);

    u_2 = u * u;
    u_3 = u * u_2;

    tmp  = 1 - u;
    B[0] = (tmp * tmp * tmp) * (1.0f / 6.0f);
    B[1] = (3.0f * u_3 - 6.0f * u_2 + 4.0f) * (1.0f / 6.0f);
    B[2] = (-3.0f * u_3 + 3.0f * u_2 + 3.0f * u + 1) * (1.0f / 6.0f);
    B[3] = u_3 * (1.0f / 6.0f);

    speed = 0;
    roll  = 0;
    for (i = 0; i < 4; i++, segment_id++) {
        j = segment_id;
        if (j < 0) {
            j = 0;
        } else if (j >= num_control_points) {
            j = num_control_points - 1;
        }

        retval += *control_points[j].GetPosition() * B[i];
        ang += *control_points[j].GetOrientation() * B[i];
        speed += *control_points[j].GetSpeed() * B[i];
        roll += *control_points[j].GetRoll() * B[i];
    }

    pos = retval;

    next_id = segment_id + 1;
    if (segment_id < 0) {
        segment_id = 0;
    }
    if (segment_id >= num_control_points) {
        segment_id = num_control_points - 1;
    }
    if (next_id < 0) {
        next_id = 0;
    }
    if (next_id >= num_control_points) {
        next_id = num_control_points - 1;
    }
    orient       = ang.toAngles();
    orient[ROLL] = roll;

    return speed;
}

Vector BSpline::Eval(float u)
{
    Vector pos;
    Vector orient;

    switch (curvetype) {
    default:
    case SPLINE_NORMAL:
        EvalNormal(u, pos, orient);
        break;

    case SPLINE_CLAMP:
        EvalClamp(u, pos, orient);
        break;

    case SPLINE_LOOP:
        if (u < 0) {
            EvalClamp(u, pos, orient);
        } else {
            EvalLoop(u, pos, orient);
        }
        break;
    }
    return pos;
}

float BSpline::Eval(float u, Vector& pos, Vector& orient)
{
    switch (curvetype) {
    default:
    case SPLINE_NORMAL:
        return EvalNormal(u, pos, orient);
        break;

    case SPLINE_CLAMP:
        return EvalClamp(u, pos, orient);
        break;

    case SPLINE_LOOP:
        if (u < 0) {
            return EvalClamp(u, pos, orient);
        } else {
            return EvalLoop(u, pos, orient);
        }
        break;
    }
}

void BSpline::DrawControlSegments(void)
{
    int i;

    G_BeginLine();
    for (i = 0; i < num_control_points; i++) {
        G_Vertex(*control_points[i].GetPosition());
    }
    G_EndLine();
}

void BSpline::DrawCurve(int num_subdivisions)
{
    float u;
    float du;

    if (!num_control_points) {
        return;
    }

    du = 1.0f / (float)num_subdivisions;

    G_BeginLine();
    for (u = -2.0f; u <= (float)num_control_points; u += du) {
        G_Vertex((Vector)Eval(u));
    }
    G_EndLine();
}

void BSpline::DrawCurve(Vector offset, int num_subdivisions)
{
    float u;
    float du;

    du = 1.0f / (float)num_subdivisions;

    G_BeginLine();
    for (u = -2.0f; u <= (float)num_control_points; u += du) {
        G_Vertex(offset + (Vector)Eval(u));
    }
    G_EndLine();
}

void BSpline::AppendControlPoint(const Vector& new_control_point)
{
    BSplineControlPoint *old_control_points;
    int                  i;

    old_control_points = control_points;
    num_control_points++;

    control_points = new BSplineControlPoint[num_control_points];
    assert(control_points);

    if (old_control_points) {
        for (i = 0; i < num_control_points - 1; i++) {
            control_points[i] = old_control_points[i];
        }
        delete[] old_control_points;
    }

    control_points[num_control_points - 1].Set(new_control_point);
}

void BSpline::AppendControlPoint(const Vector& new_control_point, const float& speed)
{
    BSplineControlPoint *old_control_points;
    int                  i;

    old_control_points = control_points;
    num_control_points++;

    control_points = new BSplineControlPoint[num_control_points];
    assert(control_points);

    if (old_control_points) {
        for (i = 0; i < num_control_points - 1; i++) {
            control_points[i] = old_control_points[i];
        }
        delete[] old_control_points;
    }

    control_points[num_control_points - 1].Set(new_control_point, speed);
}

void BSpline::AppendControlPoint(
    const Vector& new_control_point, const Vector& new_control_orient, const float& new_control_speed
)
{
    BSplineControlPoint *old_control_points;
    int                  i;

    has_orientation = true;

    old_control_points = control_points;
    num_control_points++;

    control_points = new BSplineControlPoint[num_control_points];
    assert(control_points);

    if (old_control_points) {
        for (i = 0; i < num_control_points - 1; i++) {
            control_points[i] = old_control_points[i];
        }
        delete[] old_control_points;
    }

    control_points[num_control_points - 1].Set(new_control_point, new_control_orient, new_control_speed);
}

void BSpline::SetLoopPoint(const Vector& pos)
{
    int i;

    for (i = 0; i < num_control_points; i++) {
        if (pos == *control_points[i].GetPosition()) {
            loop_control_point = i;
            break;
        }
    }
}

int BSpline::PickControlPoint(const Vector& window_point, float pick_size)
{
    int    i;
    float  closest_dist_2;
    int    closest_index;
    float  dist_2;
    Vector delta;

    closest_index  = -1;
    closest_dist_2 = 1000000.0f;
    for (i = 0; i < num_control_points; i++) {
        delta  = window_point - *control_points[i].GetPosition();
        dist_2 = delta * delta;
        if (dist_2 < closest_dist_2) {
            closest_dist_2 = dist_2;
            closest_index  = i;
        }
    }

    if (pick_size * pick_size >= closest_dist_2) {
        return closest_index;
    } else {
        return -1;
    }
}

Event EV_SplinePath_Create
(
    "SplinePath_create",
    EV_DEFAULT,
    NULL,
    NULL,
    "Creates the spline path from the target list.",
    EV_NORMAL
);
Event EV_SplinePath_Loop
(
    "loop",
    EV_CONSOLE,
    "s",
    "loop_name",
    "Sets the loop name.",
    EV_NORMAL
);
Event EV_SplinePath_Speed
(
    "speed",
    EV_DEFAULT,
    "f",
    "speed",
    "Sets the path speed.",
    EV_NORMAL
);
Event EV_SplinePath_SetTriggerTarget
(
    "triggertarget",
    EV_DEFAULT,
    "s",
    "target",
    "Sets the trigger target.",
    EV_NORMAL
);
Event EV_SplinePath_SetWatch
(
    "watch",
    EV_CONSOLE,
    "s",
    "watchEntity",
    "Sets the entity to watch at this node.",
    EV_NORMAL
);

Event EV_SplinePath_SetFov
(
    "fov",
    EV_CONSOLE,
    "f",
    "cameraFOV",
    "Sets the fov at this node.",
    EV_NORMAL
);

Event EV_SplinePath_SetFadeTime
(
    "fadetime",
    EV_DEFAULT,
    "f",
    "fadeTime",
    "Sets the fadetime at this node.",
    EV_NORMAL
);

CLASS_DECLARATION(Entity, SplinePath, "info_splinepath") {
    {&EV_SplinePath_Create,           &SplinePath::CreatePath      },
    {&EV_SplinePath_Loop,             &SplinePath::SetLoop         },
    {&EV_SplinePath_Speed,            &SplinePath::SetSpeed        },
    {&EV_SplinePath_SetTriggerTarget, &SplinePath::SetTriggerTarget},
    {&EV_SplinePath_SetWatch,         &SplinePath::SetWatch        },
    {&EV_SplinePath_SetFov,           &SplinePath::SetFov          },
    {&EV_SplinePath_SetFadeTime,      &SplinePath::SetFadeTime     },
    {NULL,                            NULL                         }
};

SplinePath::SplinePath()
{
    entflags |= ECF_SPLINEPATH;

    AddWaitTill(STRING_REACH);

    owner    = this;
    next     = NULL;
    loop     = NULL;
    speed    = 1;
    doWatch  = false;
    watchEnt = "";
    fov      = 0;
    fadeTime = -1;

    setMoveType(MOVETYPE_NONE);
    setSolidType(SOLID_NOT);
    hideModel();

    if (!LoadingSavegame) {
        PostEvent(EV_SplinePath_Create, FRAMETIME);
    }
}

SplinePath::~SplinePath()
{
    // disconnect from the chain
    if (owner != this) {
        owner->SetNext(next);
    } else if (next) {
        next->SetPrev(NULL);
        next = NULL;
    }

    assert(owner == this);
    assert(next == NULL);

    entflags &= ~ECF_SPLINEPATH;
}

void SplinePath::SetLoop(Event *ev)
{
    loop_name = ev->GetString(1);
}

void SplinePath::SetSpeed(Event *ev)
{
    speed = ev->GetFloat(1);
}

void SplinePath::SetTriggerTarget(Event *ev)
{
    SetTriggerTarget(ev->GetString(1));
}

void SplinePath::CreatePath(Event *ev)
{
    const char *target;
    Entity     *ent;

    // Make the path from the targetlist.
    target = Target();
    if (target[0]) {
        ent = (Entity *)G_FindTarget(NULL, target);
        if (ent && ent->IsSubclassOfSplinePath()) {
            next = (SplinePath *)ent;

            if (next->owner != next) {
                // Fixed in OPM
                //  If the target already has an owner, make sure to properly remove the owner
                next->owner->SetNext(NULL);
            }

            next->owner = this;
        } else if (ent) {
            ScriptError("SplinePath::CreatePath: target '%s' for '%s' not found (cannot connect to class '%s')\n", target, targetname.c_str(), ent->getClassname());
        } else {
            ScriptError("SplinePath::CreatePath: target %s not found\n", target);
        }
    }
    if (loop_name.length()) {
        ent = (Entity *)G_FindTarget(NULL, loop_name.c_str());
        if (ent) {
            loop = (SplinePath *)ent;
        }
    }
}

SplinePath *SplinePath::GetNext(void)
{
    return next;
}

SplinePath *SplinePath::GetPrev(void)
{
    if (owner == this) {
        return NULL;
    }

    return owner;
}

void SplinePath::SetNext(SplinePath *node)
{
    if (next) {
        // remove ourselves from the chain
        next->owner = next;
    }

    next = node;
    if (next) {
        // disconnect next from it's previous node
        if (next->owner != next) {
            next->owner->next = NULL;
        }
        next->owner = this;
    }
}

void SplinePath::SetPrev(SplinePath *node)
{
    if (owner != this) {
        owner->next = NULL;
    }

    if (node && (node != this)) {
        // safely remove the node from its chain
        if (node->next) {
            node->next->owner = node->next;
        }
        node->next = this;
        owner      = node;
    } else {
        owner = this;
    }
}

SplinePath *SplinePath::GetLoop(void)
{
    return loop;
}

void SplinePath::SetWatch(const char *name)
{
    if (watchEnt != name) {
        watchEnt = name;
        if (watchEnt.length()) {
            doWatch = true;
        } else {
            doWatch = false;
        }
    }
}

void SplinePath::SetWatch(Event *ev)
{
    SetWatch(ev->GetString(1));
}

void SplinePath::NoWatch(void)
{
    doWatch  = true;
    watchEnt = "none";
}

str SplinePath::GetWatch(void)
{
    return watchEnt;
}

void SplinePath::SetFov(float newFov)
{
    fov = newFov;
}

void SplinePath::SetFov(Event *ev)
{
    fov = ev->GetFloat(1);
}

float SplinePath::GetFov(void)
{
    return fov;
}

void SplinePath::SetFadeTime(float newFadeTime)
{
    fadeTime = newFadeTime;
}

void SplinePath::SetFadeTime(Event *ev)
{
    fadeTime = ev->GetFloat(1);
}

float SplinePath::GetFadeTime(void)
{
    return fadeTime;
}
