// subtitleeditor -- a tool to create or edit subtitle
//
// https://subtitleeditor.github.io/subtitleeditor/
// https://github.com/subtitleeditor/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 <extension/action.h>
#include <gtkmm_utility.h>
#include <i18n.h>
#include <player.h>
#include <utility.h>
#include <widget_config_utility.h>

class DialogTimingFromPlayerPreferences : public Gtk::Dialog {
  public:
   DialogTimingFromPlayerPreferences(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& xml) : Gtk::Dialog(cobject) {
      xml->get_widget("spin-offset", m_spinOffset);
      widget_config::read_config_and_connect(m_spinOffset, "timing-from-player", "offset");

      utility::set_transient_parent(*this);
   }

   static void create() {
      std::unique_ptr<DialogTimingFromPlayerPreferences> dialog(gtkmm_utility::get_widget_derived<DialogTimingFromPlayerPreferences>(
         SE_DEV_VALUE(SE_PLUGIN_PATH_UI, SE_PLUGIN_PATH_DEV), "dialog-timing-from-player-preferences.ui", "dialog-timing-from-player-preferences"));

      dialog->run();
   }

  protected:
   Gtk::SpinButton* m_spinOffset;
};

// Actions to set time from the current player position.
class TimingFromPlayer : public Action {
  public:
   TimingFromPlayer() {
      activate();
      update_ui();
   }

   ~TimingFromPlayer() {
      deactivate();
   }

   void activate() {
      se_dbg(SE_DBG_PLUGINS);

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

      // Set Subtitle Time
      action_group->add(
         Gtk::Action::create("menu-timing-from-player", _("Timing From Player"), _("Use the current player position to set subtitle timecodes")));
      // set current subtitle
      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-start",
                                            _("Set Subtitle _Start"),
                                            _("Use the current player position to set the start of the first "
                                              "selected subtitle and then move all the other selected "
                                              "subtitles by the same distance (like the tool Move Subtitles, but without dialogue)")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_start));

      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-end",
                                            _("Set Subtitle _End"),
                                            _("Use the current player position to set the end "
                                              "of the first selected subtitle")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_end));

      // set current subtitle and go to the next
      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-start-and-go-next",
                                            _("Set Subtitle Start And Go Next"),
                                            _("Use the current player position to set the start of the first "
                                              "selected subtitle, then move all the other selected subtitles "
                                              "by the same amount and select the subtitle next to the first "
                                              "selected one")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_start_and_go_next));

      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-end-and-go-next",
                                            _("Set Subtitle End And Go Next"),
                                            _("Use the current player position to set the end "
                                              "of the first selected subtitle and select the "
                                              "subtitle next to the first selected one")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_end_and_go_next));

      // set current subtitle and define the next
      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-start-and-next",
                                            _("Set Subtitle Start And Next"),
                                            _("Use the current player position to set the start of the current "
                                              "selected subtitle and then create a new subtitle next to it — "
                                              "use this action only when your selected subtitle is the last "
                                              "one, otherwise the results are funny")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_start_and_next));

      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-end-and-next",
                                            _("Set Subtitle End And Next"),
                                            _("Use the current player position to set the end of the current "
                                              "selected subtitle and then create a new subtitle next to it — "
                                              "use this action only when your selected subtitle is the last "
                                              "one, otherwise the results are funny")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_end_and_next));

      // set current subtile start and with one key
      action_group->add(Gtk::Action::create("timing-from-player/set-subtitle-start-and-end-with-one-key",
                                            _("Set Subtitle Start _And End"),
                                            _("Use this to time all your subtitles in one go as video plays. "
                                              "Press the associated (ideally) one-key shortcut to set the "
                                              "beginning and release to set the end of the subtitle. Then the "
                                              "next subtitle is selected or a new one created.")),
                        sigc::mem_fun(*this, &TimingFromPlayer::set_subtitle_start_and_end_with_one_key));

      // preferences
      action_group->add(Gtk::Action::create("timing-from-player/preferences", Gtk::Stock::PREFERENCES, _("Preferences"), _("Configure this submenu")),
                        sigc::mem_fun(*this, &TimingFromPlayer::create_configure_dialog));

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

      ui->insert_action_group(action_group);

      Glib::ustring submenu = R"(
      <ui>
        <menubar name='menubar'>
          <menu name='menu-video' action='menu-video'>
            <placeholder name='placeholder'>
              <menu action='menu-timing-from-player'>
                <menuitem action='timing-from-player/set-subtitle-start'/>
                <menuitem action='timing-from-player/set-subtitle-end'/>
                <separator/>
                <menuitem action='timing-from-player/set-subtitle-start-and-go-next'/>
                <menuitem action='timing-from-player/set-subtitle-end-and-go-next'/>
                <separator/>
                <menuitem action='timing-from-player/set-subtitle-start-and-next'/>
                <menuitem action='timing-from-player/set-subtitle-end-and-next'/>
                <separator/>
                <menuitem action='timing-from-player/set-subtitle-start-and-end-with-one-key'/>
                <separator/>
                <menuitem action='timing-from-player/preferences'/>
              </menu>
            </placeholder>
          </menu>
        </menubar>
      </ui>
    )";

      ui_id = ui->add_ui_from_string(submenu);

      // Connect to the player state changed
      // the actions can only be used when the player has a media
      get_subtitleeditor_window()->get_player()->signal_message().connect(sigc::mem_fun(*this, &TimingFromPlayer::on_player_message));
   }

   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 has_doc = (get_current_document() != NULL);
      bool has_media = get_subtitleeditor_window()->get_player()->get_state() != Player::NONE;

