/*********************************************************************
 *  The following code is part of the power modelling feature of VTR.
 *
 * For support:
 * http://code.google.com/p/vtr-verilog-to-routing/wiki/Power
 *
 * or email:
 * vtr.power.estimation@gmail.com
 *
 * If you are using power estimation for your researach please cite:
 *
 * Jeffrey Goeders and Steven Wilton.  VersaPower: Power Estimation
 * for Diverse FPGA Architectures.  In International Conference on
 * Field Programmable Technology, 2012.
 *
 ********************************************************************/

/**
 * This file provides utility functions used by power estimation.
 */

/************************* INCLUDES *********************************/
#include <cstring>
#include <cmath>
#include <map>
#include <string>

#include "vtr_assert.h"

#include "power_util.h"
#include "globals.h"
#include "atom_netlist.h"
#include "atom_netlist_utils.h"

/************************* GLOBALS **********************************/

/************************* FUNCTION DECLARATIONS*********************/
static void log_msg(t_log* log_ptr, const char* msg);
static void init_mux_arch_default(t_mux_arch* mux_arch, int levels, int num_inputs, float transistor_size);
static void alloc_and_load_mux_graph_recursive(t_mux_node* node,
                                               int num_primary_inputs,
                                               int level,
                                               int starting_pin_idx);
static t_mux_node* alloc_and_load_mux_graph(int num_inputs, int levels);

/************************* FUNCTION DEFINITIONS *********************/
void power_zero_usage(t_power_usage* power_usage) {
    power_usage->dynamic = 0.;
    power_usage->leakage = 0.;
}

void power_add_usage(t_power_usage* dest, const t_power_usage* src) {
    dest->dynamic += src->dynamic;
    dest->leakage += src->leakage;
}

void power_scale_usage(t_power_usage* power_usage, float scale_factor) {
    power_usage->dynamic *= scale_factor;
    power_usage->leakage *= scale_factor;
}

float power_sum_usage(t_power_usage* power_usage) {
    return power_usage->dynamic + power_usage->leakage;
}

float power_perc_dynamic(t_power_usage* power_usage) {
    return power_usage->dynamic / power_sum_usage(power_usage);
}

void power_log_msg(e_power_log_type log_type, const char* msg) {
    auto& power_ctx = g_vpr_ctx.power();
    log_msg(&power_ctx.output->logs[log_type], msg);
}

const char* transistor_type_name(e_tx_type type) {
    if (type == NMOS) {
        return "NMOS";
    } else if (type == PMOS) {
        return "PMOS";
    } else {
        return "Unknown";
    }
}

float pin_dens(t_pb* pb, t_pb_graph_pin* pin, ClusterBlockId iblk) {
    float density = 0.;

    auto& cluster_ctx = g_vpr_ctx.clustering();
    auto& power_ctx = g_vpr_ctx.mutable_power();

    if (pb) {
        if (cluster_ctx.clb_nlist.block_pb(iblk)->pb_route.count(pin->pin_count_in_cluster)) {
            AtomNetId net_id = cluster_ctx.clb_nlist.block_pb(iblk)->pb_route[pin->pin_count_in_cluster].atom_net_id;
            density = power_ctx.atom_net_power[net_id].density;
        }
    }

    return density;
}

float pin_prob(t_pb* pb, t_pb_graph_pin* pin, ClusterBlockId iblk) {
    /* Assumed pull-up on unused interconnect */
    float prob = 1.;

    auto& cluster_ctx = g_vpr_ctx.clustering();
    auto& power_ctx = g_vpr_ctx.mutable_power();

    if (pb) {
        if (cluster_ctx.clb_nlist.block_pb(iblk)->pb_route.count(pin->pin_count_in_cluster)) {
            AtomNetId net_id = cluster_ctx.clb_nlist.block_pb(iblk)->pb_route[pin->pin_count_in_cluster].atom_net_id;
            prob = power_ctx.atom_net_power[net_id].probability;
        }
    }

    return prob;
}

