/***************************** LICENSE START ***********************************

 Copyright 2013 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvProcNode.h"

#include <algorithm>
#include <iostream>
#include <sstream>

#include <cstdio>
#include <sys/types.h>
#include <csignal>

unsigned long MvProcStat::uptime_ = 0;
long MvProcStat::pageSize_ = 0;

std::vector<MvProcNode*> MvProcNode::items_;
std::vector<MvProcRequest*> MvProcNode::procRequests_;
MvProcNode* MvProcNode::root_ = nullptr;
MvProcNodeObserver* MvProcNode::observer_ = nullptr;
bool MvProcNode::hasProcFs_ = false;

//=============================================
//
// MvProcStat
//
//=============================================

MvProcStat::MvProcStat(const std::string& /*host*/, long pid) :
    cpu_(0)
{
    std::stringstream ss;
    ss << pid;

    if (MvProcNode::hasProcFs()) {
        procPath_ = "/proc/" + ss.str() + "/stat";
        sysPath_ = "/proc/stat";

        if (pageSize_ == 0)
            pageSize_ = sysconf(_SC_PAGESIZE);

        if (!hasUptime()) {
            readUptime();
        }
        readStartTime();
    }
}

void MvProcStat::readUptime()
{
    if (!MvProcNode::hasProcFs())
        return;

    // Get the system uptime
    // We need to read it only once
    FILE* fp = nullptr;

    // File uptime tells the secons since the system boot.
    // Variable uptime_ contains the POSIX time of the boot!
    if ((fp = fopen("/proc/uptime", "r"))) {
        float fv;
        if (fscanf(fp, "%f", &fv) != EOF) {
            uptime_ = time(nullptr) - static_cast<long>(fv);

            std::cout << "uptime " << uptime_ << std::endl;
        }
        fclose(fp);
    }
}

void MvProcStat::readStartTime()
{
    if (!MvProcNode::hasProcFs())
        return;

    if (!hasUptime())
        return;

    FILE* fp = nullptr;

    if ((fp = fopen(procPath_.c_str(), "r")) != nullptr) {
        for (int i = 0; i < 21; i++) {
            if (fscanf(fp, "%*s") == EOF) {
                fclose(fp);
                return;
            }
        }
        long lv = 0;
        if (fscanf(fp, "%ld", &lv) != EOF) {
            startTime_ = uptime_ + lv / sysconf(_SC_CLK_TCK);
            std::cout << "start time " << startTime_ << " " << time(nullptr) << " " << uptime_ << " " << lv << " " << lv / sysconf(_SC_CLK_TCK) << std::endl;
        }
    }

    fclose(fp);
}

bool MvProcStat::update()
{
    if (!MvProcNode::hasProcFs())
        return false;

    int cpuOri = cpu_;
    std::string vmRssOri = vmRss_;
    std::string vmSharedOri = vmShared_;

    if (updateStats())
        return false;

    if (cpuOri != cpu_ ||
        vmRssOri != vmRss_ ||
        vmSharedOri != vmShared_ ||
        lastElapsedTime_ != elapsedTime()) {
        return true;
    }

    return false;
}

