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

#include "cm_local.h"
#include "../server/server.h"
#include "../client/client.h"
#include "tiki.h"

#define LOCATION_FAIL    -2
#define MAX_HITLOCATIONS 19

const char *szLocArray[] = {
    "Bip01 Head",
    "Bip01 Head",
    "Bip01 Neck",
    "Bip01 Spine2",
    "Bip01 Spine1",
    "Bip01 Spine",
    "Bip01 Pelvis",
    "Bip01 R UpperArm",
    "Bip01 L UpperArm",
    "Bip01 R Thigh",
    "Bip01 L Thigh",
    "Bip01 R Forearm",
    "Bip01 L Forearm",
    "Bip01 R Calf",
    "Bip01 L Calf",
    "Bip01 R Hand",
    "Bip01 L Hand",
    "Bip01 R Foot",
    "Bip01 L Foot"
};

float fLocRadius[] = {
    5.0, 5.5, 4.0, 9.0, 8.0, 9.0, 9.0, 7.0, 7.0, 8.0, 8.0, 5.5, 5.5, 7.5, 7.5, 6.0, 6.0, 6.0, 6.0,
};

vec3_t vLocOffset[] = {
    {2.5,  3.5,  0.0},
    {8.5,  1.0,  0.0},
    {3.5,  -1.0, 0.0},
    {5.0,  1.0,  3.0},
    {2.0,  1.0,  3.0},
    {1.0,  1.0,  3.0},
    {-3.0, 1.0,  4.0},
    {4.0,  0.0,  0.0},
    {4.0,  0.0,  0.0},
    {12.0, 0.0,  0.0},
    {12.0, 0.0,  0.0},
    {5.0,  1.0,  0.0},
    {5.0,  1.0,  0.0},
    {8.0,  0.0,  0.0},
    {8.0,  0.0,  0.0},
    {5.0,  1.0,  0.0},
    {5.0,  1.0,  0.0},
    {3.0,  2.0,  0.0},
    {3.0,  2.0,  0.0},
};

float fLocRadius_Secondary[] = {
    0.0, 5.5, 4.0, 9.0, 8.0, 9.0, 9.0, 6.0, 6.0, 8.0, 8.0, 5.0, 5.0, 6.5, 6.5, 0.0, 0.0, 5.0, 5.0,
};

vec3_t vLocOffset_Secondary[] = {
    {0.0,  0.0,  0.0 },
    {7.0,  -2.5, 0.0 },
    {2.0,  2.0,  0.0 },
    {5.0,  1.0,  -3.0},
    {2.0,  1.0,  -3.0},
    {1.0,  1.0,  -3.0},
    {-3.0, 1.0,  -4.0},
    {11.5, 0.0,  0.0 },
    {11.5, 0.0,  0.0 },
    {22.0, 0.0,  0.0 },
    {22.0, 0.0,  0.0 },
    {11.5, 0.0,  0.0 },
    {11.5, 0.0,  0.0 },
    {19.0, 0.0,  0.0 },
    {19.0, 0.0,  0.0 },
    {0.0,  0.0,  0.0 },
    {0.0,  0.0,  0.0 },
    {4.0,  9.0,  0.0 },
    {4.0,  9.0,  0.0 },
};

