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

#include "mixer.hpp"
#include "mstate/mixer/controls.hpp"
#include "mstate/mixer/state.hpp"
#include "mwdg/event_types.hpp"
#include "mwdg/inputs_setup.hpp"
#include "mwdg/mixer/setup.hpp"
#include "mwdg/mixer/switches/proxies_model.hpp"
#include "mwdg/mixer/switches/proxy/group.hpp"
#include "qsnd/mixer/elem.hpp"
#include "qsnd/mixer/mixer.hpp"
#include "wdg/pad_proxy/column.hpp"
#include "wdg/pad_proxy/group.hpp"
#include "wdg/switches_pad/pad.hpp"
#include "wdg/switches_pad/scroll_area.hpp"
#include <QContextMenuEvent>
#include <QEvent>
#include <QKeyEvent>
#include <QVBoxLayout>
#include <iostream>

namespace MWdg::Mixer::Switches
{

Mixer::Mixer ( const Wdg::Style_Db * wdg_style_db_n,
               dpe::Image_Allocator * image_alloc_n,
               QWidget * parent_n )
: QWidget ( parent_n )
, _act_toggle_joined ( this )
{
  _switches_area = std::make_unique< Wdg::Switches_Pad::Scroll_Area > ();
  _switches_area->setFrameStyle ( QFrame::NoFrame );

  _switches_pad = std::make_unique< Wdg::Switches_Pad::Pad > ( wdg_style_db_n,
                                                               image_alloc_n );
  _switches_pad->hide ();
  _switches_pad->setAutoFillBackground ( false );
  _switches_pad->installEventFilter ( this );

  connect ( _switches_pad.get (),
            &Wdg::Switches_Pad::Pad::sig_focus_changed,
            this,
            &Mixer::update_focus_proxies );

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

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

  // Context menu
  _cmenu.addAction ( &_act_toggle_joined );

  // Center widget layout
  {
    QVBoxLayout * lay_vbox = new QVBoxLayout ();
    lay_vbox->addWidget ( _switches_area.get () );
    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;
}

void
Mixer::set_proxies_model ( MWdg::Mixer::Switches::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 );
  }
}

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

  gui_state_store ();

  if ( !_proxies_groups_pass.empty () ) {
    _switches_pad->hide ();
    _switches_area->take_widget ();
    _switches_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 () ) {
    _switches_pad->set_proxies_groups ( _proxies_groups_pass );
    _switches_area->set_widget ( _switches_pad.get () );
    _switches_pad->show ();
  }
  updateGeometry ();

  gui_state_restore ();
}

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

  if ( _act_proxies_group != nullptr ) {
    _proxies_model->toggle_joined_separated ( _act_proxies_group );
  }
}

void
Mixer::update_focus_proxies ()
{
  if ( _switches_pad->focus_info ().has_focus ) {
    // Find focus proxies_group
    const auto & proxies = proxies_model ()->proxies_visible ();
    const std::size_t idx = _switches_pad->focus_info ().group_idx;
    if ( idx < proxies.size () ) {
      _act_proxies_group = proxies[ idx ];
      _act_proxies_column = _switches_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.has_focus = _switches_pad->focus_info ().has_focus;
  } else {
    _proxy_id.clear ();
  }
}

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 ) {
    if ( _act_proxies_group != nullptr ) {
      _switches_pad->set_focus_proxy ( _act_proxies_group->group_index (),
                                       _proxy_id.column_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 * mspg_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 ) {
        mspg_res = pgrp;
        break;
      }
    }
  }

  return mspg_res;
}

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

  if ( !_cmenu.isVisible () && ( _switches_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::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 ();

  // Split/Join and level channels
  {
    const bool vis_separate = ( qsme->num_channels ( mspg->snd_dir () ) > 1 );

    _act_toggle_joined.setVisible ( vis_separate );
    if ( vis_separate ) {
      if ( inputs_setup () != 0 ) {
        const QString * str;
        if ( mspg->num_columns () <= 1 ) {
          str = &inputs_setup ()->ts_split_channels;
        } else {
          str = &inputs_setup ()->ts_join_channels;
        }
        _act_toggle_joined.setText ( *str );
      }
      ++act_vis;
    }
  }

  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 == _switches_pad.get () ) {
    if ( event_n->type () == QEvent::KeyPress ) {
      if ( inputs_setup () != nullptr ) {
        filtered = true;
        const QKeySequence kseq (
            static_cast< QKeyEvent * > ( event_n )->key () );
        if ( kseq == inputs_setup ()->ks_toggle_joined ) {
          _act_toggle_joined.trigger ();
        } else {
          filtered = false;
        }
      }
    } else if ( event_n->type () == QEvent::ContextMenu ) {
      // std::cout << "Mixer::eventFilter: QContextMenuEvent\n";
      QContextMenuEvent * ev_cmenu (
          static_cast< QContextMenuEvent * > ( event_n ) );

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

  return filtered;
}

} // namespace MWdg::Mixer::Switches
