//////////////////////////////////////////////////////////////
//
// PLANEMAP
//
// This module defines two classes: PlaneMap, and PlaneItem.  A
// PlaneItem is an object that has a plane (a string) and an
// XYZ position.  A PlaneMap keeps track of potentially thousands
// of PlaneItem objects, allowing you to search for PlaneItems
// by scanning a geographic region.
//
// CLASS SPRITE DERIVES FROM PLANEITEM
//
// A PlaneMap records the positions of PlaneItems.  The intent is
// that class Sprite should derive from PlaneItem.  That way, the
// PlaneMap can record the positions of sprites.
//
// When you put derived types like Sprite into a PlaneMap, the
// PlaneMap will work fine.  However, when you scan the PlaneMap,
// it will give return PlaneItem pointers, not Sprite pointers.
//
// If you're sure that the PlaneMap contains only sprites, then
// you can use static_cast to convert those PlaneItem pointers to
// Sprite pointers.  Sadly, there's no simple way to avoid the casting.
//
// THE SCANNABLE REGION IS FINITE
//
// A plane is a rectangle, with a finite size: if you stray more
// than 80,000,000 meters from the origin, then that PlaneItem
// is beyond the scannable region.
//
// There's nothing stopping you from moving a PlaneItem outside
// the scannable region.  It is not an error to do so.
// However, if you do move a PlaneItem outside the scannable
// region, then that PlaneItem will not show up for scan_radius.
//
// PLANES CANNOT BE DESTROYED BUT THEY CAN BE UNINHABITED
//
// You can't "create" or "destroy" planes.  Instead, we say
// that a plane is "uninhabited" until you put a PlaneItem
// there.  Initially, all possible planes exist, but they're
// all uninhabited until you put a PlaneItem there.  Planes
// that are uninhabited take no memory.
//
// THE NOWHERE PLANE
//
// If you use the literal "nowhere" as a plane name, a special case is
// triggered: you're "nowhere."  Radius scans can never find items
// whose plane is "nowhere."  The same also applies when you use the
// empty string as a plane name.
//
// THE Z COORDINATE IS IGNORED
//
// Class PlaneItem stores X, Y, and Z.  The Z coordinate is
// ignored for all plane-scanning operations.  In other words,
// planes are 2D.  The only reason we store a Z coordinate
// is for the consistency of storing the model's X, Y, and Z
// all in the same place.
//
// MEMORY MANAGEMENT
//
// PlaneMaps do not own PlaneItems.  This is a deliberate choice.
// We assume that sprites will be owned by the sprite ID table.
// So therefore, the PlaneMaps shouldn't own the sprites.
//
// So instead, we use this rule: a PlaneMap has a function 'track'
// that causes it to start tracking the location of a PlaneItem.
// However, the PlaneMap still doesn't own the PlaneItem.
// If you destroy a PlaneMap, all the PlaneItems
// will automatically be untracked, but they won't be deleted.
//
//////////////////////////////////////////////////////////////

#ifndef PLANEMAP_HPP
#define PLANEMAP_HPP

#include "wrap-vector.hpp"
#include "wrap-map.hpp"
#include "wrap-string.hpp"

#include "util.hpp"

#include <cstdint>

class PlaneMap;

class PlaneItem : public eng::nevernew {
    friend class PlaneMap;

private:
    PlaneMap *pmap_;
    eng::string plane_;
    float x_, y_, z_;
    int64_t id_;

public:
    PlaneItem();
    ~PlaneItem();

    // You may modify the ID at any time.  
    void set_id(int64_t id) { id_ = id; }

    int64_t id() const { return id_; }
    const eng::string &plane() const { return plane_; }
    const float x() const { return x_; }
    const float y() const { return y_; }
    const float z() const { return z_; }

    void untrack();
    void track(PlaneMap *pmap);

    void set_pos(const eng::string &plane, float x, float y, float z);
    void set_xyz(float x, float y, float z) { set_pos(plane_, x, y, z); }
};

class PlaneMap : public eng::nevernew  {
    friend class PlaneItem;
private:
    using EltVec = eng::vector<PlaneItem *>;
    using Plane = eng::map<int64_t, EltVec>;
    eng::map<eng::string, Plane> planes_;
    void remove(const eng::string &plane, int64_t cell, PlaneItem *client);
    void insert(const eng::string &plane, int64_t cell, PlaneItem *client);

public:
    using IdVector = util::IdVector;
    PlaneMap();
    ~PlaneMap();

    // Caution: scan_radius is not deterministically ordered unless sort=true.
    //
    // exclude_nowhere - if true, and plane="nowhere", nothing is scanned.
    // special - an ID that is considered special. If zero, there is no special ID.
    // omit    - if true, remove the special ID from the list.
    //           if false, prepend the special ID to the head of the list.
    // 
    IdVector scan_radius(const eng::string &plane, float x, float y, float radius, bool exclude_nowhere, int64_t special, bool omit) const;
    IdVector scan_radius_unsorted(const eng::string &plane, float x, float y, float radius, bool exclude_nowhere, int64_t special, bool omit) const;
    
private:
    // unit testing stuff.
    friend int lfn_unittests_planemap(lua_State *L);
    EltVec get_cell(const eng::string &plane, int64_t cell) const;
    int total_cells() const;
    void clear() { planes_.clear(); }
};


#endif // PLANEMAP_HPP


