// subtitleeditor -- a tool to create or edit subtitle
//
// https://kitone.github.io/subtitleeditor/
// https://github.com/kitone/subtitleeditor/
//
// Copyright @ 2005-2018, kitone
//
// 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 <glibmm.h>
#include <iostream>
#include <vector>
#include "cfg.h"
#include "error.h"
#include "extensionmanager.h"
#include "utility.h"

// Return the ExtensionManager instance.
ExtensionManager &ExtensionManager::instance() {
  static ExtensionManager instance;
  return instance;
}

// Constructor
ExtensionManager::ExtensionManager() {
  se_dbg(SE_DBG_APP);

  // Read the user plugins
  load_path(get_config_dir("plugins"), false);

  // Read the env var if is set or the default plugin dir
  Glib::ustring path = Glib::getenv("SE_PLUGINS_PATH");
  if (path.empty())
    path = SE_DEV_VALUE(PACKAGE_PLUGIN_DESCRIPTION_DIR, PACKAGE_PLUGIN_DIR_DEV);

  load_path(path, true);
}

// Destructor
ExtensionManager::~ExtensionManager() {
  se_dbg(SE_DBG_APP);

  destroy_extensions();
}

// Active and create extensions
void ExtensionManager::create_extensions() {
  se_dbg(SE_DBG_APP);

  for (const auto &ext_info : get_extension_info_list()) {
    if (cfg::has_key("extension-manager", ext_info->get_name())) {
      auto state = cfg::get_string("extension-manager", ext_info->get_name());
      if (state == "enable") {
        activate(ext_info);
      }
    } else {
      // Unknown extension, enable by default
      se_dbg_msg(SE_DBG_APP,
                 "First time for the plugin '%s', enable by default",
                 ext_info->get_name().c_str());

      set_extension_active(ext_info->get_name(), true);
    }
  }
}

// Delete and close all extensions
void ExtensionManager::destroy_extensions() {
  se_dbg(SE_DBG_APP);

  for (const auto &ext_info : get_extension_info_list()) {
    se_dbg_msg(SE_DBG_APP, "delete extension '%s'",
               ext_info->get_name().c_str());
    delete ext_info;
  }
  m_extension_info_map.clear();
}

// Load the path and sub path to find extension description.
// se-plugin file.
void ExtensionManager::load_path(const Glib::ustring &path,
                                 bool fhs_directory) {
  se_dbg_msg(SE_DBG_APP, "path=%s", path.c_str());

  if (Glib::file_test(path, Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_DIR) ==
      false) {
    se_dbg_msg(SE_DBG_APP, "could not open the path %s", path.c_str());
    return;
  }

  try {
    Glib::RefPtr<Glib::Regex> re = Glib::Regex::create("^(.*)\\.se-plugin$");

    Glib::Dir dir(path);

    std::vector<Glib::ustring> files(dir.begin(), dir.end());

    for (const auto &f : files) {
      Glib::ustring filename = Glib::build_filename(path, f);

      if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR))
        load_path(filename, fhs_directory);
      else if (re->match(filename))
        load_extension_info(filename, fhs_directory);
    }
  } catch (const Glib::Error &ex) {
    se_dbg_msg(SE_DBG_APP, "error: %s", ex.what().c_str());
    std::cerr << ex.what() << std::endl;
  }
}