int MvProcStat::updateStats()
{
    if (!MvProcNode::hasProcFs())
        return -1;

    FILE *fpP = nullptr, *fpS = nullptr;
    CpuTime actCpuTime;

    if ((fpP = fopen(procPath_.c_str(), "r")) == nullptr) {
        return -1;
    }

    if ((fpS = fopen(sysPath_.c_str(), "r")) == nullptr) {
        fclose(fpP);
        return -1;
    }

    long int rss = 0;

    // Read process cpu time from /proc/pid/stat
    if (fscanf(fpP,
               "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
               "%lu %ld %ld %*d %*d %*d %*d %*u %*u %ld",
               &actCpuTime.uTime_, &actCpuTime.sTime_,
               &actCpuTime.cuTime_, &actCpuTime.csTime_, &rss) == EOF) {
        fclose(fpP);
        fclose(fpS);
        return -1;
    }

    fclose(fpP);

    // Get rss memory
    vmRss_ = byteToMbyte(rss * pageSize_);

    // Read system cpu time from proc/stat
    long unsigned int cpuSys[10];
    memset(cpuSys, 0, sizeof(cpuSys));
    if (fscanf(fpS, "%*s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
               &cpuSys[0], &cpuSys[1], &cpuSys[2], &cpuSys[3],
               &cpuSys[4], &cpuSys[5], &cpuSys[6], &cpuSys[7],
               &cpuSys[8], &cpuSys[9]) == EOF) {
        fclose(fpS);
        return -1;
    }
    fclose(fpS);

    // Sum up the numbers to get the total cpu time
    long unsigned int v = 0;
    for (unsigned long cpuSy : cpuSys)
        v += cpuSy;

    actCpuTime.total_ = v;

    // Cpu usage
    if (!prevCpuTime_.isEmpty()) {
        cpu_ = getCpuUsage(&actCpuTime, &prevCpuTime_);
    }
    prevCpuTime_ = actCpuTime;

    return 0;
}

std::string MvProcStat::elapsedTime()
{
    if (!MvProcNode::hasProcFs())
        return "-1";

    time_t t = time(nullptr) - startTime_;
    int d = t / 86400;
    int h = (t - d * 86400) / 3600;
    int m = (t - d * 86400 - h * 3600) / 60;

    char c[15];
    if (d == 0) {
        if (h == 0)
            sprintf(c, "%02dm", m);
        else
            sprintf(c, "%02dh:%02dm", h, m);
    }
    else
        sprintf(c, "%dd:%02dh:%02dm", d, h, m);


    // std::stringstream ss;
    // ss << h << "h:" << m  << "m";
    // lastElapsedTime_=ss.str();
    lastElapsedTime_ = std::string(c);
    return lastElapsedTime_;
}

// Calculates the elapsed CPU usage between 2 measuring points. in percent
int MvProcStat::getCpuUsage(CpuTime* current, CpuTime* prev)
{
    if (!MvProcNode::hasProcFs())
        return 0;

    long unsigned int tdiff = current->total_ - prev->total_;

    // std::cout << "cpu " << tdiff << " " << current->uTime_ << " " << prev->uTime_ << std::endl;

    return static_cast<int>(100. * ((current->uTime_ + current->cuTime_) - (prev->uTime_ + prev->cuTime_)) / static_cast<float>(tdiff));


    //*scpu_usage = 100 * ((((current->stime_ticks + current->cstime_ticks)
    //                - (prev->stime_ticks + prev->cstime_ticks))) /
    //                (double) total_time_diff);
}

std::string MvProcStat::byteToMbyte(unsigned long int bv)
{
    std::stringstream ss;
    ss.setf(std::ios::fixed, std::ios::floatfield);
    ss.precision(1);
    ss << static_cast<double>(bv) / 1048576. << " M";

    return ss.str();
}

//=============================================
//
// MvProcNode
//
//=============================================

MvProcNode::MvProcNode(long ref, request* r) :
    ref_(ref),
    pid_(-50),
    stats_(nullptr),
    killLevel_(0),
    procRequest_(nullptr),
    parent_(nullptr)
{
    if (r) {
        if (const char* v = get_value(r, "NAME", 0))
            name_ = std::string(v);
        if (const char* v = get_value(r, "HOST", 0))
            host_ = std::string(v);
        if (const char* v = get_value(r, "USER", 0))
            user_ = std::string(v);
        if (const char* v = get_value(r, "PID", 0)) {
            pidStr_ = std::string(v);
            pid_ = atol(pidStr_.c_str());
            if (pid_ > 0) {
                stats_ = new MvProcStat(host_, pid_);
                updateStats();
            }
        }

        niceName_ = name_;
        queryNiceName();
    }

    items_.push_back(this);
}

