#include "topology.hpp"

#include <iostream>

void Topology::addBoard(const std::string& path, const std::string& boardType,
                        const std::string& boardName,
                        const nlohmann::json& exposesItem)
{
    auto findType = exposesItem.find("Type");
    if (findType == exposesItem.end())
    {
        return;
    }

    boardNames.try_emplace(boardName, path);

    PortType exposesType = findType->get<std::string>();

    if (exposesType == "DownstreamPort")
    {
        auto findConnectsTo = exposesItem.find("ConnectsToType");
        if (findConnectsTo == exposesItem.end())
        {
            std::cerr << "Board at path " << path
                      << " is missing ConnectsToType" << std::endl;
            return;
        }
        PortType connectsTo = findConnectsTo->get<std::string>();

        downstreamPorts[connectsTo].emplace_back(path);
        boardTypes[path] = boardType;
        auto findPoweredBy = exposesItem.find("PowerPort");
        if (findPoweredBy != exposesItem.end())
        {
            powerPaths.insert(path);
        }
    }
    else if (exposesType.ends_with("Port"))
    {
        upstreamPorts[exposesType].emplace_back(path);
        boardTypes[path] = boardType;
    }
}

std::unordered_map<std::string, std::vector<Association>>
    Topology::getAssocs(const std::map<Path, BoardName>& boards)
{
    std::unordered_map<std::string, std::vector<Association>> result;

    // look at each upstream port type
    for (const auto& upstreamPortPair : upstreamPorts)
    {
        auto downstreamMatch = downstreamPorts.find(upstreamPortPair.first);

        if (downstreamMatch == downstreamPorts.end())
        {
            // no match
            continue;
        }

        for (const Path& upstream : upstreamPortPair.second)
        {
            if (boardTypes[upstream] == "Chassis" ||
                boardTypes[upstream] == "Board")
            {
                for (const Path& downstream : downstreamMatch->second)
                {
                    // The downstream path must be one we care about.
                    if (boards.find(downstream) != boards.end())
                    {
                        result[downstream].emplace_back("contained_by",
                                                        "containing", upstream);
                        if (powerPaths.find(downstream) != powerPaths.end())
                        {
                            result[upstream].emplace_back(
                                "powered_by", "powering", downstream);
                        }
                    }
                }
            }
        }
    }

    return result;
}

void Topology::remove(const std::string& boardName)
{
    // Remove the board from boardNames, and then using the path
    // found in boardNames remove it from upstreamPorts and
    // downstreamPorts.
    auto boardFind = boardNames.find(boardName);
    if (boardFind == boardNames.end())
    {
        return;
    }

    std::string boardPath = boardFind->second;

    boardNames.erase(boardFind);

    for (auto it = upstreamPorts.begin(); it != upstreamPorts.end();)
    {
        auto pathIt = std::find(it->second.begin(), it->second.end(),
                                boardPath);
        if (pathIt != it->second.end())
        {
            it->second.erase(pathIt);
        }

        if (it->second.empty())
        {
            it = upstreamPorts.erase(it);
        }
        else
        {
            ++it;
        }
    }

    for (auto it = downstreamPorts.begin(); it != downstreamPorts.end();)
    {
        auto pathIt = std::find(it->second.begin(), it->second.end(),
                                boardPath);
        if (pathIt != it->second.end())
        {
            it->second.erase(pathIt);
        }

        if (it->second.empty())
        {
            it = downstreamPorts.erase(it);
        }
        else
        {
            ++it;
        }
    }
}