#ifndef LIBTRELLIS_ROUTING_GRAPH_H
#define LIBTRELLIS_ROUTING_GRAPH_H

#include <unordered_map>
#include <map>
#include <vector>
#include <string>
#include <set>
#include <regex>
#include <boost/functional/hash.hpp>

#include "Chip.hpp"
#include "Database.hpp"

using namespace std;

namespace Trellis {
typedef int32_t ident_t;

struct Location
{
    int16_t x = -1, y = -1;

    Location() : x(-1), y(-1)
    {};

    Location(int16_t x, int16_t y) : x(x), y(y)
    {};

    bool operator==(const Location &other) const
    { return x == other.x && y == other.y; }

    bool operator!=(const Location &other) const
    { return x != other.x || y == other.y; }

    bool operator<(const Location &other) const
    { return y < other.y || (y == other.y && x < other.x); }
};

extern const Location GlobalLoc;

inline Location operator+(const Location &a, const Location &b)
{ return Location(a.x + b.x, a.y + b.y); }

struct RoutingId
{
    Location loc;
    ident_t id = -1;

    bool operator==(const RoutingId &other) const
    { return loc == other.loc && id == other.id; }

    bool operator!=(const RoutingId &other) const
    { return loc != other.loc || id == other.id; }

    bool operator<(const RoutingId &other) const
    { return loc < other.loc || (loc == other.loc && id < other.id); }
};

struct RoutingArc
{
    ident_t id = -1;
    ident_t tiletype = -1;
    RoutingId source;
    RoutingId sink;
    bool configurable = false;
    uint16_t lutperm_flags = 0;
    mutable int cdb_id = 0;
};

struct RoutingWire
{
    ident_t id = -1;
    vector<RoutingId> uphill;
    vector<RoutingId> downhill;
    vector<pair<RoutingId, ident_t>> belsUphill;
    vector<pair<RoutingId, ident_t>> belsDownhill;
    mutable int cdb_id = 0;
};

inline bool operator==(const RoutingWire &a, const RoutingWire &b)
{
    return a.id == b.id;
}

enum PortDirection {
    PORT_IN = 0,
    PORT_OUT = 1,
    PORT_INOUT = 2
};

struct RoutingBel
{
    ident_t name, type;
    Location loc;
    int z;
    map<ident_t, pair<RoutingId, PortDirection>> pins;
    mutable int cdb_id = 0;
};

struct RoutingTileLoc
{
    Location loc;
    map<ident_t, RoutingWire> wires;
    map<ident_t, RoutingArc> arcs;
    map<ident_t, RoutingBel> bels;
};


inline bool operator==(const RoutingArc &a, const RoutingArc &b)
{
    return a.id == b.id;
}

class Chip;

class IdStore
{
public:
    // Core functions
    ident_t ident(const std::string &str) const;

    std::string to_str(ident_t id) const;

    RoutingId id_at_loc(int16_t x, int16_t y, const std::string &str) const;
private:

private:
    mutable std::vector<std::string> identifiers;
    mutable std::unordered_map<std::string, int32_t> str_to_id;
};

class RoutingGraph : public IdStore
{
public:
    explicit RoutingGraph(const Chip &c);

    // Must be set up beforehand
    std::string chip_name;
    std::string chip_family;
    std::string chip_prefix;
    int max_row, max_col;

    // Routing tiles
    std::map<Location, RoutingTileLoc> tiles;

    // Obtain the unique, global identifier for a net inside a tile using the database name
    // Returns an empty RoutingId if net is to be ignored
    RoutingId globalise_net(int row, int col, const std::string &db_name);

    // Add an arc to the graph, automatically adding nets and cross-references as appropriate
    void add_arc(Location loc, const RoutingArc &arc);

    // Add a wire to the graph by id (ignoring it if already existing)
    void add_wire(RoutingId wire);

    // Add a Bel to the graph
    void add_bel(RoutingBel &bel);

    // Add a Bel input or output pin
    void add_bel_input(RoutingBel &bel, ident_t pin, int wire_x, int wire_y, ident_t wire_name);
    void add_bel_output(RoutingBel &bel, ident_t pin, int wire_x, int wire_y, ident_t wire_name);

private:
    // Factory functions
    RoutingId globalise_net_ecp5(int row, int col, const std::string &db_name);
    RoutingId globalise_net_machxo2(int row, int col, const std::string &db_name);

    // Algorithm to give global nets a unique position in MachXO2 devices.
    // ECP5 defers global routing to nextpnr.
    // Given the tile from where a net was queried, find the actual physical
    // wire on chip that corresponds to the queried net rather than the
    // imprecise location stored with the database.
    RoutingId find_machxo2_global_position(int row, int col, const std::string &db_name);
    // We need access to globals.json to correctly assign global positions.
    // Internal use only- do NOT expose below methods, members, and types to
    // pytrellis for now.
    const MachXO2GlobalsInfo *global_data_machxo2;
    enum GlobalType {
        CENTER,
        SPINE_LEFT_RIGHT,
        LEFT_RIGHT,
        UP_DOWN,
        BRANCH,
        OTHER,
        NONE
    };
    // Helper function to put all regexes in one place.
    GlobalType get_global_type_from_name(const std::string &db_name, smatch &match);
};
}

namespace std {
template <> struct hash <Trellis::Location>
{
    std::size_t operator()(const Trellis::Location &loc) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<int>()(loc.x));
        boost::hash_combine(seed, hash<int>()(loc.y));
        return seed;
    }
};
}


#endif
