/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include "FetchTask.hpp"

#include <cassert>
#include <map>

#include "UIDebug.hpp"
#include "UiLog.hpp"

// #define UI_FETCHTASK_DEBUG__

static std::map<std::string, FetchTaskFactory*>* makers = nullptr;

FetchTaskFactory::FetchTaskFactory(const std::string& name) {
    if (makers == nullptr)
        makers = new std::map<std::string, FetchTaskFactory*>;

    // Put in reverse order...
    (*makers)[name] = this;
}

AbstractFetchTask* FetchTaskFactory::create(const std::string& name, FetchQueueOwner* owner) {
    AbstractFetchTask* t = nullptr;
    auto j               = makers->find(name);
    if (j != makers->end()) {
        t = (*j).second->make(owner);
    }

    UI_ASSERT(t, UI_FN_INFO + " unsupported name=" + name);
    return t;
}

//========================================
//
// OutputFetchTask
//
//========================================

void AbstractFetchTask::clear() {
    queue_        = nullptr;
    status_       = NoStatus;
    runCondition_ = NoCondition;
    server_       = nullptr;
    node_         = nullptr;
    filePath_.clear();
    deltaPos_ = 0;
    useCache_ = false;
}

void AbstractFetchTask::reset(ServerHandler* server, VNode* node, const std::string& filePath) {
    server_   = server;
    node_     = node;
    filePath_ = filePath;
}

void AbstractFetchTask::reset(ServerHandler* server,
                              VNode* node,
                              const std::string& filePath,
                              size_t deltaPos,
                              unsigned int modTime,
                              const std::string& checkSum,
                              bool useCache) {
    server_   = server;
    node_     = node;
    filePath_ = filePath;
    deltaPos_ = deltaPos;
    modTime_  = modTime;
    checkSum_ = checkSum;
    useCache_ = useCache;
}

void AbstractFetchTask::setQueue(FetchQueue* q) {
    queue_ = q;
}

bool AbstractFetchTask::checRunCondition(AbstractFetchTask* prev) const {
    if (prev && runCondition_ == RunIfPrevFailed && prev->status_ == SucceededStatus) {
        return false;
    }
    return true;
}

void AbstractFetchTask::succeed() {
    status_ = SucceededStatus;
    if (queue_) {
        queue_->taskSucceeded(this);
    }
}

void AbstractFetchTask::finish() {
    status_ = FinishedStatus;
    if (queue_) {
        queue_->taskFinished(this);
    }
}

void AbstractFetchTask::fail() {
    status_ = FailedStatus;
    if (queue_) {
        queue_->taskFailed(this);
    }
}

std::string AbstractFetchTask::print() const {
    return name_ + "[status=" + std::to_string(status_) + ",runCondition=" + std::to_string(runCondition_) +
           ",filePath=" + filePath_ + "]";
}

//========================================
//
// OutputFetchQueue
//
//========================================

void FetchQueue::clear() {
    status_ = IdleState;
    for (auto t : queue_) {
        t->stop();
        t->setQueue(nullptr);
    }
    queue_.clear();
}

void FetchQueue::add(AbstractFetchTask* t) {
    assert(t);
    t->setQueue(this);
    queue_.push_back(t);
}

void FetchQueue::run() {
    if (!queue_.empty()) {
        status_ = RunningState;
        queue_.front()->run();
    }
    else {
        finish();
    }
}

void FetchQueue::next() {
#ifdef UI_FETCHTASK_DEBUG__
    UiLog().dbg() << "OutputFetchQueue::next";
#endif
    if (status_ == RunningState) {
        if (!queue_.empty()) {
            auto prev = queue_.front();
            queue_.pop_front();
            prev->setQueue(nullptr);
            if (!queue_.empty()) {
                auto current = queue_.front();
                if (!current->checRunCondition(prev)) {
#ifdef UI_FETCHTASK_DEBUG__
                    UiLog().dbg() << " skip current";
#endif
                    next();
                }
                else {
#ifdef UI_FETCHTASK_DEBUG__
                    UiLog().dbg() << " run current";
#endif
                    current->run();
                }
            }
            else {
                finish(prev);
            }
        }
        else {
            finish();
        }
    }
}

void FetchQueue::finish(AbstractFetchTask* lastTask) {
#ifdef UI_FETCHTASK_DEBUG__
    UI_FN_DBG
#endif
    clear();
    if (lastTask) {
        owner_->fetchQueueFinished(lastTask->filePath(), lastTask->node());
    }
    else {
        owner_->fetchQueueFinished({}, nullptr);
    }
}

void FetchQueue::taskSucceeded(AbstractFetchTask*) {
    if (status_ == RunningState) {
        if (policy_ == RunUntilFirstSucceeded) {
            clear();
            owner_->fetchQueueSucceeded();
        }
        else {
            next();
        }
    }
}

void FetchQueue::taskFinished(AbstractFetchTask*) {
    if (status_ == RunningState) {
        next();
    }
    else {
        clear();
    }
}

void FetchQueue::taskFailed(AbstractFetchTask*) {
#ifdef UI_FETCHTASK_DEBUG__
    UI_FN_DBG
#endif
    if (status_ == RunningState) {
        next();
    }
    else {
        clear();
    }
}

std::string FetchQueue::print() const {
    std::string s = "OutpuFetchQueue[";
    for (auto t : queue_) {
        s += t->name() + ",";
    }
    s[s.size() - 1] = ']';
    return s;
}

//========================================
//
// OutputFetchQueueOwner
//
//========================================

FetchQueueOwner::~FetchQueueOwner() {
    if (fetchQueue_) {
        delete fetchQueue_;
        fetchQueue_ = nullptr;
    }

    for (auto it : fetchTasks_) {
        for (auto itV : it.second) {
            delete itV;
        }
    }
    fetchTasks_.clear();
    unusedTasks_.clear();
}

bool FetchQueueOwner::isEmpty() const {
    return (fetchQueue_) ? fetchQueue_->isEmpty() : true;
}

void FetchQueueOwner::clear() {
    // clear the queue and the fetch tasks
    if (fetchQueue_) {
        fetchQueue_->clear();
        for (auto it : fetchTasks_) {
            for (auto itV : it.second) {
                itV->clear();
            }
        }
    }
    unusedTasks_ = fetchTasks_;
}

AbstractFetchTask* FetchQueueOwner::makeFetchTask(const std::string& name) {
#ifdef UI_FETCHTASK_DEBUG__
    UiLog().dbg() << UI_FN_INFO << "unusedtasks";
    for (auto t : unusedTasks_) {
        UiLog().dbg() << " " << t.first << " len=" << t.second.size();
    }
    UiLog().dbg() << UI_FN_INFO << "fetchTasks";
    for (auto t : fetchTasks_) {
        UiLog().dbg() << " " << t.first << " len=" << t.second.size();
    }
#endif
    auto it = unusedTasks_.find(name);
    if (it != unusedTasks_.end()) {
        if (!it->second.empty()) {
            auto t = it->second.front();
            it->second.pop_front();
            return t;
        }
    }
#ifdef UI_FETCHTASK_DEBUG__
    UiLog().dbg() << UI_FN_INFO << "create object for name=" << name;
#endif
    AbstractFetchTask* t = FetchTaskFactory::create(name, this);
    fetchTasks_[name].emplace_back(t);
    return t;
}

std::ostream& operator<<(std::ostream& stream, AbstractFetchTask* t) {
    stream << t->print();
    return stream;
}

std::ostream& operator<<(std::ostream& stream, FetchQueue* q) {
    stream << q->print();
    return stream;
}
