
/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QWidget>


/////////////////////// QCustomPlot


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "TracePlotLabelList.hpp"


namespace MsXpS
{
namespace MineXpert
{

TracePlotLabelList::TracePlotLabelList(QObject *parent): QObject(parent)
{
}

TracePlotLabelList::TracePlotLabelList(const QString &name,
                                       const QString &plot_layer_name,
                                       QObject *parent)
  : QObject(parent), m_name(name), m_plotLayerName(plot_layer_name)
{
}

TracePlotLabelList::TracePlotLabelList(const QString &name,
                                       const QString &plot_layer_name,
                                       QCustomPlot *plot_rp,
                                       QObject *parent)
  : QObject(parent),
    m_name(name),
    m_plotLayerName(plot_layer_name),
    mrp_plot(plot_rp)
{
}

TracePlotLabelList::TracePlotLabelList(const QString &name,
                                       const QString &plot_layer_name,
                                       QCustomPlot *plot_rp,
                                       const QFont &font,
                                       const QColor &color,
                                       QObject *parent)
  : QObject(parent),
    m_name(name),
    m_plotLayerName(plot_layer_name),
    mrp_plot(plot_rp),
    m_font(font),
    m_color(color)
{
}

TracePlotLabelList::TracePlotLabelList(const QString &name,
                                       const QString &plot_layer_name,
                                       QCustomPlot *plot_rp,
                                       bool should_manage_layout,
                                       float x_axis_delta_trigger,
                                       int label_vertical_shift_count_factor,
                                       QObject *parent)
  : QObject(parent),
    m_name(name),
    m_plotLayerName(plot_layer_name),
    mrp_plot(plot_rp),
    m_shouldManageLayout(should_manage_layout),
    m_xAxisDeltaTrigger(x_axis_delta_trigger),
    m_labelVerticalShiftCountFactor(label_vertical_shift_count_factor)
{
}

TracePlotLabelList::TracePlotLabelList(const QString &name,
                                       const QString &plot_layer_name,
                                       QCustomPlot *plot_rp,
                                       bool should_manage_layout,
                                       float x_axis_delta_trigger,
                                       int label_vertical_shift_count_factor,
                                       const QFont &font,
                                       const QColor &color,
                                       QObject *parent)
  : QObject(parent),
    m_name(name),
    m_plotLayerName(plot_layer_name),
    mrp_plot(plot_rp),
    m_shouldManageLayout(should_manage_layout),
    m_xAxisDeltaTrigger(x_axis_delta_trigger),
    m_labelVerticalShiftCountFactor(label_vertical_shift_count_factor),
    m_font(font),
    m_color(color)
{
}

void
TracePlotLabelList::setName(const QString &name)
{
  m_name = name;
}

const QString &
TracePlotLabelList::getName() const
{
  return m_name;
}

void
TracePlotLabelList::setPlot(QCustomPlot *plot_rp)
{
  mrp_plot = plot_rp;
}

QCustomPlot *
TracePlotLabelList::getPlot()
{
  return mrp_plot;
}

const QCustomPlot &
TracePlotLabelList::getPlotCstRef() const
{
  return *mrp_plot;
}

void
TracePlotLabelList::setFont(const QFont &font)
{
  m_font = font;
}

const QFont &
TracePlotLabelList::getFont() const
{
  return m_font;
}

void
TracePlotLabelList::setColor(const QColor color)
{
  m_color = color;
}

const QColor &
TracePlotLabelList::getColor() const
{
  return m_color;
}

void
TracePlotLabelList::setShouldManageLayout(bool should_manage_layout)
{
  m_shouldManageLayout = should_manage_layout;
}

bool
TracePlotLabelList::getShouldManageLayout() const
{
  return m_shouldManageLayout;
}

void
TracePlotLabelList::setXAxisDeltaTrigger(float x_axis_delta_trigger)
{
  m_xAxisDeltaTrigger = x_axis_delta_trigger;
}

int
TracePlotLabelList::getXAxisDeltaTrigger() const
{
  return m_xAxisDeltaTrigger;
}

void
TracePlotLabelList::setLabelVerticalShiftCountFactor(
  int label_vertical_shift_count_factor)
{
  m_labelVerticalShiftCountFactor = label_vertical_shift_count_factor;
}

int
TracePlotLabelList::getLabelVerticalShiftCountFactor()
{
  return m_labelVerticalShiftCountFactor;
}

void
TracePlotLabelList::showLabels()
{
  // qDebug() << "Showing labels for list:" << m_name;

  for(TracePlotLabelQSPtr &trace_plot_label_qsp : m_labels)
    trace_plot_label_qsp->show();
}

void
TracePlotLabelList::hideLabels()
{
  // qDebug() << "Hiding labels for list:" << m_name;

  for(TracePlotLabelQSPtr &trace_plot_label_qsp : m_labels)
    trace_plot_label_qsp->hide();
}

void
TracePlotLabelList::setLabelsVisible(bool visible)
{
  for(TracePlotLabelQSPtr &trace_plot_label_qsp : m_labels)
    trace_plot_label_qsp->setVisible(visible);
}

void
TracePlotLabelList::toggleLabelsVisibility()
{
  for(TracePlotLabelQSPtr &trace_plot_label_qsp : m_labels)
    trace_plot_label_qsp->toggleVisibility();
}

void
TracePlotLabelList::eraseLabel(qsizetype index)
{
  if(index < 0 || index >= size())
    return;

  // Will be destructed if ref count goes to 0.
  m_labels.removeAt(index);
}

void
TracePlotLabelList::eraseLabels()
{
  // qDebug() << "Will erase all labels for label list:" << m_name;

  while(m_labels.size())
    {
      // Will be destructed if ref count goes to 0.
      m_labels.removeLast();
    }
}

int
TracePlotLabelList::size()
{
  return m_labels.size();
}

const QList<TracePlotLabelQSPtr> &
TracePlotLabelList::getLabelsCstRef()
{
  return m_labels;
}

int
TracePlotLabelList::calculateLabelVerticalShiftCount(const QPointF data_pointf)
{
  int matching_labels = 0;

  for(int iter = 0; iter < m_labels.size(); ++iter)
    {
      const TracePlotLabelQSPtr &trace_plot_label_qsp = m_labels.at(iter);

      double x_value = trace_plot_label_qsp->getDataPoint().x();
      if(data_pointf.x() > (x_value - m_xAxisDeltaTrigger / 2) &&
         data_pointf.x() < (x_value + m_xAxisDeltaTrigger / 2))
        {
          // Match !
          // qDebug() << "Found a match for peak data pointf:" <<
          // data_pointf
          //          << "and iterated peak label:" <<
          //          trace_plot_label_qsp->toString()
          //          << "with the mz tolerance:" << m_xAxisDeltaTrigger;

          ++matching_labels;
        }
    }
  return matching_labels;
}

QPointF
TracePlotLabelList::calculateDrawPoint(const QPointF &data_pointf,
                                       int vertical_shift_count)
{
  // We get a data point in a trace plot, for which a label has to be
  // positioned. Because there might be more than one label for the same
  // x-position, it might be necessary to compute a label draw position
  // that is upwards y-shifted. The member labelVerticalShiftCount parameter
  // governs that upwards y-shift.

  if(mrp_plot == nullptr)
    qFatal() << "Programming error. The plot pointer is nullptr.";

  QFont font      = mrp_plot->xAxis->labelFont();
  QScreen *screen = QGuiApplication::primaryScreen();

  int font_pixel_size = 0;
  if(screen)
    {
      qreal dpi = screen->logicalDotsPerInch();

      // Convert using the standard formula
      font_pixel_size =
        static_cast<int>(mrp_plot->fontInfo().pointSize() * dpi / 72.0);
      // qDebug() << "Screen's DPI:" << dpi
      //          << "with font pixel size:" << font_pixel_size;
    }
  else
    {
      font_pixel_size = 12;

      qDebug() << "Could not determine the screen's DPI"
               << "arbitrarily set font pixel size:" << font_pixel_size;
    }

  float pixel_y_shift =
    -1 *
    (vertical_shift_count * m_labelVerticalShiftCountFactor * font_pixel_size);

  // qDebug() << "Will apply a y-axis *upward* shift of pixel y shift of:"
  //          << pixel_y_shift;

  QPointF pixel_coordinates(mrp_plot->xAxis->coordToPixel(data_pointf.x()),
                            mrp_plot->yAxis->coordToPixel(data_pointf.y()));

  // qDebug() << "Converted to pixel coords:" << pixel_coordinates;

  // Increment the y position in pixels to avoid overlapping of the labels
  pixel_coordinates.ry() += pixel_y_shift;

  // qDebug() << "After incrementing y in pixel coordinates by " <<
  // pixel_y_shift
  //          << "new pixel coordinates:" << pixel_coordinates;

  // Now convert back to graph coordinates.

  // When setting the same data point as the one received as parameter,
  // the labels are all stacked and placed right above the peak.
  // draw_data_pointf = data_pointf;

  QPointF draw_pointf =
    QPointF(mrp_plot->xAxis->pixelToCoord(pixel_coordinates.x()),
            mrp_plot->yAxis->pixelToCoord(pixel_coordinates.y()));

  // qDebug() << "Now the draw point:" << draw_pointf;

  return draw_pointf;
}

void
TracePlotLabelList::newLabel(const QString &text,
                             const QPointF &data_pointf,
                             bool should_replot)
{
  if(mrp_plot == nullptr)
    qFatal() << "Programming error. The plot pointer is nullptr.";

  QPointF draw_pointf;
  int vertical_shift_count = 0;

  if(m_shouldManageLayout)
    {
      // qDebug() << "The label list" << m_name
      //          << "should manage the layouting of the labels.";

      vertical_shift_count = calculateLabelVerticalShiftCount(data_pointf);

      draw_pointf = calculateDrawPoint(data_pointf, vertical_shift_count);

      // qDebug() << "The data point:" << data_pointf
      //          << "the draw point:" << draw_pointf
      //          << "with vertical shift count:" << vertical_shift_count;
    }
  else
    {
      // qDebug() << "The label list" << m_name
      //          << "does not manage the layouting of the labels.";

      draw_pointf = data_pointf;
    }

  TracePlotLabelQSPtr trace_plot_label_qsp =
    QSharedPointer<TracePlotLabel>::create(text,
                                           m_plotLayerName,
                                           data_pointf,
                                           draw_pointf,
                                           vertical_shift_count,
                                           mrp_plot);

  // Now actually write the label
  trace_plot_label_qsp->write(m_font, m_color);

  m_labels.append(trace_plot_label_qsp);

  if(should_replot)
    mrp_plot->replot();
}

void
TracePlotLabelList::writeToUpdatedDrawPoint(bool should_replot)
{
  if(!m_shouldManageLayout)
    {
      // qDebug() << "The label list" << m_name
      //          << "does not manage the layouting of the labels.";
      return;
    }

  // qDebug() << "The label list" << m_name
  //          << "should manage the layouting of the labels.";

  for(TracePlotLabelQSPtr &trace_plot_label_qsp : m_labels)
    {
      // Remember, the vertical shift count is only computed once: during
      // newLabel(). Once this has been done, the label's member
      // m_verticalShiftCount is used to layout the labels correctly
      // over the y-axis direction.
      QPointF draw_pointf =
        calculateDrawPoint(trace_plot_label_qsp->getDataPoint(),
                           trace_plot_label_qsp->getVerticalShiftCount());

      trace_plot_label_qsp->setDrawPoint(draw_pointf);
      // Will ensure that if the label was hidden, then it is repositioned
      // but maintained hidden.
      trace_plot_label_qsp->write(m_font, m_color);

      // qDebug() << "Re-layouted analyte peak:" <<
      // trace_plot_label_qsp->toString();
    }

  if(should_replot)
    mrp_plot->replot();
}


} // namespace MineXpert

} // namespace MsXpS
