#include "setup_clocks.h"

#include "globals.h"
#include "vtr_expr_eval.h"

#include "vtr_assert.h"
#include "vpr_error.h"

#include "vpr_utils.h"

#include <string>
#include <iostream>
#include <sstream>

using vtr::FormulaParser;
using vtr::t_formula_data;

static MetalLayer get_metal_layer_from_name(
    std::string metal_layer_name,
    std::unordered_map<std::string, t_metal_layer> clock_metal_layers,
    std::string clock_network_name);
static void setup_clock_network_wires(const t_arch& Arch, FormulaParser& p, std::vector<t_segment_inf>& segment_inf);
static void setup_clock_connections(const t_arch& Arch, FormulaParser& p);

void setup_clock_networks(const t_arch& Arch, std::vector<t_segment_inf>& segment_inf) {
    FormulaParser p;
    setup_clock_network_wires(Arch, p, segment_inf);
    setup_clock_connections(Arch, p);
}

/**
 * @brief Parses the clock architecture information and modifies
 *        the architecture segment information.
 */
void setup_clock_network_wires(const t_arch& Arch, FormulaParser& p, std::vector<t_segment_inf>& segment_inf) {
    auto& device_ctx = g_vpr_ctx.mutable_device();
    auto& clock_networks_device = device_ctx.clock_networks;
    auto& grid = device_ctx.grid;

    const std::vector<t_clock_network_arch>& clock_networks_arch = Arch.clock_arch.clock_networks_arch;
    const std::unordered_map<std::string, t_metal_layer>& clock_metal_layers = Arch.clock_arch.clock_metal_layers;

    // TODO: copied over from SetupGrid. Ensure consistency by only assigning in one place
    t_formula_data vars;
    vars.set_var_value("W", grid.width());
    vars.set_var_value("H", grid.height());

    for (const t_clock_network_arch& clock_network_arch : clock_networks_arch) {
        switch (clock_network_arch.type) {
            case e_clock_type::SPINE: {
                std::unique_ptr<ClockSpine> spine = std::make_unique<ClockSpine>();

                spine->set_clock_name(clock_network_arch.name);
                spine->set_num_instance(clock_network_arch.num_inst);
                spine->set_metal_layer(get_metal_layer_from_name(
                    clock_network_arch.metal_layer,
                    clock_metal_layers,
                    clock_network_arch.name));
                spine->set_initial_wire_location(
                    p.parse_formula(clock_network_arch.wire.start, vars),
                    p.parse_formula(clock_network_arch.wire.end, vars),
                    p.parse_formula(clock_network_arch.wire.position, vars));
                spine->set_wire_repeat(
                    p.parse_formula(clock_network_arch.repeat.x, vars),
                    p.parse_formula(clock_network_arch.repeat.y, vars));
                spine->set_drive_location(p.parse_formula(clock_network_arch.drive.offset, vars));
                spine->set_drive_switch(clock_network_arch.drive.arch_switch_idx);
                spine->set_drive_name(clock_network_arch.drive.name);
                spine->set_tap_locations(
                    p.parse_formula(clock_network_arch.tap.offset, vars),
                    p.parse_formula(clock_network_arch.tap.increment, vars));
                spine->set_tap_name(clock_network_arch.tap.name);

                spine->create_segments(segment_inf);

                clock_networks_device.push_back(std::move(spine));
                break;
            }
            case e_clock_type::RIB: {
                std::unique_ptr<ClockRib> rib = std::make_unique<ClockRib>();

                rib->set_clock_name(clock_network_arch.name);
                rib->set_num_instance(clock_network_arch.num_inst);
                rib->set_metal_layer(get_metal_layer_from_name(
                    clock_network_arch.metal_layer,
                    clock_metal_layers,
                    clock_network_arch.name));
                rib->set_initial_wire_location(
                    p.parse_formula(clock_network_arch.wire.start, vars),
                    p.parse_formula(clock_network_arch.wire.end, vars),
                    p.parse_formula(clock_network_arch.wire.position, vars));
                rib->set_wire_repeat(
                    p.parse_formula(clock_network_arch.repeat.x, vars),
                    p.parse_formula(clock_network_arch.repeat.y, vars));
                rib->set_drive_location(p.parse_formula(clock_network_arch.drive.offset, vars));
                rib->set_drive_switch(clock_network_arch.drive.arch_switch_idx);
                rib->set_drive_name(clock_network_arch.drive.name);
                rib->set_tap_locations(
                    p.parse_formula(clock_network_arch.tap.offset, vars),
                    p.parse_formula(clock_network_arch.tap.increment, vars));
                rib->set_tap_name(clock_network_arch.tap.name);

                rib->create_segments(segment_inf);
                clock_networks_device.push_back(std::move(rib));
                break;
            }
            case e_clock_type::H_TREE: {
                VPR_FATAL_ERROR(VPR_ERROR_OTHER, "HTrees not yet supported.\n");
                break;
            }
            default: {
                VPR_FATAL_ERROR(VPR_ERROR_OTHER,
                                "Found unsupported clock network type for '%s' clock network",
                                clock_network_arch.name.c_str());
            }
        }
    }
    clock_networks_device.shrink_to_fit();
}