MvProcNode::~MvProcNode()
{
    if (items_.size() > 0) {
        auto it = std::find(items_.begin(), items_.end(), this);
        if (it != items_.end())
            items_.erase(it);
    }

    if (stats_)
        delete stats_;

    // Delete children
    for (auto& it : children_) {
        delete it;
    }

    children_.clear();

    if (procRequest_)
        delete procRequest_;
}

void MvProcNode::queryNiceName()
{
    request* s = mars.setup;
    while (s) {
        if (strcmp(s->name, "service") == 0) {
            const char* n = get_value(s, "name", 0);
            if (n && strcmp(n, name_.c_str()) == 0) {
                const char* p = get_value(s, "fullname", 0);
                if (p) {
                    niceName_ = std::string(p);
                    return;
                }
            }
        }
        s = s->next;
    }
}

MvProcNode* MvProcNode::root()
{
    if (!root_) {
        root_ = new MvProcNode(-1, nullptr);
    }

    return root_;
}

MvProcNode* MvProcNode::childAt(int i) const
{
    return (i >= 0 && i < static_cast<int>(children_.size())) ? children_.at(i) : nullptr;
}

int MvProcNode::indexOfChild(MvProcNode* item) const
{
    for (int i = 0; i < static_cast<int>(children_.size()); i++)
        if (children_.at(i) == item)
            return i;

    return -1;
}

void MvProcNode::add(long ref, request* r)
{
    auto* proc = new MvProcNode(ref, r);

    for (auto& item : items_) {
        if (item != root_ && proc != item &&
            proc->name().find(item->name()) == 0 && proc->name().find("@") != std::string::npos) {
            proc->setParent(item);
            return;
        }
    }

    proc->setParent(root_);

    // Reparent existing items if needed
    if (!(proc->name().find("@") != std::string::npos)) {
        for (auto& item : items_) {
            if (item != root_ && proc != item && item->name().find("@") != std::string::npos) {
                std::string s = item->name().substr(0, item->name().find("@"));
                if (s == proc->name())
                    item->setParent(proc);
            }
        }
    }
}

void MvProcNode::remove(long ref, std::string name)
{
    MvProcNode* proc = find(ref, name);
    if (proc) {
        std::cout << "remove " << proc->name() << std::endl;

        if (proc->parent()) {
            std::cout << "remove from parent" << proc->parent()->name() << std::endl;

            proc->parent()->removeChild(proc);
            delete proc;
        }
    }
}

void MvProcNode::setProcRequest(MvProcRequest* req)
{
    if (procRequest_) {
        delete procRequest_;
        procRequest_ = nullptr;
    }

    procRequest_ = req;
}

void MvProcNode::setParent(MvProcNode* p)
{
    if (parent_) {
        parent_->removeChild(this);
    }

    parent_ = p;
    parent_->addChild(this);
}

void MvProcNode::addChild(MvProcNode* item)
{
    children_.push_back(item);
}

void MvProcNode::removeChild(MvProcNode* item)
{
    if (children_.size() > 0) {
        auto it = std::find(children_.begin(), children_.end(), item);
        if (it != children_.end()) {
            std::cout << "remove child " << (*it)->name() << std::endl;
            children_.erase(it);
        }
    }
}

void MvProcNode::updateInfo()
{
    for (auto& item : items_) {
        item->updateStats();
    }
}

void MvProcNode::updateStats()
{
    if (pid_ < 0 || !stats_)
        return;

    if (stats_->update() && observer_)
        observer_->infoChanged(this);
}

MvProcNode* MvProcNode::find(long r, std::string name)
{
    for (auto& item : items_) {
        if (item->ref() == r && item->name() == name)
            return item;
    }
    return nullptr;
}

void MvProcNode::killProc()
{
    int sig = 0;
    switch (killLevel_++) {
        case 0:
            sig = 15;
            break;
        default:
            sig = 9;
            break;
    }

    kill(pid_, sig);
}


