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

#include "pad.hpp"
#include "wdg/color_methods.hpp"
#include "wdg/ds/slider.hpp"
#include "wdg/ds/switch.hpp"
#include "wdg/event_types.hpp"
#include "wdg/pad_proxy/column.hpp"
#include "wdg/pad_proxy/group.hpp"
#include "wdg/pad_proxy/slider.hpp"
#include "wdg/pad_proxy/style.hpp"
#include "wdg/pad_proxy/switch.hpp"
#include "wdg/pass_events.hpp"
#include "wdg/sliders_pad/data.hpp"
#include "wdg/sliders_pad/data_column.hpp"
#include "wdg/sliders_pad/data_group.hpp"
#include "wdg/sliders_pad/footer.hpp"
#include "wdg/sliders_pad/header.hpp"
#include "wdg/sliders_pad/layout.hpp"
#include "wdg/sliders_pad/style.hpp"
#include "wdg/style_db.hpp"
#include <QEvent>
#include <QFocusEvent>
#include <QKeyEvent>
#include <QPainter>
#include <iostream>

namespace Wdg::Sliders_Pad
{

Pad::Pad ( const Wdg::Style_Db * style_db_n,
           dpe::Image_Allocator * image_alloc_n,
           QWidget * parent_n )
: QWidget ( parent_n )
, _sp_data ( std::make_unique< Data > ( this ) )
, _sp_style ( std::make_unique< Style > ( _sp_data.get () ) )
, _style_db ( style_db_n )
, _image_alloc ( image_alloc_n )
{
  setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Expanding );

  {
    Header * lheader ( new Header ( _sp_data.get (), _sp_style.get (), this ) );

    Footer * lfooter ( new Footer ( _sp_data.get (), _sp_style.get (), this ) );

    _sp_data->header = lheader;
    _sp_data->footer = lfooter;
    _sp_data->header_data = &lheader->hd_data ();
    _sp_data->footer_data = &lfooter->hd_data ();

    connect ( lheader,
              SIGNAL ( sig_label_selected ( unsigned int, unsigned int ) ),
              this,
              SLOT ( header_label_selected ( unsigned int, unsigned int ) ) );

    connect ( lfooter,
              SIGNAL ( sig_label_selected ( unsigned int, unsigned int ) ),
              this,
              SLOT ( footer_label_selected ( unsigned int, unsigned int ) ) );
  }

  connect ( _style_db,
            &Wdg::Style_Db::sig_palette_changed,
            this,
            &Pad::style_palette_changed );

  update_colors ();
}

Pad::~Pad ()
{
  clear_proxies_groups ();
}

QWidget *
Pad::header ()
{
  return _sp_data->header;
}

QWidget *
Pad::footer ()
{
  return _sp_data->footer;
}

Header *
Pad::header_cast ()
{
  return _sp_data->header_cast ();
}

Footer *
Pad::footer_cast ()
{
  return _sp_data->footer_cast ();
}

Header_Data *
Pad::header_data ()
{
  return _sp_data->header_data;
}

Header_Data *
Pad::footer_data ()
{
  return _sp_data->footer_data;
}

// Show footer

bool
Pad::footer_visible () const
{
  return _sp_data->show_value_labels;
}

void
Pad::set_footer_visible ( bool flag_n )
{
  if ( _sp_data->show_value_labels != flag_n ) {
    _sp_data->show_value_labels = flag_n;
    _sp_data->footer->setVisible ( flag_n );
  }
}

void
Pad::set_wheel_degrees ( std::size_t delta_n )
{
  if ( ( delta_n == 0 ) || ( wheel_degrees () == delta_n ) ) {
    return;
  }

  _wheel_degrees = delta_n;

  std::size_t num = num_widgets ();
  for ( std::size_t ii = 0; ii < num; ++ii ) {
    Wdg::DS::Slider * slider (
        dynamic_cast< Wdg::DS::Slider * > ( widget ( ii ) ) );
    if ( slider != nullptr ) {
      slider->set_wheel_degrees ( wheel_degrees () );
    }
  }
}

void
Pad::clear_widgets ()
{
  if ( layout () != nullptr ) {
    delete layout ();
  }

  for ( std::size_t ii = 0; ii < _proxies_groups.size (); ++ii ) {
    Wdg::Pad_Proxy::Group * grp ( _proxies_groups[ ii ] );
    grp->set_pad ( nullptr );
    for ( std::size_t jj = 0; jj < grp->num_columns (); ++jj ) {
      Wdg::Pad_Proxy::Column * col ( grp->column ( jj ) );
      if ( col->slider_proxy () != nullptr ) {
        col->slider_proxy ()->set_widget ( nullptr );
      }
      if ( col->switch_proxy () != nullptr ) {
        col->switch_proxy ()->set_widget ( nullptr );
      }
    }
  }

  // Clear widgets
  _widgets.clear ();
  header_data ()->labels.clear ();
  footer_data ()->labels.clear ();
  // Clear data groups
  _sp_data->clear_groups ();
}

