/// QasTools: Desktop toolset for the Linux sound system ALSA.
/// \copyright See COPYING file.

#include "desktop_items.hpp"
#include "info_texts.hpp"
#include "main_window.hpp"
#include "qastools_config.hpp"
#include "tray/mixer.hpp"
#include "views/info_dialog.hpp"
#include <getopt.h>
#include <QApplication>
#include <QDBusConnection>
#include <QDBusError>
#include <QDBusInterface>
#include <QEvent>
#include <QKeyEvent>
#include <QSessionManager>
#include <iostream>
#include <memory>
#include <sstream>

Desktop_Items::Desktop_Items ( QObject * parent_n )
: QObject ( parent_n )
, _evt_mixer_closed ( QEvent::registerEventType () )
, _cards_db ( this )
, _mixer_states_db ( &_cards_db )
, _dbus_object_path ( "/" )
, _dbus_service_name ( "org.qastools.qasmixer" )
, _dbus_adaptor ( this )
{
}

Desktop_Items::~Desktop_Items ()
{
  if ( _started ) {
    shutdown ();
  }
}

int
Desktop_Items::init_settings ( int argc, char * argv[] )
{
  _dsetup.read_from_storage ();
  _mixer_states_db.read_from_storage ();
  return parse_cmd_options ( argc, argv );
}

int
Desktop_Items::parse_cmd_options ( int argc, char * argv[] )
{
  // Suppresses error messages by getopt_long
  opterr = 0;

  QString card_idx;
  QString ctl_address;

  bool flag_print_version ( false );
  bool flag_print_help ( false );
  bool flag_print_copy_info ( false );

  bool scan_further ( true );

  while ( scan_further ) {

    static struct option long_opts[] = {
        { "help", no_argument, 0, 'h' },
        { "card", required_argument, 0, 'c' },
        { "device", required_argument, 0, 'D' },

        { "tray", no_argument, 0, 't' },
        { "no-single", no_argument, 0, 'n' },

        { "copying", no_argument, 0, 'i' },
        { "version", no_argument, 0, 'v' },
        { 0, 0, 0, 0 } };

    // getopt_long stores the option index here.
    int long_opts_idx ( 0 );
    int opt_char =
        getopt_long ( argc, argv, "hc:D:tniv", long_opts, &long_opts_idx );

    // Leave loop
    if ( opt_char < 0 ) {
      break;
    }

    switch ( opt_char ) {
    case 0:
      break;
    case 'h':
      flag_print_help = true;
      scan_further = false;
      break;
    case 'c':
      card_idx = optarg;
      break;
    case 'D':
      ctl_address = optarg;
      break;
    case 't':
      _cmd_opts.start_tray_minimized = true;
      break;
    case 'n':
      _cmd_opts.start_single_instance = false;
      break;
    case 'i':
      flag_print_copy_info = true;
      break;
    case 'v':
      flag_print_version = true;
      break;
    default:
      // Dont't break, as the option may be for QT
      break;
    }
  }

  if ( flag_print_help ) {
    {
      std::ostringstream msg;
      msg << "Usage:" << std::endl;
      msg << "  " << PROGRAM_NAME << " [OPTION]..." << std::endl;
      msg << std::endl;
      msg << info_text_options;
      msg << std::endl;
      std::cout << msg.str () << std::flush;
    }
    return -1;
  }

  if ( flag_print_version ) {
    {
      std::ostringstream msg;
      msg << PROGRAM_NAME << " " << VERSION << std::endl;
      std::cout << msg.str () << std::flush;
    }
    return -1;
  }

  if ( flag_print_copy_info ) {
    {
      std::ostringstream msg;
      msg << PROGRAM_TITLE;
      msg << " - desktop mixer for the Linux sound system ALSA.";
      msg << std::endl;
      msg << std::endl;
      msg << license_text_short;
      msg << std::endl;
      std::cout << msg.str () << std::flush;
    }
    return -1;
  }

  if ( ctl_address.isEmpty () && !card_idx.isEmpty () ) {
    bool is_numeric;
    int card_idx_int ( card_idx.toInt ( &is_numeric ) );
    if ( is_numeric ) {
      if ( card_idx_int < 0 ) {
        card_idx_int = 0;
      }
      ctl_address = QStringLiteral ( "hw:" ) + QString::number ( card_idx_int );
    }
  }

  if ( !ctl_address.isEmpty () ) {
    _cmd_opts.start_ctl_address = ctl_address;
    _dsetup.main_window.mixer_dev.set_control_address ( ctl_address );
  }

  return 0;
}

