// 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 "documents.h"
#include "gstplayer.h"
#include "subtitleeditorwindow.h"
#include "utility.h"
#include "videoplayer.h"

// Player Controls Widgets
class PlayerControls : public Gtk::HBox {
 public:
  PlayerControls(BaseObjectType* cobject,
                 const Glib::RefPtr<Gtk::Builder>& builder)
      : Gtk::HBox(cobject) {
    m_current_seek = false;
    m_player = NULL;

    builder->get_widget("button-play", m_button_play);
    builder->get_widget("button-pause", m_button_pause);

    builder->get_widget("hscale-seek", m_hscale_seek);

    m_hscale_seek->set_range(0, 1);
    m_hscale_seek->set_value(0);
    m_hscale_seek->set_increments(
        0.05, 0.05);  // get_adjustment()->set_step_increment(0.05);
    // m_hscale_seek->set_update_policy(Gtk::UPDATE_CONTINUOUS);
    m_hscale_seek->set_value_pos(Gtk::POS_RIGHT);

    m_hscale_seek->signal_format_value().connect(
        sigc::mem_fun(*this, &PlayerControls::position_to_string));

    m_connection_scale_changed = m_hscale_seek->signal_value_changed().connect(
        sigc::mem_fun(*this, &PlayerControls::on_seek_value_changed));

    m_hscale_seek->signal_event().connect(
        sigc::mem_fun(*this, &PlayerControls::on_seek_event), true);

    // signal play/pause directly connect to the player
    m_button_play->signal_clicked().connect(
        sigc::mem_fun(*this, &PlayerControls::on_play));

    m_button_pause->signal_clicked().connect(
        sigc::mem_fun(*this, &PlayerControls::on_pause));

    show_all();

    m_button_pause->hide();

    set_sensitive(false);
  }

  void on_seek_value_changed() {
    if (m_current_seek)
      return;
    // convert pos (as percentage) to position in the stream
    double pos = m_hscale_seek->get_value();
    m_player->seek(long(m_player->get_duration() * pos));
  }

  void on_play() {
    if (m_player->is_playing() == false) {
      m_player->seek(m_player->get_position());
      m_player->play();
    }
  }

  void on_pause() {
    m_player->pause();
  }

  void init_with_player(Player* player) {
    m_player = player;

    m_player->signal_message().connect(
        sigc::mem_fun(*this, &PlayerControls::on_player_message));

    m_player->signal_tick().connect(
        sigc::mem_fun(*this, &PlayerControls::on_player_tick));
  }

  void on_player_message(Player::Message msg) {
    switch (msg) {
      case Player::STATE_NONE: {
        m_hscale_seek->set_value(0);
        m_button_play->show();
        m_button_pause->hide();
        set_sensitive(false);
      } break;
      case Player::STATE_PAUSED: {
        m_button_play->show();
        m_button_pause->hide();
        set_sensitive(true);
      } break;
      case Player::STATE_PLAYING: {
        m_button_play->hide();
        m_button_pause->show();
        set_sensitive(true);
      } break;
      case Player::STREAM_DURATION_CHANGED:
      case Player::STREAM_READY: {
        long pos = m_player->get_position();
        long dur = m_player->get_duration();
        double perc = 0;
        if (dur != 0) {
          perc = static_cast<double>(pos) / dur;
        }
        set_seek_position(perc);
      } break;
      default:
        break;
    }
  }

  void on_player_tick(long /*current_time*/, long /*stream_length*/,
                      double current_position) {
    m_connection_scale_changed.block();
    set_seek_position(current_position);
    m_connection_scale_changed.unblock();
  }

  void set_seek_position(double position) {
    if (m_current_seek == false && m_hscale_seek->get_value() != position) {
      m_hscale_seek->set_value(position);
      m_hscale_seek->queue_draw();
    }
  }

 protected:
  Gtk::Button* create_button(Gtk::StockID stock) {
    Gtk::Button* button = manage(new Gtk::Button);
    button->set_relief(Gtk::RELIEF_NONE);

    Gtk::Image* image = manage(new Gtk::Image(stock, Gtk::ICON_SIZE_MENU));

    button->add(*image);
    return button;
  }

  Glib::ustring position_to_string(double value) {
    // convert pos (as percentage) to position in the stream
    return SubtitleTime(long(m_player->get_duration() * value)).str();
  }

  bool on_seek_event(GdkEvent* ev) {
    if (ev->type == GDK_BUTTON_PRESS) {
      m_current_seek = true;
    } else if (ev->type == GDK_BUTTON_RELEASE) {
      // convert pos (as percentage) to position in the stream
      double pos = m_hscale_seek->get_value();
      m_player->seek(long(m_player->get_duration() * pos));

      m_current_seek = false;
    }
    return false;
  }

 protected:
  // controls
  Gtk::Button* m_button_play;
  Gtk::Button* m_button_pause;
  Gtk::Scale* m_hscale_seek;
  bool m_current_seek;
  sigc::connection m_connection_scale_changed;
  Player* m_player;
};

