// 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 <debug.h>
#include <error.h>
#include <extension/subtitleformat.h>
#include <filereader.h>
#include <i18n.h>
#include <libxml++/libxml++.h>
#include <player.h>
#include <subtitleeditorwindow.h>
#include <utility.h>
#include <waveformmanager.h>

// TODO:
// <subtitleview>
//  <selection>
//  <position>
// </subtitleview>
// <metadata>
//  video, title, info, comment ...
// </metadata>
class SubtitleEditorProject : public SubtitleFormatIO {
 public:
  void open(Reader &file) {
    try {
      initalize_dirname(file);

      xmlpp::DomParser parser;
      // parser.set_validate();
      parser.set_substitute_entities();
      parser.parse_memory(file.get_data());

      if (!parser)
        throw IOFileError(_("Failed to open the file for reading."));

      const xmlpp::Node *root = parser.get_document()->get_root_node();

      open_player(root);
      open_waveform(root);
      open_keyframes(root);
      open_styles(root);
      open_subtitles(root);
      open_subtitles_selection(root);
    } catch (const std::exception &ex) {
      throw IOFileError(_("Failed to open the file for reading."));
    }
  }

  void save(Writer &file) {
    try {
      xmlpp::Document xmldoc;

      xmlpp::Element *root = xmldoc.create_root_node("SubtitleEditorProject");
      root->set_attribute("version", "1.0");

      save_player(root);
      save_waveform(root);
      save_keyframes(root);
      save_styles(root);
      save_subtitles(root);
      save_subtitles_selection(root);

      file.write(xmldoc.write_to_string_formatted());
    } catch (const std::exception &ex) {
      throw IOFileError(_("Failed to write to the file."));
    }
  }

 private:
  void initalize_dirname(Reader &reader) {
    FileReader *fr = dynamic_cast<FileReader *>(&reader);
    if (fr != NULL) {
      Glib::ustring filename = Glib::filename_from_uri(fr->get_uri());
      m_project_dirname = Glib::path_get_dirname(filename);
    }
  }

  bool test_uri(const Glib::ustring &uri) {
    return test_filename(Glib::filename_from_uri(uri));
  }

  bool test_filename(const Glib::ustring &filename) {
    return Glib::file_test(filename, Glib::FILE_TEST_EXISTS);
  }

  Glib::ustring uri_to_project_relative_filename(const Glib::ustring &uri) {
    Glib::ustring basename =
        Glib::path_get_basename(Glib::filename_from_uri(uri));
    Glib::ustring relative = Glib::build_filename(m_project_dirname, basename);
    return Glib::filename_to_uri(relative);
  }

  const xmlpp::Element *get_unique_children(const xmlpp::Node *root,
                                            const Glib::ustring &name) {
    const xmlpp::Node::NodeList children = root->get_children(name);
    if (children.empty())
      return NULL;
    return dynamic_cast<const xmlpp::Element *>(children.front());
  }

  void open_player(const xmlpp::Node *root) {
    const xmlpp::Element *xml_pl = get_unique_children(root, "player");
    if (xml_pl == NULL)
      return;

    Glib::ustring uri = xml_pl->get_attribute_value("uri");

    Player *pl = SubtitleEditorWindow::get_instance()->get_player();

    if (pl->get_uri() == uri)
      return;

    if (!test_uri(uri) && test_uri(uri_to_project_relative_filename(uri)))
      uri = uri_to_project_relative_filename(uri);

    pl->open(uri);
  }

  void save_player(xmlpp::Element *root) {
    Player *pl = SubtitleEditorWindow::get_instance()->get_player();
    if (pl == NULL)
      return;

    Glib::ustring uri = pl->get_uri();
    if (uri.empty())
      return;

    xmlpp::Element *xmlpl = root->add_child("player");
    xmlpl->set_attribute("uri", uri);
  }

  void open_waveform(const xmlpp::Node *root) {
    const xmlpp::Element *xml_wf = get_unique_children(root, "waveform");
    if (xml_wf == NULL)
      return;

    Glib::ustring uri = xml_wf->get_attribute_value("uri");
    if (uri.empty())
      return;

    if (!test_uri(uri) && test_uri(uri_to_project_relative_filename(uri)))
      uri = uri_to_project_relative_filename(uri);

    SubtitleEditorWindow::get_instance()->get_waveform_manager()->open_waveform(
        uri);
  }

  void save_waveform(xmlpp::Element *root) {
    WaveformManager *wm =
        SubtitleEditorWindow::get_instance()->get_waveform_manager();
    if (wm->has_waveform() == false)
      return;  // don't need to save without Waveform...

    Glib::RefPtr<Waveform> wf = wm->get_waveform();
    if (!wf)
      return;

    xmlpp::Element *xmlwf = root->add_child("waveform");

    xmlwf->set_attribute("uri", wf->get_uri());
  }

  void open_keyframes(const xmlpp::Node *root) {
    const xmlpp::Element *xml_kf = get_unique_children(root, "keyframes");
    if (xml_kf == NULL)
      return;

    Glib::ustring uri = xml_kf->get_attribute_value("uri");
    if (uri.empty())
      return;

    if (!test_uri(uri) && test_uri(uri_to_project_relative_filename(uri)))
      uri = uri_to_project_relative_filename(uri);

    Glib::RefPtr<KeyFrames> kf = KeyFrames::create_from_file(uri);
    if (kf)
      SubtitleEditorWindow::get_instance()->get_player()->set_keyframes(kf);
  }

  void save_keyframes(xmlpp::Element *root) {
    Glib::RefPtr<KeyFrames> kf =
        SubtitleEditorWindow::get_instance()->get_player()->get_keyframes();
    if (!kf)
      return;  // don't need to save without KeyFrames...

    xmlpp::Element *xmlwf = root->add_child("keyframes");

    xmlwf->set_attribute("uri", kf->get_uri());
  }

