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

// navigate.h:
// Potentially could be an C++ implementation of the A* search algorithm, but
// is currently unfinished.
//

#pragma once

#include "g_local.h"
#include "class.h"
#include "entity.h"
#include "stack.h"
#include "container.h"
#include "doors.h"
#include "sentient.h"
#include "../qcommon/qfiles.h"

extern Event EV_AI_SavePaths;
extern Event EV_AI_SaveNodes;
extern Event EV_AI_LoadNodes;
extern Event EV_AI_ClearNodes;
extern Event EV_AI_RecalcPaths;
extern Event EV_AI_CalcPath;
extern Event EV_AI_DisconnectPath;

extern cvar_t *ai_showroutes;
extern cvar_t *ai_showroutes_distance;
extern cvar_t *ai_shownodenums;
extern cvar_t *ai_shownode;
extern cvar_t *ai_showallnode;
extern cvar_t *ai_showpath;
extern cvar_t *ai_fallheight;
extern cvar_t *ai_debugpath;
extern cvar_t *ai_pathchecktime;
extern cvar_t *ai_pathcheckdist;

extern int ai_maxnode;

#define MAX_PATHCHECKSPERFRAME 4

extern int path_checksthisframe;

#define MAX_PATH_LENGTH  128 // should be more than plenty
#define NUM_PATHSPERNODE 48

class Path;
class PathNode;
class SimpleActor;

#define NUM_WIDTH_VALUES 16
#define WIDTH_STEP       8
#define MAX_WIDTH        (WIDTH_STEP * NUM_WIDTH_VALUES)
#define MAX_HEIGHT       128

#define CHECK_PATH(path, width, height)                \
    ((((width) >= MAX_WIDTH) || ((width) < 0)) ? false \
                                               : ((int)(path)->maxheight[((width) / WIDTH_STEP) - 1] < (int)(height)))

class pathway_ref
{
public:
    short int from;
    short int to;
};

typedef struct {
    byte      numBlockers;
    byte      badPlaceTeam[2];
    short int node;
    short int fallheight;
    float     dist;
    vec2_t    dir;
    vec3_t    pos1;
    vec3_t    pos2;
} pathway_t;

class PathInfo
{
public:
    bool   bAccurate;
    vec3_t point;
    float  dist;
    vec2_t dir;

public:
    PathInfo();

    void Archive(Archiver& arc);
};

inline PathInfo::PathInfo()
    : bAccurate(false)
    , point {0, 0, 0}
    , dist(0)
    , dir {0, 0}
{}

inline void PathInfo::Archive(Archiver& arc)
{
    arc.ArchiveBool(&bAccurate);
    arc.ArchiveVec3(point);
    arc.ArchiveFloat(&dist);
    arc.ArchiveVec2(dir);
}

typedef enum {
    NOT_IN_LIST,
    IN_OPEN,
    IN_CLOSED
} pathlist_t;

#define PATH_DONT_LINK  1
#define AI_DUCK         2
#define AI_COVER        4
#define AI_CONCEALMENT  8
#define AI_CORNER_LEFT  16
#define AI_CORNER_RIGHT 32
#define AI_SNIPER       64
#define AI_CRATE        128
// Added in 2.0
#define AI_LOW_WALL_ARC 256

#define AI_MOVE_MASK \
    (AI_COVER | AI_CONCEALMENT | AI_CORNER_LEFT | AI_CORNER_RIGHT | AI_SNIPER | AI_CRATE | AI_LOW_WALL_ARC)
#define AI_COVER_MASK       (AI_COVER | AI_CONCEALMENT | AI_CORNER_LEFT | AI_CORNER_RIGHT | AI_CRATE | AI_LOW_WALL_ARC)
#define AI_CONCEALMENT_MASK (AI_CORNER_LEFT | AI_CORNER_RIGHT | AI_CRATE | AI_LOW_WALL_ARC)

void DrawNode(int iNodeCount);
void DrawAllConnections(void);

class PathNode : public SimpleEntity
{
public:
    int             findCount;
    pathway_t      *Child;
    int             numChildren;
    int             virtualNumChildren;
    float           f;
    float           h;
    float           g;
    class PathNode *Parent;
    bool            inopen;
    PathNode       *PrevNode;
    PathNode       *NextNode;
    short int       pathway;
    const vec_t    *m_PathPos;
    float           dist;
    vec2_t          dir;
    int             nodeflags;
    SafePtr<Entity> pLastClaimer;
    int             iAvailableTime;
    int             nodenum;
    short int       m_Depth;
    float           m_fLowWallArc;

