/*****************************************************************************
 *                                McPAT
 *                      SOFTWARE LICENSE AGREEMENT
 *            Copyright 2012 Hewlett-Packard Development Company, L.P.
 *            Copyright (c) 2010-2013 Advanced Micro Devices, Inc.
 *                          All Rights Reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ***************************************************************************/

#include <cassert>
#include <cmath>
#include <iostream>

#include "basic_components.h"
#include "cacheunit.h"
#include "common.h"

// Turn this to true to get debugging messages
bool McPATComponent::debug = false;

bool McPATComponent::opt_for_clk = true;
int McPATComponent::longer_channel_device = 0;
// Number of cycles per second, 2GHz = 2e9
double McPATComponent::target_core_clockrate = 2e9;
double McPATComponent::total_cycles = 0.0f;
double McPATComponent::execution_time = 0.0f;
int McPATComponent::physical_address_width = 0;
int McPATComponent::virtual_address_width = 0;
int McPATComponent::virtual_memory_page_size = 0;
int McPATComponent::data_path_width = 0;

void McPATOutput::reset() {
    storage = 0.0;
    area = 0.0;
    peak_dynamic_power = 0.0;
    subthreshold_leakage_power = 0.0;
    gate_leakage_power = 0.0;
    runtime_dynamic_energy = 0.0;
}

McPATOutput operator+(const McPATOutput &lhs, const McPATOutput &rhs) {
    McPATOutput to_return;
    to_return.storage = lhs.storage + rhs.storage;
    to_return.area = lhs.area + rhs.area;
    to_return.peak_dynamic_power = lhs.peak_dynamic_power +
        rhs.peak_dynamic_power;
    to_return.subthreshold_leakage_power = lhs.subthreshold_leakage_power +
        rhs.subthreshold_leakage_power;
    to_return.gate_leakage_power = lhs.gate_leakage_power +
        rhs.gate_leakage_power;
    to_return.runtime_dynamic_energy = lhs.runtime_dynamic_energy +
        rhs.runtime_dynamic_energy;
    return to_return;
}

void McPATOutput::operator+=(const McPATOutput &rhs) {
    storage += rhs.storage;
    area += rhs.area;
    peak_dynamic_power += rhs.peak_dynamic_power;
    subthreshold_leakage_power += rhs.subthreshold_leakage_power;
    gate_leakage_power += rhs.gate_leakage_power;
    runtime_dynamic_energy += rhs.runtime_dynamic_energy;
}

McPATComponent::McPATComponent()
        : xml_data(NULL), name("") {
}

McPATComponent::McPATComponent(XMLNode* _xml_data)
        : xml_data(_xml_data), name("") {
}

McPATComponent::McPATComponent(XMLNode* _xml_data,
                               InputParameter* _interface_ip)
        : xml_data(_xml_data), interface_ip(*_interface_ip), name("") {
}

McPATComponent::~McPATComponent() {
}

void McPATComponent::recursiveInstantiate() {
    if (debug) {
        fprintf(stderr, "WARNING: Called recursiveInstantiate from %s, with ",
                "'type' %s\n", name.c_str(), xml_data->getAttribute("type"));
    }
    int i;
    int numChildren = xml_data->nChildNode("component");
    for (i = 0; i < numChildren; i++ ) {
        // For each child node of the system,
        XMLNode* childXML = xml_data->getChildNodePtr("component", &i);
        XMLCSTR type = childXML->getAttribute("type");

        if (!type)
            warnMissingComponentType(childXML->getAttribute("id"));

        STRCMP(type, "Core")
            warnIncompleteComponentType(type);
        STRCMP(type, "CacheUnit")
            children.push_back(new CacheUnit(childXML, &interface_ip));
        STRCMP(type, "CacheController")
            warnIncompleteComponentType(type);
        STRCMP(type, "MemoryController")
            warnIncompleteComponentType(type);
        STRCMP(type, "Memory")
            warnIncompleteComponentType(type);
        STRCMP(type, "OnChipNetwork")
            warnIncompleteComponentType(type);
        STRCMP(type, "BusInterconnect")
            warnIncompleteComponentType(type);
        STRCMP(type, "Directory")
            warnIncompleteComponentType(type);

        else
            warnUnrecognizedComponent(type);
    }
}

