#ifndef GOWIN_UTILS_H
#define GOWIN_UTILS_H

#include "design_utils.h"
#include "idstringlist.h"
#include "nextpnr_namespaces.h"
#include "nextpnr_types.h"

NEXTPNR_NAMESPACE_BEGIN

namespace BelFlags {
static constexpr uint32_t FLAG_SIMPLE_IO = 0x100;
}

namespace WireFlags {
static constexpr uint32_t FLAG_CLOCK_GATE = 0x1;
static constexpr uint32_t FLAG_BOTTOM_HALF = 0x2;
} // namespace WireFlags

struct GowinUtils
{
    Context *ctx;

    GowinUtils() {}

    void init(Context *ctx) { this->ctx = ctx; }

    // tile
    IdString get_tile_class(int x, int y);
    Loc get_tile_io16_offs(int x, int y);
    bool get_i3c_capable(int x, int y);
    inline Loc get_wire_loc(WireId wire) const
    {
        Loc loc;
        tile_xy(ctx->chip_info, wire.tile, loc.x, loc.y);
        return loc;
    }

    // pin functions: GCLKT_4, SSPI_CS, READY etc
    IdStringList get_pin_funcs(BelId io_bel);

    // PLL pads (type - CLKIN, FeedBack, etc)
    BelId get_pll_bel(BelId io_bel, IdString type);

    // Bels and pips
    bool is_simple_io_bel(BelId bel);
    Loc get_pair_iologic_bel(Loc loc);
    BelId get_io_bel_from_iologic(BelId bel);
    BelId get_dqce_bel(IdString spine_name);
    BelId get_dcs_bel(IdString spine_name);
    BelId get_dhcen_bel(WireId hclkin_wire, IdString &side);
    BelId get_dlldly_bel(BelId io_bel);
    IdString get_dcs_prefix(void);

    // Segments
    int get_segments_count(void) const;
    void get_segment_region(int s_i, int &seg_idx, int &x, int &min_x, int &min_y, int &max_x, int &max_y) const;
    void get_segment_wires_loc(int s_i, Loc &top, Loc &bottom) const;
    void get_segment_wires(int s_i, WireId &top, WireId &bottom) const;
    void get_segment_top_gate_wires(int s_i, WireId &wire0, WireId &wire1) const;
    void get_segment_bottom_gate_wires(int s_i, WireId &wire0, WireId &wire1) const;

    // ports
    inline bool port_used(CellInfo *cell, IdString port_name)
    {
        if (!nextpnr_himbaechel::port_used(cell, port_name)) {
            return false;
        }
        NetInfo *ni = cell->ports.at(port_name).net;
        if (ni->driver.cell == nullptr) {
            return false;
        }
        return ni->users.entries() != 0;
    }
    // Remove [ and ] from port names
    void remove_brackets(CellInfo *ci);