void
Desktop_Items::dbus_connect ()
{
  if ( _dbus_connected ) {
    return;
  }

  auto connection = QDBusConnection::sessionBus ();

  // Check if we're connected to the session bus
  if ( !connection.isConnected () ) {
    std::cerr << "Could not connect to DBus session bus\n";
    return;
  }

  // Register object
  if ( !connection.registerObject ( _dbus_object_path, this ) ) {
    //{
    //  std::ostringstream oss;
    //  oss << "Could not register DBus object "
    //      << _dbus_object_path.toStdString () << "\n"
    //      << connection.lastError ().message ().toStdString () << "\n";
    //  std::cerr << oss.str ();
    //}
    return;
  }

  // Register service
  if ( !connection.registerService ( _dbus_service_name ) ) {
    //{
    //  std::ostringstream oss;
    //  oss << "Could not register DBus service "
    //      << _dbus_service_name.toStdString () << "\n"
    //      << connection.lastError ().message ().toStdString () << "\n";
    //  std::cerr << oss.str ();
    //}

    // Unregister object
    connection.unregisterObject ( _dbus_object_path );
    return;
  }

  // Success
  _dbus_connected = true;
}

bool
Desktop_Items::dbus_notify_remote ()
{
  auto connection = QDBusConnection::sessionBus ();

  // Check if we're connected to the session bus
  if ( !connection.isConnected () ) {
    std::cerr << "DBus notify remote instance: "
              << "Could not connect to DBus session bus\n";
    return false;
  }

  // Open the DBus interface
  QDBusInterface dbus_interface ( _dbus_service_name,
                                  _dbus_object_path,
                                  "org.QasTools.QasMixer",
                                  connection,
                                  this );

  // Check if the interface is valid
  if ( !dbus_interface.isValid () ) {
    std::cerr << "DBus notify remote instance: "
              << "DBus interface isn't valid\n";
    return false;
  }

  // Send ALSA ctl address on demand
  if ( !cmd_opts ().start_ctl_address.isEmpty () ) {
    dbus_interface.call ( QString ( "selectControl" ),
                          cmd_opts ().start_ctl_address );
  }

  // Request raise window on demand
  if ( !cmd_opts ().start_tray_minimized ) {
    dbus_interface.call ( QString ( "raise" ) );
  }

  return true;
}

void
Desktop_Items::start ( bool restore_session_n )
{
  _started = true;

  // Translation
  _dsetup.main_window.inputs.update_translation ();

  // Startup mixer device
  {
    auto & mixer_dev = _dsetup.main_window.mixer_dev;
    if ( _cmd_opts.start_ctl_address.isEmpty () ) {
      bool use_default = true;
      switch ( _dsetup.start_device_mode () ) {
      case Desktop_Items_Setup::MIXER_DEV_PREVIOUS:
        // Device should've been set during configuration reading
        use_default = !mixer_dev.control_address ().is_valid ();
        break;
      case Desktop_Items_Setup::MIXER_DEV_USER:
        mixer_dev.set_control_address ( _dsetup.start_user_device () );
        use_default = false;
        break;
      default:
        break;
      }
      if ( use_default ) {
        mixer_dev.set_control_address ( QStringLiteral ( "default" ) );
      }
    } else {
      mixer_dev.set_control_address ( _cmd_opts.start_ctl_address );
    }
  }

  if ( restore_session_n ) {
    // Restore minimized session
    if ( _dsetup.tray_is_minimized () ) {
      _cmd_opts.start_tray_minimized = true;
    }
  }

  if ( _dsetup.tray_show_icon () ||
       ( _cmd_opts.start_tray_minimized && _dsetup.tray_on_close () ) ) {
    tray_mixer_create ();
  }

  // Raise the main mixer only on demand
  if ( !( _cmd_opts.start_tray_minimized && tray_mixer_visible () ) ) {
    main_mixer_raise ();
  }
}

void
Desktop_Items::select_ctl ( const QString & ctl_str_n )
{
  if ( ctl_str_n.isEmpty () ) {
    return;
  }

  if ( !_main_mixer ) {
    _dsetup.main_window.mixer_dev.set_control_address ( ctl_str_n );
    tray_mixer_reload_current_mixer_dev ();
  } else {
    _main_mixer->select_ctl ( ctl_str_n );
  }
}

