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

// tiki_anim.cpp : TIKI Anim

#include "q_shared.h"
#include "qcommon.h"
#include "../skeletor/skeletor.h"
#include <mem_blockalloc.h>
#include <con_set.h>
#include "tiki_files.h"

/*
===============
AnimCompareFunc
===============
*/
static void *context;

static int AnimCompareFunc(const void *a, const void *b)
{
    dloaddef_t *ld = (dloaddef_t *)context;
    return stricmp(ld->loadanims[*(int *)a]->alias, ld->loadanims[*(int *)b]->alias);
}

/*
===============
TIKI_GetAnimOrder
===============
*/
void TIKI_GetAnimOrder(dloaddef_t *ld, int *order)
{
    int i;

    for (i = 0; i < ld->numanims; i++) {
        order[i] = i;
    }
    context = ld;
    qsort(order, ld->numanims, sizeof(int), AnimCompareFunc);
}

/*
===============
TIKI_Anim_NameForNum
===============
*/
const char *TIKI_Anim_NameForNum(dtiki_t *pmdl, int animnum)
{
    dtikianimdef_t *panimdef;

    if (!pmdl || !pmdl->a) {
        return NULL;
    }

    if (animnum < 0 || animnum >= pmdl->a->num_anims) {
        return NULL;
    }

    panimdef = pmdl->a->animdefs[animnum];
    return panimdef->alias;
}

/*
===============
TIKI_Anim_NumForName
===============
*/
int TIKI_Anim_NumForName(dtiki_t *pmdl, const char *name)
{
    int             iTop;
    int             iBottom;
    int             iMiddle;
    int             iComp;
    dtikianimdef_t *panimdef;
    float           fAnimWeights[MAX_FRAMEINFOS];
    float           fWeight;
    float           fTotalWeight;
    int             iAnimCount;
    int             i, k;

    if (!pmdl) {
        return -1;
    }

    iBottom = 0;
    iTop    = pmdl->a->num_anims - 1;

    while (iBottom <= iTop) {
        iMiddle = (iBottom + iTop) / 2;

        panimdef = pmdl->a->animdefs[iMiddle];
        iComp = stricmp(panimdef->alias, name);

        if (!iComp) {
            if (!(panimdef->flags & TAF_RANDOM)) {
                return iMiddle;
            }

            for (i = iMiddle; i > 0; i--) {
                if (!pmdl->a->animdefs[i - 1] || stricmp(panimdef->alias, pmdl->a->animdefs[i - 1]->alias)) {
                    break;
                }
            }

            k = i;

            for (iMiddle++; iMiddle < pmdl->a->num_anims; iMiddle++) {
                if (!pmdl->a->animdefs[iMiddle] || stricmp(panimdef->alias, pmdl->a->animdefs[iMiddle]->alias)) {
                    break;
                }
            }

            fTotalWeight = 0.0f;
            iAnimCount   = 0;

            for (; i < iMiddle; i++) {
                panimdef = pmdl->a->animdefs[i];
                if (!panimdef) {
                    continue;
                }

                if (panimdef->flags & TAF_AUTOSTEPS) {
                    fAnimWeights[iAnimCount] = 0.0f;
                    panimdef->flags &= ~TAF_AUTOSTEPS;
                } else {
                    fAnimWeights[iAnimCount] = panimdef->weight;
                    fTotalWeight += panimdef->weight;
                }

                iAnimCount++;
            }

            fWeight = randweight() * fTotalWeight;
            for (i = 0; i < iAnimCount; i++) {
                if (fWeight < fAnimWeights[i]) {
                    break;
                }

                fWeight -= fAnimWeights[i];
            }

            iMiddle  = i + k;
            panimdef = pmdl->a->animdefs[iMiddle];
            if (panimdef && panimdef->flags & TAF_NOREPEAT) {
                panimdef->flags |= TAF_AUTOSTEPS;
            }

            return iMiddle;
        }

        if (iComp > 0) {
            iTop = iMiddle - 1;
        } else {
            iBottom = iMiddle + 1;
        }
    }

    return -1;
}

/*
===============
TIKI_Anim_Random
===============
*/
int TIKI_Anim_Random(dtiki_t *pmdl, const char *name)
{
    dtikianimdef_t *panimdef;
    int             i;
    float           totalweight;
    float           weights[MAX_FRAMEINFOS];
    int             anim[MAX_FRAMEINFOS];
    int             num;
    size_t          len;
    int             diff;
    float           weight;

    len = strlen(name);
    if (!len || !pmdl) {
        return -1;
    }

    num         = 0;
    totalweight = 0.0f;
    for (i = 0; i < pmdl->a->num_anims; i++) {
        panimdef = pmdl->a->animdefs[i];
        diff     = strnicmp(panimdef->alias, name, len);
        if (diff || panimdef->alias[len] == '_') {
            if (diff > 0) {
                break;
            }
        } else {
            if (num >= MAX_FRAMEINFOS) {
                break;
            }

            totalweight += panimdef->weight;
            anim[num]    = i;
            weights[num] = panimdef->weight;

            num++;
        }
    }

    // animation name not found
    if (!num) {
        return -1;
    }

    // find a random animation based on the weight
    weight = randweight() * totalweight;
    for (i = 0; i < num; i++) {
        if (weight < weights[i]) {
            break;
        }

        weight -= weights[i];
    }

    return anim[i];
}

/*
===============
TIKI_Anim_NumFrames
===============
*/
int TIKI_Anim_NumFrames(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return 0;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return 0;
    }

    skelAnimDataGameHeader_t *animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);
    return animData->numFrames;
}

