/*
 * Copyright (C) 2015 by Multi-Tech Systems
 *
 * This file is part of libmts.
 *
 * libmts is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mts/MTS_System.h>
#include <fstream>
#include <sstream>
#include <cassert>

#ifdef WIN32
#include <windows.h>

//WIN32: FILETIME structure has a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601.

static int64_t getEpochTimeMicros() {
    const SYSTEMTIME EPOCH = {1970, 1, 4, 1, 0, 0, 0, 0};
    FILETIME ft;
    BOOL ok = SystemTimeToFileTime(&EPOCH, &ft);
    assert(ok);
    int64_t epochTimeMicros = ((static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) / 10;
    return epochTimeMicros;
}

static int64_t getSystemTimeMicros() {
    SYSTEMTIME st;
    GetSystemTime(&st);
    FILETIME ft;
    BOOL ok = SystemTimeToFileTime(&st, &ft);
    assert(ok);
    int64_t systemTimeMicros = ((static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) / 10;
    return systemTimeMicros;
}

static int64_t getClockFrequency() {
    LARGE_INTEGER freq;
    BOOL ok = QueryPerformanceFrequency(&freq);
    assert(ok);
    return freq.QuadPart;
}

static int64_t getClockValue() {
    LARGE_INTEGER value;
    BOOL ok = QueryPerformanceCounter(&value);
    assert(ok);
    return value.QuadPart;
}

#else
#include <time.h>
#endif

using namespace MTS;

uint64_t System::timeMicros() {
    int64_t micros = 0;
#ifdef WIN32
    static const int64_t EPOCH_TIME_MICROS = getEpochTimeMicros();
    micros = getSystemTimeMicros() - EPOCH_TIME_MICROS;
#else
    timespec ts;
    int result = clock_gettime(CLOCK_REALTIME, &ts);
    if (result == 0) {
        micros = (static_cast<int64_t>(ts.tv_sec) * 1000000)
        + (ts.tv_nsec / 1000);
    }
#endif
    return micros;
}

uint64_t System::precisionTimeMicros() {
    int64_t micros = 0;
#ifdef WIN32
    static const double TO_MICROS = 1000000.0 / getClockFrequency();
    int64_t value = getClockValue();
    micros = static_cast<int64_t>(value * TO_MICROS);
#else
    micros = timeMicros();
#endif
    return micros;
}

uint64_t System::monoTimeMicros() {
    uint64_t micros = 0;
#ifdef WIN32
    micros = static_cast<uint64_t>(GetTickCount64()) * 1000;
#else
    timespec ts;
    int result = clock_gettime(CLOCK_MONOTONIC, &ts);
    if (result == 0) {
        micros = (static_cast<uint64_t>(ts.tv_sec) * 1000000)
            + (ts.tv_nsec / 1000);
    }
#endif
    return micros;
}

bool System::isBigEndian() {
    static union {
            uint32_t i;
            char c[4];
    } endian = { 0x01020304 };

    return endian.c[0] == 1;
}

void System::swapBytes(uint8_t* const pBuffer, const uint32_t iSize) {
    if (iSize > 1 && pBuffer != 0) {
        uint8_t cByte = 0;
        uint32_t i;
        uint32_t j;
        for (i = 0, j = iSize - 1; i < j; i++, j--) {
            cByte = pBuffer[i];
            pBuffer[i] = pBuffer[j];
            pBuffer[j] = cByte;
        }
    }
}

int32_t System::cmd(const std::string& cmd, std::string& result) {
    std::string output;
    FILE * stream;
    const int max_buffer = 256;
    char buffer[max_buffer];
    int32_t code = -1;

    stream = popen(cmd.c_str(), "r");
    if (stream) {
        while (!feof(stream))
            if (fgets(buffer, max_buffer, stream) != NULL)
                output.append(buffer);
        code = pclose(stream);
    }

    result = output;

    return code;
}

int32_t System::readFile(const std::string& path, std::string& result) {
    std::ifstream infile(path.c_str());
    std::stringstream ss;

    if (!infile.is_open()) {
        return -1;
    }

    ss << infile.rdbuf();

    infile.close();

    result = ss.str();

    return 0;
}