void MvProcNode::addProcRequest(long reqId, long ref, const std::string& name, request* r)
{
    procRequests_.push_back(new MvProcRequest(r));
    assignProcRequest(reqId, ref, name);
}


void MvProcNode::assignProcRequest(long reqId, long ref, const std::string& name)
{
    for (auto it = procRequests_.begin();
         it != procRequests_.end(); it++) {
        if ((*it)->reqId() == reqId) {
            if (MvProcNode* proc = find(ref, name)) {
                if (proc->parent() != root_) {
                    proc->setProcRequest(*it);
                    procRequests_.erase(it);
                    return;
                }
            }
        }
    }
}

//===========================================
//  MvProcRequest
//===========================================

MvProcRequest::MvProcRequest(request* r) :
    req_(nullptr)
{
    reqId_ = atol(get_value(r, "REQ_ID", 0));
    sourceRef_ = atol(get_value(r, "SOURCE_REF", 0));

    if (const char* ch = get_value(r, "TARGET", 0))
        target_ = std::string(ch);

    if (const char* ch = get_value(r, "SOURCE", 0))
        source_ = std::string(ch);

    const char* x = get_value(r, "WAITMODE", 0);
    int sync = x ? atoi(x) : 0;

    if (r->next) {
        req_ = clone_all_requests(r->next);

        if (const char* ch = get_value(req_, "_CLASS", 0)) {
            className_ = std::string(ch);
        }
        else {
            className_ = "REQUEST";
        }

        if (const char* ch = get_value(req_, "_NAME", 0)) {
            name_ = std::string(ch);  // mbasename(
        }
        else {
            name_ = std::string(req_->name);
        }

        if (sync) {
            name_ += " (synchrone)";
        }

        if (char* rc = request2string(req_))
            reqStr_ = std::string(rc);
    }
}

MvProcRequest::~MvProcRequest()
{
    if (req_)
        free_all_requests(req_);
}


MvServiceInfo::MvServiceInfo(long ref, request* r) :
    MvProcNode(ref, r)
{
    // name        = strcache(get_value(r,"NAME",0));

    // Look for  @
    // int at  = 0;
    // const char *p = name;
    // while(*p) { if(*p == '@') at++; p++; }

    // host        = strcache(get_value(r,"HOST",0));
    // user        = strcache(get_value(r,"USER",0));
    // pid         = atol(get_value(r,"PID",0));

    // const char *refp = get_value(r,"REF",0);
    // if( refp ) ref = atol( refp );
    // else       ref = 0;

    // name        = strcache(nice_name(name));
    // if(!at) icon  = DragAddIcon(drag_service,"SERVICE",name,this,0,0);
    killLevel_ = 0;
}

MvServiceInfo::~MvServiceInfo() = default;

void MvServiceInfo::debug()
{
    // kill(pid_,SIGUSR2);
}

/*void MvServiceInfo::kill()
{
    int sig;
    switch(killLevel_++)
    {
        case 0:  sig = 15; break;
        default: sig = 9;  break;
    }
    //kill(pid_,sig);
}*/

void MvServiceInfo::open()
{
    // Info::Open();
    // char buf[1024];
    // sprintf(buf,"Host: %s\nUser: %s\nPID : %d",
    //	host,user,pid);
    //	XmTextSetString(request_text,buf);
}


MvRequestInfo::MvRequestInfo(long ref, long /*f*/, long t, request* s, int sync) :
    MvProcNode(ref, s),
    to_(t)
{
    req_ = clone_all_requests(s);
    const char* n = mbasename(get_value(req_, "_NAME", 0));

    if (!n || !*n)
        n = req_->name;

    if (sync) {
        char buf[1024];
        sprintf(buf, "%s (synchrone)", n);
        name_ = std::string(strcache(buf));
    }
    else
        name_ = std::string(strcache(n));

    // icon    = DragAddIcon(drag_request,c,name,this,0,0);
}

MvRequestInfo::~MvRequestInfo()
{
    free_all_requests(req_);
}
