/**
 * MaNGOS is a full featured server for World of Warcraft, supporting
 * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
 *
 * Copyright (C) 2005-2022 MaNGOS <https://getmangos.eu>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * World of Warcraft, and all World of Warcraft or Warcraft art, images,
 * and lore are copyrighted by Blizzard Entertainment, Inc.
 */

#ifndef MANGOS_NGRID_H
#define MANGOS_NGRID_H

#include "GameSystem/Grid.h"
#include "GameSystem/GridReference.h"
#include "Timer.h"

#include <cassert>

/**
 * @brief NGrid is nothing more than a wrapper of the Grid with an NxN cells
 *
 */
class GridInfo
{
    public:

        /**
         * @brief
         *
         */
        GridInfo()
            : i_timer(0), i_unloadActiveLockCount(0), i_unloadExplicitLock(false)
        {
        }

        /**
         * @brief
         *
         * @param expiry
         * @param unload
         */
        GridInfo(time_t expiry, bool unload = true)
            : i_timer(expiry), i_unloadActiveLockCount(0), i_unloadExplicitLock(!unload)
        {
        }

        /**
         * @brief
         *
         * @return const TimeTracker
         */
        const TimeTracker& getTimeTracker() const { return i_timer; }

        /**
         * @brief
         *
         * @return bool
         */
        bool getUnloadLock() const
        {
            return i_unloadActiveLockCount || i_unloadExplicitLock;
        }

        /**
         * @brief
         *
         * @param on
         */
        void setUnloadExplicitLock(bool on) { i_unloadExplicitLock = on; }
        /**
         * @brief
         *
         */
        void incUnloadActiveLock() { ++i_unloadActiveLockCount; }
        /**
         * @brief
         *
         */
        void decUnloadActiveLock() { if (i_unloadActiveLockCount) { --i_unloadActiveLockCount; } }

        /**
         * @brief
         *
         * @param pTimer
         */
        void setTimer(const TimeTracker& pTimer) { i_timer = pTimer; }
        /**
         * @brief
         *
         * @param interval
         */
        void ResetTimeTracker(time_t interval) { i_timer.Reset(interval); }
        /**
         * @brief
         *
         * @param diff
         */
        void UpdateTimeTracker(time_t diff) { i_timer.Update(diff); }

    private:

        TimeTracker i_timer;                                /**< TODO */
        uint16 i_unloadActiveLockCount : 16;                /**< lock from active object spawn points (prevent clone loading) */
        bool i_unloadExplicitLock      : 1;                 /**< explicit manual lock or config setting */
};

/**
 * @brief
 *
 */
typedef enum
{
    GRID_STATE_INVALID = 0,
    GRID_STATE_ACTIVE = 1,
    GRID_STATE_IDLE = 2,
    GRID_STATE_REMOVAL = 3,
    MAX_GRID_STATE = 4
} grid_state_t;

template
<
uint32 N,
       class ACTIVE_OBJECT,
       class WORLD_OBJECT_TYPES,
       class GRID_OBJECT_TYPES
       >
/**
 * @brief
 *
 */
class NGrid
{
    public:

        /**
         * @brief
         *
         */
        using GridType = Grid<ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES>;

        /**
         * @brief
         *
         * @param id
         * @param x
         * @param y
         * @param expiry
         * @param unload
         */
        NGrid(uint32 id, uint32 x, uint32 y, time_t expiry, bool unload = true)
            : i_gridId(id), i_x(x), i_y(y), i_cellstate(GRID_STATE_INVALID), i_GridObjectDataLoaded(false)
        {
            i_GridInfo = GridInfo(expiry, unload);
        }

        /**
         * @brief
         *
         * @param x
         * @param y
         * @return const GridType &operator
         */
        const GridType& operator()(uint32 x, uint32 y) const
        {
            assert(x < N);
            assert(y < N);
            return i_cells[x][y];
        }

        /**
         * @brief
         *
         * @param x
         * @param y
         * @return GridType &operator
         */
        GridType& operator()(uint32 x, uint32 y)
        {
            assert(x < N);
            assert(y < N);
            return i_cells[x][y];
        }

        /**
         * @brief
         *
         * @return const uint32
         */
        const uint32& GetGridId() const { return i_gridId; }
        /**
         * @brief
         *
         * @param id
         */
        void SetGridId(const uint32 id) { i_gridId = id; }
        /**
         * @brief
         *
         * @return grid_state_t
         */
        grid_state_t GetGridState() const { return i_cellstate; }
        /**
         * @brief
         *
         * @param s
         */
        void SetGridState(grid_state_t s) { i_cellstate = s; }
        /**
         * @brief
         *
         * @return uint32
         */
        uint32 getX() const { return i_x; }
        /**
         * @brief
         *
         * @return uint32
         */
        uint32 getY() const { return i_y; }

