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

/**
 * \brief Basic debugging tools
 * \file debug.h
 */
#pragma once

#include <cstdlib>
#include <cstdarg>
#include <string>
#include <ctime>
#include <cstdint>
#include <algorithm>
#include <functional>
#include <cctype>
#include <iostream>
#include <sstream>
#include <vector>
#include <iterator>
#include <numeric>
#include <w_unistd.h>
#include "ie_algorithm.hpp"

#ifdef _WIN32
#include <windows.h>

#define POSIX_EPOCH_AS_FILETIME 116444736000000000ULL
#define OPT_USAGE
static void gettimeofday(struct timeval * tp, struct timezone *) {
    SYSTEMTIME  system_time;
    FILETIME    file_time;
    uint64_t    time;

    GetSystemTime(&system_time);
    SystemTimeToFileTime(&system_time, &file_time);

    time = file_time.dwLowDateTime;
    time += static_cast<uint64_t>(file_time.dwHighDateTime) << 32;

    tp->tv_sec = static_cast<long>((time - POSIX_EPOCH_AS_FILETIME) / 10000000L);
    tp->tv_usec = (system_time.wMilliseconds * 1000);
}
#else
#define vsnprintf_s vsnprintf

#include <string.h>
#include <sys/time.h>

#ifndef OPT_USAGE
#ifdef __GNUC__
#define OPT_USAGE __attribute__ ((unused))
#else
#define OPT_USAGE
#endif
#endif
#endif
/// Diff in uSec
inline int64_t operator-(const timeval& lhs, const timeval& rhs) {
    return static_cast<int64_t>(lhs.tv_sec - rhs.tv_sec) * 1000000 + lhs.tv_usec - rhs.tv_usec;
}

namespace InferenceEngine {
namespace details {

/**
* @brief vector serialisation to be used in exception
*/
template <typename T>
inline std::ostream & operator << (std::ostream &out, const std::vector<T> &vec) {
    if (vec.empty()) return std::operator<<(out, "[]");
    out << "[" << vec[0];
    for (unsigned i=1; i < vec.size(); i++) {
        out << ", " << vec[i];
    }
    return out << "]";
}


/**
 * @brief trim from start (in place)
 * @param s - string to trim
 */
inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int c){
        return !std::isspace(c);
    }));
}

/**
 * @brief trim from end (in place)
 * @param s - string to trim
 */
inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), [](int c) {
        return !std::isspace(c);
    }).base(), s.end());
}

/**
 * @brief trim from both ends (in place)
 * @param s - string to trim
 */
inline std::string &trim(std::string &s) {
    ltrim(s);
    rtrim(s);
    return s;
}

/**
 * @brief split string into a vector of substrings
 * @param src - string to split
 * @param delimiter - string used as a delimiter
 * @return vector of substrings
 */
inline std::vector<std::string> split(const std::string &src, const std::string &delimiter) {
    std::vector<std::string> tokens;
    std::string tokenBuf;
    size_t prev = 0,
            pos = 0,
            srcLength = src.length(),
            delimLength = delimiter.length();
    do {
        pos = src.find(delimiter, prev);
        if (pos == std::string::npos) {
            pos = srcLength;
        }
        tokenBuf = src.substr(prev, pos - prev);
        if (!tokenBuf.empty()) {
            tokens.push_back(tokenBuf);
        }
        prev = pos + delimLength;
    } while (pos < srcLength && prev < srcLength);
    return tokens;
}

/**
 * @brief create a string representation for a vector of values
 * @param vec - vector of values
 * @return string representation
 */
template<typename T, typename A>
std::string dumpVec(std::vector<T, A> const &vec) {
    if (vec.empty()) return "[]";
    std::stringstream oss;
    oss << "[" << vec[0];
    for (size_t i = 1; i < vec.size(); i++) oss << "," << vec[i];
    oss << "]";
    return oss.str();
}

/**
 * @brief multiply vector's values
 * @param vec - vector with values
 * @return result of multiplication
 */