void McPATComponent::computeArea() {
    if (debug) {
        fprintf(stderr, "WARNING: Called computeArea from %s, with 'type' ",
                "%s\n", name.c_str(), xml_data->getAttribute("type"));
    }

    // TODO: This calculation is incorrect and is overwritten by computeEnergy
    // Fix it up so that the values are available at the correct times
    int i;
    int numChildren = children.size();
    area.set_area(0.0);
    output_data.area = 0.0;
    for (i = 0; i < numChildren; i++) {
        children[i]->computeArea();
        output_data.area += area.get_area();
    }
}

void McPATComponent::computeEnergy() {
    if (debug) {
        fprintf(stderr, "WARNING: Called computeEnergy from %s, with 'type' ",
                "%s\n", name.c_str(), xml_data->getAttribute("type"));
    }

    power.reset();
    rt_power.reset();
    memset(&output_data, 0, sizeof(McPATOutput));
    int i;
    int numChildren = children.size();
    for (i = 0; i < numChildren; i++) {
        children[i]->computeEnergy();
        output_data += children[i]->output_data;
    }
}

void McPATComponent::displayData(uint32_t indent, int plevel) {
    if (debug) {
        fprintf(stderr, "WARNING: Called displayData from %s, with 'type' ",
                "%s\n", name.c_str(), xml_data->getAttribute("type"));
    }

    string indent_str(indent, ' ');
    string indent_str_next(indent + 2, ' ');

    double leakage_power = output_data.subthreshold_leakage_power +
        output_data.gate_leakage_power;
    double total_runtime_energy = output_data.runtime_dynamic_energy +
        leakage_power * execution_time;
    cout << indent_str << name << ":" << endl;
    cout << indent_str_next << "Area = " << output_data.area << " mm^2"
         << endl;
    cout << indent_str_next << "Peak Dynamic Power = "
         << output_data.peak_dynamic_power << " W" << endl;
    cout << indent_str_next << "Subthreshold Leakage Power = "
         << output_data.subthreshold_leakage_power << " W" << endl;
    cout << indent_str_next << "Gate Leakage Power = "
         << output_data.gate_leakage_power << " W" << endl;
    cout << indent_str_next << "Runtime Dynamic Power = "
         << (output_data.runtime_dynamic_energy / execution_time) << " W"
         << endl;
    cout << indent_str_next << "Runtime Dynamic Energy = "
         << output_data.runtime_dynamic_energy << " J" << endl;
    cout << indent_str_next << "Total Runtime Energy = "
         << total_runtime_energy << " J" << endl;
    cout << endl;

    // Recursively print children
    int i;
    int numChildren = children.size();
    for (i = 0; i < numChildren; i++) {
        children[i]->displayData(indent + 4, plevel);
    }
}

void McPATComponent::errorUnspecifiedParam(string param) {
    fprintf(stderr, "ERROR: Parameter must be specified in %s: %s\n",
            name.c_str(), param.c_str());
    exit(1);
}

void McPATComponent::errorNonPositiveParam(string param) {
    fprintf(stderr, "ERROR: Parameter must be positive in %s: %s\n",
            name.c_str(), param.c_str());
    exit(1);
}

void McPATComponent::warnUnrecognizedComponent(XMLCSTR component) {
    fprintf(stderr, "WARNING: Component type not recognized in %s: %s\n",
            name.c_str(), component);
}

void McPATComponent::warnUnrecognizedParam(XMLCSTR param) {
    fprintf(stderr, "WARNING: Parameter not recognized in %s: %s\n",
            name.c_str(), param);
}

