// This file is part of necsim project which is released under MIT license.
// See file **LICENSE.txt** or visit https://opensource.org/licenses/MIT) for full license details.

/**
 * @author Samuel Thompson
 * @date 19/07/2017
 * @file Filesystem.cpp
 *
 * @copyright <a href="https://opensource.org/licenses/MIT"> MIT Licence.</a>
 * @brief Contains routines for checking files and folder exist, opening sqlite databases safely, with support for various
 * virtual filesystems, and checking parents of a file exist.
 *
 * Contact: samuel.thompson14@imperial.ac.uk or thompsonsed@gmail.com
 */

#include <string>
#include <sstream>
#include <boost/filesystem.hpp>

#ifdef WIN_INSTALL
#include <windows.h>
#define sleep Sleep
#endif

#include "file_system.h"
#include "custom_exceptions.h"
#include "Logging.h"
namespace necsim
{
    void openSQLiteDatabase(const string &database_name, sqlite3*&database)
    {
        int rc;
        if(database_name == ":memory:")
        {
            rc = sqlite3_open(":memory:", &database);
            if(rc != SQLITE_OK && rc != SQLITE_DONE)
            {
                stringstream ss;
                ss << "Could not connect to in-memory database. Error: " << rc << endl;
                ss << " (" << sqlite3_errmsg(database) << ")" << endl;
                throw FatalException(ss.str());
            }
            return;
        }
#ifdef WIN_INSTALL
        rc = sqlite3_open_v2(database_name.c_str(), &database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "win32");
#else
        rc = sqlite3_open_v2(database_name.c_str(), &database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
                             "unix-dotfile");
#endif
        if(rc != SQLITE_OK && rc != SQLITE_DONE)
        {
            int i = 0;
            while((rc != SQLITE_OK && rc != SQLITE_DONE) && i < 10)
            {
                i++;
                sleep(1);
                rc = sqlite3_open_v2(database_name.c_str(), &database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
                                     "unix-dotfile");
            }
            // Attempt different opening method if the first fails.
            int j = 0;
            while((rc != SQLITE_OK && rc != SQLITE_DONE) && j < 10)
            {
                j++;
                sleep(1);
                rc = sqlite3_open(database_name.c_str(), &database);
            }
            if(rc != SQLITE_OK && rc != SQLITE_DONE)
            {
                stringstream ss;
                ss << "ERROR_SQL_010: SQLite database file could not be opened. Check the folder exists and you "
                      "have write permissions. (REF1) Error code: " << rc << endl;
                ss << " Attempted call " << max(i, j) << " times" << endl;
                throw FatalException(ss.str());
            }
        }
    }

    void createParent(string file)
    {
        // Boost < 1.59 support
        boost::filesystem::path file_path(file);
        auto parent = file_path.parent_path().string();
        if(parent.length() > 0)
        {
            std::string::iterator it = parent.end() - 1;
            if(*it == '/')
            {
                parent.erase(it);
            }
            boost::filesystem::path parent_path(parent);
            if(!boost::filesystem::exists(parent_path))
            {
                if(!parent_path.empty())
                {
                    if(!boost::filesystem::create_directories(parent_path))
                    {
                        throw runtime_error("Cannot create parent folder for " + file);
                    }
                }
            }
        }
    }

    bool doesExist(string testfile)
    {
        if(boost::filesystem::exists(testfile))
        {
            stringstream os;
            os << "\rChecking folder existance..." << testfile << " exists.               " << endl;
            writeInfo(os.str());
            return true;
        }
        else
        {
            throw runtime_error(
                    string("ERROR_MAIN_008: FATAL. Input or output folder does not exist: " + testfile + "."));
        }
    }

    bool doesExistNull(string testfile)
    {
        return testfile == "null" || testfile == "none" || doesExist(testfile);
    }

    unsigned long cantorPairing(const unsigned long &x1, const unsigned long &x2)
    {
        return ((x1 + x2) * (x1 + x2 + 1) / 2) + x2;
    }

    unsigned long elegantPairing(const unsigned long &x1, const unsigned long &x2)
    {
        if(x1 > x2)
        {
            return static_cast<unsigned long>(pow(x1, 2) + x1 + x2);
        }
        return static_cast<unsigned long>(pow(x2, 2) + x1);
    }

    vector<string> getCsvLineAndSplitIntoTokens(istream &str)
    {
        vector<string> result;
        string line;
        getline(str, line);

        stringstream lineStream(line);
        string cell;

        while(getline(lineStream, cell, ','))
        {
            result.push_back(cell);
        }
        // This checks for a trailing comma with no data after it.
        if(!lineStream && cell.empty())
        {
            // If there was a trailing comma then add an empty element.
            result.emplace_back("");
        }
        return result;
    }
}