/*
==================
LineSegmentToSphereIntersect
==================
*/
static qboolean
LineSegmentToSphereIntersect(trace_t *pTrace, float fRadius, const vec3_t vCntr, const vec3_t vStart, const vec3_t vEnd)
{
    vec3_t vDiff, vDir;
    float  fDist;
    float  fLSqrd;
    float  fRSqrd;
    float  fDSqrd;
    float  fLength;
    float  fOfs;
    float  fAngSin;
    vec3_t vProject, vOfs;

    fRSqrd = fRadius * fRadius;

    VectorSubtract(vEnd, vStart, vDir);
    fLength = VectorNormalize(vDir);

    VectorSubtract(vCntr, vStart, vDiff);
    fDSqrd = DotProduct(vDiff, vDir);
    fDist  = VectorLengthSquared(vDiff);

    if (fDSqrd < 0 && fDist > fRSqrd) {
        return qfalse;
    }

    fLSqrd = fDist - fDSqrd * fDSqrd;
    if (fLSqrd > fRSqrd || fDSqrd > fLength && fDSqrd - fLength > fRadius) {
        return qfalse;
    }

    vOfs[0] = vStart[0] + vDir[0] * fDSqrd;
    vOfs[1] = vStart[1] + vDir[1] * fDSqrd;
    vOfs[2] = vStart[2] + vDir[2] * fDSqrd;
    VectorSubtract(vCntr, vOfs, vProject);

    fOfs = VectorLength(vProject) / fRadius;

    if (fOfs >= -0.5) {
        if (fOfs <= 0.5) {
            fAngSin = fOfs * (-0.531387674508458 * fOfs - 2.11e-14) + 1.00086138065435;
        } else {
            fAngSin = fOfs
                        * (((((-1507.55394345521 * fOfs + 6580.58002318442) * fOfs - 11860.0735285953) * fOfs
                             + 11290.7510782536)
                                * fOfs
                            - 5986.89654545347)
                               * fOfs
                           + 1675.66417006387)
                    - 192.426950291139;
        }
    } else {
        fAngSin = fOfs
                * (((((-337.31875783205 * fOfs - 1237.54375255107) * fOfs - 1802.11467325687) * fOfs - 1303.19904613494)
                        * fOfs
                    - 471.347871690988)
                       * fOfs
                   - 70.0883838161826);
    }

    pTrace->fraction = (fDSqrd - fRadius * fAngSin) / fLength;
    return qtrue;
}

/*
==================
CM_GetHitLocationInfo
==================
*/
const char *CM_GetHitLocationInfo(int i_iLocation, float *o_fRadius, vec3_t o_vOffset)
{
    if (i_iLocation < MAX_HITLOCATIONS) {
        *o_fRadius = fLocRadius[i_iLocation];
        VectorCopy(vLocOffset[i_iLocation], o_vOffset);
        return szLocArray[i_iLocation];
    } else {
        *o_fRadius = 8.0f;
        VectorClear(o_vOffset);
        return NULL;
    }
}

/*
==================
CM_GetHitLocationInfoSecondary
==================
*/
const char *CM_GetHitLocationInfoSecondary(int i_iLocation, float *o_fRadius, vec3_t o_vOffset)
{
    if (i_iLocation < MAX_HITLOCATIONS) {
        *o_fRadius = fLocRadius_Secondary[i_iLocation];
        VectorCopy(vLocOffset_Secondary[i_iLocation], o_vOffset);
        return szLocArray[i_iLocation];
    } else {
        *o_fRadius = 8.0f;
        VectorClear(o_vOffset);
        return NULL;
    }
}

