//  SuperTux
//  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "util/file_system.hpp"
#include "supertux/globals.hpp"

#include <physfs.h>
#include <filesystem>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <sys/stat.h>
#include <sys/types.h>
#ifndef WIN32
#include <unistd.h>
#include <spawn.h>
#endif
#include <vector>
#if defined(WIN32)
  #include <windows.h>
  #include <shellapi.h>
#else
  #include <cstdlib>
#endif
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#else
#include <curl/curl.h>
#endif

#include <SDL2/SDL_version.h>
#if SDL_VERSION_ATLEAST(2,0,14)
#include <SDL2/SDL_misc.h>
#endif

#include "gui/dialog.hpp"
#include "util/log.hpp"
#include "util/string_util.hpp"
#include "supertux/gameconfig.hpp"

#ifndef WIN32
extern char **environ;
#endif

namespace fs = std::filesystem;

namespace FileSystem {

bool exists(const std::string& path)
{
  fs::path location(path);
  std::error_code ec;

  // If we get an error (such as "Permission denied"), then ignore it
  // and pretend that the path doesn't exist.
  return fs::exists(location, ec);
}

bool is_directory(const std::string& path)
{
  fs::path location(path);
  return fs::is_directory(location);
}

void mkdir(const std::string& directory)
{
  fs::path location(directory);
  if (!fs::create_directory(location))
  {
    throw std::runtime_error("failed to create directory: "  + directory);
  }
}

void copy(const std::string& source_path, const std::string& target_path)
{
  fs::copy_file(source_path, target_path, fs::copy_options::overwrite_existing);
}

std::string strip_leading_dirs(std::string filename)
{
  while (filename.size() > 0 && (filename[filename.size()-1] == '/' || filename[filename.size()-1] == '\\'))
  {
    filename.pop_back();
  }
  return filename;
}

std::string dirname(const std::string& filename)
{
  std::string::size_type p = filename.find_last_of('/');
  if (p == std::string::npos)
    p = filename.find_last_of('\\');
  if (p == std::string::npos)
    return "./";

  return filename.substr(0, p+1);
}

std::string basename(std::string filename, bool greedy)
{
  if (greedy)
    filename = strip_leading_dirs(filename);

  std::string::size_type p = filename.find_last_of('/');
  if (p == std::string::npos)
    p = filename.find_last_of('\\');
  if (p == std::string::npos)
    return filename;

  return filename.substr(p+1, filename.size()-p-1);
}

std::string relpath(const std::string& filename, const std::string& basedir)
{
  return fs::relative(filename, basedir).string();
}

std::string extension(const std::string& filename)
{
  std::string::size_type p = filename.find_last_of('.');
  if (p == std::string::npos)
    return "";

  return filename.substr(p);
}

std::string strip_extension(const std::string& filename)
{
  std::string::size_type p = filename.find_last_of('.');
  if (p == std::string::npos)
    return filename;

  return filename.substr(0, p);
}

std::string normalize(const std::string& filename)
{
  std::vector<std::string> path_stack;

  const char* p = filename.c_str();

  while (true) {
    while (*p == '/' || *p == '\\') {
      p++;
    }

    const char* pstart = p;
    while (*p != '/' && *p != '\\' && *p != 0) {
      ++p;
    }

    size_t len = p - pstart;
    if (len == 0)
      break;

    std::string pathelem(pstart, p-pstart);
    if (pathelem == ".")
      continue;

    if (pathelem == "..") {
      if (path_stack.empty()) {

        log_warning << "Invalid '..' in path '" << filename << "'" << std::endl;
        // Push it into the result path so that the user sees this error...
        path_stack.push_back(pathelem);
      } else {
        path_stack.pop_back();
      }
    } else {
      path_stack.push_back(pathelem);
    }
  }

  // Construct path.
  std::ostringstream result;
  for (std::vector<std::string>::iterator i = path_stack.begin();
       i != path_stack.end(); ++i) {
    result << '/' << *i;
  }
  if (path_stack.empty())
    result << '/';

  return result.str();
}

std::string join(const std::string& lhs, const std::string& rhs)
{
  if (lhs.empty())
  {
    return rhs;
  }
  else if (rhs.empty())
  {
    return lhs + "/";
  }
  else if (lhs.back() == '/' && rhs.front() != '/')
  {
    return lhs + rhs;
  }
  else if (lhs.back() != '/' && rhs.front() == '/')
  {
    return lhs + rhs;
  }
  else if (lhs.back() == '/' && rhs.front() == '/')
  {
    return lhs + rhs.substr(1);
  }
  else
  {
    return lhs + "/" + rhs;
  }
}

bool remove(const std::string& path)
{
  fs::path location(path);
  return fs::remove(location);
}

bool
rename(const std::string& old_filename, const std::string& new_filename)
{
  std::unique_ptr<PHYSFS_File, decltype(&PHYSFS_close)> oldfile
    { PHYSFS_openRead(old_filename.c_str()), PHYSFS_close };

  if (!oldfile)
  {
    return false;
  }

  size_t oldfile_len = PHYSFS_fileLength(oldfile.get());
  char* oldfile_data = new char[oldfile_len];

  if (PHYSFS_readBytes(oldfile.get(), oldfile_data, oldfile_len) != oldfile_len)
  {
    // give up to be safe
    delete[] oldfile_data;
    return false;
  }

  std::unique_ptr<PHYSFS_File, decltype(&PHYSFS_close)> newfile =
    { PHYSFS_openWrite(new_filename.c_str()), PHYSFS_close };
  if (PHYSFS_writeBytes(newfile.get(), oldfile_data, oldfile_len) != oldfile_len)
  {
    delete[] oldfile_data;
    return false;
  }

  oldfile.reset();
  return PHYSFS_delete(old_filename.c_str()) != 0;
}

void open_path(const std::string& path)
{
#ifdef __ANDROID__
  // SDL >= 2.0.14 is strictly required for Android
  SDL_OpenURL(("file://" + path).c_str());
#elif defined(_WIN32) || defined (_WIN64)
  ShellExecute(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL);
#elif defined(__EMSCRIPTEN__)
  emscripten_run_script(("window.supertux_download('" + path + "');").c_str());
#else
  // TODO Use posix_spawn
  #if defined(__APPLE__)
  std::string cmd = "open \"" + path + "\"";
  #else
  std::string cmd = "xdg-open \"" + path + "\"";
  #endif

  int ret = system(cmd.c_str());
  if (ret < 0)
  {
    log_fatal << "failed to spawn: " << cmd << std::endl;
  }
  else if (ret > 0)
  {
    log_fatal << "error " << ret << " while executing: " << cmd << std::endl;
  }
#endif
}

void
open_editor(const std::string& filename)
{
#if !defined(ANDROID) && !defined(EMSCRIPTEN)
  std::string editor =
#ifdef WIN32
    "notepad.exe"; // *shrugs*
#elif defined(__APPLE__)
    "open"; // use TextEdit
#else
    getenv("EDITOR") != NULL ? getenv("EDITOR") : "";
#endif
  if (!g_config->preferred_text_editor.empty())
    editor = g_config->preferred_text_editor;

  if(editor.empty()) {
    log_warning << "No text editor configured, cannot open file: " << filename << std::endl;
    return;
  }

#ifndef WIN32
  const char *argv[] = { editor.c_str(), filename.c_str(), NULL };
  pid_t proc;
  if (posix_spawnp(&proc, editor.c_str(), NULL, NULL, const_cast<char** const>(argv), environ) != 0)
  {
    log_fatal << "Failed to spawn editor: " << editor << std::endl;
  }
#else
  ShellExecute(NULL, NULL, editor.c_str(), filename.c_str(), NULL, SW_SHOWNORMAL);
#endif
#endif
}

std::string escape_url(const std::string& url)
{
#ifdef HAVE_CURL
  std::string result = url;
  char *output = curl_easy_escape(nullptr, url.c_str(), static_cast<int>(url.length()));
  if(output) {
    result = std::string(output);
    curl_free(output);
  }

  return result;
#elif defined(__EMSCRIPTEN__)
  return emscripten_run_script_string(("encodeURIComponent(" + url + ")").c_str());
#else
  // just to be safe, lets... return nothing ;-/
  return "";
#endif
}

void open_url(const std::string& url)
{
#if SDL_VERSION_ATLEAST(2,0,14)
  SDL_OpenURL(url.c_str());
#elif defined(__EMSCRIPTEN__)
  emscripten_run_script(("window.open('" + url + "');").c_str());
#else
  open_path(url);
#endif
}

} // namespace FileSystem