void McPATComponent::warnUnrecognizedStat(XMLCSTR stat) {
    fprintf(stderr, "WARNING: Statistic not recognized in %s: %s\n",
            name.c_str(), stat);
}

void McPATComponent::warnIncompleteComponentType(XMLCSTR type) {
    fprintf(stderr, "  WARNING: %s handling not yet complete\n", type);
}

void McPATComponent::warnMissingComponentType(XMLCSTR id) {
    if (id) {
        fprintf(stderr,
                "WARNING: Ignoring a component due to the missing type: %s\n",
                id);
    } else {
        fprintf(stderr,
                "WARNING: Ignoring a component in %s due to the missing type\n",
                name.c_str());
    }
}

void McPATComponent::warnMissingParamName(XMLCSTR id) {
    if (id) {
        fprintf(stderr,
                "WARNING: Ignoring a parameter due to the missing name: %s\n",
                id);
    } else {
        fprintf(stderr,
                "WARNING: Ignoring a parameter in %s due to the missing name\n",
                name.c_str());
    }
}

void McPATComponent::warnMissingStatName(XMLCSTR id) {
    if (id) {
        fprintf(stderr,
                "WARNING: Ignoring a statistic due to the missing name: %s\n",
                id);
    } else {
        fprintf(stderr,
                "WARNING: Ignoring a statistic in %s due to the missing name\n",
                name.c_str());
    }
}

double longer_channel_device_reduction(
    enum Device_ty device_ty,
    enum Core_type core_ty) {

    double longer_channel_device_percentage_core;
    double longer_channel_device_percentage_uncore;
    double longer_channel_device_percentage_llc;

    double long_channel_device_reduction;

    longer_channel_device_percentage_llc    = 1.0;
    longer_channel_device_percentage_uncore = 0.82;
    if (core_ty == OOO) {
        //0.54 Xeon Tulsa //0.58 Nehelam
        longer_channel_device_percentage_core   = 0.56;
    } else {
        //0.8;//Niagara
        longer_channel_device_percentage_core   = 0.8;
    }

    if (device_ty == Core_device) {
        long_channel_device_reduction =
            (1 - longer_channel_device_percentage_core) +
            longer_channel_device_percentage_core *
            g_tp.peri_global.long_channel_leakage_reduction;
    } else if (device_ty == Uncore_device) {
        long_channel_device_reduction =
            (1 - longer_channel_device_percentage_uncore) +
            longer_channel_device_percentage_uncore *
            g_tp.peri_global.long_channel_leakage_reduction;
    } else if (device_ty == LLC_device) {
        long_channel_device_reduction =
            (1 - longer_channel_device_percentage_llc) +
            longer_channel_device_percentage_llc *
            g_tp.peri_global.long_channel_leakage_reduction;
    } else {
        cout << "ERROR: Unknown device category: " << device_ty << endl;
        exit(0);
    }

    return long_channel_device_reduction;
}

statsComponents operator+(const statsComponents & x, const statsComponents & y) {
    statsComponents z;

    z.access = x.access + y.access;
    z.hit    = x.hit + y.hit;
    z.miss   = x.miss  + y.miss;

    return z;
}

statsComponents operator*(const statsComponents & x, double const * const y) {
    statsComponents z;

    z.access = x.access * y[0];
    z.hit    = x.hit * y[1];
    z.miss   = x.miss * y[2];

    return z;
}

statsDef operator+(const statsDef & x, const statsDef & y) {
    statsDef z;

    z.readAc   = x.readAc  + y.readAc;
    z.writeAc  = x.writeAc + y.writeAc;
    z.searchAc  = x.searchAc + y.searchAc;
    return z;
}

statsDef operator*(const statsDef & x, double const * const y) {
    statsDef z;

    z.readAc   = x.readAc * y;
    z.writeAc  = x.writeAc * y;
    z.searchAc  = x.searchAc * y;
    return z;
}