/*
==================
CM_TraceDeepSimple
==================
*/
static qboolean CM_TraceDeepSimple(
    trace_t     *results,
    const vec3_t vStart,
    const vec3_t vEnd,
    int          iBrushMask,
    int          iEntContents,
    const vec3_t vEntMins,
    const vec3_t vEntMaxs,
    const vec3_t vEntAngles,
    vec3_t       vTransStart,
    vec3_t       vTransEnd
)
{
    vec3_t       vCntr;
    vec3_t       vTemp;
    vec3_t       vNewMins, vNewMaxs;
    vec3_t       vForward, vLeft, vUp;
    clipHandle_t clipHandle;

    vNewMins[0] = vEntMins[0] - 40.0;
    vNewMins[1] = vEntMins[1] - 40.0;
    vNewMins[2] = vEntMins[2] - 40.0;
    vNewMaxs[0] = vEntMaxs[0] + 40.0;
    vNewMaxs[1] = vEntMaxs[1] + 40.0;
    vNewMaxs[2] = vEntMaxs[2] + 40.0;

    clipHandle = CM_TempBoxModel(vNewMins, vNewMaxs, iEntContents);
    AngleVectorsLeft(vEntAngles, vForward, vLeft, vUp);

    VectorCopy(vTransStart, vTemp);
    vTransStart[0] = DotProduct(vTemp, vForward);
    vTransStart[1] = DotProduct(vTemp, vLeft);
    vTransStart[2] = DotProduct(vTemp, vUp);

    VectorCopy(vTransEnd, vTemp);
    vTransEnd[0] = DotProduct(vTemp, vForward);
    vTransEnd[1] = DotProduct(vTemp, vLeft);
    vTransEnd[2] = DotProduct(vTemp, vUp);

    // don't use mins and maxs
    VectorClear(vCntr);
    CM_BoxTrace(results, vTransStart, vTransEnd, vCntr, vCntr, clipHandle, iBrushMask, qfalse);

    if (results->fraction == 1.0 && !results->startsolid && !results->allsolid) {
        VectorCopy(vEnd, results->endpos);
        return qfalse;
    }

    return qtrue;
}

/*
==================
CM_TraceDeepSimple2
==================
*/
static qboolean CM_TraceDeepSimple2(
    trace_t             *results,
    const orientation_t *orPosition,
    vec3_t               vOffset,
    float                fRad,
    const char          *pszTagName,
    int                  iLocation,
    const vec3_t         vTransStart,
    const vec3_t         vTransEnd
)
{
    vec3_t vCntr;

    VectorCopy(orPosition->origin, vCntr);

    if (vOffset[0] != 0.0) {
        VectorMA(vCntr, vOffset[0], orPosition->axis[0], vCntr);
    }
    if (vOffset[1] != 0.0) {
        VectorMA(vCntr, vOffset[1], orPosition->axis[1], vCntr);
    }
    if (vOffset[2] != 0.0) {
        VectorMA(vCntr, vOffset[2], orPosition->axis[2], vCntr);
    }

    if (LineSegmentToSphereIntersect(results, fRad, vCntr, vTransStart, vTransEnd)) {
        return qtrue;
    }

    // get the secondary location if any
    if (!CM_GetHitLocationInfoSecondary(iLocation, &fRad, vOffset)) {
        return qfalse;
    }

    VectorCopy(orPosition->origin, vCntr);

    if (vOffset[0] != 0.0) {
        VectorMA(vCntr, vOffset[0], orPosition->axis[0], vCntr);
    }
    if (vOffset[1] != 0.0) {
        VectorMA(vCntr, vOffset[1], orPosition->axis[1], vCntr);
    }
    if (vOffset[2] != 0.0) {
        VectorMA(vCntr, vOffset[2], orPosition->axis[2], vCntr);
    }

    if (LineSegmentToSphereIntersect(results, fRad, vCntr, vTransStart, vTransEnd)) {
        return qtrue;
    }

    return qfalse;
}

/*
==================
CM_TraceDeepFail
==================
*/
static void CM_TraceDeepFail(trace_t *results, const vec3_t vEnd)
{
    results->fraction   = 1.0;
    results->allsolid   = qfalse;
    results->startsolid = qfalse;
    VectorCopy(vEnd, results->endpos);
    results->entityNum = ENTITYNUM_NONE;
    results->location  = LOCATION_FAIL;
    results->ent       = NULL;
}

/*
==================
CM_TraceDeepSuccess
==================
*/
static void CM_TraceDeepSuccess(
    trace_t *results, const vec3_t vStart, const vec3_t vEnd, int iEntNum, int iEntContents, int iLocation
)
{
    results->endpos[0]  = vStart[0] + results->fraction * (vEnd[0] - vStart[0]);
    results->endpos[1]  = vStart[1] + results->fraction * (vEnd[1] - vStart[1]);
    results->endpos[2]  = vStart[2] + results->fraction * (vEnd[2] - vStart[2]);
    results->allsolid   = qfalse;
    results->startsolid = qfalse;
    results->contents   = iEntContents;
    results->entityNum  = iEntNum;
    results->location   = iLocation;
    results->ent        = NULL;
}

