/*
 * Copyright (c) 2014 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * 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.
 *
 * Authors: Andrew Bardsley
 */

#include "sim/cxx_manager.hh"

#include <cstdlib>
#include <sstream>

#include "base/str.hh"
#include "base/trace.hh"
#include "debug/CxxConfig.hh"
#include "mem/mem_object.hh"
#include "sim/serialize.hh"

CxxConfigManager::CxxConfigManager(CxxConfigFileBase &configFile_) :
    configFile(configFile_), flags(configFile_.getFlags()),
    simObjectResolver(*this)
{
}

const CxxConfigDirectoryEntry &
CxxConfigManager::findObjectType(const std::string &object_name,
    std::string &object_type)
{
    if (!configFile.objectExists(object_name))
        throw Exception(object_name, "Can't find sim object");

    if (!configFile.getParam(object_name, "type", object_type))
        throw Exception(object_name, "Sim object has no 'type' field");

    if (cxx_config_directory.find(object_type) ==
        cxx_config_directory.end())
    {
        throw Exception(object_name, csprintf(
            "No sim object type %s is available", object_type));
    }

    const CxxConfigDirectoryEntry *entry = cxx_config_directory[object_type];

    return *entry;
}

std::string
CxxConfigManager::rename(const std::string &from_name)
{
    for (auto i = renamings.begin(); i != renamings.end(); ++ i) {
        const Renaming &renaming = *i;

        if (from_name.find(renaming.fromPrefix) == 0) {
            return renaming.toPrefix +
                from_name.substr(renaming.fromPrefix.length());
        }
    }

    return from_name;
}

std::string
CxxConfigManager::unRename(const std::string &to_name)
{
    for (auto i = renamings.begin(); i != renamings.end(); ++ i) {
        const Renaming &renaming = *i;

        if (to_name.find(renaming.toPrefix) == 0) {
            return renaming.fromPrefix +
                to_name.substr(renaming.toPrefix.length());
        }
    }

    return to_name;
}

static
std::string formatParamList(const std::vector<std::string> &param_values)
{
    std::ostringstream params;

    auto i = param_values.begin();
    auto end_i = param_values.end();

    params << '[';
    while (i != end_i) {
        params << (*i);
        ++i;

        if (i != end_i)
            params << ", ";
    }
    params << ']';

    return params.str();
}

