#include <divine/utility/sysinfo.h>

#include <time.h>
#include <wibble/regexp.h>
#include <fstream>
#include <stdint.h>

#ifdef POSIX
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#endif

#ifdef _WIN32
#include <windows.h>
#include <psapi.h>
#include <setupapi.h>

#define CPU_REG_KEY    HKEY_LOCAL_MACHINE
#define CPU_REG_SUBKEY "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"
#define CPU_SPEED      "~MHz"
#define CPU_NAME       "ProcessorNameString"
#endif

namespace divine {
namespace sysinfo {

Data *Info::data = 0;

struct Data {
#ifdef POSIX
    struct timeval tv, now;
    struct rusage usage;
#endif

#ifdef _WIN32
    HANDLE hProcess;
    FILETIME ftCreation, ftExit, ftKernel, ftUser;
    SYSTEMTIME stStart, stFinish, stKernel, stUser;
    unsigned __int64 dwSpeed;
#endif
};

static bool matchLine( std::string file, wibble::ERegexp &r ) {
    std::string line;
    std::ifstream f( file.c_str() );
    while ( !f.eof() ) {
        std::getline( f, line );
        if ( r.match( line ) )
            return true;
    }
    return false;
}

#ifdef POSIX
double interval( struct timeval from, struct timeval to ) {
    return to.tv_sec - from.tv_sec +
        double(to.tv_usec - from.tv_usec) / 1000000;
}

struct timeval zeroTime() {
    struct timeval r = { 0, 0 };
    return r;
}
#endif

#ifdef _WIN32
double SystemTimeToDouble(SYSTEMTIME &time)
{
    return (double)(time.wHour * 3600 + time.wMinute * 60 +
                    time.wSecond + (double)(time.wMilliseconds) / 1000);
}
#endif

double Info::userTime() const {
#ifdef POSIX
    return interval( zeroTime(), data->usage.ru_utime );
#elif defined(_WIN32)
    return SystemTimeToDouble(data->stUser);
#endif
}

double Info::systemTime() const {
#ifdef POSIX
    return interval( zeroTime(), data->usage.ru_stime );
#elif defined(_WIN32)
    return SystemTimeToDouble(data->stKernel);
#endif
}

double Info::wallTime() const {
#ifdef POSIX
    return interval( data->tv, data->now );
#elif defined(_WIN32)
    return SystemTimeToDouble(data->stFinish) - SystemTimeToDouble(data->stStart);
#endif
}

Info::Info() {
    if (data)
        return;

    data = new Data;
    start();
}

void Info::start() const {
#ifdef POSIX
    gettimeofday(&data->tv, NULL);
#endif

#ifdef _WIN32
    data->hProcess = GetCurrentProcess();
    GetLocalTime(&data->stStart);
#endif
}

void Info::update() const {
#ifdef POSIX
    gettimeofday(&data->now, NULL);
    getrusage( RUSAGE_SELF, &data->usage );
#elif defined(_WIN32)
    GetLocalTime(&data->stFinish);
    GetProcessTimes(data->hProcess, &data->ftCreation, &data->ftExit,
                    &data->ftKernel, &data->ftUser);
#endif
}

void Info::stop() const {
#if defined(_WIN32)
    CloseHandle(data->hProcess);
#endif
}

uint64_t Info::peakVmSize() const {
    uint64_t vmsz = 0;
#ifdef __linux
    std::stringstream file;
    file << "/proc/" << uint64_t( getpid() ) << "/status";
    wibble::ERegexp r( "VmPeak:[\t ]*([0-9]+)", 2 );
    if ( matchLine( file.str(), r ) ) {
        vmsz = atoll( r[1].c_str() );
    }
#endif

#ifdef _WIN32
    PROCESS_MEMORY_COUNTERS pmc;
    if (data->hProcess != NULL && GetProcessMemoryInfo(data->hProcess, &pmc, sizeof(pmc)))
        vmsz = pmc.PeakWorkingSetSize/(1024);
#endif
    return vmsz;
}

std::string Info::architecture() const {
#ifdef __linux
    wibble::ERegexp r( "model name[\t ]*: (.+)", 2 );
    if ( matchLine( "/proc/cpuinfo", r ) )
        return r[1];
    return "Unknown";
#endif

#ifdef _WIN32
    HKEY hKey;
    DWORD dwType;
    DWORD dwSize;
    LONG ret = RegOpenKeyEx(CPU_REG_KEY, CPU_REG_SUBKEY, 0, KEY_READ, &hKey);

    // key could not be opened
    if(ret != ERROR_SUCCESS)
        return "Unknown";

    // get CPU name
    dwSize = 0;
    ret = RegQueryValueEx(hKey, CPU_NAME, NULL, &dwType, NULL, &dwSize);
    LPTSTR name;
    name = new TCHAR[dwSize];
    ret = RegQueryValueEx(hKey, CPU_NAME, NULL, &dwType, (LPBYTE)name, &dwSize);

    if(ret != ERROR_SUCCESS)
    {
	RegCloseKey(hKey);
	return "Unknown";
    }

    RegCloseKey(hKey);
    std::string result( name );
    delete[] name;
    return result;
#endif

    return "Unknown";
}

ResourceGuard::ResourceGuard()
    : memory( 0 ), time( 0 )
{}

void *ResourceGuard::main() {
    Info info;
    while ( true ) {
        info.update();

        if ( memory && info.peakVmSize() > memory )
            throw ResourceLimit( "Memory limit exceeded: used "
                                 + wibble::str::fmt( info.peakVmSize() ) + "K / "
                                 + wibble::str::fmt( memory ) + "K." );
        if ( time && info.wallTime() > time )
            throw ResourceLimit( "Time limit exceeded: used "
                                 + wibble::str::fmt( info.wallTime() ) + "s / "
                                 + wibble::str::fmt( time ) + "s." );
        wibble::sys::sleep( 1 );
    }
    return nullptr;
}

std::vector< ReportLine > Info::report() const {
    update();
    return { { "Architecture", architecture() },
             { "Memory-Used", std::to_string( peakVmSize() ) },
             { "User-Time", std::to_string( userTime() ) },
             { "System-Time", std::to_string( systemTime() ) },
             { "Wall-Time", std::to_string( wallTime() ) }
           };
}

}
}

