// 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 <i18n.h>

#include <algorithm>

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

   ~TypewriterPlugin() {
      deactivate();
   }

   void activate() {
      se_dbg(SE_DBG_PLUGINS);

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

      action_group->add(
         Gtk::Action::create("typewriter",
                             _("_Typewriter"),
                             _("Produces a typewriter effect by creating several head-to-head subtitles instead of the selected ones in "
                               "the same total duration making it appear as if letters or words are being added gradually")));

      action_group->add(Gtk::Action::create("typewriter-characters-linear",
                                            _("Characters — Linear"),
                                            _("Produces a typewriter effect growing character by "
                                              "character with even delay "
                                              "between the characters")),
                        sigc::bind(sigc::mem_fun(*this, &TypewriterPlugin::split_selected_subtitles), CHARACTERS, LINEAR));

      action_group->add(Gtk::Action::create("typewriter-characters-random",
                                            _("Characters — Random"),
                                            _("Produces a typewriter effect growing character "
                                              "by character with random "
                                              "delay between the characters")),
                        sigc::bind(sigc::mem_fun(*this, &TypewriterPlugin::split_selected_subtitles), CHARACTERS, RANDOM));

      action_group->add(Gtk::Action::create("typewriter-words-linear",
                                            _("Words — Linear"),
                                            _("Produces a typewriter effect growing word by word with "
                                              "even delay between "
                                              "the characters")),
                        sigc::bind(sigc::mem_fun(*this, &TypewriterPlugin::split_selected_subtitles), WORDS, LINEAR));

      action_group->add(Gtk::Action::create("typewriter-words-random",
                                            _("Words — Random"),
                                            _("Produces a typewriter effect growing word by word with "
                                              "random delay between "
                                              "the characters")),
                        sigc::bind(sigc::mem_fun(*this, &TypewriterPlugin::split_selected_subtitles), WORDS, RANDOM));

      // 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-extensions' action='menu-extensions'>
            <placeholder name='placeholder'>
              <menu action='typewriter'>
                <menuitem action='typewriter-characters-linear'/>
                <menuitem action='typewriter-characters-random'/>
                <separator/>
                <menuitem action='typewriter-words-linear'/>
                <menuitem action='typewriter-words-random'/>
              </menu>
            </placeholder>
          </menu>
        </menubar>
      </ui>
    )";

      ui_id = ui->add_ui_from_string(submenu);
   }

   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("typewriter-characters-linear")->set_sensitive(visible);
      action_group->get_action("typewriter-characters-random")->set_sensitive(visible);
      action_group->get_action("typewriter-words-linear")->set_sensitive(visible);
      action_group->get_action("typewriter-words-random")->set_sensitive(visible);
   }

  protected:
   enum SPLIT_TYPE { CHARACTERS, WORDS };

   enum SPLIT_TIME { LINEAR, RANDOM };

   void split_selected_subtitles(SPLIT_TYPE split_type, SPLIT_TIME split_time) {
      se_dbg(SE_DBG_PLUGINS);

      Document* doc = get_current_document();

      g_return_if_fail(doc);

      Subtitles subtitles = doc->subtitles();

      std::vector<Subtitle> selection = subtitles.get_selection();
      if (selection.empty()) {
         doc->flash_message(_("Please select at least one subtitle."));
         return;
      }

      doc->start_command(_("Split subtitles"));
      // To keep subtitles valid, we start change from the end
      for (std::vector<Subtitle>::reverse_iterator it = selection.rbegin(); it != selection.rend(); ++it) {
         split(subtitles, *it, split_type, split_time);
      }
      doc->emit_signal("subtitle-time-changed");
      doc->finish_command();
   }

   void split(Subtitles& subtitles, Subtitle& sub, SPLIT_TYPE type, SPLIT_TIME time) {
      Glib::ustring text = sub.get_text();
      if (text.empty())
         return;
      // Original values
      SubtitleTime ostart = sub.get_start();
      SubtitleTime oduration = sub.get_duration();

      // Array for new subtitles
      std::vector<Subtitle> newsubs;

      // Array for the split text (characters, word ...)
      std::vector<Glib::ustring> vtext;

      if (type == CHARACTERS)
         vtext = split_by_character(text);
      else if (type == WORDS)
         vtext = split_by_word(text);

      // Create really the subtitles
      newsubs = create_subtitles_from_text_array(subtitles, sub, vtext);

      // Setup time
      if (time == LINEAR)
         setup_time_linear(newsubs, ostart, oduration);
      else if (time == RANDOM)
         setup_time_random(newsubs, ostart, oduration);

      // We add to the selection the new subtitles
      subtitles.select(newsubs);
   }

   std::vector<Subtitle> create_subtitles_from_text_array(Subtitles& subtitles,
                                                          Subtitle& original_subtitle,
                                                          const std::vector<Glib::ustring>& vtext) {
      // Array for new subtitles
      std::vector<Subtitle> newsubs;

      // We can add directly the original subtitle, it will be reused
      // we just need to add other lines (size-1)
      newsubs.push_back(original_subtitle);

      // Create subtitle foreach text in the array
      // start at 1 because we already add the original subtitle in the list
      for (guint c = 1; c < vtext.size(); ++c) {
         Subtitle next = subtitles.insert_after(newsubs[c - 1]);
         original_subtitle.copy_to(next);  // Copy all values (style, note...)
         newsubs.push_back(next);
      }
      // Update subtitles text from vtext
      for (guint i = 0; i < vtext.size(); ++i) {
         newsubs[i].set_text(vtext[i]);
      }
      return newsubs;
   }

   std::vector<Glib::ustring> split_by_character(const Glib::ustring& text) {
      std::vector<Glib::ustring> characters;

      characters.resize(text.size());
      for (guint i = 1; i <= text.size(); ++i) {
         characters[i - 1] = text.substr(0, i);
      }
      return characters;
   }

   std::vector<Glib::ustring> split_by_word(const Glib::ustring& text) {
      std::vector<Glib::ustring> splitted, words;
      // If you have a better idea, fell free to send a patch.
      splitted = Glib::Regex::split_simple("\\s", text);

      for (guint i = 0; i < splitted.size(); ++i) {
         Glib::ustring w;
         for (guint j = 0; j <= i; ++j) {
            if (j > 0)
               w += text.at(w.size());
            w += splitted[j];
         }
         words.push_back(w);
      }

      return words;
   }

   void setup_time_linear(std::vector<Subtitle>& subs, const SubtitleTime& start, const SubtitleTime& duration) {
      // We update the time of each subtitles (linear)
      SubtitleTime s = start;
      SubtitleTime d = duration / static_cast<long>(subs.size());

      for (guint i = 0; i < subs.size(); ++i) {
         subs[i].set_start_and_end(s, s + d);
         s = s + d;  // Update the beginning of the next subtitle
      }
   }

   void setup_time_random(std::vector<Subtitle>& subs, const SubtitleTime& start, const SubtitleTime& duration) {
      std::vector<long> rand_times;
      // Create a random time between 0 and duration
      // and sort the random times
      Glib::Rand rand(start.totalmsecs);
      for (guint i = 0; i < subs.size(); ++i) rand_times.push_back(rand.get_int_range(0, duration.totalmsecs));
      std::sort(rand_times.begin(), rand_times.end());

      // We update the time of each subtitles (linear)
      SubtitleTime s = start;

      for (guint i = 0; i < subs.size(); ++i) {
         SubtitleTime e(start.totalmsecs + rand_times[i]);

         subs[i].set_start_and_end(s, e);

         s = e;  // Update the beginning of the next subtitle
      }
   }

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

REGISTER_EXTENSION(TypewriterPlugin)