SimObject *
CxxConfigManager::findObject(const std::string &object_name,
    bool visit_children)
{
    std::string instance_name = rename(object_name);

    if (object_name == "Null")
        return NULL;

    /* Already constructed */
    if (objectsByName.find(instance_name) != objectsByName.end())
        return objectsByName[instance_name];

    if (inVisit.find(instance_name) != inVisit.end())
        throw Exception(instance_name, "Cycle in configuration");

    std::string object_type;
    const CxxConfigDirectoryEntry &entry =
        findObjectType(object_name, object_type);

    SimObject *object = NULL;

    CxxConfigParams *object_params = findObjectParams(object_name);

    try {
        DPRINTF(CxxConfig, "Configuring sim object references for: %s"
            " (%s from object %s)\n", instance_name, object_type,
            object_name);

        /* Remember the path back to the top of the recursion to detect
         *  cycles */
        inVisit.insert(instance_name);

        /* Resolve pointed-to SimObjects by recursing into them */
        for (auto i = entry.parameters.begin();
            i != entry.parameters.end(); ++i)
        {
            const CxxConfigDirectoryEntry::ParamDesc *param = (*i).second;

            if (param->isSimObject) {
                if (param->isVector) {
                    std::vector<std::string> sub_object_names;

                    if (!configFile.getParamVector(object_name, param->name,
                        sub_object_names))
                    {
                        throw Exception(object_name, csprintf(
                            "Element not found: %s", param->name));
                    }

                    std::vector<SimObject *> sub_objects;

                    for (auto n = sub_object_names.begin();
                        n != sub_object_names.end(); ++n)
                    {
                        SimObject *sub_object = findObject(*n,
                            visit_children);

                        if (sub_object)
                            sub_objects.push_back(sub_object);
                    }

                    if (!object_params->setSimObjectVector(param->name,
                        sub_objects))
                    {
                        throw Exception(object_name, csprintf(
                            "Can't assign sim object element %s from \"%s\"",
                            param->name, formatParamList(sub_object_names)));
                    }

                    DPRINTF(CxxConfig, "Setting sim object(s): %s.%s=%s\n",
                        object_name, param->name,
                        formatParamList(sub_object_names));
                } else {
                    std::string sub_object_name;

                    if (!configFile.getParam(object_name, param->name,
                        sub_object_name))
                    {
                        throw Exception(object_name, csprintf(
                            "Element not found: %s", param->name));
                    }

                    SimObject *sub_object = findObject(sub_object_name,
                        visit_children);

                    if (sub_object) {
                        if (!object_params->setSimObject(param->name,
                            sub_object))
                        {
                            throw Exception(object_name, csprintf(
                                "Can't assign sim object element %s from"
                                " \"%s\"", param->name, sub_object_name));
                        }
                    }

                    DPRINTF(CxxConfig, "Setting sim object(s):"
                        " %s.%s=%s\n", object_name, param->name,
                        sub_object_name);
                }
            }
        }

        DPRINTF(CxxConfig, "Creating SimObject: %s\n", instance_name);
        object = object_params->simObjectCreate();

        if (!object) {
            throw Exception(object_name, csprintf("Couldn't create object of"
                " type: %s", object_type));
        }

        objectsByName[instance_name] = object;
        objectParamsByName[instance_name] = object_params;

        if (visit_children) {
            std::vector<std::string> children;
            configFile.getObjectChildren(object_name, children, true);

            /* Visit all your children */
            for (auto i = children.begin(); i != children.end(); ++i)
                findObject(*i, visit_children);
        }
    } catch (Exception &) {
        delete object_params;
        throw;
    }

    /* Mark that we've exited object
     *  construction and so 'find'ing this object again won't be a
     *  configuration loop */
    inVisit.erase(object_name);
    return object;
}