// Try to load an ExtensionInfo file.
bool ExtensionManager::load_extension_info(const Glib::ustring &file,
                                           bool fhs_directory) {
  se_dbg_msg(SE_DBG_APP, "try to read '%s'", file.c_str());

  try {
    Glib::KeyFile keyfile;
    if (keyfile.load_from_file(file) == false)
      throw SubtitleError(Glib::ustring::compose(
          "Could not open the ExtensionInfo '%1'", file));

    // Check if it is a good file
    if (keyfile.has_group("SubtitleEditor Extension") == false)
      throw SubtitleError(
          Glib::ustring::compose("Bad extension file '%1'", file));

    // Get values
    Glib::ustring name, label, description, categorie, type, module, authors;
    bool hidden = false;

    // Get Name
    name = keyfile.get_string("SubtitleEditor Extension", "Name");
    if (name.empty())
      throw SubtitleError(
          Glib::ustring::compose("Could not find 'Name' in %1", file));

    // Get Label
    label = keyfile.get_locale_string("SubtitleEditor Extension", "Name");
    if (label.empty())
      throw SubtitleError(
          Glib::ustring::compose("Could not find 'Name' in %1", file));

    // Get Description
    description =
        keyfile.get_locale_string("SubtitleEditor Extension", "Description");
    if (description.empty())
      throw SubtitleError(
          Glib::ustring::compose("Could not find 'Description' in %1", file));

    // Get Categorie
    categorie = keyfile.get_string("SubtitleEditor Extension", "Categorie");
    if (categorie.empty())
      throw SubtitleError(
          Glib::ustring::compose("Could not find 'Categorie' in %1", file));

    // Get Type
    type = keyfile.get_string("SubtitleEditor Extension", "Type");
    if (type.empty())
      throw SubtitleError(
          Glib::ustring::compose("Could not find 'Type' in %1", file));

    // Get Module
    module = keyfile.get_string("SubtitleEditor Extension", "Module");
    if (module.empty())
      throw SubtitleError(
          Glib::ustring::compose("Could not find 'Module' in %1", file));

    // Get Hidden
    if (keyfile.has_key("SubtitleEditor Extension", "Hidden"))
      hidden = keyfile.get_boolean("SubtitleEditor Extension", "Hidden");

    // Get Authors
    authors = keyfile.get_locale_string("SubtitleEditor Extension", "Authors");

    // If a plugin with this name has already been loaded
    // drop this one (user plugins override system plugins)
    if (get_extension_info(name) != NULL) {
      throw SubtitleError(Glib::ustring::compose(
          "Two or more plugins named '%1'. Only the first will be considered.",
          name));
    }

    // Create the extension and init
    ExtensionInfo *info = new ExtensionInfo;

    info->file = file;
    info->name = name;
    info->label = label;
    info->description = description;
    info->categorie = categorie;
    info->type = type;
    info->module_name = module;
    info->authors = authors;
    info->hidden = hidden;
    info->fhs_directory = fhs_directory;

    // Append to the list
    m_extension_info_map[categorie].push_back(info);

    // Display Debug information
    se_dbg_msg(SE_DBG_APP,
               Glib::ustring::compose("New ExtensionInfo: '%1' '%2' '%3' '%4'",
                                      name, categorie, type, module)
                   .c_str());

    return true;
  } catch (const Glib::Exception &ex) {
    se_dbg_msg(SE_DBG_APP, "error: %s", ex.what().c_str());
    std::cerr << "Error:" << ex.what() << std::endl;
  } catch (const std::exception &ex) {
    se_dbg_msg(SE_DBG_APP, "error: %s", ex.what());
    std::cerr << "Error:" << ex.what() << std::endl;
  }

  std::cerr << "Failed to read " << file << std::endl;

  return false;
}

// Return All ExtensionInfo.
std::list<ExtensionInfo *> ExtensionManager::get_extension_info_list() {
  se_dbg(SE_DBG_APP);
  std::list<ExtensionInfo *> list;

  for (const auto &ext_map : m_extension_info_map) {
    list.insert(list.end(), ext_map.second.begin(), ext_map.second.end());
  }
  return list;
}

// Return all ExtensionInfo in the categorie.
std::list<ExtensionInfo *> ExtensionManager::get_info_list_from_categorie(
    const Glib::ustring &categorie) {
  // FIXME
  std::list<ExtensionInfo *> list = m_extension_info_map[categorie];
  se_dbg_msg(SE_DBG_APP, "categorie='%s' size='%d'", categorie.c_str(),
             list.size());

  return list;
}

// Return an ExtensionInfo from this name or NULL if failed.
ExtensionInfo *ExtensionManager::get_extension_info(const Glib::ustring &name) {
  se_dbg_msg(SE_DBG_APP, "name='%s'", name.c_str());

  for (const auto &ext_map : m_extension_info_map) {
    for (const auto &ext_info : ext_map.second) {
      if (ext_info->name == name)
        return ext_info;
    }
  }
  return nullptr;
}

// Enable or disable extension.
bool ExtensionManager::set_extension_active(const Glib::ustring &name,
                                            bool state) {
  se_dbg_msg(SE_DBG_APP, "name='%s' active='%d'", name.c_str(), state);

  ExtensionInfo *info = get_extension_info(name);

  if (info == nullptr)
    return false;

  bool res = (state) ? activate(info) : deactivate(info);
  if (!res) {
    se_dbg_msg(SE_DBG_APP, "Failed to change the extansion state");
    return false;
  }

  cfg::set_string("extension-manager", name, (state) ? "enable" : "disable");

  se_dbg_msg(SE_DBG_APP, "extension state is changed with success");
  return true;
}