        /**
         * @brief
         *
         * @param GridRefManager<NGrid<N
         * @param ACTIVE_OBJECT
         * @param WORLD_OBJECT_TYPES
         * @param pTo
         */
        void link(GridRefManager<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES> >* pTo)
        {
            i_Reference.link(pTo, this);
        }

        /**
         * @brief
         *
         * @return bool
         */
        bool isGridObjectDataLoaded() const { return i_GridObjectDataLoaded; }
        /**
         * @brief
         *
         * @param pLoaded
         */
        void setGridObjectDataLoaded(bool pLoaded) { i_GridObjectDataLoaded = pLoaded; }

        /**
         * @brief
         *
         * @return GridInfo
         */
        GridInfo* getGridInfoRef() { return &i_GridInfo; }
        /**
         * @brief
         *
         * @return const TimeTracker
         */
        const TimeTracker& getTimeTracker() const { return i_GridInfo.getTimeTracker(); }
        /**
         * @brief
         *
         * @return bool
         */
        bool getUnloadLock() const { return i_GridInfo.getUnloadLock(); }
        /**
         * @brief
         *
         * @param on
         */
        void setUnloadExplicitLock(bool on) { i_GridInfo.setUnloadExplicitLock(on); }
        /**
         * @brief
         *
         */
        void incUnloadActiveLock() { i_GridInfo.incUnloadActiveLock(); }
        /**
         * @brief
         *
         */
        void decUnloadActiveLock() { i_GridInfo.decUnloadActiveLock(); }
        /**
         * @brief
         *
         * @param interval
         */
        void ResetTimeTracker(time_t interval) { i_GridInfo.ResetTimeTracker(interval); }
        /**
         * @brief
         *
         * @param diff
         */
        void UpdateTimeTracker(time_t diff) { i_GridInfo.UpdateTimeTracker(diff); }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         */
        void AddWorldObject(const uint32 x, const uint32 y, SPECIFIC_OBJECT* obj)
        {
            getGridType(x, y).AddWorldObject(obj);
        }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         */
        void RemoveWorldObject(const uint32 x, const uint32 y, SPECIFIC_OBJECT* obj)
        {
            getGridType(x, y).RemoveWorldObject(obj);
        }

        template<class T, class TT>
        /**
         * @brief
         *
         * @param TypeContainerVisitor<T
         * @param visitor
         */
        void Visit(TypeContainerVisitor<T, TT>& visitor)
        {
            for (uint32 x = 0; x < N; ++x)
                for (uint32 y = 0; y < N; ++y)
                {
                    i_cells[x][y].Visit(visitor);
                }
        }

        template<class T, class TT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param TypeContainerVisitor<T
         * @param visitor
         */
        void Visit(const uint32& x, const uint32& y, TypeContainerVisitor<T, TT>& visitor)
        {
            getGridType(x, y).Visit(visitor);
        }

        /**
         * @brief
         *
         * @return uint32
         */
        uint32 ActiveObjectsInGrid() const
        {
            uint32 count = 0;
            for (uint32 x = 0; x < N; ++x)
                for (uint32 y = 0; y < N; ++y)
                {
                    count += i_cells[x][y].ActiveObjectsInGrid();
                }

            return count;
        }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         * @return bool
         */
        bool AddGridObject(const uint32 x, const uint32 y, SPECIFIC_OBJECT* obj)
        {
            return getGridType(x, y).AddGridObject(obj);
        }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         * @return bool
         */
        bool RemoveGridObject(const uint32 x, const uint32 y, SPECIFIC_OBJECT* obj)
        {
            return getGridType(x, y).RemoveGridObject(obj);
        }

    private:

        /**
         * @brief
         *
         * @param x
         * @param y
         * @return GridType
         */
        GridType& getGridType(const uint32& x, const uint32& y)
        {
            assert(x < N);
            assert(y < N);
            return i_cells[x][y];
        }

        uint32 i_gridId; /**< TODO */
        GridInfo i_GridInfo; /**< TODO */
        GridReference<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES> > i_Reference; /**< TODO */
        uint32 i_x; /**< TODO */
        uint32 i_y; /**< TODO */
        grid_state_t i_cellstate; /**< TODO */
        GridType i_cells[N][N]; /**< TODO */
        bool i_GridObjectDataLoaded; /**< TODO */
};

#endif
