/****************************************************************************/
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
// Copyright (C) 2001-2026 German Aerospace Center (DLR) and others.
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0/
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License 2.0 are satisfied: GNU General Public License, version 2
// or later which is available at
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
/****************************************************************************/
/// @file    MSDetectorControl.cpp
/// @author  Daniel Krajzewicz
/// @author  Jakob Erdmann
/// @author  Clemens Honomichl
/// @author  Sascha Krieg
/// @author  Michael Behrisch
/// @author  Laura Bieker
/// @date    2005-09-15
///
// Detectors container; responsible for string and output generation
/****************************************************************************/
#include <config.h>

#include <iostream>
#include <utils/options/OptionsCont.h>
#include <utils/options/Option.h>
#include <utils/common/MsgHandler.h>
#include "MSMeanData_Emissions.h"
#include "MSMeanData_Net.h"
#include "MSDetectorControl.h"


// ===========================================================================
// member method definitions
// ===========================================================================
MSDetectorControl::MSDetectorControl() {
}


MSDetectorControl::~MSDetectorControl() {
    for (auto i = myDetectors.begin(); i != myDetectors.end(); ++i) {
        (*i).second.clear();
    }
    for (auto item : myMeanData) {
        for (MSMeanData* md : item.second) {
            delete md;
        }
    }
    myMeanData.clear();
}


void
MSDetectorControl::close(SUMOTime step) {
    // flush the last values
    writeOutput(step, true);
    // [...] files are closed on another place [...]
    myIntervals.clear();
}


void
MSDetectorControl::add(SumoXMLTag type, MSDetectorFileOutput* d, const std::string& device, SUMOTime interval, SUMOTime begin) {
    if (!myDetectors[type].add(d->getID(), d)) {
        const std::string id = d->getID();
        delete d;
        throw ProcessError(toString(type) + " detector '" + id + "' could not be built (declared twice?).");
    }
    addDetectorAndInterval(d, &OutputDevice::getDevice(device), interval, begin);
}


void
MSDetectorControl::add(SumoXMLTag type, MSDetectorFileOutput* d) {
    if (!myDetectors[type].add(d->getID(), d)) {
        const std::string id = d->getID();
        delete d;
        throw ProcessError(toString(type) + " detector '" + id + "' could not be built (declared twice?).");
    }
}


void
MSDetectorControl::add(MSMeanData* md, const std::string& device,
                       SUMOTime frequency, SUMOTime begin) {
    myMeanData[md->getID()].push_back(md);
    addDetectorAndInterval(md, &OutputDevice::getDevice(device), frequency, begin);
    if (begin <= string2time(OptionsCont::getOptions().getString("begin"))) {
        md->init();
    }
    MSGlobals::gHaveEmissions |= typeid(*md) == typeid(MSMeanData_Emissions);
}


const std::vector<SumoXMLTag>
MSDetectorControl::getAvailableTypes() const {
    std::vector<SumoXMLTag> result;
    for (std::map<SumoXMLTag, NamedObjectCont<MSDetectorFileOutput*> >::const_iterator i = myDetectors.begin(); i != myDetectors.end(); ++i) {
        result.push_back(i->first);
    }
    return result;
}


const NamedObjectCont<MSDetectorFileOutput*>&
MSDetectorControl::getTypedDetectors(SumoXMLTag type) const {
    if (myDetectors.find(type) == myDetectors.end()) {
        return myEmptyContainer;
    }
    return myDetectors.find(type)->second;
}


void
MSDetectorControl::updateDetectors(const SUMOTime step) {
    for (const auto& i : myDetectors) {
        for (const auto& j : getTypedDetectors(i.first)) {
            j.second->detectorUpdate(step);
        }
    }
    for (auto item : myMeanData) {
        for (MSMeanData* md : item.second) {
            md->detectorUpdate(step);
        }
    }
}


void
MSDetectorControl::writeOutput(SUMOTime step, bool closing) {
    for (Intervals::iterator i = myIntervals.begin(); i != myIntervals.end(); ++i) {
        IntervalsKey interval = (*i).first;
        if (myLastCalls[interval] + interval.first <= step || (closing && myLastCalls[interval] < step)) {
            DetectorFileVec dfVec = (*i).second;
            SUMOTime startTime = myLastCalls[interval];
            // check whether at the end the output was already generated
            for (DetectorFileVec::iterator it = dfVec.begin(); it != dfVec.end(); ++it) {
                MSDetectorFileOutput* det = it->first;
                det->writeXMLOutput(*(it->second), startTime, step);
            }
            myLastCalls[interval] = step;
        }
    }
}


void
MSDetectorControl::addDetectorAndInterval(MSDetectorFileOutput* det,
        OutputDevice* device,
        SUMOTime interval,
        SUMOTime begin) {
    const SUMOTime simBegin = string2time(OptionsCont::getOptions().getString("begin"));
    if (begin == -1) {
        begin = simBegin;
    }
    IntervalsKey key = std::make_pair(interval, begin);
    Intervals::iterator it = myIntervals.find(key);
    // Add command for given key only once to MSEventControl...
    if (it == myIntervals.end()) {
        DetectorFileVec detAndFileVec;
        detAndFileVec.push_back(std::make_pair(det, device));
        myIntervals.insert(std::make_pair(key, detAndFileVec));
        SUMOTime lastCall = begin;
        if (begin < simBegin) {
            SUMOTime divRest = (simBegin - begin) % interval;
            lastCall = simBegin - divRest;
        }
        myLastCalls[key] = lastCall;
    } else {
        DetectorFileVec& detAndFileVec = it->second;
        if (find_if(detAndFileVec.begin(), detAndFileVec.end(), [&](const DetectorFilePair & pair) {
        return pair.first == det;
    }) == detAndFileVec.end()) {
            detAndFileVec.push_back(std::make_pair(det, device));
        } else {
            // detector already in container. Don't add several times
            WRITE_WARNING("MSDetectorControl::addDetectorAndInterval: detector already in container. Ignoring.");
            return;
        }
    }
    det->writeXMLDetectorProlog(*device);
}

const std::map<std::string, MSMoveReminder*>&
MSDetectorControl::getAllReminders() {
    if (myReminders.empty()) {
        for (auto item : myMeanData) {
            for (MSMeanData* md : item.second) {
                for (MSMoveReminder* rem : md->getReminders()) {
                    myReminders[rem->getDescription()] = rem;
                }
            }
        }
        if (myReminders.empty()) {
            // ensure non-empty map
            myReminders[""] = nullptr;
        }
    }
    return myReminders;
}

void
MSDetectorControl::clearState(SUMOTime step) {
    for (const auto& i : myDetectors) {
        for (const auto& j : getTypedDetectors(i.first)) {
            j.second->clearState(step);
        }
    }
    myReminders.clear();
}

/****************************************************************************/
