/*
 * Copyright (c) 2022, Technische Universität Kaiserslautern
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER
 * 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:
 *    Iron Prando da Silva
 */

#include "dramtimedependenciesbase.h"

#include <QJsonDocument>

DRAMTimeDependenciesBase::DRAMTimeDependenciesBase(const QJsonObject& memspec, const uint inTCK) {
    mMemspecJson = memspec;
    tCK = inTCK;

}

DependencyMap
DRAMTimeDependenciesBase::getDependencies(std::vector<QString>& dependencyFilter) const {
    DependencyMap dependenciesMap;

    std::vector<StringMapper> dependencyFilterStrMapper(dependencyFilter.begin(), dependencyFilter.end());
    std::sort(
        dependencyFilterStrMapper.begin(), 
        dependencyFilterStrMapper.end()
    );
    
    dependenciesMap = mSpecializedGetDependencies();
    
    mFilterDependencyMap(dependenciesMap, dependencyFilterStrMapper);

    auto it = dependenciesMap.begin();
    while (it != dependenciesMap.end()) {
        mFilterDependencyList(it->second.dependencies, dependencyFilterStrMapper);
        it->second.maxTime = mFindVectorMaximum(it->second.dependencies);

        ++it;
    }

    return dependenciesMap;
}

PoolControllerMap DRAMTimeDependenciesBase::getPools() const {
    return PoolControllerMap(mPools);
}

void DRAMTimeDependenciesBase::mFilterDependencyList(std::vector<TimeDependency>& dependencyList, const std::vector<StringMapper>& dependencyFilter) const {
    std::vector<TimeDependency> newDepList(dependencyList.size());
    
    // TODO - probably there is a smarter way to filter these values,
    // although the lists are not to be greater than 20-50 elements

    std::copy_if(
        dependencyList.begin(),
        dependencyList.end(),
        newDepList.begin(),
        [ dependencyFilter ](const TimeDependency& dep) { 
            auto it = std::lower_bound(
                dependencyFilter.begin(),
                dependencyFilter.end(),
                dep.phaseDep,
                [](const StringMapper& cmd, const StringMapper& depName){
                    return depName.isPool() || cmd < depName;
                }
            );

            if (dep.phaseDep.isPool() || it != dependencyFilter.end() && *it == dep.phaseDep) 
                return true;

            return false;
        }
    );

    newDepList.shrink_to_fit();

    dependencyList = newDepList;

    std::sort(
        dependencyList.begin(), 
        dependencyList.end(), 
        [](const TimeDependency& v1, const TimeDependency& v2) {
            return v1.timeValue < v2.timeValue;
        }
    );

}

void DRAMTimeDependenciesBase::mFilterDependencyMap(DependencyMap& dependencyMap, const std::vector<StringMapper>& dependencyFilter) const {
    if (!dependencyMap.empty()) {

        auto itFilter = dependencyFilter.begin();
        auto itFilterLast = std::lower_bound(
            dependencyFilter.begin(), 
            dependencyFilter.end(), 
            dependencyMap.rbegin()->first
        );

        auto itDependencyMap = dependencyMap.begin();

        while (true) {

            auto pair = std::mismatch(
                itFilter,
                itFilterLast,
                itDependencyMap,
                [](const StringMapper& cmd, const std::pair<StringMapper, PhaseTimeDependencies>& vpair) {
                    return cmd == vpair.first;
                }
            );

            if (pair.first == dependencyFilter.end() || pair.second == dependencyMap.end()) {
                dependencyMap.erase(pair.second, dependencyMap.end());
                break;

            } else if (*(pair.first) < pair.second->first) {
                ++(pair.first);

            } else if (*(pair.first) == pair.second->first) {
                ++(pair.second);

            } else {
                pair.second = dependencyMap.erase(pair.second);

            }

            itFilter = pair.first;
            itDependencyMap = pair.second;

        }
    }

}

uint DRAMTimeDependenciesBase::mFindVectorMaximum(const std::vector<TimeDependency>& dependencyList) const {
    auto maxElement = std::max_element(
        dependencyList.begin(),
        dependencyList.end(),
        [](const TimeDependency& dep1, const TimeDependency& dep2) {
            return dep1.timeValue < dep2.timeValue;
        }
    );

    if (maxElement == dependencyList.end()) return 0;

    return maxElement->timeValue;
}