/*
==================
SV_TraceDeep_DebugDraw
==================
*/
void SV_TraceDeep_DebugDraw(gentity_t *touch, orientation_t& orTag, int iLocation, qboolean bHit)
{
    int           i;
    float         fRad;
    vec3_t        vOffset;
    vec3_t        vCntr;
    orientation_t orEnt;
    orientation_t orPosition;
    const char   *pszLocationName;

    if (!sv_deeptracedebug->integer) {
        return;
    }

    VectorCopy(touch->s.origin, orEnt.origin);
    AngleVectors(touch->s.angles, orEnt.axis[0], orEnt.axis[1], orEnt.axis[2]);
    VectorInverse(orEnt.axis[1]);

    pszLocationName = CM_GetHitLocationInfo(iLocation, &fRad, vOffset);

    if (bHit) {
        Com_Printf("Hit %i '%s'\n", iLocation, pszLocationName);
    }

    // get the orientation
    VectorCopy(orEnt.origin, orPosition.origin);

    for (i = 0; i < 3; i++) {
        VectorMA(orPosition.origin, orEnt.origin[i], orEnt.axis[i], orPosition.origin);
    }
    MatrixMultiply(orTag.axis, orEnt.axis, orPosition.axis);

    VectorCopy(orPosition.origin, vCntr);

    if (vOffset[0] != 0.0) {
        VectorMA(vCntr, vOffset[0], orPosition.axis[0], vCntr);
    }
    if (vOffset[1] != 0.0) {
        VectorMA(vCntr, vOffset[1], orPosition.axis[1], vCntr);
    }
    if (vOffset[2] != 0.0) {
        VectorMA(vCntr, vOffset[2], orPosition.axis[2], vCntr);
    }

    if (bHit) {
        ge->DebugCircle(vCntr, fRad, 1.0, 0.5, 0.0, 1.0, qfalse);
    } else {
        ge->DebugCircle(vCntr, fRad, 0.0, 0.5, 1.0, 1.0, qfalse);
    }

    // get the secondary location if any
    if (!CM_GetHitLocationInfoSecondary(iLocation, &fRad, vOffset)) {
        return;
    }

    VectorCopy(orPosition.origin, vCntr);

    if (vOffset[0] != 0.0) {
        VectorMA(vCntr, vOffset[0], orPosition.axis[0], vCntr);
    }
    if (vOffset[1] != 0.0) {
        VectorMA(vCntr, vOffset[1], orPosition.axis[1], vCntr);
    }
    if (vOffset[2] != 0.0) {
        VectorMA(vCntr, vOffset[2], orPosition.axis[2], vCntr);
    }

    if (bHit) {
        ge->DebugCircle(vCntr, fRad, 1.0, 0.0, 0.5, 1.0, qfalse);
    } else {
        ge->DebugCircle(vCntr, fRad, 0.5, 0.0, 1.0, 1.0, qfalse);
    }
}