/**
 * This function determines the values of the selectors in a static mux, based
 * on the routing information.
 * - selector_values: (Return values) selected index at each mux level
 * - mux_node:
 * - selected_input_pin: The input index to the multi-level mux that is chosen
 */
bool mux_find_selector_values(int* selector_values, t_mux_node* mux_node, int selected_input_pin) {
    if (mux_node->level == 0) {
        if ((selected_input_pin >= mux_node->starting_pin_idx)
            && (selected_input_pin
                <= (mux_node->starting_pin_idx + mux_node->num_inputs))) {
            selector_values[mux_node->level] = selected_input_pin
                                               - mux_node->starting_pin_idx;
            return true;
        }
    } else {
        int input_idx;
        for (input_idx = 0; input_idx < mux_node->num_inputs; input_idx++) {
            if (mux_find_selector_values(selector_values,
                                         &mux_node->children[input_idx], selected_input_pin)) {
                selector_values[mux_node->level] = input_idx;
                return true;
            }
        }
    }
    return false;
}

static void log_msg(t_log* log_ptr, const char* msg) {
    int msg_idx;

    /* Check if this message is already in the log */
    for (msg_idx = 0; msg_idx < log_ptr->num_messages; msg_idx++) {
        if (strcmp((log_ptr->messages[msg_idx]).c_str(), msg) == 0) {
            return;
        }
    }

    if (log_ptr->num_messages <= MAX_LOGS) {
        log_ptr->num_messages++;
        log_ptr->messages.resize(log_ptr->num_messages);
    } else {
        /* Can't add any more messages */
        return;
    }

    if (log_ptr->num_messages == (MAX_LOGS + 1)) {
        log_ptr->messages[log_ptr->num_messages - 1] = "\n***LOG IS FULL***\n";
    } else {
        log_ptr->messages[log_ptr->num_messages - 1] = msg;
    }
}

/**
 * Calculates the number of buffer stages required, to achieve a given buffer fanout
 * final_stage_size: Size of the final inverter in the buffer, relative to a min size
 * desired_stage_effort: The desired gain between stages, typically 4
 */
int power_calc_buffer_num_stages(float final_stage_size,
                                 float desired_stage_effort) {
    int N = 1;

    if (final_stage_size <= 1.0) {
        N = 1;
    } else if (final_stage_size < desired_stage_effort)
        N = 2;
    else {
        N = (int)(log(final_stage_size) / log(desired_stage_effort) + 1);

        /* We always round down.
         * Perhaps N+1 would be closer to the desired stage effort, but the delay savings
         * would likely not be worth the extra power/area
         */
    }

    return N;
}

/**
 * Calculates the required effort of each stage of a buffer
 * - N: The number of stages of the buffer
 * - final_stage_size: Size of the final inverter in the buffer, relative to a min size
 */
float calc_buffer_stage_effort(int N, float final_stage_size) {
    if (N > 1)
        return pow((double)final_stage_size, (1.0 / ((double)N - 1)));
    else
        return 1.0;
}

/**
 * This functions returns the LUT SRAM values from the given logic terms
 *  - LUT_size: The number of LUT inputs
 *  - truth_table: The logic terms saved from the BLIF file
 */