void setup_clock_connections(const t_arch& Arch, FormulaParser& p) {
    auto& device_ctx = g_vpr_ctx.mutable_device();
    auto& clock_connections_device = device_ctx.clock_connections;
    auto& grid = device_ctx.grid;

    const std::vector<t_clock_connection_arch>& clock_connections_arch = Arch.clock_arch.clock_connections_arch;

    // TODO: copied over from SetupGrid. Ensure consistency by only assigning in one place
    t_formula_data vars;
    vars.set_var_value("W", grid.width());
    vars.set_var_value("H", grid.height());

    for (const t_clock_connection_arch& clock_connection_arch : clock_connections_arch) {
        if (clock_connection_arch.from == "ROUTING") {
            clock_connections_device.emplace_back(new RoutingToClockConnection);
            if (RoutingToClockConnection* routing_to_clock = dynamic_cast<RoutingToClockConnection*>(clock_connections_device.back().get())) {
                //TODO: Add error check to check that clock name and tap name exist and that only
                //      two names are returned by the below function
                std::vector<std::string> names = vtr::StringToken(clock_connection_arch.to).split(".");
                VTR_ASSERT_MSG(names.size() == 2, "Invalid clock name.\n");
                routing_to_clock->set_clock_name_to_connect_to(names[0]);
                routing_to_clock->set_clock_switch_point_name(names[1]);

                routing_to_clock->set_switch_location(
                    p.parse_formula(clock_connection_arch.locationx, vars),
                    p.parse_formula(clock_connection_arch.locationy, vars));
                routing_to_clock->set_switch(clock_connection_arch.arch_switch_idx);
                routing_to_clock->set_fc_val(clock_connection_arch.fc);
            }

        } else if (clock_connection_arch.to == "CLOCK") {
            clock_connections_device.emplace_back(new ClockToPinsConnection);
            if (ClockToPinsConnection* clock_to_pins = dynamic_cast<ClockToPinsConnection*>(clock_connections_device.back().get())) {
                //TODO: Add error check to check that clock name and tap name exist and that only
                //      two names are returned by the below function
                std::vector<std::string> names = vtr::StringToken(clock_connection_arch.from).split(".");
                VTR_ASSERT_MSG(names.size() == 2, "Invalid clock name.\n");
                clock_to_pins->set_clock_name_to_connect_from(names[0]);
                clock_to_pins->set_clock_switch_point_name(names[1]);

                clock_to_pins->set_switch(clock_connection_arch.arch_switch_idx);
                clock_to_pins->set_fc_val(clock_connection_arch.fc);
            }
        } else {
            clock_connections_device.emplace_back(new ClockToClockConneciton);
            if (ClockToClockConneciton* clock_to_clock = dynamic_cast<ClockToClockConneciton*>(clock_connections_device.back().get())) {
                //TODO: Add error check to check that clock name and tap name exist and that only
                //      two names are returned by the below function
                std::vector<std::string> to_names = vtr::StringToken(clock_connection_arch.to).split(".");
                std::vector<std::string> from_names = vtr::StringToken(clock_connection_arch.from).split(".");
                VTR_ASSERT_MSG(to_names.size() == 2, "Invalid clock name.\n");
                clock_to_clock->set_to_clock_name(to_names[0]);
                clock_to_clock->set_to_clock_switch_point_name(to_names[1]);
                clock_to_clock->set_from_clock_name(from_names[0]);
                clock_to_clock->set_from_clock_switch_point_name(from_names[1]);

                clock_to_clock->set_switch(clock_connection_arch.arch_switch_idx);
                clock_to_clock->set_fc_val(clock_connection_arch.fc);
            }
        }
    }
}

MetalLayer get_metal_layer_from_name(
    std::string metal_layer_name,
    std::unordered_map<std::string, t_metal_layer> clock_metal_layers,
    std::string clock_network_name) {
    auto itter = clock_metal_layers.find(metal_layer_name);

    if (itter == clock_metal_layers.end()) {
        VPR_FATAL_ERROR(VPR_ERROR_OTHER,
                        "Metal layer '%s' for clock network '%s' not found. Check to make sure that it is"
                        "included in the clock architecture description",
                        metal_layer_name.c_str(),
                        clock_network_name.c_str());
    }

    // Metal layer was found. Copy over from arch description to proper data type
    t_metal_layer arch_metal_layer = itter->second;
    MetalLayer metal_layer;
    metal_layer.r_metal = arch_metal_layer.r_metal;
    metal_layer.c_metal = arch_metal_layer.c_metal;

    return metal_layer;
}
