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

#include "header.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/style.hpp"
#include <QMouseEvent>
#include <QPainter>
#include <QPicture>
#include <cmath>
#include <iostream>

namespace Wdg::Sliders_Pad
{

Header::Header ( Data * sp_data_n, Style * sp_style_n, QWidget * parent_n )
: QWidget ( parent_n )
, _sp_data ( sp_data_n )
, _sp_style ( sp_style_n )
, _focus_idx ( ~0 )
, _weak_focus_idx ( ~0 )
, _invalid_idx ( ~0 )
{
  hd_data ().max_str_length_px = fontMetrics ().averageCharWidth () * 18;
  hd_data ().widget = this;

  setMouseTracking ( true );

  int max_h ( hd_data ().spacing_vertical );
  max_h += hd_data ().pad_left;
  max_h += hd_data ().pad_right;
  max_h += hd_data ().max_str_length_px;
  setMaximumHeight ( max_h );

  update_painter_states ();
}

Header::~Header () {}

QSize
Header::minimumSizeHint () const
{
  QSize res ( 64, 0 );
  res.rheight () += hd_data ().spacing_vertical;
  res.rheight () += fontMetrics ().height () * 5 / 2;

  return res;
}

QSize
Header::sizeHint () const
{
  return minimumSizeHint ();
}

unsigned int
Header::label_str_length_px_max ( const QString & str_n ) const
{
  unsigned int res = fontMetrics ().boundingRect ( str_n ).width ();
  res += 2; // Add a few pixel to ensure the string fits
  res = qMin ( res, hd_data ().max_str_length_px );
  return res;
}

void
Header::set_focus_idx ( unsigned int group_idx_n, unsigned int column_idx_n )
{
  unsigned int idx ( _invalid_idx );
  if ( group_idx_n < sp_data ()->num_groups () ) {
    const Data_Group * sp_grp ( sp_data ()->group ( group_idx_n ) );
    if ( hd_data ().column_labels ) {
      if ( column_idx_n < sp_grp->num_columns () ) {
        const Data_Column * sp_col ( sp_grp->column ( column_idx_n ) );
        idx = sp_col->col_total_idx;
      }
    } else {
      idx = group_idx_n;
    }

    if ( idx >= hd_data ().labels.size () ) {
      idx = _invalid_idx;
    }
  }
  _focus_idx = idx;
}

void
Header::set_label_text ( unsigned int lbl_idx_n, const QString & txt_n )
{
  if ( lbl_idx_n < hd_data ().labels.size () ) {
    Header_Label & lbl ( hd_data ().labels[ lbl_idx_n ] );
    lbl.text = txt_n;
    elided_label_text ( lbl );
    update ( lbl.label_txt_bbox );
  }
}

void
Header::enterEvent ( QEnterEvent * )
{
  if ( _weak_focus_idx < hd_data ().labels.size () ) {
    _weak_focus_idx = _invalid_idx;
    update ();
  }
}

void
Header::leaveEvent ( QEvent * )
{
  if ( _weak_focus_idx < hd_data ().labels.size () ) {
    _weak_focus_idx = _invalid_idx;
    update ();
  }
}

void
Header::mousePressEvent ( QMouseEvent * event_n )
{
  const Header_Label * lbl ( find_label ( event_n->pos () ) );
  if ( lbl != 0 ) {
    Q_EMIT sig_label_selected ( lbl->group_idx, lbl->column_idx );
  }
}

void
Header::mouseMoveEvent ( QMouseEvent * event_n )
{
  const Header_Label * lbl ( find_label ( event_n->pos () ) );

  bool changed ( false );
  {
    unsigned int idx ( _invalid_idx );
    if ( lbl != 0 ) {
      idx = lbl->column_total_idx;
    }
    if ( idx != _weak_focus_idx ) {
      _weak_focus_idx = idx;
      changed = true;
    }
  }

  if ( changed ) {
    Qt::CursorShape cursor_shape ( Qt::ArrowCursor );
    if ( lbl == 0 ) {
      setToolTip ( QString () );
    } else {
      setToolTip ( lbl->tool_tip );
      if ( ( event_n->buttons () & Qt::LeftButton ) != 0 ) {
        if ( hd_data ().label_sliding ) {
          Q_EMIT sig_label_selected ( lbl->group_idx, lbl->column_idx );
        }
      }
      cursor_shape = Qt::PointingHandCursor;
    }

    {
      // Adjust the cursor
      QCursor cursor_new ( cursor () );
      if ( cursor_new.shape () != cursor_shape ) {
        cursor_new.setShape ( cursor_shape );
        setCursor ( cursor_new );
      }
    }
    update ();
  }
}

void
Header::changeEvent ( QEvent * event_n )
{
  QWidget::changeEvent ( event_n );
  update_painter_states ();
  update ();
}

void
Header::paintEvent ( QPaintEvent * event_n )
{
  // std::cout << "Header::paintEvent" << "\n";
  QWidget::paintEvent ( event_n );

  // Update elided text strings on demand
  if ( hd_data ().update_elided_texts ) {
    hd_data ().update_elided_texts = false;
    update_elided_texts ();
  }

  if ( hd_data ().update_decoration ) {
    hd_data ().update_decoration = false;
    if ( hd_data ().upside_down ) {
      sp_style ()->paint_footer_decoration ();
    } else {
      sp_style ()->paint_header_decoration ();
    }
  }

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

  // Draw debug area
  {
    // painter.setBrush ( hd_data().upside_down ? Qt::cyan : Qt::yellow );
    // painter.setPen ( Qt::NoPen );
    // painter.drawRect ( rect() );
  }

  if ( hd_data ().labels.size () > 0 ) {
    paint_label_rects ( pnt );
    paint_label_decos ( pnt );
    paint_label_texts ( pnt );
  }
}

void
Header::paint_label_rects ( QPainter & pnt_n )
{
  pnt_n.setPen ( Qt::NoPen );

  if ( _lbl_rect_brush[ 0 ].style () != Qt::NoBrush ) {
    pnt_n.setBrush ( _lbl_rect_brush[ 0 ] );
    for ( std::size_t lii = 0; lii < hd_data ().labels.size (); ++lii ) {
      // Draw focus items later
      if ( ( lii != _focus_idx ) && ( lii != _weak_focus_idx ) ) {
        const Header_Label & lbl ( hd_data ().labels[ lii ] );
        pnt_n.setTransform ( lbl.label_trans );
        pnt_n.drawRect ( lbl.label_rect );
      }
    }
  }

  if ( ( _focus_idx < hd_data ().labels.size () ) &&
       ( _lbl_rect_brush[ 1 ].style () != Qt::NoBrush ) ) {
    pnt_n.setBrush ( _lbl_rect_brush[ 1 ] );
    const Header_Label & lbl ( hd_data ().labels[ _focus_idx ] );
    pnt_n.setTransform ( lbl.label_trans );
    pnt_n.drawRect ( lbl.label_rect );
  }

  if ( ( _weak_focus_idx < hd_data ().labels.size () ) &&
       ( _lbl_rect_brush[ 2 ].style () != Qt::NoBrush ) ) {
    pnt_n.setBrush ( _lbl_rect_brush[ 2 ] );
    const Header_Label & lbl ( hd_data ().labels[ _weak_focus_idx ] );
    pnt_n.setTransform ( lbl.label_trans );
    pnt_n.drawRect ( lbl.label_rect );
  }
}

void
Header::paint_label_decos ( QPainter & pnt_n )
{
  pnt_n.setPen ( Qt::NoPen );
  pnt_n.setBrush ( Qt::NoBrush );
  pnt_n.resetTransform ();

  // Draw groups/columns decoration graphics
  const unsigned int pic_idx ( hd_data ().upside_down ? 1 : 0 );
  for ( std::size_t gii = 0; gii < sp_data ()->num_groups (); ++gii ) {
    // Group picture
    const Data_Group * sp_grp ( sp_data ()->group ( gii ) );
    pnt_n.drawPicture ( 0, 0, sp_grp->hd_pics[ pic_idx ] );

    // Columns pictures
    for ( std::size_t cii = 0; cii < sp_grp->num_columns (); ++cii ) {
      const Data_Column * sp_col ( sp_grp->column ( cii ) );
      pnt_n.drawPicture ( 0, 0, sp_col->hd_pics[ pic_idx ] );
    }
  }
}

void
Header::paint_label_texts ( QPainter & pnt_n )
{
  Qt::Alignment txt_align ( Qt::AlignLeft | Qt::AlignVCenter );
  if ( hd_data ().upside_down ) {
    txt_align = Qt::AlignRight | Qt::AlignVCenter;
  }

  pnt_n.setPen ( _lbl_txt_pen[ 0 ] );
  pnt_n.setBrush ( Qt::NoBrush );
  pnt_n.setFont ( _lbl_txt_font[ 0 ] );

  for ( std::size_t lii = 0; lii < hd_data ().labels.size (); ++lii ) {
    // Draw focus items later
    if ( ( lii != _focus_idx ) && ( lii != _weak_focus_idx ) ) {
      const Header_Label & lbl ( hd_data ().labels[ lii ] );

      { // Pen color
        const QPen & ppen = pnt_n.pen ();
        if ( lbl.col_fg != ppen.color () ) {
          QPen npen ( ppen );
          npen.setColor ( lbl.col_fg );
          pnt_n.setPen ( npen );
        }
      }

      // Draw text
      pnt_n.setTransform ( lbl.label_trans );
      pnt_n.drawText ( lbl.text_rect, txt_align, lbl.text_elided );
    }
  }

  if ( _weak_focus_idx != _focus_idx ) {
    paint_label_text ( pnt_n, txt_align, _focus_idx, 1 );
    paint_label_text ( pnt_n, txt_align, _weak_focus_idx, 2 );
  } else {
    paint_label_text ( pnt_n, txt_align, _focus_idx, 3 );
  }
}

void
Header::paint_label_text ( QPainter & pnt_n,
                           Qt::Alignment txt_align_n,
                           unsigned int lbl_idx_n,
                           unsigned int state_idx_n )
{
  if ( lbl_idx_n < hd_data ().labels.size () ) {
    pnt_n.setFont ( _lbl_txt_font[ state_idx_n ] );
    pnt_n.setPen ( _lbl_txt_pen[ state_idx_n ] );

    const Header_Label & lbl ( hd_data ().labels[ lbl_idx_n ] );
    pnt_n.setTransform ( lbl.label_trans );
    pnt_n.drawText ( lbl.text_rect, txt_align_n, lbl.text_elided );
  }
}

void
Header::update_painter_states ()
{
  _lbl_rect_brush[ 0 ].setStyle ( Qt::NoBrush );
  _lbl_rect_brush[ 1 ].setStyle ( Qt::SolidPattern );
  if ( hd_data ().upside_down ) {
    _lbl_rect_brush[ 1 ].setColor ( palette ().color ( QPalette::Button ) );
  } else {
    _lbl_rect_brush[ 1 ].setColor ( palette ().color ( QPalette::Highlight ) );
  }
  _lbl_rect_brush[ 2 ].setStyle ( Qt::NoBrush );

  const QFont fnt = font ();
  _lbl_txt_font[ 0 ] = fnt;
  _lbl_txt_font[ 1 ] = fnt;
  _lbl_txt_font[ 2 ] = fnt;
  _lbl_txt_font[ 2 ].setUnderline ( !fnt.underline () );
  _lbl_txt_font[ 3 ] = _lbl_txt_font[ 2 ];

  QPen pen;
  pen.setColor ( palette ().color ( QPalette::WindowText ) );
  _lbl_txt_pen[ 0 ] = pen;
  _lbl_txt_pen[ 1 ] = pen;
  if ( hd_data ().upside_down ) {
    _lbl_txt_pen[ 1 ].setColor ( palette ().color ( QPalette::ButtonText ) );
  } else {
    _lbl_txt_pen[ 1 ].setColor (
        palette ().color ( QPalette::HighlightedText ) );
  }
  _lbl_txt_pen[ 2 ] = pen;
  _lbl_txt_pen[ 3 ] = _lbl_txt_pen[ 1 ];
}

const Header_Label *
Header::find_label ( const QPoint & pos_n )
{
  const Header_Label * res = nullptr;

  const QPointF pos_f ( pos_n );
  const std::size_t num_lbl = hd_data ().labels.size ();
  std::size_t lbl_idx ( num_lbl );
  for ( std::size_t lii = 0; lii < num_lbl; ++lii ) {
    --lbl_idx;

    const Header_Label & lbl ( hd_data ().labels[ lbl_idx ] );
    const Data_Group * sp_grp ( sp_data ()->group ( lbl.group_idx ) );

    if ( pos_n.x () >= (int)sp_grp->group_pos ) {
      const QPointF pos_map ( lbl.label_trans_inv.map ( pos_f ) );
      QRectF rect_f ( lbl.label_rect );
      rect_f.adjust ( 0, -1.5, 0, 3.0 );
      if ( rect_f.contains ( pos_map ) ) {
        res = &lbl;
        break;
      }
    }
  }

  return res;
}

void
Header::elided_label_text ( Header_Label & lbl_n )
{
  const QFontMetrics & fmet ( fontMetrics () );
  lbl_n.text_elided =
      fmet.elidedText ( lbl_n.text, Qt::ElideRight, lbl_n.text_area.width () );

  // Update text rectangle
  lbl_n.text_rect = lbl_n.text_area;
  if ( lbl_n.text_rect.width () > lbl_n.label_length_max ) {
    lbl_n.text_rect.setWidth ( lbl_n.label_length_max );
  }
}

void
Header::update_elided_texts ()
{
  for ( auto & label : hd_data ().labels ) {
    elided_label_text ( label );
  }
}

} // namespace Wdg::Sliders_Pad
