#ifndef DEDUP_CHIPDB_H
#define DEDUP_CHIPDB_H

#include "RoutingGraph.hpp"
#include <map>
#include <unordered_map>
#include <boost/functional/hash.hpp>
#include <cstdint>
#include <memory>

using namespace std;

namespace Trellis {
namespace DDChipDb {
/*
A deduplicated chip database is a database with the following properties, intended to be used in place-and-route flows.

 - All coordinates are relative
 - All wire, bel and arc IDs are sequential starting from zero at a location
 - Locations with identical wire, bel and arc data once converted to relative coordinates are only stored once
 - The database is fully linked: wires contain arcs and bel pins up and downhill, arcs store their up and down wires,
   etc
 */

struct RelId
{
    Location rel;
    int32_t id = -1;
};

inline bool operator<(RelId a, RelId b)
{
    return (a.rel < b.rel) || (a.rel == b.rel && a.id < b.id);
}

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

inline bool operator!=(RelId a, RelId b)
{
    return (a.rel != b.rel) || (a.id != b.id);
}


struct BelPort
{
    RelId bel;
    ident_t pin = -1;
};

inline bool operator==(const BelPort &a, const BelPort &b)
{
    return a.bel == b.bel && a.pin == b.pin;
}

struct BelWire
{
    RelId wire;
    ident_t pin = -1;
    PortDirection dir;
};

inline bool operator==(const BelWire &a, const BelWire &b)
{
    return a.wire == b.wire && a.pin == b.pin && a.dir == b.dir;
}


enum ArcClass : int8_t
{
    ARC_STANDARD = 0,
    ARC_FIXED = 1
};

struct DdArcData
{
    RelId srcWire;
    RelId sinkWire;
    ArcClass cls;
    int32_t delay;
    ident_t tiletype;
    int16_t lutperm_flags;
};

inline bool operator==(const DdArcData &a, const DdArcData &b)
{
    return a.srcWire == b.srcWire && a.sinkWire == b.sinkWire && a.cls == b.cls && a.delay == b.delay &&
           a.tiletype == b.tiletype && a.lutperm_flags == b.lutperm_flags;
}

struct WireData
{
    ident_t name;
    set<RelId> arcsDownhill, arcsUphill;
    vector<BelPort> belPins;
};

inline bool operator==(const WireData &a, const WireData &b)
{
    return a.name == b.name && a.arcsDownhill.size() == b.arcsDownhill.size() &&
           equal(a.arcsDownhill.begin(), a.arcsDownhill.end(), b.arcsDownhill.begin())
           && a.arcsUphill.size() == b.arcsUphill.size()
           && equal(a.arcsUphill.begin(), a.arcsUphill.end(), b.arcsUphill.begin())
           && a.belPins.size() == b.belPins.size()
           && equal(a.belPins.begin(), a.belPins.end(), b.belPins.begin());
}

struct BelData
{
    ident_t name, type;
    int z;
    vector<BelWire> wires;
};

inline bool operator==(const BelData &a, const BelData &b)
{
    return a.name == b.name && a.type == b.type && a.z == b.z && a.wires.size() == b.wires.size()
           && equal(a.wires.begin(), a.wires.end(), b.wires.begin());
}

typedef pair<uint64_t, uint64_t> checksum_t;

struct LocationData
{
    vector<WireData> wires;
    vector<DdArcData> arcs;
    vector<BelData> bels;

    checksum_t checksum() const;
};

inline bool operator==(const LocationData &a, const LocationData &b)
{
    return a.wires.size() == b.wires.size() && equal(a.wires.begin(), a.wires.end(), b.wires.begin()) &&
           a.arcs.size() == b.arcs.size() && equal(a.arcs.begin(), a.arcs.end(), b.arcs.begin())
           && a.bels.size() == b.bels.size() && equal(a.bels.begin(), a.bels.end(), b.bels.begin());

}


}
}

namespace std {
template<>
struct hash<Trellis::DDChipDb::RelId>
{
    std::size_t operator()(const Trellis::DDChipDb::RelId &rid) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<Trellis::Location>()(rid.rel));
        boost::hash_combine(seed, hash<int32_t>()(rid.id));
        return seed;
    }
};

template<>
struct hash<set<Trellis::DDChipDb::RelId>>
{
    std::size_t operator()(const set<Trellis::DDChipDb::RelId> &rids) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &rid : rids)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::RelId>()(rid));
        return seed;
    }
};

template<>
struct hash<vector<Trellis::DDChipDb::RelId>>
{
    std::size_t operator()(const vector<Trellis::DDChipDb::RelId> &rids) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &rid : rids)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::RelId>()(rid));
        return seed;
    }
};


template<>
struct hash<Trellis::DDChipDb::BelPort>
{
    std::size_t operator()(const Trellis::DDChipDb::BelPort &port) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<Trellis::DDChipDb::RelId>()(port.bel));
        boost::hash_combine(seed, hash<Trellis::ident_t>()(port.pin));
        return seed;
    }
};

template<>
struct hash<Trellis::DDChipDb::BelWire>
{
    std::size_t operator()(const Trellis::DDChipDb::BelWire &port) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<Trellis::DDChipDb::RelId>()(port.wire));
        boost::hash_combine(seed, hash<Trellis::ident_t>()(port.pin));
        boost::hash_combine(seed, hash<Trellis::ident_t>()(port.dir));
        return seed;
    }
};