CxxConfigParams *
CxxConfigManager::findObjectParams(const std::string &object_name)
{
    std::string instance_name = rename(object_name);

    /* Already constructed */
    if (objectParamsByName.find(instance_name) != objectParamsByName.end())
        return objectParamsByName[instance_name];

    std::string object_type;
    const CxxConfigDirectoryEntry &entry =
        findObjectType(object_name, object_type);

    DPRINTF(CxxConfig, "Configuring parameters of object: %s (%s)\n",
        instance_name, object_type);

    CxxConfigParams *object_params = entry.makeParamsObject();

    try {
        /* Fill in the implicit parameters that don't necessarily
         *  appear in config files */
        object_params->setName(instance_name);

        /* Fill in parameters */
        for (auto i = entry.parameters.begin();
            i != entry.parameters.end(); ++i)
        {
            const CxxConfigDirectoryEntry::ParamDesc *param = (*i).second;

            if (!param->isSimObject) {
                /* Only handle non-SimObject parameters here (see below) */

                if (param->isVector) {
                    std::vector<std::string> param_values;

                    if (!configFile.getParamVector(object_name, param->name,
                        param_values))
                    {
                        throw Exception(object_name, csprintf(
                            "Element not found for parameter: %s",
                            param->name));
                    }

                    if (!object_params->setParamVector(param->name,
                        param_values, flags))
                    {
                        throw Exception(instance_name, csprintf(
                            "Bad parameter value: .%s=X=\"%s\"",
                            param->name, formatParamList(param_values)));
                    }

                    DPRINTF(CxxConfig, "Setting parameter"
                        " %s.%s=%s\n", instance_name, param->name,
                        formatParamList(param_values));
                } else {
                    std::string param_value;

                    if (!configFile.getParam(object_name, param->name,
                        param_value))
                    {
                        throw Exception(object_name, csprintf(
                            "Element not found for parameter: %s",
                            param->name));
                    }

                    if (!object_params->setParam(param->name, param_value,
                        flags))
                    {
                        throw Exception(instance_name, csprintf(
                            "Bad parameter value: .%s=X=\"%s\"",
                            param->name, param_value));
                    }

                    DPRINTF(CxxConfig, "Setting parameter %s.%s=%s\n",
                        instance_name, param->name, param_value);
                }
            }
        }

        /* Find the number of ports that will need binding and set the
         *  appropriate port_..._connection_count parameters */
        for (auto i = entry.ports.begin(); i != entry.ports.end(); ++i) {
            const CxxConfigDirectoryEntry::PortDesc *port = (*i).second;
            std::vector<std::string> peers;

            if (!configFile.getPortPeers(object_name, port->name, peers)) {
                DPRINTF(CxxConfig, "Port not found: %s.%s,"
                    " assuming there are no connections\n",
                    instance_name, port->name);
            }

            unsigned int peer_count = peers.size();

            /* It would be more efficient to split the peer list and
             *  save the values for peer binding later but that would
             *  require another annoying intermediate structure to
             *  hold for little performance increase */

            if (!object_params->setPortConnectionCount(port->name,
                peer_count))
            {
                throw Exception(instance_name, csprintf(
                    "Unconnected port: %s", port->name));
            }

            DPRINTF(CxxConfig, "Setting port connection count"
                " for: %s.%s to %d\n",
                instance_name, port->name, peer_count);
        }

        /* Set pointed-to SimObjects to NULL */
        for (auto i = entry.parameters.begin();
            i != entry.parameters.end(); ++i)
        {
            const CxxConfigDirectoryEntry::ParamDesc *param = (*i).second;

            if (param->isSimObject) {
                bool ret;

                DPRINTF(CxxConfig, "Nulling sim object reference: %s.%s\n",
                    instance_name, param->name);

                if (param->isVector) {
                    /* Clear the reference list. */
                    std::vector<SimObject *> empty;
                    ret = object_params->setSimObjectVector(param->name,
                        empty);
                } else {
                    ret = object_params->setSimObject(param->name, NULL);
                }

                if (!ret) {
                    throw Exception(instance_name, csprintf(
                        "Error nulling sim object reference(s): %s",
                        param->name));
                }
            }
        }
    } catch (Exception &) {
        delete object_params;
        throw;
    }

    objectParamsByName[instance_name] = object_params;

    return object_params;
}

void
CxxConfigManager::findAllObjects()
{
    std::vector<std::string> objects;
    configFile.getAllObjectNames(objects);

    /* Set the traversal order for further iterators */
    objectsInOrder.clear();
    findTraversalOrder("root");
}

void
CxxConfigManager::findTraversalOrder(const std::string &object_name)
{
    SimObject *object = findObject(object_name);

    if (object) {
        objectsInOrder.push_back(object);

        std::vector<std::string> children;
        configFile.getObjectChildren(object_name, children, true);

        /* Visit all your children */
        for (auto i = children.begin(); i != children.end(); ++i)
            findTraversalOrder(*i);
    }
}

void
CxxConfigManager::bindAllPorts()
{
    for (auto i = objectsInOrder.begin(); i != objectsInOrder.end(); ++i)
        bindObjectPorts(*i);
}