    friend class PathSearch;
    friend void DrawAllConnections(void);

private:
    void ConnectTo(PathNode *node);
    void SetNodeFlags(Event *ev);
    void SetLowWallArc(Event *ev); // Added in 2.0
    void Remove(Event *ev);

public:
    CLASS_PROTOTYPE(PathNode);

    PathNode();
    virtual ~PathNode();

    void *operator new(size_t size);
    void  operator delete(void *ptr);

    void Archive(Archiver& arc) override;
    void ArchiveDynamic(Archiver& arc);
    void ArchiveStatic(Archiver& arc);

    bool     CheckPathTo(PathNode *node);
    void     CheckPathToDefault(PathNode *node, pathway_t *pathway);
    qboolean LadderTo(PathNode *node, pathway_t *pathway);

    void      DrawConnections(void);
    void      Claim(Entity *pClaimer);
    void      Relinquish(void);
    Entity   *GetClaimHolder(void) const;
    bool      IsClaimedByOther(Entity *pPossibleClaimer) const;
    void      MarkTemporarilyBad(void);
    void      ConnectChild(int i);
    void      DisconnectChild(int i);
    const_str GetSpecialAttack(class Actor *pActor);
    void      IsTouching(Event *ev);
    qboolean  IsTouching(Entity *e1);
    void      setOriginEvent(Vector org) override;
};

typedef SafePtr<PathNode> PathNodePtr;

#define PATHMAP_CELLSIZE 256
#define PATHMAP_GRIDSIZE (MAX_MAP_BOUNDS * 2 / PATHMAP_CELLSIZE)

#define PATHMAP_NODES    128 // 128 - sizeof( int ) / sizeof( short )

#define MAX_PATHNODES    4096

class MapCell : public Class
{
private:
    int    numnodes;
    short *nodes;

    friend class PathSearch;

public:
    MapCell();
    ~MapCell();

    void AddNode(PathNode *node);
    int  NumNodes(void);
};

class PathSearch : public Listener
{
    friend class PathNode;

private:
    static MapCell   PathMap[PATHMAP_GRIDSIZE][PATHMAP_GRIDSIZE];
    static PathNode *open;
    static int       findFrame;
    static qboolean  m_bNodesloaded;
    static qboolean  m_NodeCheckFailed;
    static int       m_LoadIndex;

public:
    static PathNode   *pathnodes[4096];
    static int         nodecount;
    static float       total_dist;
    static const char *last_error;

private:
    static void     LoadAddToGrid(int x, int y);
    static void     LoadAddToGrid2(PathNode *node, int x, int y);
    static void     AddToGrid(PathNode *node, int x, int y);
    static bool     Connect(PathNode *node, int x, int y);
    static int      NodeCoordinate(float coord);
    static int      GridCoordinate(float coord);
    static qboolean ArchiveSaveNodes(void);
    static void     ArchiveLoadNodes(void);
    static void     Init(void);

public:
    CLASS_PROTOTYPE(PathSearch);

    PathSearch();
    virtual ~PathSearch();

    static void ArchiveStaticLoad(Archiver& arc);
    static void ArchiveStaticSave(Archiver& arc);
    static bool ArchiveDynamic(Archiver& arc);

    static void AddNode(PathNode *node);
    static void Connect(PathNode *node);
    static void UpdateNode(PathNode *node);

    static MapCell *GetNodesInCell(int x, int y);
    static MapCell *GetNodesInCell(const vec3_t pos);

    static class PathNode *DebugNearestStartNode(const vec3_t pos, Entity *ent = NULL);
    static class PathNode *NearestStartNode(const vec3_t pos, SimpleActor *ent);
    static class PathNode *NearestEndNode(const vec3_t pos);
    static int             DebugNearestNodeList(const vec3_t pos, PathNode **nodelist, int iMaxNodes);
    static int             DebugNearestNodeList2(const vec3_t pos, PathNode **nodelist, int iMaxNodes);

    static void  ShowNodes(void);
    static void  LoadNodes(void);
    static void  CreatePaths(void);
    static void *AllocPathNode(void);
    static void  FreePathNode(void *);
    static void  ResetNodes(void);
    static void  ClearNodes(void); // Added in OPM