#define SET_SENSITIVE(action, state)                                    \
   {                                                                    \
      Glib::RefPtr<Gtk::Action> act = action_group->get_action(action); \
      if (act)                                                          \
         act->set_sensitive(state);                                     \
      else                                                              \
         g_warning(action);                                             \
   }

      SET_SENSITIVE("timing-from-player/set-subtitle-start", has_media && has_doc);
      SET_SENSITIVE("timing-from-player/set-subtitle-end", has_media && has_doc);

      SET_SENSITIVE("timing-from-player/set-subtitle-start-and-go-next", has_media && has_doc);
      SET_SENSITIVE("timing-from-player/set-subtitle-end-and-go-next", has_media && has_doc);

      SET_SENSITIVE("timing-from-player/set-subtitle-start-and-next", has_media && has_doc);
      SET_SENSITIVE("timing-from-player/set-subtitle-end-and-next", has_media && has_doc);

      SET_SENSITIVE("timing-from-player/set-subtitle-start-and-end-with-one-key", has_media && has_doc);

#undef SET_SENSITIVE
   }

   bool is_configurable() {
      return true;
   }

   void create_configure_dialog() {
      DialogTimingFromPlayerPreferences::create();
   }

   // Check the state of the player.
   // Update the menu from the current state of the player.
   void on_player_message(Player::Message msg) {
      se_dbg(SE_DBG_PLUGINS);
      // only if the player is enable or disable
      // don't update if is playing or paused
      if (msg == Player::STATE_NONE || msg == Player::STREAM_READY)
         update_ui();
   }

   enum OPTIONS { SET_SUBTITLE_START = 1 << 0, SET_SUBTITLE_END = 1 << 1, SELECT_NEXT_OR_CREATE = 1 << 2, SET_NEXT_SUBTITLE_POS = 1 << 3 };

   Glib::ustring get_command_name_from_option(int op) {
      if (op & SET_SUBTITLE_START)
         return _("Set subtitle start");
      else if (op & SET_SUBTITLE_END)
         return _("Set subtitle end");
      return _("Set subtitle");  // should not have happened
   }

   bool set_subtitle_from_player(int op) {
      se_dbg(SE_DBG_PLUGINS);

      Document* doc = get_current_document();
      g_return_val_if_fail(doc, false);

      Subtitle sub = doc->subtitles().get_first_selected();
      if (!sub)
         return false;

      Player* player = get_subtitleeditor_window()->get_player();
      SubtitleTime pos = player->get_position();
      // Apply offset correction only if playing
      if (player->get_state() == Player::PLAYING)
         pos = pos - get_prefered_offset();

      // Start recording
      doc->start_command(get_command_name_from_option(op));

      if (op & SET_SUBTITLE_START) {
         // Define the start of the subtitle from the
         // video position, we keep the duration
         std::vector<Subtitle> selection = doc->subtitles().get_selection();
         std::vector<Subtitle>::iterator it = selection.begin();
         std::vector<Subtitle>::iterator eit = selection.end();
         SubtitleTime diff = pos - it->get_start();
         while (it != eit) {
            it->set_start_and_end(it->get_start() + diff, it->get_end() + diff);
            ++it;
         }
      } else if (op & SET_SUBTITLE_END) {
         sub.set_end(pos);
      }

      // Select or create the next subtitle
      if (op & SELECT_NEXT_OR_CREATE) {
         Subtitle next = doc->subtitles().get_next(sub);
         if (!next) {
            next = doc->subtitles().append();
            next.set_duration(cfg::get_int("timing", "min-display"));
         }
         if (op & SET_NEXT_SUBTITLE_POS) {
            SubtitleTime sub_end = sub.get_end();
            SubtitleTime gap = cfg::get_int("timing", "min-gap-between-subtitles");
            next.set_start_and_end(sub_end + gap, sub_end + next.get_duration());
         }
         doc->subtitles().select(next);
      }

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

   void set_subtitle_start() {
      set_subtitle_from_player(SET_SUBTITLE_START);
   }

   void set_subtitle_end() {
      set_subtitle_from_player(SET_SUBTITLE_END);
   }

   void set_subtitle_start_and_go_next() {
      set_subtitle_from_player(SET_SUBTITLE_START | SELECT_NEXT_OR_CREATE);
   }

   void set_subtitle_end_and_go_next() {
      set_subtitle_from_player(SET_SUBTITLE_END | SELECT_NEXT_OR_CREATE);
   }

   void set_subtitle_start_and_next() {
      set_subtitle_from_player(SET_SUBTITLE_START | SELECT_NEXT_OR_CREATE | SET_NEXT_SUBTITLE_POS);
   }

   void set_subtitle_end_and_next() {
      set_subtitle_from_player(SET_SUBTITLE_END | SELECT_NEXT_OR_CREATE | SET_NEXT_SUBTITLE_POS);
   }

   // Update the subtitle start.
   // We connect the signal key_release_event to update the
   // end of the subtitle when the key is released.
   void set_subtitle_start_and_end_with_one_key() {
      se_dbg(SE_DBG_PLUGINS);

      if (co)
         return;

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

      // connect the keyboard release signal to the main window
      SubtitleEditorWindow* win = get_subtitleeditor_window();
      Gtk::Window* gtk_win = dynamic_cast<Gtk::Window*>(win);
      Glib::RefPtr<Gdk::Window> gdk_win = gtk_win->get_window();

      co = gtk_win->signal_key_release_event().connect(sigc::mem_fun(*this, &TimingFromPlayer::on_key_release_event), false);

      set_subtitle_start();
   }

   // Any key have been released.
   // Update the end of the subtitle and disconnect
   // the callback.
   bool on_key_release_event(GdkEventKey*) {
      se_dbg(SE_DBG_PLUGINS);

      set_subtitle_end_and_go_next();
      co.disconnect();
      return true;
   }

   SubtitleTime get_prefered_offset() {
      // FIXME: config offset
      // int offset = 0;
      // get_config().get_value_int("timing-from-player", "offset", offset);
      // return SubtitleTime(offset);
      return cfg::get_int("timing-from-player", "offset");
   }

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

REGISTER_EXTENSION(TimingFromPlayer)