void
CxxConfigManager::bindPort(
    SimObject *master_object, const std::string &master_port_name,
    PortID master_port_index,
    SimObject *slave_object, const std::string &slave_port_name,
    PortID slave_port_index)
{
    MemObject *master_mem_object = dynamic_cast<MemObject *>(master_object);
    MemObject *slave_mem_object = dynamic_cast<MemObject *>(slave_object);

    if (!master_mem_object) {
        throw Exception(master_object->name(), csprintf(
            "Object isn't a mem object and so can have master port:"
            " %s[%d]", master_port_name, master_port_index));
    }

    if (!slave_mem_object) {
        throw Exception(slave_object->name(), csprintf(
            "Object isn't a mem object and so can have slave port:"
            " %s[%d]", slave_port_name, slave_port_index));
    }

    /* FIXME, check slave_port_index against connection_count
     *  defined for port, need getPortConnectionCount and a
     *  getCxxConfigDirectoryEntry for each object. */

    /* It would be nice to be able to catch the errors from these calls. */
    BaseMasterPort &master_port = master_mem_object->getMasterPort(
        master_port_name, master_port_index);
    BaseSlavePort &slave_port = slave_mem_object->getSlavePort(
        slave_port_name, slave_port_index);

    if (master_port.isConnected()) {
        throw Exception(master_object->name(), csprintf(
            "Master port: %s[%d] is already connected\n", master_port_name,
            master_port_index));
    }

    if (slave_port.isConnected()) {
        throw Exception(slave_object->name(), csprintf(
            "Slave port: %s[%d] is already connected\n", slave_port_name,
            slave_port_index));
    }

    DPRINTF(CxxConfig, "Binding port %s.%s[%d]"
        " to %s:%s[%d]\n",
        master_object->name(), master_port_name, master_port_index,
        slave_object->name(), slave_port_name, slave_port_index);

    master_port.bind(slave_port);
}

void
CxxConfigManager::bindMasterPort(SimObject *object,
    const CxxConfigDirectoryEntry::PortDesc &port,
    const std::vector<std::string> &peers)
{
    unsigned int master_port_index = 0;

    for (auto peer_i = peers.begin(); peer_i != peers.end();
        ++peer_i)
    {
        const std::string &peer = *peer_i;
        std::string slave_object_name;
        std::string slave_port_name;
        unsigned int slave_port_index;

        parsePort(peer, slave_object_name, slave_port_name,
            slave_port_index);

        std::string slave_instance_name = rename(slave_object_name);

        if (objectsByName.find(slave_instance_name) == objectsByName.end()) {
            throw Exception(object->name(), csprintf(
                "Can't find slave port object: %s", slave_instance_name));
        }

        SimObject *slave_object = objectsByName[slave_instance_name];

        bindPort(object, port.name, master_port_index,
            slave_object, slave_port_name, slave_port_index);

        master_port_index++;
    }
}

void
CxxConfigManager::bindObjectPorts(SimObject *object)
{
    /* We may want to separate object->name() from the name in configuration
     *  later to allow (for example) repetition of fragments of configs */
    const std::string &instance_name = object->name();

    std::string object_name = unRename(instance_name);

    std::string object_type;
    const CxxConfigDirectoryEntry &entry =
        findObjectType(object_name, object_type);

    DPRINTF(CxxConfig, "Binding ports of object: %s (%s)\n",
        instance_name, object_type);

    for (auto i = entry.ports.begin(); i != entry.ports.end(); ++i) {
        const CxxConfigDirectoryEntry::PortDesc *port = (*i).second;

        DPRINTF(CxxConfig, "Binding port: %s.%s\n", instance_name,
            port->name);

        std::vector<std::string> peers;
        configFile.getPortPeers(object_name, port->name, peers);

        /* Only handle master ports as binding only needs to happen once
         *  for each observed pair of ports */
        if (port->isMaster) {
            if (!port->isVector && peers.size() > 1) {
                throw Exception(instance_name, csprintf(
                    "Too many connections to non-vector port %s (%d)\n",
                    port->name, peers.size()));
            }

            bindMasterPort(object, *port, peers);
        }
    }
}

void
CxxConfigManager::parsePort(const std::string &inp,
    std::string &path, std::string &port, unsigned int &index)
{
    std::size_t dot_i = inp.rfind('.');
    std::size_t open_square_i = inp.rfind('[');

    if (dot_i == std::string::npos) {
        DPRINTF(CxxConfig, "Bad port string: %s\n", inp);
        path = "";
        port = "";
        index = 0;
    } else {
        path = std::string(inp, 0, dot_i);

        if (open_square_i == std::string::npos) {
            /* Singleton port */
            port = std::string(inp, dot_i + 1, inp.length() - dot_i);
            index = 0;
        } else {
            /* Vectored port elemnt */
            port = std::string(inp, dot_i + 1, (open_square_i - 1) - dot_i);
            index = std::atoi(inp.c_str() + open_square_i + 1);
        }
    }
}

