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

// tiki_cache.cpp : TIKI's fast implementation

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

struct pchar {
    const char *m_Value;

    pchar() { m_Value = NULL; }

    pchar(const char *value) { m_Value = value; }

    friend bool operator==(const pchar& l, const pchar& r) { return !strcmp(l.m_Value, r.m_Value); }
};

con_map<pchar, dtikianim_t *> *tikianimcache;
con_map<pchar, dtiki_t *>     *tikicache;
static skeletor_c             *skel_entity_cache[TIKI_MAX_ENTITY_CACHE];

template<>
int HashCode<pchar>(const pchar& key)
{
    return HashCode<const char *>(key.m_Value);
}

/*
===============
TIKI_FindTikiAnim
===============
*/
dtikianim_t *TIKI_FindTikiAnim(const char *path)
{
    char filename[1024];

    if (tikianimcache) {
        dtikianim_t **t;

        t = tikianimcache->find(filename);
        if (t) {
            return *t;
        }
    }

    return NULL;
}

/*
===============
TIKI_FindTiki
===============
*/
dtiki_t *TIKI_FindTiki(const char *path)
{
    char filename[1024];

    if (tikicache) {
        dtiki_t **t;

        Q_strncpyz(filename, path, sizeof(filename));
        FS_CanonicalFilename(filename);

        t = tikicache->find(filename);
        if (t) {
            return *t;
        }
    }

    return NULL;
}

/*
===============
TIKI_RegisterTikiAnimFlags
===============
*/
dtikianim_t *TIKI_RegisterTikiAnimFlags(const char *path, qboolean use)
{
    dtikianim_t *tiki;
    char         filename[1024];

    Q_strncpyz(filename, path, sizeof(filename));
    FS_CanonicalFilename(filename);

    if (tikianimcache) {
        dtikianim_t **t;

        t = tikianimcache->find(filename);
        if (t) {
            return *t;
        }
    } else {
        tikianimcache = new con_map<pchar, dtikianim_t *>;
    }

    tiki = TIKI_LoadTikiAnim(filename);
    if (tiki) {
        if (use) {
            Com_DPrintf("^~^~^ Add the following line to the *_precache.scr map script:\n");
            Com_DPrintf("cache %s\n", filename);
        }

        (*tikianimcache)[tiki->name] = tiki;
    }

    return tiki;
}

/*
===============
TIKI_RegisterTikiAnim
===============
*/
dtikianim_t *TIKI_RegisterTikiAnim(const char *path)
{
    return TIKI_RegisterTikiAnimFlags(path, qfalse);
}

/*
===============
TIKI_RegisterTikiFlags
===============
*/
dtiki_t *TIKI_RegisterTikiFlags(const char *path, qboolean use)
{
    dtiki_t          *tiki     = NULL;
    dtikianim_t      *tikianim = NULL;
    con_map<str, str> keyValues;
    const char       *next_path;
    str               key;
    str               value;
    const char       *name;
    char              filename[1024];
    char              full_filename[1024];

    full_filename[0] = 0;

    name = path;
    for (next_path = strstr(name, "|"); next_path; next_path = strstr(name, "|")) {
        key                   = name;
        key[next_path - name] = 0;

        name      = next_path + 1;
        next_path = strstr(name, "|");
        if (!next_path) {
            break;
        }

        value                   = name;
        value[next_path - name] = 0;
        name                    = next_path + 1;

        // add it to the entry
        keyValues[key] = value;

        strcat(full_filename, key.c_str());
        strcat(full_filename, "|");
        strcat(full_filename, value.c_str());
        strcat(full_filename, "|");
    }

    Q_strncpyz(filename, name, sizeof(filename));
    FS_CanonicalFilename(filename);
    Q_strcat(full_filename, sizeof(full_filename), filename);

    if (!tikicache) {
        tikicache = new con_map<pchar, dtiki_t *>;
    } else {
        dtiki_t **t;

        t = tikicache->find(full_filename);
        if (t) {
            return *t;
        }
    }

    tikianim = TIKI_RegisterTikiAnimFlags(filename, use);
    if (tikianim) {
        tiki = TIKI_LoadTikiModel(tikianim, full_filename, &keyValues);
        if (tiki) {
            // cache the tiki
            (*tikicache)[tiki->name] = tiki;
        }
    }

    return tiki;
}

/*
===============
TIKI_RegisterTiki
===============
*/
dtiki_t *TIKI_RegisterTiki(const char *path)
{
    return TIKI_RegisterTikiFlags(path, qfalse);
}

/*
===============
TIKI_FreeAll
===============
*/
void TIKI_FreeAll()
{
    dtiki_t     **entry;
    dtikianim_t **entryanim;
    dtiki_t      *tiki;
    dtikianim_t  *tikianim;
    int           i;

    if (tikicache) {
        con_map_enum<pchar, dtiki_t *> en = *tikicache;

        for (entry = en.NextValue(); entry != NULL; entry = en.NextValue()) {
            skeletor_c *skeletor;

            tiki     = *entry;
            skeletor = (skeletor_c *)tiki->skeletor;
            if (skeletor) {
                delete skeletor;
            }

            tiki->m_boneList.CleanUpChannels();
            // Fixed in OPM
            //  It's better to free aliases when actually clearing the anim cache
            /*
            if (tiki->a->m_aliases) {
                TIKI_Free(tiki->a->m_aliases);
                tiki->a->m_aliases = NULL;
                tiki->a->num_anims = 0;
            }
            */

            TIKI_Free(tiki);
        }

        tikicache->clear();
    }

    if (tikianimcache) {
        con_map_enum<pchar, dtikianim_t *> en = *tikianimcache;

        for (entryanim = en.NextValue(); entryanim != NULL; entryanim = en.NextValue()) {
            tikianim = *entryanim;

            TIKI_RemoveTiki(tikianim);

            // Fixed in OPM
            //  Each tikianim should free their aliases
            if (tikianim->m_aliases) {
                TIKI_Free(tikianim->m_aliases);
                tikianim->m_aliases = NULL;
                tikianim->num_anims = 0;
            }

            TIKI_Free(tikianim);
        }

        tikianimcache->clear();
    }

    tiki_loading = true;

    for (i = 0; i < cache_maxskel; i++) {
        if (skelcache[i].skel) {
            TIKI_FreeSkel(i);
        }
    }
}

