//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//*****************************************************************************

#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#endif
#include <fcntl.h>
#include <fstream>
#include <iomanip>
#include <sstream>

#include "util.hpp"

using namespace std;

string trim(const string& s)
{
    string rc = s;
    // trim trailing spaces
    size_t pos = rc.find_last_not_of(" \t");
    if (string::npos != pos)
    {
        rc = rc.substr(0, pos + 1);
    }

    // trim leading spaces
    pos = rc.find_first_not_of(" \t");
    if (string::npos != pos)
    {
        rc = rc.substr(pos);
    }
    return rc;
}

vector<string> split(const string& src, char delimiter, bool do_trim)
{
    size_t pos;
    string token;
    size_t start = 0;
    vector<string> rc;
    while ((pos = src.find(delimiter, start)) != std::string::npos)
    {
        token = src.substr(start, pos - start);
        start = pos + 1;
        if (do_trim)
        {
            token = trim(token);
        }
        rc.push_back(token);
    }
    if (start <= src.size())
    {
        token = src.substr(start);
        if (do_trim)
        {
            token = trim(token);
        }
        rc.push_back(token);
    }
    return rc;
}

bool is_version_number(const string& path)
{
    bool rc = true;
    vector<string> tokens = split(path, '.');
    for (string s : tokens)
    {
        for (char c : s)
        {
            if (!isdigit(c))
            {
                rc = false;
            }
        }
    }
    return rc;
}

string path_join(const string& s1, const string& s2)
{
    string rc;
    if (s2.size() > 0)
    {
        if (s2[0] == '/')
        {
            rc = s2;
        }
        else if (s1.size() > 0)
        {
            rc = s1;
            if (rc[rc.size() - 1] != '/')
            {
                rc += "/";
            }
            rc += s2;
        }
        else
        {
            rc = s2;
        }
    }
    else
    {
        rc = s1;
    }
    return rc;
}

std::string read_file_to_string(const std::string& path)
{
    std::ifstream f(path);
    std::stringstream ss;
    ss << f.rdbuf();
    return ss.str();
}

#ifndef _WIN32
void iterate_files_worker(const string& path,
                          std::function<void(const string& file, bool is_dir)> func,
                          bool recurse)
{
    DIR* dir;
    struct dirent* ent;

    // If we cannot open the directory, we silently ignore it.
    if ((dir = opendir(path.c_str())) != nullptr)
    {
        while ((ent = readdir(dir)) != nullptr)
        {
            string name = ent->d_name;
            switch (ent->d_type)
            {
            case DT_DIR:
                if (name != "." && name != "..")
                {
                    string dir_path = path_join(path, name);
                    if (recurse)
                    {
                        iterate_files(dir_path, func, recurse);
                    }
                    func(dir_path, true);
                }
                break;
            case DT_LNK: break;
            case DT_REG:
            {
                string file_name = path_join(path, name);
                func(file_name, false);
                break;
            }
            default: break;
            }
        }
        closedir(dir);
    }
}
#endif

void iterate_files(const string& path,
                   std::function<void(const string& file, bool is_dir)> func,
                   bool recurse)
{
    vector<string> files;
    vector<string> dirs;
#ifdef _WIN32
    string file_match = path_join(path, "*");
    WIN32_FIND_DATA data;
    HANDLE hFind = FindFirstFile(file_match.c_str(), &data);
    if (hFind != INVALID_HANDLE_VALUE)
    {
        do
        {
            bool is_dir = data.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY;
            if (is_dir)
            {
                if (string(data.cFileName) != "." && string(data.cFileName) != "..")
                {
                    string dir_path = path_join(path, data.cFileName);
                    if (recurse)
                    {
                        iterate_files(dir_path, func, recurse);
                    }
                    func(dir_path, true);
                }
            }
            else
            {
                string file_name = path_join(path, data.cFileName);
                func(file_name, false);
            }
        } while (FindNextFile(hFind, &data));
        FindClose(hFind);
    }
#else
    iterate_files_worker(path,
                         [&files, &dirs](const string& file, bool is_dir) {
                             if (is_dir)
                                 dirs.push_back(file);
                             else
                                 files.push_back(file);
                         },
                         recurse);
#endif
    for (auto f : files)
    {
        func(f, false);
    }
    for (auto f : dirs)
    {
        func(f, true);
    }
}

std::string get_file_name(const std::string& s)
{
    string rc = s;
    auto pos = s.find_last_of('/');
    if (pos != string::npos)
    {
        rc = s.substr(pos + 1);
    }
    return rc;
}

std::string get_file_ext(const std::string& s)
{
    string rc = get_file_name(s);
    auto pos = rc.find_last_of('.');
    if (pos != string::npos)
    {
        rc = rc.substr(pos);
    }
    else
    {
        rc = "";
    }
    return rc;
}

string to_hex(int value)
{
    stringstream ss;
    ss << "0x" << std::hex << std::setw(2) << std::setfill('0') << value;
    return ss.str();
}

void dump(ostream& out, const void* vdata, size_t size)
{
    const uint8_t* data = reinterpret_cast<const uint8_t*>(vdata);
    size_t index = 0;
    while (index < size)
    {
        out << "        ";
        for (size_t i = 0; i < 16 && index < size; i++)
        {
            if (i != 0)
            {
                out << ", ";
            }
            out << to_hex(data[index++]);
        }
        out << ",\n";
    }
}

time_t get_timestamp(const std::string& filename)
{
    time_t rc = 0;
    struct stat st;
    if (stat(filename.c_str(), &st) == 0)
    {
        rc = st.st_mtime;
    }
    return rc;
}