template<typename T, typename A>
T product(std::vector<T, A> const &vec) {
    if (vec.empty()) return 0;
    T ret = vec[0];
    for (size_t i = 1; i < vec.size(); ++i) ret *= vec[i];
    return ret;
}

/**
 * @brief check if vectors contain same values
 * @param v1 - first vector
 * @param v2 - second vector
 * @return true if vectors contain same values
 */
template<typename T, typename A>
bool equal(const std::vector<T, A> &v1, const std::vector<T, A> &v2) {
    if (v1.size() != v2.size()) return false;
    for (auto i1 = v1.cbegin(), i2 = v2.cbegin(); i1 != v1.cend(); ++i1, ++i2) {
        if (*i1 != *i2)
            return false;
    }
    return true;
}

inline bool equal(const std::string &lhs, const std::string &rhs, bool ignoreCase = true) {
    return (lhs.size() == rhs.size()) && (ignoreCase ?
        0 == strncasecmp(lhs.c_str(), rhs.c_str(), lhs.size()) :
        0 == strncmp(lhs.c_str(), rhs.c_str(), lhs.size()));
}

/**
 * @brief check string end with given substring
 * @param src - string to check
 * @param with - given substring
 * @return true if string end with given substring
 */
inline bool endsWith(const std::string &src, const char *with) {
    int wl = static_cast<int>(strlen(with));
    int so = static_cast<int>(src.length()) - wl;
    if (so < 0) return false;
    return 0 == strncmp(with, &src[so], wl);
}

/**
* @brief converts all upper-case letters in a string to lower case
* @param s - string to convert
*/
inline std::string tolower(const std::string &s) {
    std::string ret;
    ret.resize(s.length());
    std::transform(s.begin(), s.end(), ret.begin(), ::tolower);
    return ret;
}
}  // namespace details
}  // namespace InferenceEngine

/**
 * @brief print a log message
 * @param isErr - is message an error or not
 * @param level - string containing message level, like "ERROR" or "DEBUG"
 * @param file - file the message was produced by
 * @param line - string in file the message was dispatched from
 * @param msg - format for message + arguments
 */
static void OPT_USAGE print_log(bool isErr, const char *level, const char *file, int line, const char *msg, ...) {
    va_list va;
    va_start(va, msg);
    char buffer[64];
    struct tm tm_info;

    struct timeval tval;
    gettimeofday(&tval, NULL);

    auto outFd = isErr ? stderr : stdout;

#ifdef _WIN32
    time_t timer;
    timer = tval.tv_sec;
    localtime_s(&tm_info, &timer);
#else
    localtime_r(&tval.tv_sec, &tm_info);
#endif

    strftime(buffer, 64, "%Y:%m:%d %H:%M:%S", &tm_info);

    fprintf(outFd, "%s.%06ld [%s] %s:%d : ", buffer, (long)tval.tv_usec, level, file, line);
    vfprintf(outFd, msg, va);
    va_end(va);
    fprintf(outFd, "\r\n");
    fflush(outFd);
}

/**
 * @def LogError
 * @brief Log an error message
 */
#define LogError(...) {print_log(true , "ERROR", __FILE__, __LINE__, ##__VA_ARGS__); }

/**
 * @def LogWarning
 * @brief Log warning message
 */
#define LogWarning(...) { print_log(true , "WARNING", __FILE__, __LINE__, ##__VA_ARGS__); }

/**
 * @def LogInfo
 * @brief Log info message
 */
#define LogInfo(...) { print_log(false, "INFO", __FILE__, __LINE__, ##__VA_ARGS__); }

/**
 * @def LogDebug
 * @brief Log debug message
 */
#define LogDebug(...) { print_log(false, "DEBUG", __FILE__, __LINE__, ##__VA_ARGS__); }

/**
 * @def OssDebug
 * @brief Log oss debug message
 */
#define OssDebug(x) { std::stringstream oss; oss << x ; LogDebug("%s", oss.str().c_str()); }