/*
===============
TIKI_GetSkeletor
===============
*/
static qboolean tiki_started;

void *TIKI_GetSkeletor(dtiki_t *tiki, int entnum)
{
    skeletor_c *skel;
    int         i;
    int         index;

    if (entnum == ENTITYNUM_NONE) {
        if (!tiki->skeletor) {
            tiki->skeletor = new skeletor_c(tiki);
        }
        skel = (skeletor_c *)tiki->skeletor;
    } else {
        // Added in 2.30
        //  Multiple caches per entity
        for (i = 0; i < TIKI_MAX_ENTITY_CACHE_PER_ENT; i++) {
            index = ((entnum % TIKI_MAX_ENTITIES) * TIKI_MAX_ENTITY_CACHE_PER_ENT) + i;

            skel = skel_entity_cache[index];
            if (!skel) {
                break;
            }

            if (skel->m_Tiki == tiki) {
                return skel;
            }
        }

        if (i == TIKI_MAX_ENTITY_CACHE_PER_ENT) {
            i = 0;
        }

        index = ((entnum % TIKI_MAX_ENTITIES) * TIKI_MAX_ENTITY_CACHE_PER_ENT) + i;
        skel  = skel_entity_cache[index];

        if (skel) {
            delete skel;
        }

        skel                     = new skeletor_c(tiki);
        skel_entity_cache[index] = skel;
    }

    return skel;
}

/*
===============
TIKI_DeleteSkeletor
===============
*/
static void TIKI_DeleteSkeletor(int entnum)
{
    skeletor_c *skel;
    int         i;

    if (entnum == ENTITYNUM_NONE) {
        return;
    }

    for (i = 0; i < TIKI_MAX_ENTITY_CACHE_PER_ENT; i++) {
        skel = skel_entity_cache[entnum * TIKI_MAX_ENTITY_CACHE_PER_ENT + i];
        if (skel) {
            delete skel;
        }
    }
}

/*
===============
TIKI_Begin
===============
*/
void TIKI_Begin(void)
{
    int i;

    for (i = 0; i < TIKI_MAX_ENTITY_CACHE; i++) {
        skel_entity_cache[i] = 0;
    }

    tiki_started = true;
}

/*
===============
TIKI_End
===============
*/
void TIKI_End(void)
{
    int i;

    for (i = 0; i < MAX_GENTITIES; i++) {
        TIKI_DeleteSkeletor(i);
    }

    tiki_started = false;
}

/*
===============
TIKI_FinishLoad
===============
*/
void TIKI_FinishLoad(void)
{
    con_map_enum<pchar, dtikianim_t *> en;
    dtikianim_t                      **entry;

    if (!tiki_loading) {
        return;
    }

    tiki_loading = false;
    SkeletorCacheCleanCache();

    if (!low_anim_memory || !low_anim_memory->integer) {
        return;
    }

    if (tikianimcache) {
        en = *tikianimcache;
        for (entry = en.NextValue(); entry != NULL; entry = en.NextValue()) {
            TIKI_LoadAnim(*entry);
        }
    }
}

/*
===============
TIKI_FreeImages
===============
*/
void TIKI_FreeImages(void)
{
    dtikisurface_t                *dsurf;
    dtiki_t                       *tiki;
    int                            j, k;
    dtiki_t                      **entry;
    con_map_enum<pchar, dtiki_t *> en;

    if (!tikicache) {
        return;
    }

    en = *tikicache;
    for (entry = en.NextValue(); entry != NULL; entry = en.NextValue()) {
        tiki = *entry;
        for (k = 0; k < tiki->num_surfaces; k++) {
            dsurf = &tiki->surfaces[k];

            for (j = 0; j < dsurf->numskins; j++) {
                dsurf->hShader[j] = 0;
            }
        }
    }
}

/*
===============
TIKI_TikiAnimList_f
===============
*/
void TIKI_TikiAnimList_f(void)
{
    con_map_enum<pchar, dtikianim_t *> en;
    dtikianim_t                      **entry;

    Com_Printf("\ntikianimlist:\n");
    if (tikicache) {
        en = *tikianimcache;
        for (entry = en.NextValue(); entry != NULL; entry = en.NextValue()) {
            Com_Printf("%s\n", (*entry)->name);
        }
    }
}

/*
===============
TIKI_TikiList_f
===============
*/
void TIKI_TikiList_f(void)
{
    con_map_enum<pchar, dtiki_t *> en;
    dtiki_t                      **entry;

    Com_Printf("\ntikilist:\n");
    if (tikicache) {
        en = *tikicache;
        for (entry = en.NextValue(); entry != NULL; entry = en.NextValue()) {
            Com_Printf("%s\n", (*entry)->name);
        }
    }
}