    static void      UpdatePathwaysForBadPlace(const Vector& origin, float radius, int dir, int team);
    static PathInfo *GeneratePath(PathInfo *path);
    static PathInfo *GeneratePathNear(PathInfo *path);
    static PathInfo *GeneratePathAway(PathInfo *path);

    static class PathNode *GetSpawnNode(ClassDef *cls);

    static int FindPath(
        const vec3_t start,
        const vec3_t end,
        Entity      *ent,
        float        maxPath,
        const vec3_t vLeashHome,
        float        fLeashDistSquared,
        int          fallheight
    );
    static int FindPathAway(
        const vec3_t start,
        const vec3_t avoid,
        const vec3_t vPreferredDir,
        Entity      *ent,
        float        fMinSafeDist,
        const vec3_t vLeashHome,
        float        fLeashDistSquared,
        int          fallheight
    );
    static int FindPathNear(
        const vec3_t start,
        const vec3_t end,
        Entity      *ent,
        float        maxPath,
        float        fRadiusSquared,
        const vec3_t vLeashHome,
        float        fLeashDistSquared,
        int          fallheight
    );

    static class PathNode *
    FindCornerNodeForWall(const vec3_t start, const vec3_t end, Entity *ent, float maxPath, const vec4_t plane);
    static class PathNode *FindCornerNodeForExactPath(Entity *self, Sentient *enemy, float fMaxPath);
    static int  FindPotentialCover(Entity *pEnt, Vector& vPos, Entity *pEnemy, PathNode **ppFoundNodes, int iMaxFind);
    static void PlayerCover(class Player *pPlayer);
    static class PathNode *FindNearestCover(Entity *pEnt, Vector& vPos, Entity *pEnemy);
    static class PathNode *FindNearestSniperNode(Entity *pEnt, Vector& vPos, Entity *pEnemy);

private:
    static int NearestNodeSetup(const vec3_t pos, MapCell *cell, int *nodes, vec3_t *deltas);
};

inline MapCell::~MapCell()
{
    numnodes = 0;
    nodes    = NULL;
}

extern PathSearch PathManager;

//===============
// Added in OPM
//===============

class NavMaster : public Listener
{
public:
    CLASS_PROTOTYPE(NavMaster);

    NavMaster();

    void Init(void);

    void CreatePaths(Event *ev);
    void CreateNode(Event *ev);
    void SetNodeFlags(Event *ev);
    void SetNodeTargetName(Event *ev);
    void SetNodeTarget(Event *ev);
    void RemoveNode(Event *ev);
    void Frame();

private:
    void      CheckNodeSelected();
    PathNode *DetermineCurrentNode() const;
    bool      FovCheck(const Vector& dir, const Vector& delta, float fov) const;

private:
    SafePtr<PathNode> selectedNode;
};

extern NavMaster navMaster;

PathNode *AI_FindNode(const char *name);
void      AI_AddNode(PathNode *node);
void      AI_RemoveNode(PathNode *node);
void      AI_ResetNodes(void);

class AttractiveNode : public SimpleArchivedEntity
{
public:
    int       m_iPriority;
    bool      m_bUse;
    float     m_fMaxStayTime;
    float     m_fMaxDistance;
    float     m_fMaxDistanceSquared;
    float     m_fRespawnTime;
    const_str m_csTeam;
    int       m_iTeam;

private:
    //	Container< SafePtr< Sentient > >	m_pSentList;

public:
    CLASS_PROTOTYPE(AttractiveNode);

    AttractiveNode();
    ~AttractiveNode();

    bool CheckTeam(Sentient *sent);
    void setMaxDist(float dist);

    void GetPriority(Event *ev);
    void SetPriority(Event *ev);
    void GetDistance(Event *ev);
    void SetDistance(Event *ev);
    void GetStayTime(Event *ev);
    void SetStayTime(Event *ev);
    void GetRespawnTime(Event *ev);
    void SetRespawnTime(Event *ev);
    void GetTeam(Event *ev);
    void SetTeam(Event *ev);
    void SetUse(Event *ev);

    void Archive(Archiver& arc) override;
};

typedef SafePtr<AttractiveNode> AttractiveNodePtr;

inline void AttractiveNode::Archive(Archiver& arc)
{
    arc.ArchiveInteger(&m_iPriority);
    arc.ArchiveBool(&m_bUse);
    arc.ArchiveFloat(&m_fMaxStayTime);
    arc.ArchiveFloat(&m_fMaxDistanceSquared);
}

extern Container<AttractiveNode *> attractiveNodes;