std::string alloc_SRAM_values_from_truth_table(int LUT_size,
                                               const AtomNetlist::TruthTable& truth_table) {
    size_t num_SRAM_bits = 1 << LUT_size;

    //SRAM value stored as a string of '0' and '1' characters
    // Initialize to all zeros
    std::string SRAM_values(num_SRAM_bits, '0');

    if (truth_table.empty()) {
        for (size_t i = 0; i < num_SRAM_bits; i++) {
            SRAM_values[i] = '1';
        }
        return SRAM_values;
    }

    /* Check if this is an unconnected node - hopefully these will be
     * ignored by VPR in the future
     */
    if (truth_table.size() == 1) {
        //Single row check to see if a constant node
        if (truth_table[0].size() == 1) {
            if (truth_table[0][0] == vtr::LogicValue::TRUE) {
                //Mark all the SRAM values as ON
                for (size_t i = 0; i < num_SRAM_bits; i++) {
                    SRAM_values[i] = '1';
                }
                return SRAM_values;
            } else {
                VTR_ASSERT(truth_table[0][0] == vtr::LogicValue::FALSE);
                return SRAM_values;
            }
        }
    }
    auto expanded_truth_table = expand_truth_table(truth_table, LUT_size);
    std::vector<vtr::LogicValue> lut_mask = truth_table_to_lut_mask(expanded_truth_table, LUT_size);

    VTR_ASSERT(lut_mask.size() == num_SRAM_bits);

    //Convert to string
    for (size_t i = 0; i < lut_mask.size(); ++i) {
        switch (lut_mask[i]) {
            case vtr::LogicValue::TRUE:
                SRAM_values[i] = '1';
                break;
            case vtr::LogicValue::FALSE:
                SRAM_values[i] = '0';
                break;
            default:
                VTR_ASSERT(false);
        }
    }

    return SRAM_values;
}

/* Reduce mux levels for multiplexers that are too small for the preset number of levels */
void mux_arch_fix_levels(t_mux_arch* mux_arch) {
    while (((1 << mux_arch->levels) > mux_arch->num_inputs)
           && (mux_arch->levels > 1)) {
        mux_arch->levels--;
    }
}

float clb_net_density(ClusterNetId net_idx) {
    if (net_idx == ClusterNetId::INVALID()) {
        return 0.;
    } else {
        auto& power_ctx = g_vpr_ctx.power();
        return power_ctx.clb_net_power[net_idx].density;
    }
}

float clb_net_prob(ClusterNetId net_idx) {
    if (net_idx == ClusterNetId::INVALID()) {
        return 0.;
    } else {
        auto& power_ctx = g_vpr_ctx.power();
        return power_ctx.clb_net_power[net_idx].probability;
    }
}

const char* interconnect_type_name(enum e_interconnect type) {
    switch (type) {
        case COMPLETE_INTERC:
            return "complete";
        case MUX_INTERC:
            return "mux";
        case DIRECT_INTERC:
            return "direct";
        default:
            return "";
    }
}

void output_log(t_log* log_ptr, FILE* fp) {
    int msg_idx;

    for (msg_idx = 0; msg_idx < log_ptr->num_messages; msg_idx++) {
        fprintf(fp, "%s\n", log_ptr->messages[msg_idx].c_str());
    }
}

void output_logs(FILE* fp, t_log* logs, int num_logs) {
    int log_idx;

    for (log_idx = 0; log_idx < num_logs; log_idx++) {
        if (logs[log_idx].num_messages) {
            power_print_title(fp, logs[log_idx].name.c_str());
            output_log(&logs[log_idx], fp);
            fprintf(fp, "\n");
        }
    }
}

float power_buffer_size_from_logical_effort(float C_load) {
    auto& power_ctx = g_vpr_ctx.power();
    return std::max(1.0f,
                    C_load / power_ctx.commonly_used->INV_1X_C_in / (2 * power_ctx.arch->logical_effort_factor));
}

void power_print_title(FILE* fp, const char* title) {
    int i;
    const int width = 80;

    int firsthalf = (width - strlen(title) - 2) / 2;
    int secondhalf = width - strlen(title) - 2 - firsthalf;

    for (i = 1; i <= firsthalf; i++)
        fprintf(fp, "-");
    fprintf(fp, " %s ", title);
    for (i = 1; i <= secondhalf; i++)
        fprintf(fp, "-");
    fprintf(fp, "\n");
}