void
Pad::create_widgets ()
{
  const std::size_t num_groups = _proxies_groups.size ();
  if ( num_groups == 0 ) {
    return;
  }

  if ( layout () != nullptr ) {
    delete layout ();
  }

  // Shared data setup
  _sp_data->reserve_groups ( num_groups );
  std::size_t num_columns_total = 0;
  std::size_t col_total_idx = 0;
  for ( std::size_t ii = 0; ii < num_groups; ++ii ) {
    Wdg::Pad_Proxy::Group * sppg = proxies_group ( ii );
    Data_Group * sp_grp = new Data_Group ( _sp_data.get () );

    sp_grp->sppg = sppg;
    sp_grp->num_sliders = sppg->num_sliders ();
    sp_grp->num_switches = sppg->num_switches ();
    const std::size_t num_cols = sppg->num_columns ();
    sp_grp->reserve_columns ( num_cols );
    for ( std::size_t jj = 0; jj < num_cols; ++jj ) {
      Wdg::Pad_Proxy::Column * sppc = sppg->column ( jj );
      Data_Column * sp_col = new Data_Column ( sp_grp );

      if ( sppc->show_value_string () && sppc->has_slider () ) {
        connect ( sppc,
                  SIGNAL ( sig_value_string_changed () ),
                  sp_col,
                  SLOT ( update_footer_label () ) );
      }

      sp_col->col_idx = jj;
      sp_col->col_total_idx = col_total_idx;
      sp_col->show_value_label = sppc->show_value_string ();
      sp_col->sppc = sppc;

      sp_grp->append_column ( sp_col );
      ++col_total_idx;
    }
    _sp_data->append_group ( sp_grp );
    num_columns_total += num_cols;
  }

  // Header and footer setup
  header_data ()->labels.resize ( num_groups );
  header_data ()->update_elided_texts = true;

  footer_data ()->labels.resize ( num_columns_total );
  footer_data ()->update_elided_texts = true;

  col_total_idx = 0;
  for ( std::size_t ii = 0; ii < num_groups; ++ii ) {
    Wdg::Pad_Proxy::Group * sppg = proxies_group ( ii );

    // Setup header labels variables
    {
      Header_Label & lbl = header_data ()->labels[ ii ];
      lbl.group_idx = ii;
      lbl.column_idx = 0;
      lbl.column_total_idx = ii;
      lbl.text = sppg->group_name ();
      lbl.tool_tip = sppg->tool_tip ();
      if ( style_db () != nullptr ) {
        lbl.col_fg =
            style_db ()->color ( sppg->style_id (), QPalette::WindowText );
      } else {
        lbl.col_fg = palette ().color ( QPalette::WindowText );
      }
      lbl.label_length_max =
          header_cast ()->label_str_length_px_max ( sppg->group_name () );
    }

    // Setup footer labels variables
    const std::size_t num_cols = sppg->num_columns ();
    for ( std::size_t jj = 0; jj < num_cols; ++jj ) {
      Wdg::Pad_Proxy::Column * sppc = sppg->column ( jj );
      Header_Label & lbl = footer_data ()->labels[ col_total_idx ];

      lbl.group_idx = ii;
      lbl.column_idx = jj;
      lbl.column_total_idx = col_total_idx;
      lbl.tool_tip = sppg->tool_tip ();
      if ( style_db () != nullptr ) {
        lbl.col_fg =
            style_db ()->color ( sppg->style_id (), QPalette::WindowText );
      } else {
        lbl.col_fg = palette ().color ( QPalette::WindowText );
      }
      {
        unsigned int llength[ 2 ] = { 0, 0 };
        llength[ 0 ] = footer_cast ()->label_str_length_px_max (
            sppc->value_min_string () );
        llength[ 1 ] = footer_cast ()->label_str_length_px_max (
            sppc->value_max_string () );
        lbl.label_length_max = qMax ( llength[ 0 ], llength[ 1 ] );
      }
      if ( sppc->show_value_string () ) {
        lbl.text = sppc->value_string ();
      }
      ++col_total_idx;
    }
  }

  // Inputs area layout and widget creation
  Layout * sp_layout = new Layout ( _sp_data.get () );
  sp_layout->setContentsMargins ( 0, 0, 0, 0 );
  sp_layout->set_header_widget ( header () );
  sp_layout->set_footer_widget ( footer () );

  for ( std::size_t gii = 0; gii < num_groups; ++gii ) {

    Wdg::Pad_Proxy::Group * sppg ( proxies_group ( gii ) );
    sppg->set_pad ( this );
    sppg->set_group_index ( gii );

    // Add widgets to the widgets groups
    for ( std::size_t cii = 0; cii < sppg->num_columns (); ++cii ) {
      Wdg::Pad_Proxy::Column * sppc ( sppg->column ( cii ) );

      // Slider widget
      if ( sppc->has_slider () ) {
        Wdg::Pad_Proxy::Slider * spps ( sppc->slider_proxy () );

        Wdg::DS::Slider * sl_wdg =
            new Wdg::DS::Slider ( style_db (), _image_alloc );
        sl_wdg->set_maximum_index ( spps->slider_index_max () );
        sl_wdg->set_current_index ( spps->slider_index () );
        sl_wdg->setToolTip ( spps->tool_tip () );
        sl_wdg->setEnabled ( spps->is_enabled () );
        sl_wdg->set_wheel_degrees ( wheel_degrees () );
        sl_wdg->set_style_id ( spps->style_id () );

        if ( spps->style () != 0 ) {
          Wdg::DS::Slider_Meta_Bg & meta_bg ( sl_wdg->meta_bg () );
          meta_bg.bg_show_image = spps->style ()->slider_has_minimum;
          meta_bg.bg_tick_min_idx = spps->style ()->slider_minimum_idx;
        }

        connect ( spps,
                  SIGNAL ( sig_enabled_changed ( bool ) ),
                  sl_wdg,
                  SLOT ( setEnabled ( bool ) ) );

        connect ( sl_wdg,
                  SIGNAL ( sig_current_index_changed ( unsigned long ) ),
                  spps,
                  SLOT ( set_slider_index ( unsigned long ) ) );

        connect ( spps,
                  SIGNAL ( sig_slider_index_changed ( unsigned long ) ),
                  sl_wdg,
                  SLOT ( set_current_index ( unsigned long ) ) );

        connect ( spps,
                  SIGNAL ( sig_slider_index_max_changed ( unsigned long ) ),
                  sl_wdg,
                  SLOT ( set_maximum_index ( unsigned long ) ) );

        spps->set_widget ( sl_wdg );
        sp_layout->add_group_widget ( sl_wdg, gii, cii, 0 );
        _widgets.emplace_back ( sl_wdg );
      }

      // Switch widget
      if ( sppc->has_switch () ) {
        Wdg::Pad_Proxy::Switch * spps ( sppc->switch_proxy () );

        Wdg::DS::Switch * sw_wdg =
            new Wdg::DS::Switch ( style_db (), _image_alloc );

        sw_wdg->setChecked ( spps->switch_state () );
        sw_wdg->setToolTip ( spps->tool_tip () );
        sw_wdg->setEnabled ( spps->is_enabled () );
        sw_wdg->set_variant_id ( spps->variant_id () );
        sw_wdg->set_style_id ( spps->style_id () );

        connect ( spps,
                  SIGNAL ( sig_enabled_changed ( bool ) ),
                  sw_wdg,
                  SLOT ( setEnabled ( bool ) ) );

        connect ( sw_wdg,
                  SIGNAL ( toggled ( bool ) ),
                  spps,
                  SLOT ( set_switch_state ( bool ) ) );

        connect ( spps,
                  SIGNAL ( sig_switch_state_changed ( bool ) ),
                  sw_wdg,
                  SLOT ( setChecked ( bool ) ) );

        spps->set_widget ( sw_wdg );
        sp_layout->add_group_widget ( sw_wdg, gii, cii, 1 );
        _widgets.emplace_back ( sw_wdg );
      }
    }
  }

  setLayout ( sp_layout );
}