template<>
struct hash<vector<Trellis::DDChipDb::BelPort>>
{
    std::size_t operator()(const vector<Trellis::DDChipDb::BelPort> &bps) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &bp : bps)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::BelPort>()(bp));
        return seed;
    }
};

template<>
struct hash<vector<Trellis::DDChipDb::BelWire>>
{
    std::size_t operator()(const vector<Trellis::DDChipDb::BelWire> &bps) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &bp : bps)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::BelWire>()(bp));
        return seed;
    }
};

template<>
struct hash<Trellis::DDChipDb::DdArcData>
{
    std::size_t operator()(const Trellis::DDChipDb::DdArcData &arc) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<Trellis::DDChipDb::RelId>()(arc.srcWire));
        boost::hash_combine(seed, hash<Trellis::DDChipDb::RelId>()(arc.sinkWire));
        boost::hash_combine(seed, hash<int8_t>()(arc.cls));
        boost::hash_combine(seed, hash<int32_t>()(arc.delay));
        boost::hash_combine(seed, hash<Trellis::ident_t>()(arc.tiletype));
        boost::hash_combine(seed, hash<uint16_t>()(arc.lutperm_flags));
        return seed;
    }
};

template<>
struct hash<Trellis::DDChipDb::WireData>
{
    std::size_t operator()(const Trellis::DDChipDb::WireData &wire) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<Trellis::ident_t>()(wire.name));
        boost::hash_combine(seed, hash<set<Trellis::DDChipDb::RelId>>()(wire.arcsDownhill));
        boost::hash_combine(seed, hash<set<Trellis::DDChipDb::RelId>>()(wire.arcsUphill));
        boost::hash_combine(seed, hash<vector<Trellis::DDChipDb::BelPort>>()(wire.belPins));
        return seed;
    }
};

template<>
struct hash<Trellis::DDChipDb::BelData>
{
    std::size_t operator()(const Trellis::DDChipDb::BelData &bel) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<Trellis::ident_t>()(bel.name));
        boost::hash_combine(seed, hash<Trellis::ident_t>()(bel.type));
        boost::hash_combine(seed, hash<vector<Trellis::DDChipDb::BelWire>>()(bel.wires));
        boost::hash_combine(seed, hash<int>()(bel.z));
        return seed;
    }
};

template<>
struct hash<vector<Trellis::DDChipDb::BelData>>
{
    std::size_t operator()(const vector<Trellis::DDChipDb::BelData> &vec) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &item : vec)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::BelData>()(item));
        return seed;
    }
};

template<>
struct hash<vector<Trellis::DDChipDb::DdArcData>>
{
    std::size_t operator()(const vector<Trellis::DDChipDb::DdArcData> &vec) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &item : vec)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::DdArcData>()(item));
        return seed;
    }
};


template<>
struct hash<vector<Trellis::DDChipDb::WireData>>
{
    std::size_t operator()(const vector<Trellis::DDChipDb::WireData> &vec) const noexcept
    {
        std::size_t seed = 0;
        for (const auto &item : vec)
            boost::hash_combine(seed, hash<Trellis::DDChipDb::WireData>()(item));
        return seed;
    }
};

template<>
struct hash<Trellis::DDChipDb::checksum_t>
{
    std::size_t operator()(const Trellis::DDChipDb::checksum_t &cs) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<uint64_t>()(cs.first));
        boost::hash_combine(seed, hash<uint64_t>()(cs.second));
        return seed;
    }
};


template<>
struct hash<Trellis::DDChipDb::LocationData>
{
    std::size_t operator()(const Trellis::DDChipDb::LocationData &ld) const noexcept
    {
        std::size_t seed = 0;
        boost::hash_combine(seed, hash<vector<Trellis::DDChipDb::WireData>>()(ld.wires));
        boost::hash_combine(seed, hash<vector<Trellis::DDChipDb::DdArcData>>()(ld.arcs));
        boost::hash_combine(seed, hash<vector<Trellis::DDChipDb::BelData>>()(ld.bels));
        return seed;
    }
};

}

namespace Trellis {
class Chip;
namespace DDChipDb {


struct DedupChipdb : public IdStore
{
    DedupChipdb();

    DedupChipdb(const IdStore &base);

    map<checksum_t, LocationData> locationTypes;
    map<Location, checksum_t> typeAtLocation;

    LocationData get_cs_data(checksum_t id);
};

shared_ptr<DedupChipdb> make_dedup_chipdb(Chip &chip, bool include_lutperm_pips = false, bool split_slice_mode = false);

/*
An optimized chip database is a database with the following properties, intended to be used in place-and-route flows.
 - All wire, bel and arc IDs are sequential starting from zero at a location
 - Sequential IDs means it is possible to store wires, bels, and arcs in vectors instead of maps
 - The database is fully linked: wires contain arcs and bel pins up and downhill, arcs store their up and down wires,
   etc

This database is _not_ deduplicated, meaning global data is still stored directly
in this database and relative coordinates are NOT used! Types are reused when
possible.
*/

typedef RelId OptId;
typedef DdArcData OptArcData;

struct OptimizedChipdb : public IdStore
{
    OptimizedChipdb();

    OptimizedChipdb(const IdStore &base);

    map<Location, LocationData> tiles;
};

shared_ptr<OptimizedChipdb> make_optimized_chipdb(Chip &chip, bool include_lutperm_pips = false, bool split_slice_mode = false);

}
}

#endif