t_mux_arch* power_get_mux_arch(int num_mux_inputs, float transistor_size) {
    int i;

    t_power_mux_info* mux_info = nullptr;
    auto& power_ctx = g_vpr_ctx.power();

    /* Find the mux archs for the given transistor size */
    std::map<float, t_power_mux_info*>::iterator it;

    it = power_ctx.commonly_used->mux_info.find(transistor_size);

    if (it == power_ctx.commonly_used->mux_info.end()) {
        mux_info = new t_power_mux_info;
        mux_info->mux_arch_max_size = 0;
        VTR_ASSERT(power_ctx.commonly_used->mux_info[transistor_size] == nullptr);
        power_ctx.commonly_used->mux_info[transistor_size] = mux_info;
    } else {
        mux_info = it->second;
    }

    if (num_mux_inputs > mux_info->mux_arch_max_size) {
        mux_info->mux_arch.resize(num_mux_inputs + 1);

        for (i = mux_info->mux_arch_max_size + 1; i <= num_mux_inputs; i++) {
            init_mux_arch_default(&mux_info->mux_arch[i], 2, i,
                                  transistor_size);
        }
        mux_info->mux_arch_max_size = num_mux_inputs;
    }
    return &mux_info->mux_arch[num_mux_inputs];
}

/**
 * Generates a default multiplexer architecture of given size and number of levels
 */
static void init_mux_arch_default(t_mux_arch* mux_arch, int levels, int num_inputs, float transistor_size) {
    mux_arch->levels = levels;
    mux_arch->num_inputs = num_inputs;

    mux_arch_fix_levels(mux_arch);

    mux_arch->transistor_size = transistor_size;

    mux_arch->mux_graph_head = alloc_and_load_mux_graph(num_inputs,
                                                        mux_arch->levels);
}

/**
 * Allocates a builds a multiplexer graph with given # inputs and levels
 */
static t_mux_node* alloc_and_load_mux_graph(int num_inputs, int levels) {
    t_mux_node* node;

    node = new t_mux_node;
    alloc_and_load_mux_graph_recursive(node, num_inputs, levels - 1, 0);

    return node;
}

static void alloc_and_load_mux_graph_recursive(t_mux_node* node,
                                               int num_primary_inputs,
                                               int level,
                                               int starting_pin_idx) {
    int child_idx;
    int pin_idx = starting_pin_idx;

    node->num_inputs = (int)(pow(num_primary_inputs, 1 / ((float)level + 1))
                             + 0.5);
    node->level = level;
    node->starting_pin_idx = starting_pin_idx;

    if (level != 0) {
        node->children = new t_mux_node[node->num_inputs];
        for (child_idx = 0; child_idx < node->num_inputs; child_idx++) {
            int num_child_pi = num_primary_inputs / node->num_inputs;
            if (child_idx < (num_primary_inputs % node->num_inputs)) {
                num_child_pi++;
            }
            alloc_and_load_mux_graph_recursive(&node->children[child_idx],
                                               num_child_pi, level - 1, pin_idx);
            pin_idx += num_child_pi;
        }
    }
}

bool power_method_is_transistor_level(e_power_estimation_method estimation_method) {
    switch (estimation_method) {
        case POWER_METHOD_AUTO_SIZES:
        case POWER_METHOD_SPECIFY_SIZES:
            return true;
        default:
            return false;
    }
}

bool power_method_is_recursive(e_power_estimation_method method) {
    switch (method) {
        case POWER_METHOD_IGNORE:
        case POWER_METHOD_TOGGLE_PINS:
        case POWER_METHOD_C_INTERNAL:
        case POWER_METHOD_ABSOLUTE:
            return false;
        case POWER_METHOD_AUTO_SIZES:
        case POWER_METHOD_SPECIFY_SIZES:
        case POWER_METHOD_SUM_OF_CHILDREN:
            return true;
        case POWER_METHOD_UNDEFINED:
        default:
            VTR_ASSERT(0);
    }

    // to get rid of warning
    return false;
}