// Constructor
// Create the GStreamer Player, the PlayerControls (play/pause + seek)
VideoPlayer::VideoPlayer(BaseObjectType* cobject,
                         const Glib::RefPtr<Gtk::Builder>& builder)
    : Gtk::VBox(cobject) {
  m_cfg_display_translated_subtitle = false;

  m_player = manage(new GstPlayer);

  Gtk::Frame* m_framePlayer = NULL;
  PlayerControls* m_playerControls = NULL;

  builder->get_widget("frame-player", m_framePlayer);
  builder->get_widget_derived("player-controls", m_playerControls);

  m_framePlayer->add(*dynamic_cast<Gtk::Widget*>(m_player));
  m_playerControls->init_with_player(m_player);

  load_config();

  cfg::signal_changed("video-player")
      .connect(
          sigc::mem_fun(*this, &VideoPlayer::on_config_video_player_changed));

  se::documents::signal_active_changed().connect(
      sigc::mem_fun(*this, &VideoPlayer::on_active_document_changed));

  m_player->signal_tick().connect(
      sigc::mem_fun(*this, &VideoPlayer::on_player_tick));

  m_player->signal_message().connect(
      sigc::mem_fun(*this, &VideoPlayer::on_player_message));
}

// Destructor
VideoPlayer::~VideoPlayer() {
  se_dbg(SE_DBG_VIDEO_PLAYER);
}

// Load the video player config.
void VideoPlayer::load_config() {
  if (cfg::get_boolean("video-player", "display")) {
    show();
  } else {
    hide();
  }
  m_player->set_repeat(cfg::get_boolean("video-player", "repeat"));

  m_cfg_display_translated_subtitle =
      cfg::get_boolean("video-player", "display-translated-subtitle");
}

// Return the gstreamer player.
Player* VideoPlayer::player() {
  return m_player;
}

// The player state has changed.
// Clear subtitle.
void VideoPlayer::on_player_message(Player::Message msg) {
  switch (msg) {
    case Player::STATE_NONE:
    case Player::STATE_PAUSED:
    case Player::STATE_PLAYING:
      clear_subtitle();
      break;
    default:
      break;
  }
}

// The config of video player has changed.
void VideoPlayer::on_config_video_player_changed(const Glib::ustring& key,
                                                 const Glib::ustring& value) {
  if (key == "display") {
    bool state = utility::string_to_bool(value);

    if (state)
      show();
    else
      hide();
  } else if (key == "display-translated-subtitle") {
    m_cfg_display_translated_subtitle = utility::string_to_bool(value);
  }
}

// The current document has changed.
// Clear subtitle (sub and player text) and try to found the good subtitle.
void VideoPlayer::on_active_document_changed(Document* doc) {
  m_connection_document_changed.disconnect();
  if (doc != NULL)
    m_connection_document_changed =
        doc->get_signal("document-changed")
            .connect(sigc::mem_fun(*this, &VideoPlayer::clear_subtitle));

  clear_subtitle();
  find_subtitle();
}

// Check or search the good subtitle (find_subtitle).
void VideoPlayer::on_player_tick(long /*current_time*/, long /*stream_length*/,
                                 double /*current_position*/) {
  find_subtitle();
}

// Initialize the current subtitle and the player text to NULL.
void VideoPlayer::clear_subtitle() {
  m_subtitle = Subtitle();

  m_player->set_subtitle_text("");
}

// Check if time is in subtitle.
bool VideoPlayer::is_good_subtitle(const Subtitle& sub, long time) {
  if (sub)
    if (time >= sub.get_start().totalmsecs && time < sub.get_end().totalmsecs)
      return true;

  return false;
}

// Try to found the good subtitle and init the player (text).
// FIXME: this's call each time
bool VideoPlayer::find_subtitle() {
  Document* doc = SubtitleEditorWindow::get_instance()->get_current_document();
  if (doc == NULL) {
    clear_subtitle();
    return false;
  }

  long position = m_player->get_position();

  SubtitleTime time(position);

  if (!m_subtitle) {
    m_subtitle = doc->subtitles().find(time);
    if (m_subtitle)
      show_subtitle_text();
    else
      show_subtitle_null();
  } else {
    if (is_good_subtitle(m_subtitle, position)) {  // is good ?
      show_subtitle_text();
    } else if (time < m_subtitle.get_start()) {  // is the next ?
      show_subtitle_null();
    } else if (time >= m_subtitle.get_end()) {  // it's the old...
      // ... try with the next subtitle
      show_subtitle_null();

      Subtitle next = doc->subtitles().get_next(m_subtitle);
      if (next) {
        m_subtitle = next;
        if (is_good_subtitle(m_subtitle, position))
          show_subtitle_text();
      } else {
        m_subtitle = Subtitle();
      }
    }
  }
  return true;
}

// Sets the text of the player to NULL.
void VideoPlayer::show_subtitle_null() {
  m_player->set_subtitle_text("");
}

// Sets the text of the player with the current subtitle.
void VideoPlayer::show_subtitle_text() {
  if (!m_subtitle)
    return;

  Glib::ustring text;

  if (m_cfg_display_translated_subtitle &&
      !m_subtitle.get_translation().empty())
    text = m_subtitle.get_translation();
  else
    text = m_subtitle.get_text();

  m_player->set_subtitle_text(text);
}