void
Desktop_Items::main_mixer_create ()
{
  if ( _main_mixer ) {
    return;
  }

  _main_mixer =
      std::make_unique< Main_Window > ( &_cards_db, &_mixer_states_db );
  _main_mixer->installEventFilter ( this );

  connect ( _main_mixer.get (),
            &Main_Window::sig_quit,
            this,
            &Desktop_Items::quit,
            Qt::QueuedConnection );

  connect ( _main_mixer.get (),
            &Main_Window::sig_control_changed,
            this,
            &Desktop_Items::tray_mixer_reload_current_mixer_dev,
            Qt::QueuedConnection );

  connect ( _main_mixer.get (),
            &Main_Window::sig_show_settings,
            this,
            &Desktop_Items::show_dialog_settings );

  connect ( _main_mixer.get (),
            &Main_Window::sig_show_about,
            this,
            &Desktop_Items::show_dialog_about );

  connect ( _main_mixer.get (),
            &Main_Window::sig_show_about_qt,
            this,
            &Desktop_Items::show_dialog_about_qt );

  _main_mixer->set_window_setup ( &_dsetup.main_window );
  _main_mixer->show ();
}

void
Desktop_Items::main_mixer_destroy ()
{
  if ( _main_mixer ) {
    _main_mixer.reset ();
    // std::cout << "Main mixer destroyed\n";
  }
}

bool
Desktop_Items::main_mixer_visible ()
{
  bool res = false;
  if ( _main_mixer ) {
    res = !_main_mixer->isHidden ();
  }
  return res;
}

void
Desktop_Items::main_mixer_close ()
{
  if ( !_main_mixer ) {
    return;
  }

  // Make sure the settings dialog receives a close event
  if ( !_dialog_settings.isNull () ) {
    _dialog_settings->close ();
  }
  if ( !_dialog_about.isNull () ) {
    _dialog_about->close ();
  }
  // Close the main mixer
  _main_mixer->close ();
}

void
Desktop_Items::main_mixer_raise ()
{
  if ( !_main_mixer ) {
    main_mixer_create ();
  }

  _main_mixer->show ();
  if ( _main_mixer->isMinimized () ) {
    _main_mixer->showNormal ();
  }
  _main_mixer->activateWindow ();
  _main_mixer->raise ();

  _dsetup.set_tray_is_minimized ( false );

  tray_mixer_update_visibility ();
}

void
Desktop_Items::main_mixer_toggle_by_tray ()
{
  bool raise_main = true;
  if ( _main_mixer ) {
    if ( _main_mixer->isActiveWindow () ) {
      raise_main = false;
    }
  }

  if ( raise_main ) {
    main_mixer_raise ();
  } else {
    if ( _dsetup.tray_show_icon () || _dsetup.tray_on_close () ) {
      if ( tray_mixer_visible () ) {
        _dsetup.set_tray_is_minimized ( true );
        main_mixer_close ();
      }
    }
  }
}

void
Desktop_Items::main_mixer_closed ()
{
  // Destroy main mixer to be sure
  main_mixer_destroy ();

  tray_mixer_update_visibility ();
  if ( tray_mixer_visible () && _dsetup.tray_on_close () ) {
    _dsetup.set_tray_is_minimized ( true );
  }

  // Save settings
  save_settings ();

  if ( !_dsetup.tray_is_minimized () ) {
    quit ();
  }
}

void
Desktop_Items::main_mixer_reload_view ()
{
  if ( _main_mixer ) {
    _main_mixer->reload_mixer_view ();
  }
}

void
Desktop_Items::tray_mixer_create ()
{
  if ( _tray_mixer ) {
    return;
  }

  _tray_mixer = std::make_unique< Tray::Mixer > (
      &_dsetup.tray, &_dsetup.main_window.mixer_dev, &_cards_db, this );

  connect ( _tray_mixer.get (),
            &Tray::Mixer::sig_toggle_mixer,
            this,
            &Desktop_Items::main_mixer_toggle_by_tray );

  connect ( _tray_mixer.get (),
            &Tray::Mixer::sig_quit,
            this,
            &Desktop_Items::quit,
            Qt::QueuedConnection );
}

void
Desktop_Items::tray_mixer_destroy ()
{
  if ( _tray_mixer ) {
    _tray_mixer.reset ();
  }
}

void
Desktop_Items::tray_mixer_update_visibility ()
{
  bool tray_vis = false;

  if ( !_shutdown ) {
    if ( _dsetup.tray_show_icon () ) {
      tray_vis = true;
    } else if ( _dsetup.tray_on_close () ) {
      tray_vis = !main_mixer_visible ();
    }
  }

  if ( tray_vis ) {
    tray_mixer_create ();
  } else {
    tray_mixer_destroy ();
  }
}

void
Desktop_Items::tray_mixer_update_icon ()
{
  if ( _tray_mixer ) {
    _tray_mixer->update_icon_setup ();
  }
}

bool
Desktop_Items::tray_mixer_visible ()
{
  if ( _tray_mixer ) {
    return _tray_mixer->is_visible ();
  }
  return false;
}