/*
===============
TIKI_Anim_Time
===============
*/
float TIKI_Anim_Time(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return 0.0;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return 0.0;
    }

    skelAnimDataGameHeader_t *animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);

    return animData->flags & TAF_DELTADRIVEN ? animData->frameTime * animData->numFrames
                                             : animData->frameTime * (animData->numFrames - 1);
}

/*
===============
TIKI_Anim_Frametime
===============
*/
float TIKI_Anim_Frametime(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return 0.0;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return 0.0;
    }

    skelAnimDataGameHeader_t *animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);
    return animData->frameTime;
}

/*
===============
TIKI_Anim_Delta
===============
*/
void TIKI_Anim_Delta(dtiki_t *pmdl, int animnum, float *delta)
{
    if (!pmdl) {
        VectorClear(delta);
        return;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        VectorClear(delta);
        return;
    }

    skelAnimDataGameHeader_t *animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);
    VectorScale(animData->totalDelta, pmdl->load_scale, delta);
}

/*
===============
TIKI_Anim_AngularDelta

Added in 2.0
===============
*/
void TIKI_Anim_AngularDelta(dtiki_t *pmdl, int animnum, float *delta)
{
    if (!pmdl) {
        *delta = 0;
        return;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        *delta = 0;
        return;
    }

    skelAnimDataGameHeader_t *animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);
    *delta = animData->totalAngleDelta;
}

/*
===============
TIKI_Anim_HasDelta
===============
*/
qboolean TIKI_Anim_HasDelta(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return qfalse;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return qfalse;
    }

    skelAnimDataGameHeader_t *animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);
    return animData->bHasDelta;
}

/*
===============
TIKI_Anim_DeltaOverTime
===============
*/
void TIKI_Anim_DeltaOverTime(dtiki_t *pTiki, int iAnimnum, float fTime1, float fTime2, vec3_t vDelta)
{
    int                       realAnimIndex;
    skelAnimDataGameHeader_t *animData;
    SkelVec3                  absDelta;

    if (!pTiki || !pTiki->a) {
        return;
    }

    if (iAnimnum >= pTiki->a->num_anims || iAnimnum < 0) {
        return;
    }

    if (fTime2 < fTime1) {
        return;
    }

    realAnimIndex = pTiki->a->m_aliases[iAnimnum];
    if (realAnimIndex != -1) {
        animData = SkeletorCacheGetData(realAnimIndex);
        absDelta = animData->GetDeltaOverTime(fTime1, fTime2);
        VectorScale(absDelta, pTiki->load_scale, vDelta);
    } else {
        TIKI_Error("Skeletor GetDeltaOverTime: Couldn't find animation with index %i\n", iAnimnum);
        VectorClear(vDelta);
    }
}

/*
===============
TIKI_Anim_AngularDeltaOverTime
===============
*/
void TIKI_Anim_AngularDeltaOverTime(dtiki_t *pTiki, int iAnimnum, float fTime1, float fTime2, float *fDelta)
{
    int                       realAnimIndex;
    skelAnimDataGameHeader_t *animData;

    *fDelta = 0;

    if (!pTiki || !pTiki->a) {
        return;
    }

    if (iAnimnum >= pTiki->a->num_anims || iAnimnum < 0) {
        return;
    }

    if (fTime2 < fTime1) {
        return;
    }

    realAnimIndex = pTiki->a->m_aliases[iAnimnum];
    if (realAnimIndex != -1) {
        animData = SkeletorCacheGetData(realAnimIndex);
        *fDelta  = animData->GetAngularDeltaOverTime(fTime1, fTime2);
    } else {
        TIKI_Error("Skeletor GetAngularDeltaOverTime: Couldn't find animation with index %i\n", iAnimnum);
        *fDelta = 0;
    }
}

/*
===============
TIKI_Anim_Flags
===============
*/
int TIKI_Anim_Flags(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return 0;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return 0;
    }

    dtikianimdef_t *panimdef = pmdl->a->animdefs[animnum];
    return panimdef->flags;
}

/*
===============
TIKI_Anim_FlagsSkel
===============
*/
int TIKI_Anim_FlagsSkel(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return 0;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return 0;
    }

    skelAnimDataGameHeader_t *animData;
    int                       flags;

    animData = SkeletorCacheGetData(pmdl->a->m_aliases[animnum]);
    flags    = animData->flags;

    if (animData->bHasDelta) {
        flags |= TAF_HASDELTA;
    }

    if (animData->bHasMorph) {
        flags |= TAF_HASMORPH;
    }

    if (animData->bHasUpper) {
        flags |= TAF_HASUPPER;
    }

    return flags;
}

/*
===============
TIKI_Anim_HasServerCommands
===============
*/
qboolean TIKI_Anim_HasServerCommands(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return qfalse;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return qfalse;
    }

    dtikianimdef_t *panimdef = pmdl->a->animdefs[animnum];
    return panimdef->num_server_cmds > 0;
}

/*
===============
TIKI_Anim_HasClientCommands
===============
*/
qboolean TIKI_Anim_HasClientCommands(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return qfalse;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return qfalse;
    }

    dtikianimdef_t *panimdef = pmdl->a->animdefs[animnum];
    return panimdef->num_client_cmds > 0;
}

/*
===============
TIKI_Anim_CrossblendTime
===============
*/
float TIKI_Anim_CrossblendTime(dtiki_t *pmdl, int animnum)
{
    if (!pmdl) {
        return 0.0;
    }

    if (!pmdl->a || animnum < 0 || animnum >= pmdl->a->num_anims) {
        return 0.0;
    }

    dtikianimdef_t *panimdef = pmdl->a->animdefs[animnum];
    return panimdef->blendtime;
}
