/* Copyright (c) 2014 Mark D. Hill and David A. Wood
 * 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 <Python.h>
#include <cstdio>

#include "DSENT.h"
#include "libutil/String.h"
#include "model/Model.h"

using namespace std;
using namespace LibUtil;

static PyObject *DSENTError;
static PyObject* dsent_initialize(PyObject*, PyObject*);
static PyObject* dsent_finalize(PyObject*, PyObject*);
static PyObject* dsent_computeRouterPowerAndArea(PyObject*, PyObject*);
static PyObject* dsent_computeLinkPower(PyObject*, PyObject*);

// Create DSENT configuration map.  This map is supposed to retain all
// the information between calls to initialize() and finalize().
map<String, String> params;
DSENT::Model *ms_model;


static PyMethodDef DSENTMethods[] = {
    {"initialize", dsent_initialize, METH_O,
     "initialize dsent using a config file."},

    {"finalize", dsent_finalize, METH_NOARGS,
     "finalize dsent by dstroying the config object"},

    {"computeRouterPowerAndArea", dsent_computeRouterPowerAndArea,
     METH_VARARGS, "compute quantities related power consumption of a router"},

    {"computeLinkPower", dsent_computeLinkPower, METH_O,
     "compute quantities related power consumption of a link"},

    {NULL, NULL, 0, NULL}
};


PyMODINIT_FUNC
initdsent(void)
{
    PyObject *m;

    m = Py_InitModule("dsent", DSENTMethods);
    if (m == NULL) return;

    DSENTError = PyErr_NewException("dsent.error", NULL, NULL);
    Py_INCREF(DSENTError);
    PyModule_AddObject(m, "error", DSENTError);

    ms_model = nullptr;
}


static PyObject *
dsent_initialize(PyObject *self, PyObject *arg)
{
    const char *config_file = PyString_AsString(arg);
    //Read the arguments sent from the python script
    if (!config_file) {
        Py_RETURN_NONE;
    }

    // Initialize DSENT
    ms_model = DSENT::initialize(config_file, params);
    Py_RETURN_NONE;
}


static PyObject *
dsent_finalize(PyObject *self, PyObject *args)
{
    // Finalize DSENT
    DSENT::finalize(params, ms_model);
    ms_model = nullptr;
    Py_RETURN_NONE;
}


static PyObject *
dsent_computeRouterPowerAndArea(PyObject *self, PyObject *args)
{
    uint64_t frequency;
    unsigned int num_in_port;
    unsigned int num_out_port;
    unsigned int num_vclass;
    unsigned int num_vchannels;
    unsigned int num_buffers;

    unsigned int flit_width;
    const char *input_port_buffer_model;
    const char *crossbar_model;
    const char *sa_arbiter_model;
    const char *clk_tree_model;
    unsigned int clk_tree_num_levels;
    const char *clk_tree_wire_layer;
    double clk_tree_wire_width_mult;

    // Read the arguments sent from the python script
    if (!PyArg_ParseTuple(args, "KIIIIII", &frequency, &num_in_port,
                          &num_out_port, &num_vclass, &num_vchannels,
                          &num_buffers, &flit_width)) {
        Py_RETURN_NONE;
    }

    assert(frequency > 0.0);
    assert(num_in_port != 0);
    assert(num_out_port != 0);
    assert(num_vclass != 0);
    assert(flit_width != 0);

    vector<unsigned int> num_vchannels_vec(num_vclass, num_vchannels);
    vector<unsigned int> num_buffers_vec(num_vclass, num_buffers);
    // DSENT outputs
    map<string, double> outputs;

    params["Frequency"] = String(frequency);
    params["NumberInputPorts"] = String(num_in_port);
    params["NumberOutputPorts"] = String(num_out_port);
    params["NumberVirtualNetworks"] = String(num_vclass);
    params["NumberVirtualChannelsPerVirtualNetwork"] =
        vectorToString<unsigned int>(num_vchannels_vec);
    params["NumberBuffersPerVirtualChannel"] =
        vectorToString<unsigned int>(num_buffers_vec);
    params["NumberBitsPerFlit"] = String(flit_width);

    // Run DSENT
    DSENT::run(params, ms_model, outputs);

    // Store outputs
    PyObject *r = PyTuple_New(outputs.size());
    int index = 0;

    // Prepare the output.  The assumption is that all the output
    for (const auto &it : outputs) {
        PyObject *s = PyTuple_New(2);
        PyTuple_SetItem(s, 0, PyString_FromString(it.first.c_str()));
        PyTuple_SetItem(s, 1, PyFloat_FromDouble(it.second));

        PyTuple_SetItem(r, index, s);
        index++;
    }

    return r;
}


static PyObject *
dsent_computeLinkPower(PyObject *self, PyObject *arg)
{
    uint64_t frequency = PyLong_AsLongLong(arg);

    // Read the arguments sent from the python script
    if (frequency == -1) {
        Py_RETURN_NONE;
    }

    // DSENT outputs
    map<string, double> outputs;
    params["Frequency"] = String(frequency);

    // Run DSENT
    DSENT::run(params, ms_model, outputs);

    // Store outputs
    PyObject *r = PyTuple_New(outputs.size());
    int index = 0;

    // Prepare the output.  The assumption is that all the output
    for (const auto &it : outputs) {
        PyObject *s = PyTuple_New(2);
        PyTuple_SetItem(s, 0, PyString_FromString(it.first.c_str()));
        PyTuple_SetItem(s, 1, PyFloat_FromDouble(it.second));

        PyTuple_SetItem(r, index, s);
        index++;
    }

    return r;
}

static PyObject *
dsent_printAvailableModels(PyObject* self, PyObject *args)
{
}