#pragma once

#include "terrain.hh"
#include "faction.hh"
#include <Box2D/Box2D.h>
#include "pkgman.hh"
#include "types.hh"

#include <vector>
#include <map>
#include <set>

class entity;
class group;
class connection;
class cable;
class level_chunk;

enum {
    GENTYPE_NOMAD_HIDEOUT  = 0,
    GENTYPE_COWS           = 1,
    GENTYPE_GRAVETHINGY    = 2,
    GENTYPE_HOLE           = 3,
    GENTYPE_TEST           = 4,
    GENTYPE_MINE           = 5,
    GENTYPE_MINERALS       = 6,
    GENTYPE_CAVE           = 7,
    GENTYPE_HIDDEN_FACTORY = 8,
    GENTYPE_DEEP_CAVE      = 9,
    GENTYPE_ENEMIES        = 10,

    NUM_GENTYPES
};

extern uint32_t _gentype_id;

static inline bool is_surface_level(int pos_y, float *heights) {
    return ((pos_y+1)*8.f > heights[7] && (pos_y)*8.f < heights[7]);
}

struct gentype_generator
{
    const char *name;
    bool        enabled;
    float       x_prevalence;
    float       y_prevalence; /* ==0 surface level only, !=0 random in y */

    /* if randomly generated by y, threshold values for min max, distance from ground */
    float       y_min;
    float       y_max;
    gentype* (*occupy)(struct gentype_data);
    gentype* (*allocate)();
};

class gentype
{
  public:
    uint32_t type;
    uint32_t id;

    terrain_coord coord;
    float priority;
    bool  applied;
    int   sorting;

    bool loaded; /* just for debugging */
    bool lock; /* prevent this gentype from touching the preloader's gentypes map */

    std::map<uint32_t, entity*> entities;
    std::map<uint32_t, group*>  groups;
    std::set<connection*>       connections;
    std::set<cable*>            cables;

    terrain_transaction         transaction;
    std::vector<genslot>        genslots;

    gentype(){
        this->applied = false;
        this->sorting = 0;
        this->loaded = true;
        this->lock = false;
    };

    gentype(terrain_coord _coord, float _priority)
        : coord(_coord)
        , priority(_priority)
    {
        this->type = 0;
        this->id = _gentype_id++;
        this->transaction.start_x = _coord.chunk_x;
        this->transaction.start_y = _coord.chunk_y;
        this->applied = false;
        this->sorting = 0;
        this->loaded = false;
        this->lock = false;
    };

    virtual ~gentype();

    virtual void read_state(lvlinfo *lvl, lvlbuf *lb)=0;
    virtual void write_state(lvlinfo *lvl, lvlbuf *lb)=0;

    virtual void create_entities()=0;

    /* helper functions for creating entities */
    entity *create_enemy(int robot_type, b2Vec2 pos, int layer=1, int faction=FACTION_ENEMY);

    /* static function occupy() per-type called to allocate the gentype */
    bool post_occupy();
    void apply();
    void add_to_world();

    static void generate(level_chunk *c);

    static gentype_generator gentypes[NUM_GENTYPES];
};

struct gentype_data
{
    terrain_coord coord;
    float         base_x;
    float         base_y;
    float        *heights;
    level_chunk  *c;

    void update_coord()
    {
        this->coord = terrain_coord(this->base_x, this->base_y);
    }

    void move_to_surface(float depth)
    {
#ifdef DEBUG_PRELOADER_SANITY
        tms_assertf(this->coord.get_local_x() < 16 && this->coord.get_local_x() >= 0, "invalid local x");
        tms_assertf(this->heights[this->coord.get_local_x()] > -1000.f, "suspicious height %f", this->heights[0]);
        tms_debugf("height at surface: %f", this->heights[this->coord.get_local_x()]);
#endif
        this->coord = terrain_coord(this->base_x, this->heights[this->coord.get_local_x()]);;
    }
};