void
Pad::set_proxies_groups (
    const std::vector< Wdg::Pad_Proxy::Group * > & list_n )
{
  bool do_update ( false );
  if ( _proxies_groups.size () > 0 ) {
    clear_widgets ();
    do_update = true;
  }

  _proxies_groups = list_n;

  if ( _proxies_groups.size () > 0 ) {
    create_widgets ();
    do_update = true;
  }

  if ( do_update ) {
    _update_decoration = true;
    header_data ()->update_decoration = true;
    footer_data ()->update_decoration = true;
    update ();
  }
}

void
Pad::clear_proxies_groups ()
{
  if ( _proxies_groups.size () > 0 ) {
    set_proxies_groups ( std::vector< Wdg::Pad_Proxy::Group * > () );
  }
}

void
Pad::style_palette_changed ( unsigned int style_id_n [[maybe_unused]] )
{
  update_colors ();
  update ();
}

void
Pad::update_colors ()
{
  {
    const QPalette & pal ( palette () );
    const QColor col_bg ( pal.color ( QPalette::Button ) );
    const QColor col_fg ( pal.color ( QPalette::ButtonText ) );
    QColor col = Wdg::col_mix ( col_bg, col_fg, 1, 1 );

    _sp_style->stem_pen.setColor ( col );
  }

  std::size_t col_total_idx = 0;
  const std::size_t num_groups = _proxies_groups.size ();
  for ( std::size_t ii = 0; ii < num_groups; ++ii ) {
    Wdg::Pad_Proxy::Group * sppg = proxies_group ( ii );

    // Update header label colors
    {
      Header_Label & lbl = header_data ()->labels[ ii ];
      if ( style_db () != nullptr ) {
        lbl.col_fg =
            style_db ()->color ( sppg->style_id (), QPalette::WindowText );
      } else {
        lbl.col_fg = palette ().color ( QPalette::WindowText );
      }
    }

    // Setup footer labels colors
    const std::size_t num_cols = sppg->num_columns ();
    for ( std::size_t jj = 0; jj < num_cols; ++jj ) {
      Header_Label & lbl = footer_data ()->labels[ col_total_idx ];
      if ( style_db () != nullptr ) {
        lbl.col_fg =
            style_db ()->color ( sppg->style_id (), QPalette::WindowText );
      } else {
        lbl.col_fg = palette ().color ( QPalette::WindowText );
      }
      ++col_total_idx;
    }
  }
}

