// Copyright (C) 2018-2019 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include <vector>
#include <mutex>
#include <memory>
#include <condition_variable>
#include <thread>
#include <queue>
#include <ie_profiling.hpp>
#include "details/ie_exception.hpp"
#include "exception2status.hpp"
#include "ie_task_synchronizer.hpp"
#include "ie_task.hpp"
#include "ie_task_executor.hpp"

namespace InferenceEngine {

Task::Task() : _status(TS_INITIAL) {
    _function = [&]() {
        _status = TS_DONE;
        return;
    };
}

Task::Task(const std::function<void()> &function) : _status(TS_INITIAL), _function(function) {
    if (!function) THROW_IE_EXCEPTION << "Failed to create Task object with null function";
}

Task::Status Task::runNoThrowNoBusyCheck() noexcept {
    IE_PROFILING_AUTO_SCOPE(TaskExecution);
    try {
        _exceptionPtr = nullptr;
        _function();
        setStatus(TS_DONE);
    } catch (...) {
        _exceptionPtr = std::current_exception();
        setStatus(TS_ERROR);
    }
    _isTaskDoneCondVar.notify_all();
    return getStatus();
}

Task::Status Task::runWithSynchronizer(TaskSynchronizer::Ptr &taskSynchronizer) {
    if (occupy()) {
        ScopedSynchronizer scopedSynchronizer(taskSynchronizer);
        runNoThrowNoBusyCheck();
    }
    return getStatus();
}

Task::Status Task::wait(int64_t millis_timeout) {
    _isOnWait = true;
    std::exception_ptr exceptionPtr;
    try {
        std::unique_lock<std::mutex> lock(_taskStatusMutex);
        if (_status != TS_INITIAL) {
            auto predicate = [&]() -> bool { return _status == TS_DONE || _status == TS_ERROR; };
            if (millis_timeout < 0) {
                _isTaskDoneCondVar.wait(lock, predicate);
            } else {
                _isTaskDoneCondVar.wait_for(lock, std::chrono::milliseconds(millis_timeout), predicate);
            }
        }
    } catch (...) {
        exceptionPtr = std::current_exception();
    }
    if (exceptionPtr) std::rethrow_exception(exceptionPtr);
    _isOnWait = false;
    return _status;
}

bool Task::occupy() {
    std::unique_lock<std::mutex> guard(_taskStatusMutex);
    if (_status == Task::TS_BUSY) return false;
    _status = TS_BUSY;
    return true;
}

Task::Status Task::getStatus() {
    std::unique_lock<std::mutex> guard(_taskStatusMutex);
    return _status;
}

void Task::checkException() {
    if (_exceptionPtr) {
        std::rethrow_exception(_exceptionPtr);
    }
}

StatusCode Task::TaskStatus2StatusCode(Task::Status status) {
    switch (status) {
        case Status::TS_DONE:
            return OK;
        case Status::TS_ERROR:
            return GENERAL_ERROR;
        case Status::TS_BUSY:
        case Status::TS_POSTPONED:
            return RESULT_NOT_READY;
        case Status::TS_INITIAL:
            return INFER_NOT_STARTED;
        default:
            THROW_IE_EXCEPTION << "Logic error: unknown state of InferRequest!";
    }
}

void Task::setStatus(Task::Status status) {
    std::unique_lock<std::mutex> guard(_taskStatusMutex);
    _status = status;
}

bool Task::isOnWait() {
    return _isOnWait;
}

}  // namespace InferenceEngine