void
Desktop_Items::tray_mixer_reload_mixer_dev ()
{
  if ( _tray_mixer ) {
    _tray_mixer->reload_mixer ();
  }
}

void
Desktop_Items::tray_mixer_reload_current_mixer_dev ()
{
  if ( _tray_mixer ) {
    _tray_mixer->reload_current_mixer_device ();
  }
}

void
Desktop_Items::tray_mixer_update_balloon_setup ()
{
  if ( _tray_mixer ) {
    _tray_mixer->update_balloon_setup ();
  }
}

void
Desktop_Items::tray_mixer_update_input_setup ()
{
  if ( _tray_mixer ) {
    _tray_mixer->update_input_setup ();
  }
}

void
Desktop_Items::reload_inputs_setup ()
{
  // Main mixer
  if ( _main_mixer ) {
    _main_mixer->reload_mixer_inputs ();
  }
}

void
Desktop_Items::show_dialog_settings ()
{
  if ( !_main_mixer ) {
    return;
  }

  if ( _dialog_settings.isNull () ) {
    Views::Settings_Dialog * dlg =
        new Views::Settings_Dialog ( _main_mixer.get () );
    dlg->set_size_from_parent ( _main_mixer.get () );
    dlg->setAttribute ( Qt::WA_DeleteOnClose );
    dlg->set_setup ( &_dsetup );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_mixer_view,
              this,
              &Desktop_Items::main_mixer_reload_view );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_input,
              this,
              &Desktop_Items::reload_inputs_setup );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_tray_visibility,
              this,
              &Desktop_Items::tray_mixer_update_visibility );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_tray_icon,
              this,
              &Desktop_Items::tray_mixer_update_icon );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_tray_mixer_dev,
              this,
              &Desktop_Items::tray_mixer_reload_mixer_dev );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_tray_balloon,
              this,
              &Desktop_Items::tray_mixer_update_balloon_setup );

    connect ( dlg,
              &Views::Settings_Dialog::sig_change_tray_input,
              this,
              &Desktop_Items::tray_mixer_update_input_setup );

    _dialog_settings = dlg;
  }
  _dialog_settings->show ();
}

void
Desktop_Items::show_dialog_about ()
{
  if ( !_main_mixer ) {
    return;
  }

  if ( _dialog_about == nullptr ) {
    Views::Info_Dialog * dlg = new Views::Info_Dialog ( _main_mixer.get () );
    dlg->set_size_from_parent ( _main_mixer.get () );
    dlg->setAttribute ( Qt::WA_DeleteOnClose );
    _dialog_about = dlg;
  }
  _dialog_about->show ();
}

void
Desktop_Items::show_dialog_about_qt ()
{
  QApplication::aboutQt ();
}

void
Desktop_Items::commit_data ( QSessionManager & manager_n [[maybe_unused]] )
{
  // std::cout << "Desktop_Items::commit_data\n";
  save_settings ();
}

void
Desktop_Items::save_state ( QSessionManager & manager_n [[maybe_unused]] )
{
  // std::cout << "Desktop_Items::save_state\n";
  save_settings ();
}

void
Desktop_Items::save_settings ()
{
  // std::cout << "Desktop_Items::save_settings\n";

  if ( _main_mixer ) {
    _main_mixer->save_state ();
  }
  if ( _dialog_settings ) {
    _dialog_settings->save_state ();
  }

  // Write to storage
  _mixer_states_db.write_to_storage ();
  _dsetup.write_to_storage ();
}

void
Desktop_Items::shutdown ()
{
  if ( _shutdown ) {
    return;
  }
  _shutdown = true;
  save_settings ();
  main_mixer_close ();
  main_mixer_destroy ();
  tray_mixer_destroy ();
}

void
Desktop_Items::quit ()
{
  shutdown ();
  Q_EMIT sig_quit ();
}

bool
Desktop_Items::event ( QEvent * event_n )
{
  if ( event_n->type () == _evt_mixer_closed ) {
    main_mixer_closed ();
    return true;
  }
  return QObject::event ( event_n );
}

bool
Desktop_Items::eventFilter ( QObject * obj_n, QEvent * event_n )
{
  bool filtered = false;
  if ( obj_n == _main_mixer.get () ) {
    if ( event_n->type () == QEvent::Close ) {
      // std::cout << "Close event catched\n";
      if ( !_shutdown ) {
        QEvent * ev_req = new QEvent ( QEvent::Type ( _evt_mixer_closed ) );
        QCoreApplication::postEvent ( this, ev_req );
      }
    }
  }
  return filtered;
}