  void open_styles(const xmlpp::Node *root) {
    const xmlpp::Element *xmlstyles = get_unique_children(root, "styles");
    if (xmlstyles == NULL)
      return;

    Styles styles = document()->styles();

    const xmlpp::Node::NodeList list_styles = xmlstyles->get_children("style");

    for (const auto &node : list_styles) {
      auto el = dynamic_cast<const xmlpp::Element *>(node);

      Style style = styles.append();

      auto attr_list = el->get_attributes();

      for (const auto &att : attr_list) {
        style.set(att->get_name(), att->get_value());
      }
    }
  }

  void save_styles(xmlpp::Element *root) {
    xmlpp::Element *xmlstyles = root->add_child("styles");

    Styles styles = document()->styles();

    for (Style style = styles.first(); style; ++style) {
      xmlpp::Element *xml = xmlstyles->add_child("style");

      std::map<Glib::ustring, Glib::ustring> values;
      style.get(values);

      for (const auto &i : values) {
        xml->set_attribute(i.first, i.second);
      }
    }
  }

  void open_subtitles(const xmlpp::Node *root) {
    const xmlpp::Element *xmlsubtitles = get_unique_children(root, "subtitles");
    if (xmlsubtitles == NULL)
      return;

    Glib::ustring timing_mode =
        xmlsubtitles->get_attribute_value("timing_mode");
    if (!timing_mode.empty()) {
      if (timing_mode == "TIME")
        document()->set_timing_mode(TIME);
      else if (timing_mode == "FRAME")
        document()->set_timing_mode(FRAME);
    }

    Glib::ustring edit_timing_mode =
        xmlsubtitles->get_attribute_value("edit_timing_mode");
    if (!edit_timing_mode.empty()) {
      if (edit_timing_mode == "TIME")
        document()->set_edit_timing_mode(TIME);
      else if (edit_timing_mode == "FRAME")
        document()->set_edit_timing_mode(FRAME);
    }

    Glib::ustring framerate = xmlsubtitles->get_attribute_value("framerate");
    if (!framerate.empty()) {
      float value = static_cast<float>(utility::string_to_double(framerate));
      if (value > 0)
        document()->set_framerate(get_framerate_from_value(value));
    }

    auto list_subtitles = xmlsubtitles->get_children("subtitle");

    Subtitles subtitles = document()->subtitles();

    for (const auto &node : list_subtitles) {
      auto el = dynamic_cast<const xmlpp::Element *>(node);

      Subtitle sub = subtitles.append();

      auto attr_list = el->get_attributes();

      for (const auto &att : attr_list) {
        sub.set(att->get_name(), att->get_value());
      }
    }
  }

  void save_subtitles(xmlpp::Element *root) {
    xmlpp::Element *xmlsubtitles = root->add_child("subtitles");

    // document property
    xmlsubtitles->set_attribute(
        "timing_mode",
        (document()->get_timing_mode() == TIME) ? "TIME" : "FRAME");
    xmlsubtitles->set_attribute(
        "edit_timing_mode",
        (document()->get_edit_timing_mode() == TIME) ? "TIME" : "FRAME");
    xmlsubtitles->set_attribute(
        "framerate",
        to_string(get_framerate_value(document()->get_framerate())));

    // subtitles
    Subtitles subtitles = document()->subtitles();

    for (Subtitle sub = subtitles.get_first(); sub; ++sub) {
      xmlpp::Element *xmlsub = xmlsubtitles->add_child("subtitle");

      std::map<Glib::ustring, Glib::ustring> values;
      sub.get(values);

      for (const auto &i : values) {
        xmlsub->set_attribute(i.first, i.second);
      }
    }
  }

  void open_subtitles_selection(const xmlpp::Node *root) {
    const xmlpp::Element *xmlsubtitles =
        get_unique_children(root, "subtitles-selection");
    if (xmlsubtitles == NULL)
      return;

    auto list_subtitles = xmlsubtitles->get_children("subtitle");

    std::vector<Subtitle> selection(list_subtitles.size());

    Subtitles subtitles = document()->subtitles();

    unsigned int i = 0;
    for (auto it = list_subtitles.begin(); it != list_subtitles.end();
         ++it, ++i) {
      const xmlpp::Element *el = dynamic_cast<const xmlpp::Element *>(*it);
      long path = utility::string_to_long(el->get_attribute_value("path"));

      selection[i] = subtitles.get(path + 1);  // /!\ warning: PATH is not NUM
    }
    subtitles.select(selection);
  }

  void save_subtitles_selection(xmlpp::Element *root) {
    xmlpp::Element *xml = root->add_child("subtitles-selection");

    std::vector<Subtitle> selection = document()->subtitles().get_selection();

    for (const auto &subtitle : selection) {
      xmlpp::Element *xmlsub = xml->add_child("subtitle");
      xmlsub->set_attribute("path", subtitle.get("path"));
    }
  }

 protected:
  Glib::ustring m_project_dirname;
};

class SubtitleEditorProjectPlugin : public SubtitleFormat {
 public:
  SubtitleFormatInfo get_info() {
    SubtitleFormatInfo info;
    info.name = "Subtitle Editor Project";
    info.extension = "sep";
    info.pattern = "^<SubtitleEditorProject\\s.*>$";

    return info;
  }

  SubtitleFormatIO *create() {
    SubtitleEditorProject *sf = new SubtitleEditorProject();
    return sf;
  }
};

REGISTER_EXTENSION(SubtitleEditorProjectPlugin)
