/*
 * (C) Copyright 1996- 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 <signal.h>
#include <sys/time.h>
#include <unistd.h>

#include "eckit/log/Timer.h"
#include "eckit/memory/Zero.h"
#include "eckit/os/SignalHandler.h"
#include "eckit/runtime/Main.h"
#include "eckit/runtime/Monitor.h"
#include "eckit/runtime/ProcessControler.h"
#include "eckit/runtime/TaskInfo.h"
#include "eckit/log/JSON.h"


namespace eckit {

//----------------------------------------------------------------------------------------------------------------------

TaskInfo::TaskInfo() {
    ASSERT(busy_ == false);

    eckit::zero(*this);

    pid_    = getpid();
    thread_ = pthread_self();
    pos_    = 0;
    start_  = ::time(0);
    if (Main::ready()) {
        ::strncpy(name_, Main::instance().displayName().c_str(), sizeof(name_) - 1);
        ::strncpy(kind_, Main::instance().name().c_str(), sizeof(kind_) - 1);
        ::strncpy(application_, Main::instance().name().c_str(), sizeof(application_) - 1);
        ::strcpy(status_, "Starting");
    }
    show_ = true;
    start(0, 0);
    busy_      = true;
    stoppable_ = true;
    parent_    = -1;
    state_     = ' ';
}


TaskInfo::~TaskInfo() {
    busy_ = false;
}

void TaskInfo::kind(const std::string& s) {
    touch();
    strncpy(kind_, s.c_str(), sizeof(kind_) - 1);
}

void TaskInfo::name(const std::string& s) {
    touch();
    strncpy(name_, s.c_str(), sizeof(name_) - 1);
}

void TaskInfo::status(const std::string& s) {
    touch();
    strncpy(status_, s.c_str(), sizeof(status_) - 1);
}

void TaskInfo::message(const std::string& s) {
    touch();
    zero(message_);
    strncpy(message_, s.c_str(), sizeof(message_) - 1);
}

void TaskInfo::progressName(const std::string& s) {
    touch();
    strncpy(progress_.name_, s.c_str(), sizeof(progress_.name_) - 1);
}

void TaskInfo::out(char* from, char* to) {
    touch();
    for (char* p = from; p != to; p++)
        buffer_[(pos_++) % size_] = *p;
}

unsigned long TaskInfo::text(char* buf, unsigned long max, unsigned long& pos) const {
    unsigned long len = pos_ - pos;
    if (len > size_)
        len = size_;
    if (len > max)
        len = max;

    unsigned long p = pos_ - len + size_;
    unsigned long n = len;

    while (n-- > 0)
        *buf++ = buffer_[(p++) % size_];

    pos = pos_;

    return len;
}

bool TaskInfo::busy(bool check) {
    if (!busy_)
        return false;

    time_t now = ::time(0);

    // After 2 minutes, force the check
    if ((now - check_) > 120)
        check = true;

    check_ = now;

    if (!check)
        return busy_;

    // Check first

    if (!ProcessControler::isRunning(pid_)) {
        this->TaskInfo::~TaskInfo();
        return false;
    }

    return true;
}

void TaskInfo::start(unsigned long long min, unsigned long long max) {
    progress_.rate_ = progress_.speed_ = 0;
    progress_.min_                     = min;
    progress_.max_                     = max;
    progress_.val_                     = min;
    gettimeofday(&progress_.start_, 0);
    gettimeofday(&progress_.last_, 0);

    touch();
}

void TaskInfo::progress(unsigned long long val) {
    ::timeval now;
    gettimeofday(&now, 0);

    ::timeval diff = now - progress_.last_;

    progress_.rate_ = (val - progress_.val_) / ((double)diff.tv_sec + ((double)diff.tv_usec / 1000000.));

    diff = now - progress_.start_;

    progress_.speed_ = (val - progress_.min_) / ((double)diff.tv_sec + ((double)diff.tv_usec / 1000000.));

    progress_.val_ = val;

    gettimeofday(&progress_.last_, 0);
    touch();
}

void TaskInfo::done() {
    start(0, 0);
}

void TaskInfo::touch() {
    checkAbort();

    // FIXME: potential race condition (reported by Clang ThreadSanitizer)
    check_ = last_ = ::time(0);
    busy_          = true;

    SignalHandler::checkInterrupt();

    if (stop_ && stoppable_) {
        stopped_ = true;
        stop_    = false;
        exit(1);
    }
}

void TaskInfo::checkAbort() {
    if (abort_) {
        abort_ = false;
        throw Abort("ThreadControler aborted by request");
    }
}

void TaskInfo::parent(long p) {
    parent_ = p;
    depth_  = 0;
    if (p != -1) {
        depth_ = Monitor::instance().task(p).depth() + 1;
    }
}

//----------------------------------------------------------------------------------------------------------------------

void TaskInfo::json(JSON& json) const {

    Monitor& monitor = Monitor::instance();
    size_t n = this - monitor.tasks().begin();

    json.startObject();
    json << "id" << n;
    json << "busy" << busy_;
    // json << "thread" << thread_;
    json << "pid" << pid_;
    json << "start" << start_;
    json << "last" << last_;
    json << "check" << check_;
    json << "show" << show_;
    json << "late" << late_;


    // Logging
    // enum { size_ = 10240 };
    // char           buffer_[size_];
    // unsigned long  pos_;

    char buffer[size_ + 1] = {0,};
    unsigned long pos = 0;
    unsigned long len = text(buffer, sizeof(buffer), pos);

    json << "log" << buffer;

    json << "name" << name_;
    json << "kind" << kind_;
    json << "application" << application_;

    json << "progress";
    json.startObject();

    json << "min" << progress_.min_;
    json << "max" << progress_.max_;
    json << "val" << progress_.val_;
    json << "name" << progress_.name_;
    json << "rate" << progress_.rate_;
    json << "speed" << progress_.speed_;

    json.endObject();

    json << "taskID" << taskID_;

    json << "stop" << stop_;
    json << "abort" << abort_;
    json << "stoppable" << stoppable_;
    json << "stopped" << stopped_;
    json << "canceled" << canceled_;
    json << "exception" << exception_;
    json << "cancelMsg" << cancelMsg_;
    json << "config" << config_;
    // json << "resource" << resource_;
    json << "parent" << parent_;
    json << "depth" << depth_;
    json << "state" << state_;
    json << "port" << port_;
    json << "host" << host_;
    json << "message" << message_;

    json.endObject();

}

//----------------------------------------------------------------------------------------------------------------------

}  // namespace eckit
