/*
 * Strawberry Music Player
 * This file was part of Clementine.
 * Copyright 2010, David Sansome <me@davidsansome.com>
 * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
 *
 * Strawberry is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Strawberry is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <algorithm>
#include <numeric>

#include <QtGlobal>
#include <QWidget>
#include <QHeaderView>
#include <QAbstractItemModel>
#include <QIODevice>
#include <QList>
#include <QByteArray>
#include <QDataStream>
#include <QResizeEvent>
#include <QMouseEvent>

#include "stretchheaderview.h"

namespace {
constexpr int kMagicNumber = 0x502C9510;
}

StretchHeaderView::StretchHeaderView(const Qt::Orientation orientation, QWidget *parent)
    : QHeaderView(orientation, parent),
      stretch_enabled_(false),
      in_mouse_move_event_(false),
      forced_resize_logical_index_(-1) {

  setDefaultSectionSize(100);
  setMinimumSectionSize(30);
  setTextElideMode(Qt::ElideRight);

  QObject::connect(this, &StretchHeaderView::sectionResized, this, &StretchHeaderView::SectionResized);

}

void StretchHeaderView::setModel(QAbstractItemModel *model) {

  QHeaderView::setModel(model);

  if (stretch_enabled_) {
    column_widths_.resize(count());
    std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count());
  }

}

void StretchHeaderView::resizeEvent(QResizeEvent *e) {

  QHeaderView::resizeEvent(e);

  if (stretch_enabled_) {
    ResizeSections();
  }

}

void StretchHeaderView::mouseMoveEvent(QMouseEvent *e) {

  in_mouse_move_event_ = true;
  QHeaderView::mouseMoveEvent(e);
  in_mouse_move_event_ = false;

}

QByteArray StretchHeaderView::SaveState() const {

  QList<int> visual_indices;
  QList<int> column_pixel_widths;
  QList<int> columns_visible;
  visual_indices.reserve(count());
  column_pixel_widths.reserve(count());
  for (int i = 0; i < count(); ++i) {
    visual_indices << logicalIndex(i);
    column_pixel_widths << sectionSize(i);
    if (!isSectionHidden(i)) {
      columns_visible << i;
    }
  }

  QByteArray state;
  QDataStream s(&state, QIODevice::WriteOnly);
  s.setVersion(QDataStream::Qt_5_12);
  s << kMagicNumber;
  s << stretch_enabled_;
  s << visual_indices;
  s << column_pixel_widths;
  s << columns_visible;
  s << column_widths_;
  s << static_cast<int>(sortIndicatorOrder());
  s << sortIndicatorSection();

  return state;

}

bool StretchHeaderView::RestoreState(const QByteArray &state) {

  if (state.isEmpty()) return false;

  int magic_number = 0;
  QList<int> visual_indices;
  QList<int> column_pixel_widths;
  QList<int> columns_visible;
  int sort_indicator_order = Qt::AscendingOrder;
  int sort_indicator_section = 0;

  QDataStream s(state);
  s.setVersion(QDataStream::Qt_5_12);

  s >> magic_number;

  if (magic_number != kMagicNumber || s.atEnd()) {
    return false;
  }

  s >> stretch_enabled_;
  s >> visual_indices;
  s >> column_pixel_widths;
  s >> columns_visible;
  s >> column_widths_;
  s >> sort_indicator_order;
  s >> sort_indicator_section;

  for (int i = 0; i < count(); ++i) {
    if (i < visual_indices.count()) {
      moveSection(visualIndex(visual_indices[i]), i);
    }
    if (i < column_pixel_widths.count() && column_pixel_widths[i] > 0) {
      resizeSection(i, column_pixel_widths[i]);
    }
    setSectionHidden(i, !columns_visible.contains(i));
  }

  // Have we added more columns since the last time?
  while (column_widths_.count() < count()) {
    column_widths_ << 0;
  }

  setSortIndicator(sort_indicator_section, static_cast<Qt::SortOrder>(sort_indicator_order));

  if (stretch_enabled_) {
    // In stretch mode, we've already set the proportional column widths so apply them now.
    ResizeSections();
  }

  Q_EMIT StretchEnabledChanged(stretch_enabled_);

  return true;

}

QByteArray StretchHeaderView::ResetState() {

  stretch_enabled_ = false;
  column_widths_.resize(count());
  std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count());

  setSortIndicator(-1, Qt::AscendingOrder);

  for (int i = 0; i < count(); ++i) {
    setSectionHidden(i, false);
    resizeSection(i, defaultSectionSize());
    moveSection(visualIndex(i), i);
  }

  return SaveState();

}

void StretchHeaderView::ToggleStretchEnabled() {
  SetStretchEnabled(!is_stretch_enabled());
}

void StretchHeaderView::SetStretchEnabled(const bool enabled) {

  stretch_enabled_ = enabled;

  if (enabled) {
    // Initialize the list of widths from the current state of the widget
    column_widths_.resize(count());
    for (int i = 0; i < count(); ++i) {
      column_widths_[i] = static_cast<ColumnWidthType>(sectionSize(i)) / width();
    }

    // Stretch the columns to fill the widget
    NormaliseWidths();
    ResizeSections();
  }

  Q_EMIT StretchEnabledChanged(enabled);

}

void StretchHeaderView::SetColumnWidth(const int logical_index, const ColumnWidthType width) {

  column_widths_[logical_index] = width;

  if (!stretch_enabled_) return;

  QList<int> other_columns;
  for (int i = 0; i < count(); ++i) {
    if (!isSectionHidden(i) && i != logical_index) {
      other_columns << i;
    }
  }

  NormaliseWidths(other_columns);

}

void StretchHeaderView::NormaliseWidths(const QList<int> &sections) {

  if (!stretch_enabled_) return;

  const ColumnWidthType total_sum = std::accumulate(column_widths_.begin(), column_widths_.end(), 0.0);
  ColumnWidthType selected_sum = total_sum;

  if (!sections.isEmpty()) {
    selected_sum = 0.0;
    for (int i = 0; i < count(); ++i) {
      if (sections.contains(i)) {
        selected_sum += column_widths_.value(i);
      }
    }
  }

  if (total_sum != 0.0 && !qFuzzyCompare(total_sum, 1.0)) {
    const ColumnWidthType mult = (selected_sum + (1.0 - total_sum)) / selected_sum;
    for (int i = 0; i < column_widths_.count(); ++i) {
      if (sections.isEmpty() || sections.contains(i)) {
        column_widths_[i] *= mult;
      }
    }
  }

}

void StretchHeaderView::ResizeSections(const QList<int> &sections) {

  if (!stretch_enabled_) return;

  for (int i = 0; i < column_widths_.count(); ++i) {
    if (isSectionHidden(i) || (!sections.isEmpty() && !sections.contains(i))) {
      continue;
    }
    const int pixels = static_cast<int>(column_widths_.value(i) * width());
    if (pixels != 0) {
      resizeSection(i, pixels);
    }
  }

}

void StretchHeaderView::ShowSection(const int logical_index) {

  showSection(logical_index);

  if (stretch_enabled_) {

    // How many sections are visible already?
    int visible_count = 0;
    for (int i = 0; i < count(); ++i) {
      if (!isSectionHidden(i)) {
        ++visible_count;
      }
    }

    column_widths_[logical_index] = visible_count == 0 ? 1.0 : 1.0 / visible_count;

    NormaliseWidths();
    ResizeSections();

  }

  else {
    if (sectionSize(logical_index) == 0) {
      resizeSection(logical_index, defaultSectionSize());
    }
  }

}

void StretchHeaderView::HideSection(const int logical_index) {

  // Would this hide the last section?
  bool all_hidden = true;
  for (int i = 0; i < count(); ++i) {
    if (i != logical_index && !isSectionHidden(i) && sectionSize(i) > 0) {
      all_hidden = false;
      break;
    }
  }
  if (all_hidden) {
    return;
  }

  hideSection(logical_index);

  if (stretch_enabled_) {
    column_widths_[logical_index] = 0.0;
    NormaliseWidths();
    ResizeSections();
  }

}

void StretchHeaderView::SetSectionHidden(const int logical_index, const bool hidden) {

  if (hidden) {
    HideSection(logical_index);
  }
  else {
    ShowSection(logical_index);
  }

}

void StretchHeaderView::SectionResized(const int logical_index, const int old_size, const int new_size) {

  if (!stretch_enabled_) {
    return;
  }

  if (logical_index == forced_resize_logical_index_) {
    forced_resize_logical_index_ = -1;
    return;
  }

  if (!in_mouse_move_event_) {
    return;
  }

  bool resized = false;
  if (new_size >= minimumSectionSize()) {
    // Find the visible section to the right of the section that's being resized
    const int visual_index = visualIndex(logical_index);
    int right_section_logical_index = -1;
    int right_section_visual_index = -1;
    for (int i = 0; i < count(); ++i) {
      if (!isSectionHidden(i) &&
          visualIndex(i) > visual_index &&
          (right_section_visual_index == -1 || visualIndex(i) < right_section_visual_index)) {
        right_section_logical_index = i;
        right_section_visual_index = visualIndex(i);
      }
    }
    if (right_section_logical_index != -1) {
      const int right_section_size = sectionSize(right_section_logical_index) + (old_size - new_size);
      if (right_section_size >= minimumSectionSize()) {
        column_widths_[logical_index] = static_cast<ColumnWidthType>(new_size) / width();
        column_widths_[right_section_logical_index] = static_cast<ColumnWidthType>(right_section_size) / width();
        in_mouse_move_event_ = false;
        NormaliseWidths(QList<int>() << right_section_logical_index);
        ResizeSections(QList<int>() << right_section_logical_index);
        in_mouse_move_event_ = true;
        resized = true;
      }
    }
  }

  if (!resized) {
    forced_resize_logical_index_ = logical_index;
    in_mouse_move_event_ = false;
    resizeSection(logical_index, old_size);
    in_mouse_move_event_ = true;
  }

}