/*
==================
SV_TraceDeep
==================
*/
void SV_TraceDeep(trace_t *results, const vec3_t vStart, const vec3_t vEnd, int iBrushMask, gentity_t *touch)
{
    int           iLocation, iBoneNum;
    float         fRad;
    vec3_t        vOffset;
    orientation_t orPosition;
    const char   *pszTagName;
    dtiki_t      *tiki;
    vec3_t        vTransStart, vTransEnd;

    // subtract the origin
    VectorSubtract(vStart, touch->s.origin, vTransStart);
    VectorSubtract(vEnd, touch->s.origin, vTransEnd);

    // check if something was not hit first
    if (!CM_TraceDeepSimple(
            results,
            vStart,
            vEnd,
            iBrushMask,
            touch->r.contents,
            touch->r.mins,
            touch->r.maxs,
            touch->s.angles,
            vTransStart,
            vTransEnd
        )) {
        return;
    }

    tiki = touch->tiki;

    // trace through each locations
    for (iLocation = 0; iLocation < MAX_HITLOCATIONS; iLocation++) {
        pszTagName = CM_GetHitLocationInfo(iLocation, &fRad, vOffset);
        iBoneNum   = tiki->GetBoneNumFromName(pszTagName);

        if (iBoneNum == -1) {
            continue;
        }

        // retrieve the orientation from the game dll
        orPosition = ge->TIKI_Orientation(touch, iBoneNum);

        // check if the tag was hit
        if (CM_TraceDeepSimple2(results, &orPosition, vOffset, fRad, pszTagName, iLocation, vTransStart, vTransEnd)) {
            SV_TraceDeep_DebugDraw(touch, orPosition, iLocation, qtrue);

            // set the location
            CM_TraceDeepSuccess(results, vStart, vEnd, touch->s.number, touch->r.contents, iLocation);
            return;
        }

        SV_TraceDeep_DebugDraw(touch, orPosition, iLocation, qfalse);
    }

    // fail
    CM_TraceDeepFail(results, vEnd);
}

/*
==================
CL_CheckWeights
==================
*/
qboolean CL_CheckWeights(refEntity_t *ent)
{
    int i;

    if (!ent->tiki) {
        return qtrue;
    }

    for (i = 0; i < MAX_FRAMEINFOS; i++) {
        if (ent->frameInfo[i].weight > 0.0f) {
            return qtrue;
        }
    }

    return qfalse;
}

#ifdef CLIENT

/*
==================
CL_TraceDeep
==================
*/
qboolean CL_TraceDeep(
    trace_t     *results,
    const vec3_t vStart,
    const vec3_t vEnd,
    int          iBrushMask,
    int          iEntNum,
    int          iEntContents,
    const vec3_t vEntMins,
    const vec3_t vEntMaxs,
    const vec3_t vEntOrigin,
    const vec3_t vEntAngles,
    refEntity_t *model
)
{
    int           iLocation, iBoneNum;
    float         fRad;
    vec3_t        vOffset;
    orientation_t orPosition;
    const char   *pszTagName;
    dtiki_t      *tiki;
    vec3_t        vTransStart, vTransEnd;

    // subtract the origin
    VectorSubtract(vStart, vEntOrigin, vTransStart);
    VectorSubtract(vEnd, vEntOrigin, vTransEnd);

    // check if something was not hit first
    if (!CM_TraceDeepSimple(
            results, vStart, vEnd, iBrushMask, iEntContents, vEntMins, vEntMaxs, vEntAngles, vTransStart, vTransEnd
        )) {
        return qfalse;
    }

    tiki = model->tiki;

    // trace through each locations
    for (iLocation = 0; iLocation < MAX_HITLOCATIONS; iLocation++) {
        pszTagName = CM_GetHitLocationInfo(iLocation, &fRad, vOffset);
        iBoneNum   = tiki->GetBoneNumFromName(pszTagName);

        if (iBoneNum == -1) {
            continue;
        }

        // retrieve the orientation from the game dll
        orPosition = re.TIKI_Orientation(model, iBoneNum);

        // check if the tag was hit
        if (CM_TraceDeepSimple2(results, &orPosition, vOffset, fRad, pszTagName, iLocation, vTransStart, vTransEnd)) {
            // set the location
            CM_TraceDeepSuccess(results, vStart, vEnd, iEntNum, iEntContents, iLocation);
            return qtrue;
        }
    }

    // fail
    CM_TraceDeepFail(results, vEnd);
    return qfalse;
}

#endif