    // net sources
    inline bool driver_is_io(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_IBUF, id_O); }
    inline bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); }
    inline bool driver_is_dqce(const PortRef &driver)
    {
        return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT);
    }
    inline bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); }
    inline bool driver_is_dhcen(const PortRef &driver)
    {
        return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT);
    }
    inline bool driver_is_mipi(const PortRef &driver)
    {
        return CellTypePort(driver) == CellTypePort(id_IOBUF, id_O) && driver.cell->params.count(id_MIPI_IBUF);
    }
    bool driver_is_clksrc(const PortRef &driver);

    // clock nets
    bool wire_is_clock_gate(WireId wire) const
    {
        return chip_wire_info(ctx->chip_info, wire).flags & WireFlags::FLAG_CLOCK_GATE;
    }

    bool wire_in_bottom_half(WireId wire) const
    {
        return chip_wire_info(ctx->chip_info, wire).flags & WireFlags::FLAG_BOTTOM_HALF;
    }
    bool get_spine_select_wire(WireId spine, std::vector<std::pair<WireId, int>> &);

    // BSRAM
    bool has_SP32(void);
    bool need_SP_fix(void);
    bool need_SDP_fix(void);
    bool need_BSRAM_OUTREG_fix(void);
    bool need_BSRAM_DP_CE_fix(void);
    bool need_BSRAM_RESET_fix(void);
    bool need_BLKSEL_fix(void);
    bool has_PLL_HCLK(void);
    bool has_CLKDIV_HCLK(void);

    // Power saving
    bool has_BANDGAP(void);

    // Pin function configuration via wires
    bool has_PINCFG(void) const;
    bool need_CFGPINS_INVERSION(void) const;
    bool has_I2CCFG(void) const;

    // Logic cell structure
    bool has_DFF67(void) const;

    // ALU
    bool has_CIN_MUX(void) const;

    // Clock MUX
    bool has_spine_enable_nets(void) const;

    // DSP
    bool has_5A_DSP(void) const;
    inline int get_dsp_18_z(int z) const { return z & (~3); }
    inline int get_dsp_9_idx(int z) const { return z & 3; }
    inline int get_dsp_18_idx(int z) const { return z & 4; }
    inline int get_dsp_paired_9(int z) const { return (3 - get_dsp_9_idx(z)) | (z & (~3)); }
    inline int get_dsp_paired_12(int z) const { return BelZ::MULT12X12_1_Z - (z & 1); }
    inline int get_dsp_mult_from_padd(int padd_z) const { return padd_z + 8; }
    inline int get_dsp_padd_from_mult(int mult_z) const { return mult_z - 8; }
    inline int get_dsp_next_macro(int z) const { return z + 32; }
    inline int get_dsp(int z) const { return BelZ::DSP_Z; }
    inline int get_dsp_macro(int z) const { return (z & 0x20) + BelZ::DSP_0_Z; }
    inline int get_dsp_macro_num(int z) const { return (z & 0x20) >> 5; }
    Loc get_dsp_next_9_in_chain(Loc from) const;
    Loc get_dsp_next_macro_in_chain(Loc from) const;
    Loc get_dsp_next_in_chain(Loc from, IdString dsp_type) const;
    Loc get_dsp_next_in_chain_5a(Loc from) const;

    // check bus.
    // This is necessary to find the head in the DSP chain - these buses are
    // not switched in the hardware, but in software you can leave them
    // unconnected or connect them to VCC or VSS, which is the same - as I
    // already said, they are hard-wired and we are only discovering the fact
    // that they are not connected to another DSP in the chain.
    CellInfo *dsp_bus_src(const CellInfo *ci, const char *bus_prefix, int wire_num) const;
    CellInfo *dsp_bus_dst(const CellInfo *ci, const char *bus_prefix, int wire_num) const;

    bool is_diff_io_supported(IdString type);
    bool has_bottom_io_cnds(void);
    IdString get_bottom_io_wire_a_net(int8_t condition);
    IdString get_bottom_io_wire_b_net(int8_t condition);

    // wires
    inline bool is_wire_type_default(IdString wire_type) { return wire_type == IdString(); }
    // If wire is an important part of the global network (like SPINExx)
    inline bool is_global_wire(WireId wire) const
    {
        return ctx->getWireName(wire)[1].in(
                id_SPINE0, id_SPINE1, id_SPINE2, id_SPINE3, id_SPINE4, id_SPINE5, id_SPINE6, id_SPINE7, id_SPINE8,
                id_SPINE9, id_SPINE10, id_SPINE11, id_SPINE12, id_SPINE13, id_SPINE14, id_SPINE15, id_SPINE16,
                id_SPINE17, id_SPINE18, id_SPINE19, id_SPINE20, id_SPINE21, id_SPINE22, id_SPINE23, id_SPINE24,
                id_SPINE25, id_SPINE26, id_SPINE27, id_SPINE28, id_SPINE29, id_SPINE30, id_SPINE31);
    }

    // pips
    inline bool is_global_pip(PipId pip) const
    {
        return is_global_wire(ctx->getPipSrcWire(pip)) || is_global_wire(ctx->getPipDstWire(pip));
    }

    inline bool is_segment_pip(PipId pip) const { return ctx->getWireType(ctx->getPipDstWire(pip)) == id_LW_TAP; }

    // construct name
    IdString create_aux_name(IdString main_name, int idx = 0, const char *str_suffix = "_aux$");

    // make cell but do not include it in the list of chip cells.
    std::unique_ptr<CellInfo> create_cell(IdString name, IdString type);

    // HCLK
    BelId get_clkdiv_for_clkdiv2(BelId clkdiv2_bel) const;
    BelId get_other_hclk_clkdiv2(BelId clkdiv2_bel) const;
    BelId get_other_hclk_clkdiv(BelId clkdiv_bel) const;
    BelId get_clkdiv2_for_clkdiv(BelId clkdiv_bel) const;
    IdStringList get_hclk_id(BelId hclk_bel) const; // use the upper CLKDIV2 (CLKDIV2_0 orCLKDIV2_2) as an id

    // Find Bels connected to a bound cell
    void find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin, int iter_limit,
                             std::vector<BelId> &candidates);

    // Find a maximum bipartite matching
    template <typename T1, typename T2> std::map<T1, T2> find_maximum_bipartite_matching(std::map<T1, std::set<T2>> &G)
    {
        std::map<int, T1> U;
        std::map<int, T2> V;
        std::map<T2, int> V_IDX;
        std::vector<std::vector<int>> int_graph(G.size());

        int u_idx = 0;
        int v_idx = 0;

        // Translate the input graph to an integer graph
        for (auto row : G) {
            U.insert(std::pair(u_idx, row.first));
            for (auto v : row.second) {
                if (V_IDX.find(v) == V_IDX.end()) {
                    V_IDX[v] = v_idx;
                    V[v_idx] = v;
                    v_idx++;
                }
                int_graph[u_idx].push_back(V_IDX[v]);
            }
            u_idx++;
        }

        std::vector<int> int_matching = kuhn_find_maximum_bipartite_matching(u_idx, v_idx, int_graph);
        std::map<T1, T2> ret_matching;

        int m_idx = 0;
        for (auto val : int_matching) {
            if (val >= 0) { // elements that are not matched have a value of -1
                ret_matching[U[val]] = V[m_idx];
            }
            m_idx++;
        }

        return ret_matching;
    }

    // Find a maximum matching in a bipartite graph, g
    std::vector<int> kuhn_find_maximum_bipartite_matching(int n, int k, std::vector<std::vector<int>> &g);

    // Get spec locations
    int get_center_row(void) const;
    int get_center_col(void) const;
};

NEXTPNR_NAMESPACE_END

#endif
