// 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 <extension/action.h>
#include <gtkmm_utility.h>
#include <gui/comboboxtextcolumns.h>
#include <gui/dialogutility.h>
#include <utility.h>
#include <memory>

// TODO Add FPS finder
// (new_sub_time x fps_video) / old_sub_time = fps_src

class DialogChangeFramerate : public DialogActionMultiDoc {
  class ComboBoxEntryText : public Gtk::ComboBoxText {
   public:
    ComboBoxEntryText(BaseObjectType *cobject,
                      const Glib::RefPtr<Gtk::Builder> &)
        : Gtk::ComboBoxText(cobject) {
      get_entry()->signal_focus_out_event().connect(
          sigc::mem_fun(*this, &ComboBoxEntryText::on_focus_out));
    }

    // Add new value to the combobox, only if it doesn't already there
    void append_text(const Glib::ustring &text) {
      Gtk::TreeNodeChildren rows = get_model()->children();

      ComboBoxTextColumns m_columns;

      for (auto it = rows.begin(); it; ++it) {
        if ((*it)[m_columns.m_col_name] == text) {
          set_active(it);
          return;
        }
      }
      Gtk::ComboBoxText::append(text);
    }

    // Add the value if it's a number when the focus out
    bool on_focus_out(GdkEventFocus *) {
      Glib::ustring text = get_entry()->get_text();

      double value = 0;
      if (from_string(text, value)) {
        if (value > 0)
          append_text(to_string(value));
        else
          set_active(0);
      } else {
        set_active(0);
      }
      return true;
    }
  };

 public:
  DialogChangeFramerate(BaseObjectType *cobject,
                        const Glib::RefPtr<Gtk::Builder> &xml)
      : DialogActionMultiDoc(cobject, xml) {
    utility::set_transient_parent(*this);

    xml->get_widget_derived("combo-src", m_comboSrc);
    xml->get_widget_derived("combo-dest", m_comboDest);

    m_comboSrc->get_entry()->signal_activate().connect(
        sigc::bind<ComboBoxEntryText *>(
            sigc::mem_fun(*this, &DialogChangeFramerate::combo_activate),
            m_comboSrc));
    m_comboDest->get_entry()->signal_activate().connect(
        sigc::bind<ComboBoxEntryText *>(
            sigc::mem_fun(*this, &DialogChangeFramerate::combo_activate),
            m_comboDest));

    m_comboSrc->append_text(to_string(23.976));
    m_comboSrc->append_text(to_string(24.000));
    m_comboSrc->append_text(to_string(25.000));
    m_comboSrc->append_text(to_string(29.970));

    m_comboDest->append_text(to_string(23.976));
    m_comboDest->append_text(to_string(24.000));
    m_comboDest->append_text(to_string(25.000));
    m_comboDest->append_text(to_string(29.970));

    m_comboSrc->set_active(0);
    m_comboDest->set_active(1);

    set_default_response(Gtk::RESPONSE_OK);
  }

  void execute() {
    show();

    if (run() == Gtk::RESPONSE_OK) {
      DocumentList docs;

      if (apply_to_all_documents()) {
        docs = SubtitleEditorWindow::get_instance()->get_documents();
      } else {
        Document *doc =
            SubtitleEditorWindow::get_instance()->get_current_document();
        docs.push_back(doc);
      }

      double src = get_value(m_comboSrc);
      double dest = get_value(m_comboDest);

      if (src != 0 && dest != 0) {
        for (const auto &d : docs) {
          signal_change_framerate(d, src, dest);
        }
      }
    }

    hide();
  }

  sigc::signal<void, Document *, double, double> signal_change_framerate;

 protected:
  double get_value(ComboBoxEntryText *combo) {
    Glib::ustring text = combo->get_entry()->get_text();

    double value = 0;

    if (from_string(text, value))
      return value;
    return 0;
  }

  void combo_activate(ComboBoxEntryText *combo) {
    Glib::ustring text = combo->get_entry()->get_text();

    double value = 0;
    if (from_string(text, value)) {
      if (value > 0) {
        combo->append_text(to_string(value));
        combo->set_active_text(to_string(value));
        return;
      }
    }

    combo->set_active(0);
  }

 protected:
  ComboBoxEntryText *m_comboSrc;
  ComboBoxEntryText *m_comboDest;
};

class ChangeFrameratePlugin : public Action {
 public:
  ChangeFrameratePlugin() {
    activate();
    update_ui();
  }

  ~ChangeFrameratePlugin() {
    deactivate();
  }

  void activate() {
    se_dbg(SE_DBG_PLUGINS);

    // actions
    action_group = Gtk::ActionGroup::create("ChangeFrameratePlugin");

    action_group->add(
        Gtk::Action::create("change-framerate", Gtk::Stock::CONVERT,
                            _("Change _Framerate"), _("Convert framerate")),
        sigc::mem_fun(*this, &ChangeFrameratePlugin::on_execute));

    // ui
    Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

    ui_id = ui->new_merge_id();

    ui->insert_action_group(action_group);

    ui->add_ui(ui_id, "/menubar/menu-timings/change-framerate",
               "change-framerate", "change-framerate");
  }

  void deactivate() {
    se_dbg(SE_DBG_PLUGINS);

    Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

    ui->remove_ui(ui_id);
    ui->remove_action_group(action_group);
  }

  void update_ui() {
    se_dbg(SE_DBG_PLUGINS);

    bool visible = (get_current_document() != NULL);

    action_group->get_action("change-framerate")->set_sensitive(visible);
  }

 protected:
  void on_execute() {
    se_dbg(SE_DBG_PLUGINS);

    Document *doc = get_current_document();
    g_return_if_fail(doc);

    // create dialog
    std::unique_ptr<DialogChangeFramerate> dialog(
        gtkmm_utility::get_widget_derived<DialogChangeFramerate>(
            SE_DEV_VALUE(SE_PLUGIN_PATH_UI, SE_PLUGIN_PATH_DEV),
            "dialog-change-framerate.ui", "dialog-change-framerate"));

    dialog->signal_change_framerate.connect(
        sigc::mem_fun(*this, &ChangeFrameratePlugin::change_framerate));

    dialog->execute();
  }

  void change_framerate(Document *doc, double src_fps, double dest_fps) {
    se_dbg(SE_DBG_PLUGINS);

    g_return_if_fail(doc);

    doc->start_command(_("Change Framerate"));

    Subtitles subtitles = doc->subtitles();

    Subtitle subtitle = subtitles.get_first();

    while (subtitle) {
      SubtitleTime start = change_fps(subtitle.get_start(), src_fps, dest_fps);
      SubtitleTime end = change_fps(subtitle.get_end(), src_fps, dest_fps);

      subtitle.set_start_and_end(start, end);

      ++subtitle;
    }

    doc->emit_signal("subtitle-time-changed");
    doc->finish_command();

    doc->flash_message(_("The new framerate was applied. (%s to %s)"),
                       to_string(src_fps).c_str(), to_string(dest_fps).c_str());
  }

  SubtitleTime change_fps(const SubtitleTime &time, double src, double dest) {
    se_dbg(SE_DBG_PLUGINS);

    double frame = time.totalmsecs * src;
    double tot_sec = frame / dest;

    return SubtitleTime((long)tot_sec);
  }

 protected:
  Gtk::UIManager::ui_merge_id ui_id;
  Glib::RefPtr<Gtk::ActionGroup> action_group;
};

REGISTER_EXTENSION(ChangeFrameratePlugin)