// Try to activate the extension.
// Load and create Extension.
bool ExtensionManager::activate(ExtensionInfo *info) {
  se_dbg_msg(SE_DBG_APP, "extension '%s'", info->get_name().c_str());

  // FIXME: add available value to info.
  try {
    // FIXME:
    // if(info->type == "module")
    open_module(info);

    info->active = true;

    return true;
  } catch (const SubtitleError &ex) {
    se_dbg_msg(SE_DBG_APP, "activate the extension failed: %s", ex.what());
    std::cerr << ex.what() << std::endl;
  } catch (const Glib::Error &ex) {
    se_dbg_msg(SE_DBG_APP, "activate the extension failed: %s",
               ex.what().c_str());
  } catch (...) {
    se_dbg_msg(SE_DBG_APP, "activate the extension failed");
  }

  return false;
}

// Deactivate the extension.
// Delete the extension and the module.
bool ExtensionManager::deactivate(ExtensionInfo *info) {
  se_dbg_msg(SE_DBG_APP, "extension '%s'", info->get_name().c_str());

  if (info->module == NULL || info->extension == NULL) {
    se_dbg_msg(SE_DBG_APP, "The Module or the Extension are NULL");
    return false;
  }

  try {
    se_dbg_msg(SE_DBG_APP, "delete extension...");

    if (info->extension)
      delete info->extension;
    info->extension = nullptr;

    se_dbg_msg(SE_DBG_APP, "delete module...");

    if (info->module)
      delete info->module;
    info->module = NULL;
  } catch (...) {
    se_dbg_msg(SE_DBG_APP, "Error unknown exception!");
  }

  info->active = false;

  se_dbg_msg(SE_DBG_APP, "extension deactivate with success");
  return true;
}

// Open a module and create the extension.
// If failed return a SubtitleError.
void ExtensionManager::open_module(ExtensionInfo *info) {
  se_dbg(SE_DBG_APP);

  if (info->type != "module")
    throw SubtitleError("The type of the extension is not a 'module'");

  typedef Extension *(*ExtensionRegisterFunc)(void);

  Glib::ustring dirname = Glib::path_get_dirname(info->file);

  // It's only used for reading plugin without installing SE
  if (Glib::getenv("SE_DEV") == "1") {
    // ext/.libs/libext.so
    dirname = Glib::build_filename(dirname, ".libs");
  } else if (info->fhs_directory) {
    // If the extension is installed in the system,
    // Filesystem Hierarchy Standard is used for the directory
    // The description and the module are not in the same directory
    utility::replace(dirname, PACKAGE_PLUGIN_DESCRIPTION_DIR,
                     PACKAGE_PLUGIN_LIB_DIR);
  }

  // Build module name (path/libname.so)
  Glib::ustring file = Glib::Module::build_path(dirname, info->module_name);

  se_dbg_msg(SE_DBG_APP, "try to open module '%s'", file.c_str());

  // Create the module
  Glib::Module *module = new Glib::Module(file);
  if (!*module) {
    throw SubtitleError(
        Glib::ustring::compose("Failed to create the Glib::Module: %1",
                               Glib::Module::get_last_error()));
  }

  // Get the register function
  void *func = nullptr;
  if (module->get_symbol("extension_register", func) == false) {
    throw SubtitleError(Glib::ustring::compose(
        "Failed to get the extension_register function: %1",
        Glib::Module::get_last_error()));
  }

  // Fix: bug #12651 : 0.30.0 build error
  // ExtensionRegisterFunc extension_register = (ExtensionRegisterFunc)func;
  ExtensionRegisterFunc extension_register =
      reinterpret_cast<ExtensionRegisterFunc>(func);

  if (extension_register == NULL)
    throw SubtitleError(
        "reinterpret from the function to the ExtensionRegisterFunc failed");

  // create the extension
  Extension *ext = extension_register();

  if (ext == NULL)
    throw SubtitleError(
        "Could not create Extension, extension_register return NULL");

  info->module = module;
  info->extension = ext;

  se_dbg_msg(
      SE_DBG_APP,
      "Opening and the creating the extension from the module is a success");
}