void
CxxConfigManager::forEachObject(void (SimObject::*mem_func)())
{
    for (auto i = objectsInOrder.begin(); i != objectsInOrder.end(); ++i)
        ((*i)->*mem_func)();
}

void
CxxConfigManager::instantiate(bool build_all)
{
    if (build_all) {
        findAllObjects();
        bindAllPorts();
    }

    DPRINTF(CxxConfig, "Initialising all objects\n");
    forEachObject(&SimObject::init);

    DPRINTF(CxxConfig, "Registering stats\n");
    forEachObject(&SimObject::regStats);

    DPRINTF(CxxConfig, "Registering probe points\n");
    forEachObject(&SimObject::regProbePoints);

    DPRINTF(CxxConfig, "Connecting probe listeners\n");
    forEachObject(&SimObject::regProbeListeners);
}

void
CxxConfigManager::initState()
{
    DPRINTF(CxxConfig, "Calling initState on all objects\n");
    forEachObject(&SimObject::initState);
}

void
CxxConfigManager::startup()
{
    DPRINTF(CxxConfig, "Starting up all objects\n");
    forEachObject(&SimObject::startup);
}

unsigned int
CxxConfigManager::drain()
{
    return DrainManager::instance().tryDrain() ? 0 : 1;
}

void
CxxConfigManager::drainResume()
{
    DrainManager::instance().resume();
}

void
CxxConfigManager::serialize(std::ostream &os)
{
    for (auto i = objectsInOrder.begin(); i != objectsInOrder.end(); ++ i) {
        // (*i)->nameOut(os); FIXME, change access spec. for nameOut
        os << '[' << (*i)->name() << "]\n";
        (*i)->serialize(os);
    }
}

void
CxxConfigManager::loadState(CheckpointIn &checkpoint)
{
    for (auto i = objectsInOrder.begin(); i != objectsInOrder.end(); ++ i)
        (*i)->loadState(checkpoint);
}

void
CxxConfigManager::deleteObjects()
{
    for (auto i = objectsInOrder.rbegin(); i != objectsInOrder.rend(); ++i) {
        DPRINTF(CxxConfig, "Freeing sim object: %s\n", (*i)->name());
        delete *i;
    }

    for (auto i = objectParamsByName.rbegin();
        i != objectParamsByName.rend(); ++i)
    {
        CxxConfigParams *params = (*i).second;

        DPRINTF(CxxConfig, "Freeing sim object params: %s\n",
            params->getName());
        delete params;
    }

    objectsInOrder.clear();
    objectsByName.clear();
}

void
CxxConfigManager::setParam(const std::string &object_name,
    const std::string &param_name, const std::string &param_value)
{
    CxxConfigParams *params = findObjectParams(object_name);

    if (!params->setParam(param_name, param_value, flags)) {
        throw Exception(object_name, csprintf("Bad parameter value:"
            " .%s=X=\"%s\"", param_name, param_value));
    } else {
        std::string instance_name = rename(object_name);

        DPRINTF(CxxConfig, "Setting parameter %s.%s=%s\n",
            instance_name, param_name, param_value);
    }
}

void
CxxConfigManager::setParamVector(const std::string &object_name,
    const std::string &param_name,
    const std::vector<std::string> &param_values)
{
    CxxConfigParams *params = findObjectParams(object_name);

    if (!params->setParamVector(param_name, param_values, flags)) {
        throw Exception(object_name, csprintf("Bad vector parameter value:"
            " .%s=X=\"%s\"", param_name, formatParamList(param_values)));
    } else {
        std::string instance_name = rename(object_name);

        DPRINTF(CxxConfig, "Setting parameter %s.%s=\"%s\"\n",
            instance_name, param_name, formatParamList(param_values));
    }
}

void CxxConfigManager::addRenaming(const Renaming &renaming)
{
    renamings.push_back(renaming);
}