// 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 <gstreamermm.h>
#include <gtkmm.h>
#include <utility.h>
#include <waveform.h>
#include <iomanip>
#include <iostream>
#include "mediadecoder.h"

class WaveformGenerator : public Gtk::Dialog, public MediaDecoder {
 public:
  WaveformGenerator(const Glib::ustring &uri, Glib::RefPtr<Waveform> &wf)
      : Gtk::Dialog(_("Generate Waveform"), true),
        MediaDecoder(1000),
        m_duration(GST_CLOCK_TIME_NONE),
        m_n_channels(0) {
    se_dbg_msg(SE_DBG_PLUGINS, "uri=%s", uri.c_str());

    set_border_width(12);
    set_default_size(300, -1);
    get_vbox()->pack_start(m_progressbar, false, false);
    add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    m_progressbar.set_text(_("Waiting..."));
    show_all();

    try {
      create_pipeline(uri);

      if (run() == Gtk::RESPONSE_OK) {
        wf = Glib::RefPtr<Waveform>(new Waveform);
        wf->m_duration = m_duration / GST_MSECOND;
        wf->m_n_channels = m_n_channels;
        for (guint i = 0; i < m_n_channels; ++i)
          wf->m_channels[i] =
              std::vector<double>(m_values[i].begin(), m_values[i].end());
        wf->m_video_uri = uri;
      }
    } catch (const std::runtime_error &ex) {
      std::cerr << ex.what() << std::endl;
    }
  }

  // Create audio bin
  Glib::RefPtr<Gst::Element> create_element(
      const Glib::ustring &structure_name) {
    se_dbg_msg(SE_DBG_PLUGINS, "structure_name=%s", structure_name.c_str());
    try {
      // We only need and want create the video sink
      if (structure_name.find("audio") == Glib::ustring::npos)
        return Glib::RefPtr<Gst::Element>(NULL);

      Glib::RefPtr<Gst::Bin> audiobin = Glib::RefPtr<Gst::Bin>::cast_dynamic(
          Gst::Parse::create_bin("audioconvert ! "
                                 // "audioresample ! "
                                 // "audio/x-raw-float, channels=1 ! "
                                 "level name=level ! "
                                 "fakesink name=asink",
                                 true));
      // Set the new sink tp READY as well
      Gst::StateChangeReturn retst = audiobin->set_state(Gst::STATE_READY);
      if (retst == Gst::STATE_CHANGE_FAILURE)
        std::cerr << "Could not change state of new sink: " << retst
                  << std::endl;

      return Glib::RefPtr<Gst::Element>::cast_dynamic(audiobin);
    } catch (std::runtime_error &ex) {
      se_dbg_msg(SE_DBG_PLUGINS, "runtime_error=%s", ex.what());
      std::cerr << "create_audio_bin: " << ex.what() << std::endl;
    }
    return Glib::RefPtr<Gst::Element>(NULL);
  }

  // BUS MESSAGE
  bool on_bus_message(const Glib::RefPtr<Gst::Bus> &bus,
                      const Glib::RefPtr<Gst::Message> &msg) {
    MediaDecoder::on_bus_message(bus, msg);

    if (msg->get_message_type() == Gst::MESSAGE_ELEMENT) {
      if (msg->get_structure().get_name() == "level")
        return on_bus_message_element_level(msg);
    }
    return true;
  }

  // Update the progress bar
  bool on_timeout() {
    se_dbg(SE_DBG_PLUGINS);

    if (!m_pipeline)
      return false;

    Gst::Format fmt = Gst::FORMAT_TIME;
    gint64 pos = 0, len = 0;
    if (m_pipeline->query_position(fmt, pos) &&
        m_pipeline->query_duration(fmt, len)) {
      double percent = static_cast<double>(pos) / static_cast<double>(len);

      percent = CLAMP(percent, 0.0, 1.0);

      m_progressbar.set_fraction(percent);
      m_progressbar.set_text(time_to_string(pos) + " / " + time_to_string(len));

      return pos != len;
    }

    return true;
  }

  bool on_bus_message_element_level(Glib::RefPtr<Gst::Message> msg) {
    se_dbg_msg(SE_DBG_PLUGINS, "type='%s' name='%s'",
               GST_MESSAGE_TYPE_NAME(msg->gobj()),
               GST_OBJECT_NAME(GST_MESSAGE_SRC(msg->gobj())));

    Gst::Structure structure = msg->get_structure();
    const GValue *array_val =
        gst_structure_get_value(GST_STRUCTURE(structure.gobj()), "rms");
    GValueArray *rms_arr =
        static_cast<GValueArray *>(g_value_get_boxed(array_val));

    gint num_channels = rms_arr->n_values;

    guint first_channel, last_channel;
    if (num_channels >= 6) {
      first_channel = 1;
      last_channel = 3;
    } else if (num_channels == 5) {
      first_channel = 1;
      last_channel = 2;
    } else if (num_channels == 2) {
      first_channel = 0;
      last_channel = 1;
    } else {
      first_channel = last_channel = 0;
    }
    // build the number of channels
    m_n_channels = last_channel - first_channel + 1;

    // get peak from channels
    for (guint c = first_channel, i = 0; c <= last_channel; ++c, ++i) {
      double peak =
          pow(10, g_value_get_double(g_value_array_get_nth(rms_arr, c)) / 20);
      m_values[i].push_back(peak);
    }
    return true;
  }

  void on_work_finished() {
    se_dbg(SE_DBG_PLUGINS);

    // set duration to position at eos
    Gst::Format fmt = Gst::FORMAT_TIME;
    gint64 pos = 0;

    if (m_pipeline && m_pipeline->query_position(fmt, pos)) {
      m_duration = pos;
      response(Gtk::RESPONSE_OK);
    } else {
      GST_ELEMENT_ERROR(
          m_pipeline->gobj(), STREAM, FAILED,
          (_("Could not determinate the duration of the stream.")), (NULL));
    }
  }

  void on_work_cancel() {
    se_dbg(SE_DBG_PLUGINS);

    response(Gtk::RESPONSE_CANCEL);
  }

 protected:
  Gtk::ProgressBar m_progressbar;
  guint64 m_duration;
  guint m_n_channels;
  std::list<gdouble> m_values[3];
};

Glib::RefPtr<Waveform> generate_waveform_from_file(const Glib::ustring &uri) {
  Glib::RefPtr<Waveform> wf;
  WaveformGenerator ui(uri, wf);
  return wf;
}
