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

#include "qsnd/mixer/mixer.hpp"
#include "mixer.hpp"
#include "mstate/mixer/controls.hpp"
#include "mwdg/event_types.hpp"
#include "mwdg/inputs_setup.hpp"
#include "mwdg/mixer/proxy/group.hpp"
#include "mwdg/mixer/setup.hpp"
#include "mwdg/mixer/sliders/proxies_model.hpp"
#include "mwdg/mixer/sliders/proxy/slider.hpp"
#include "wdg/pad_proxy/column.hpp"
#include "wdg/scroll_area_horizontal.hpp"
#include "wdg/sliders_pad/pad.hpp"
#include <QContextMenuEvent>
#include <QKeyEvent>
#include <QKeySequence>
#include <QVBoxLayout>
#include <iostream>

namespace MWdg::Mixer::Sliders
{

Mixer::Mixer ( const Wdg::Style_Db * wdg_style_db_n,
               dpe::Image_Allocator * image_alloc_n,
               QWidget * parent_n )
: QWidget ( parent_n )
, _cmenu ( this )
, _act_toggle_joined ( this )
, _act_level_channels ( this )
, _act_separator_channels ( this )
, _act_toggle_mute ( this )
{
  // Sliders pad
  _sliders_pad = std::make_unique< Wdg::Sliders_Pad::Pad > ( wdg_style_db_n,
                                                             image_alloc_n );
  _sliders_pad->hide ();
  _sliders_pad->setAutoFillBackground ( false );
  _sliders_pad->installEventFilter ( this );

  connect ( _sliders_pad.get (),
            &Wdg::Sliders_Pad::Pad::sig_focus_changed,
            this,
            &Mixer::update_focus_proxies );

  connect ( _sliders_pad.get (),
            &Wdg::Sliders_Pad::Pad::sig_footer_label_selected,
            this,
            &Mixer::sig_footer_label_selected );

  // Sliders area
  _sliders_area = new Wdg::Scroll_Area_Horizontal;
  _sliders_area->setFrameStyle ( QFrame::NoFrame );
  _sliders_area->set_widget ( _sliders_pad.get () );

  // Actions
  _act_toggle_joined.setIcon ( QIcon::fromTheme ( "object-flip-horizontal" ) );

  _act_level_channels.setIcon ( QIcon::fromTheme ( "object-flip-vertical" ) );

  _act_separator_channels.setSeparator ( true );

  _act_str_mute[ 0 ] = tr ( "&Mute" );
  _act_str_mute[ 1 ] = tr ( "&Mute all" );
  _act_str_unmute[ 0 ] = tr ( "Un&mute" );
  _act_str_unmute[ 1 ] = tr ( "Un&mute all" );
  _act_str_toggle_mute = tr ( "Toggle &mutes" );

  _icon_vol_high = QIcon::fromTheme ( "audio-volume-high" );
  _icon_vol_med = QIcon::fromTheme ( "audio-volume-medium" );
  _icon_muted = QIcon::fromTheme ( "audio-volume-muted" );

  connect ( &_act_toggle_joined,
            &QAction::triggered,
            this,
            &Mixer::action_toggle_joined );

  connect ( &_act_level_channels,
            &QAction::triggered,
            this,
            &Mixer::action_level_volumes );

  connect ( &_act_toggle_mute,
            &QAction::triggered,
            this,
            &Mixer::action_toggle_mute );

  // Context menu
  _cmenu.addAction ( &_act_toggle_joined );
  _cmenu.addAction ( &_act_level_channels );
  _cmenu.addAction ( &_act_separator_channels );
  _cmenu.addAction ( &_act_toggle_mute );

  // Layout
  {
    QVBoxLayout * lay_vbox = new QVBoxLayout ();
    lay_vbox->addWidget ( _sliders_area, 1 );
    setLayout ( lay_vbox );
  }
}

Mixer::~Mixer ()
{
  set_mixer_setup ( nullptr );
  set_proxies_model ( nullptr );
}

void
Mixer::set_mixer_setup ( const MWdg::Mixer::Setup * setup_n )
{
  _mixer_setup = setup_n;

  if ( mixer_setup () != nullptr ) {
    _sliders_pad->set_footer_visible (
        mixer_setup ()->show_slider_value_labels );
  }
}

void
Mixer::set_proxies_model ( MWdg::Mixer::Sliders::Proxies_Model * model_n )
{
  _cmenu.close ();

  reload_proxies_groups_begin ();

  if ( proxies_model () != nullptr ) {
    // Disconnect from reload signals
    disconnect ( proxies_model (),
                 &Proxies_Model::sig_reload_visible_end,
                 this,
                 &Mixer::reload_proxies_groups_end );
    disconnect ( proxies_model (),
                 &Proxies_Model::sig_reload_visible_begin,
                 this,
                 &Mixer::reload_proxies_groups_begin );
  }

  _proxies_model = model_n;

  if ( proxies_model () != nullptr ) {
    // Connect to reload signals
    connect ( proxies_model (),
              &Proxies_Model::sig_reload_visible_begin,
              this,
              &Mixer::reload_proxies_groups_begin );
    connect ( proxies_model (),
              &Proxies_Model::sig_reload_visible_end,
              this,
              &Mixer::reload_proxies_groups_end );
  }

  reload_proxies_groups_end ();
}

void
Mixer::set_inputs_setup ( const MWdg::Inputs_Setup * setup_n )
{
  _inputs_setup = setup_n;

  if ( inputs_setup () != nullptr ) {
    _act_toggle_joined.setShortcut ( inputs_setup ()->ks_toggle_joined );
    _act_level_channels.setShortcut ( inputs_setup ()->ks_level_channels );
    _act_toggle_mute.setShortcut ( inputs_setup ()->ks_mute_volumes );

    _act_level_channels.setText ( inputs_setup ()->ts_level_channels );

    _sliders_pad->set_wheel_degrees ( inputs_setup ()->wheel_degrees () );
  }
}

void
Mixer::reload_proxies_groups_begin ()
{
  // std::cout << "Mixer::reload_proxies_groups_begin\n";

  gui_state_store ();

  if ( !_proxies_groups_pass.empty () ) {
    _sliders_pad->hide ();
    _sliders_area->take_widget ();
    _sliders_pad->clear_proxies_groups ();

    _proxies_groups_pass.clear ();
  }

  if ( proxies_model () != nullptr ) {
    for ( MWdg::Mixer::Proxy::Group * pgrp :
          proxies_model ()->proxies_visible () ) {
      // Disconnect from context menu update slot
      disconnect ( pgrp,
                   &MWdg::Mixer::Proxy::Group::sig_value_change,
                   this,
                   &Mixer::context_menu_update_visible );
    }
  }
}

void
Mixer::reload_proxies_groups_end ()
{
  // std::cout << "Mixer::reload_proxies_groups_end\n";

  if ( proxies_model () != nullptr ) {
    const auto & proxies_vis = proxies_model ()->proxies_visible ();
    _proxies_groups_pass.reserve ( proxies_vis.size () );
    for ( MWdg::Mixer::Proxy::Group * pgrp : proxies_vis ) {
      // Connect to context menu update slot
      connect ( pgrp,
                &MWdg::Mixer::Proxy::Group::sig_value_change,
                this,
                &Mixer::context_menu_update_visible );
      // Append to pass list
      _proxies_groups_pass.push_back ( pgrp );
    }
  }

  if ( !_proxies_groups_pass.empty () ) {
    _sliders_pad->set_proxies_groups ( _proxies_groups_pass );
    _sliders_area->set_widget ( _sliders_pad.get () );
    _sliders_pad->show ();
  }
  updateGeometry ();

  gui_state_restore ();
}

void
Mixer::action_level_volumes ()
{
  // std::cout << "Mixer::action_level_volumes" << "\n";

  MWdg::Mixer::Proxy::Group * pgrp = _act_proxies_group;
  if ( pgrp == nullptr ) {
    return;
  }

  Wdg::Pad_Proxy::Column * pcol = nullptr;
  {
    std::size_t col_idx = 0;
    if ( _act_proxies_column < pgrp->num_columns () ) {
      col_idx = _act_proxies_column;
    }
    pcol = pgrp->column ( col_idx );
  }

  Proxy::Slider * psl = nullptr;
  if ( pcol != nullptr ) {
    psl = static_cast< Proxy::Slider * > ( pcol->slider_proxy () );
  }

  if ( psl != nullptr ) {
    pgrp->qsnd_mixer_elem ()->set_volume_all ( pgrp->snd_dir (),
                                               psl->volume_value () );
  } else {
    pgrp->qsnd_mixer_elem ()->level_volumes ( pgrp->snd_dir () );
  }
}

void
Mixer::action_toggle_joined ()
{
  // std::cout << "Mixer::action_toggle_joined\n";

  MWdg::Mixer::Proxy::Group * pgrp = _act_proxies_group;
  if ( ( pgrp != nullptr ) && ( proxies_model () != nullptr ) ) {
    proxies_model ()->toggle_joined_separated ( pgrp );
  }
}

void
Mixer::action_toggle_mute ()
{
  // std::cout << "Mixer::action_toggle_mute" << "\n";

  MWdg::Mixer::Proxy::Group * pgrp = _act_proxies_group;
  if ( pgrp != nullptr ) {
    pgrp->qsnd_mixer_elem ()->invert_switches ( pgrp->snd_dir () );
  }
}

void
Mixer::update_focus_proxies ()
{
  if ( _sliders_pad->focus_info ().has_focus ) {
    // Find focus proxies_group
    if ( proxies_model () != nullptr ) {
      const auto & proxies = proxies_model ()->proxies_visible ();
      const std::size_t idx = _sliders_pad->focus_info ().group_idx;
      if ( idx < proxies.size () ) {
        _act_proxies_group = proxies[ idx ];
        _act_proxies_column = _sliders_pad->focus_info ().column_idx;
      }
    }
  }
}

void
Mixer::gui_state_store ()
{
  if ( _act_proxies_group != nullptr ) {
    MWdg::Mixer::Proxy::Group * pgrp = _act_proxies_group;
    _proxy_id.group_id = pgrp->group_id ();
    _proxy_id.column_idx = pgrp->focus_column ();
    _proxy_id.row_idx = pgrp->focus_row ();
    _proxy_id.has_focus = _sliders_pad->focus_info ().has_focus;
  } else {
    _proxy_id.clear ();
  }

  _act_proxies_group = nullptr;
  _act_proxies_column = 0;
}

void
Mixer::gui_state_restore ()
{
  _act_proxies_group = find_visible_proxy ( _proxy_id );
  _act_proxies_column = _proxy_id.column_idx;

  // Restore focus
  if ( _proxy_id.has_focus && ( _act_proxies_group != nullptr ) ) {
    _sliders_pad->set_focus_proxy ( _act_proxies_group->group_index (),
                                    _proxy_id.column_idx,
                                    _proxy_id.row_idx );
  }

  context_menu_update_visible ();
}

MWdg::Mixer::Proxy::Group *
Mixer::find_visible_proxy ( const MWdg::Mixer::Proxy_Identifier & proxy_id_n )
{
  MWdg::Mixer::Proxy::Group * pgrp_res = nullptr;

  if ( proxy_id_n.is_valid () && ( _proxies_model != nullptr ) ) {
    for ( MWdg::Mixer::Proxy::Group * pgrp :
          _proxies_model->proxies_visible () ) {
      if ( pgrp->group_id () == proxy_id_n.group_id ) {
        pgrp_res = pgrp;
        break;
      }
    }
  }

  return pgrp_res;
}

bool
Mixer::context_menu_start ( const QPoint & pos_n )
{
  bool res = false;

  if ( !_cmenu.isVisible () && ( _sliders_pad->focus_info ().has_focus ) &&
       ( _act_proxies_group != nullptr ) ) {
    if ( context_menu_update () > 0 ) {
      _cmenu.setTitle ( _act_proxies_group->group_name () );
      _cmenu.popup ( pos_n );
      res = true;
    }
  }

  return res;
}

std::size_t
Mixer::context_menu_update ()
{
  // std::cout << "Mixer::context_menu_update\n";

  std::size_t act_vis = 0;

  MWdg::Mixer::Proxy::Group * mspg = _act_proxies_group;

  if ( mspg == nullptr ) {
    _cmenu.close ();
    return act_vis;
  }

  const QSnd::Mixer::Elem * qsme = mspg->qsnd_mixer_elem ();
  const std::size_t snd_dir = mspg->snd_dir ();

  // Update split/join and level channels actions
  {
    const bool vis_joined ( mspg->can_be_separated () );

    _act_toggle_joined.setVisible ( vis_joined );
    if ( vis_joined ) {
      ++act_vis;

      if ( inputs_setup () != nullptr ) {
        const QString * str;
        if ( mspg->is_joined () ) {
          str = &inputs_setup ()->ts_split_channels;
        } else {
          str = &inputs_setup ()->ts_join_channels;
        }
        _act_toggle_joined.setText ( *str );
      }
    }

    const bool vis_level ( ( mspg->num_sliders () > 1 ) &&
                           !qsme->volumes_equal ( snd_dir ) );

    _act_level_channels.setVisible ( vis_level );
    if ( vis_level ) {
      ++act_vis;
    }
  }

  // Update mute / unmute channels actions
  {
    std::size_t num_sw = mspg->num_switches ();

    if ( num_sw > 0 ) {
      ++act_vis;

      QString * act_txt = &_act_str_toggle_mute;
      QIcon * act_icon = &_icon_vol_med;
      const bool is_on = qsme->switch_state ( snd_dir, 0 );

      if ( is_on ) {
        act_icon = &_icon_muted;
      } else {
        act_icon = &_icon_vol_high;
      }

      if ( num_sw == 1 ) {
        if ( is_on ) {
          act_txt = &_act_str_mute[ 0 ];
        } else {
          act_txt = &_act_str_unmute[ 0 ];
        }
      } else {
        if ( qsme->switches_equal ( snd_dir ) ) {
          if ( is_on ) {
            act_txt = &_act_str_mute[ 1 ];
          } else {
            act_txt = &_act_str_unmute[ 1 ];
          }
        }
      }

      _act_toggle_mute.setText ( *act_txt );
      _act_toggle_mute.setIcon ( *act_icon );
    }

    _act_toggle_mute.setVisible ( num_sw > 0 );
  }

  if ( act_vis == 0 ) {
    _cmenu.close ();
  }

  return act_vis;
}

void
Mixer::context_menu_update_visible ()
{
  if ( _cmenu.isVisible () ) {
    context_menu_update ();
  }
}

bool
Mixer::eventFilter ( QObject * watched_n, QEvent * event_n )
{
  bool filtered = false;

  if ( watched_n == _sliders_pad.get () ) {

    if ( event_n->type () == QEvent::KeyPress ) {

      if ( inputs_setup () != nullptr ) {
        filtered = true;
        const QKeySequence kseq =
            static_cast< QKeyEvent * > ( event_n )->key ();
        // Trigger actions on a key press
        if ( kseq == inputs_setup ()->ks_toggle_joined ) {
          _act_toggle_joined.trigger ();
        } else if ( kseq == inputs_setup ()->ks_level_channels ) {
          _act_level_channels.trigger ();
        } else if ( kseq == inputs_setup ()->ks_mute_volumes ) {
          _act_toggle_mute.trigger ();
        } else {
          filtered = false;
        }
      }

    } else if ( event_n->type () == QEvent::ContextMenu ) {

      QContextMenuEvent * ev_cmenu =
          static_cast< QContextMenuEvent * > ( event_n );

      if ( context_menu_start ( ev_cmenu->globalPos () ) ) {
        filtered = true;
      }
    }
  }

  return filtered;
}

} // namespace MWdg::Mixer::Sliders