bool
Pad::set_focus_proxy ( unsigned int group_idx_n )
{
  return set_focus_proxy ( group_idx_n, 0, 0 );
}

bool
Pad::set_focus_proxy ( unsigned int group_idx_n,
                       unsigned int column_idx_n,
                       unsigned int row_idx_n )
{
  bool success ( false );
  const unsigned int num_groups ( _proxies_groups.size () );
  if ( group_idx_n < num_groups ) {
    Wdg::Pad_Proxy::Group * sppg ( proxies_group ( group_idx_n ) );
    success = sppg->set_focus ( column_idx_n, row_idx_n );
  }
  return success;
}

void
Pad::header_label_selected ( unsigned int group_idx_n,
                             unsigned int column_idx_n )
{
  set_focus_proxy ( group_idx_n, column_idx_n, 0 );
}

void
Pad::footer_label_selected ( unsigned int group_idx_n,
                             unsigned int column_idx_n )
{
  if ( set_focus_proxy ( group_idx_n, column_idx_n, 0 ) ) {
    Q_EMIT sig_footer_label_selected ( group_idx_n, column_idx_n );
  }
}

bool
Pad::event ( QEvent * event_n )
{
  if ( event_n->type () == Wdg::evt_pass_event_focus ) {
    Wdg::Pass_Event_Focus * ev_fp (
        static_cast< Wdg::Pass_Event_Focus * > ( event_n ) );

    _focus_info.clear ();
    if ( ev_fp->ev_focus.gotFocus () &&
         ( ev_fp->group_idx < num_proxies_groups () ) ) {
      _focus_info.has_focus = true;
      _focus_info.group_idx = ev_fp->group_idx;
      _focus_info.column_idx = ev_fp->column_idx;
      _focus_info.row_idx = ev_fp->row_idx;
    }

    { // Update header focus
      unsigned int group_idx ( ~0 );
      unsigned int col_idx ( ~0 );
      if ( _focus_info.has_focus ) {
        group_idx = _focus_info.group_idx;
        col_idx = _focus_info.column_idx;
      }
      header_cast ()->set_focus_idx ( group_idx, col_idx );
      footer_cast ()->set_focus_idx ( group_idx, col_idx );
    }

    Q_EMIT sig_focus_changed ();
    update ();

    return true;
  }

  return QWidget::event ( event_n );
}

void
Pad::changeEvent ( QEvent * event_n )
{
  QWidget::changeEvent ( event_n );

  switch ( event_n->type () ) {
  case QEvent::StyleChange:
  case QEvent::PaletteChange:
    update_colors ();
    update ();
    break;
  default:
    break;
  }
}

void
Pad::resizeEvent ( QResizeEvent * event )
{
  // std::cout << "Resize event " << width() << ":" << height() << "\n";
  QWidget::resizeEvent ( event );
  _update_decoration = true;
  header_data ()->update_decoration = true;
  footer_data ()->update_decoration = true;
}

void
Pad::paintEvent ( QPaintEvent * event )
{
  QWidget::paintEvent ( event );

  if ( _update_decoration ) {
    _update_decoration = false;
    _sp_style->paint_base_decoration ();
  }

  {
    QPainter painter ( this );
    painter.setRenderHints ( QPainter::Antialiasing |
                             QPainter::TextAntialiasing |
                             QPainter::SmoothPixmapTransform );

    // Debug area painting
    // painter.setBrush ( Qt::yellow );
    // painter.setPen ( Qt::NoPen );
    // painter.drawRect ( rect() );

    for ( std::size_t gii = 0; gii < _sp_data->num_groups (); ++gii ) {
      painter.drawPicture ( 0, 0, _sp_data->group ( gii )->center_pic );
    }
  }
}

} // namespace Wdg::Sliders_Pad
