

#include "tcolorstyles.h"

#include <QVBoxLayout>
#include <QComboBox>
#include <QPushButton>
#include <QIcon>
#include <QStackedWidget>
#include <QPainter>

#include "toonzqt/histogram.h"
#include "toonzqt/gutil.h"

const int HistogramGraph::drawMargin = 10;

//*****************************************************************************
//    Local namespace
//*****************************************************************************

namespace {

void computeGreyValues(const TRasterGR8P &ras, int *greyValues) {
  int y, lx = ras->getLx(), ly = ras->getLy();
  TPixelGR8 *pix, *endPix;

  for (y = 0; y < ly; ++y)
    for (pix = ras->pixels(y), endPix = pix + lx; pix < endPix; ++pix)
      ++greyValues[pix->value];
}

//-----------------------------------------------------------------------------

void computeGreyValues(const TRasterGR16P &ras, int *greyValues) {
  int y, lx = ras->getLx(), ly = ras->getLy();
  TPixelGR16 *pix, *endPix;

  for (y = 0; y < ly; ++y)
    for (pix = ras->pixels(y), endPix = pix + lx; pix < endPix; ++pix)
      ++greyValues[pix->value >> 8];
}

//-----------------------------------------------------------------------------

void computeRGBMValues32(const TRaster32P &ras, int (*rgbmValues)[256]) {
  int y, lx = ras->getLx(), ly = ras->getLy();
  TPixel32 *pix, *endPix;

  for (y = 0; y < ly; ++y)
    for (pix = ras->pixels(y), endPix = pix + lx; pix < endPix; ++pix) {
      // NOTE: The insertion order is related to the way channels are
      // displayed inside the histogram's QStackedWidget instance
      if (pix->m) {
        ++rgbmValues[0][pix->r];
        ++rgbmValues[1][pix->g];
        ++rgbmValues[2][pix->b];
      }
      ++rgbmValues[3][pix->m];
    }
}

//-----------------------------------------------------------------------------

void computeRGBMValues64(const TRaster64P &ras, int (*rgbmValues)[256]) {
  int y, lx = ras->getLx(), ly = ras->getLy();
  TPixel64 *pix, *endPix;

  for (y = 0; y < ly; ++y)
    for (pix = ras->pixels(y), endPix = pix + lx; pix < endPix; ++pix) {
      // NOTE: The insertion order is related to the way channels are
      // displayed inside the histogram's QStackedWidget instance
      if (pix->m) {
        ++rgbmValues[0][pix->r >> 8];
        ++rgbmValues[1][pix->g >> 8];
        ++rgbmValues[2][pix->b >> 8];
      }
      ++rgbmValues[3][pix->m >> 8];
    }
}

//-----------------------------------------------------------------------------

void computeRGBMValues(const TRasterCM32P &ras, TPaletteP pal,
                       int (*rgbmValues)[256]) {
  assert(pal);

  int y, lx = ras->getLx(), ly = ras->getLy();
  TPixelCM32 *pix, *endPix;

  int styleId;
  TColorStyle *colorStyle;
  TPixel32 color;

  for (y = 0; y < ly; ++y)
    for (pix = ras->pixels(y), endPix = pix + lx; pix < endPix; ++pix) {
      styleId    = (pix->getTone() < 127) ? pix->getInk() : pix->getPaint();
      colorStyle = pal->getStyle(styleId);
      if (!colorStyle) continue;

      color = colorStyle->getAverageColor();
      if (color.m) {
        ++rgbmValues[0][color.r];
        ++rgbmValues[1][color.g];
        ++rgbmValues[2][color.b];
      }
      ++rgbmValues[3][color.m];
    }
}

//-----------------------------------------------------------------------------

void computeRGB(int (*channelValues)[256], int *rgbValues) {
  int i;
  for (i = 0; i < 256; ++i)
    rgbValues[i] =
        channelValues[0][i] + channelValues[1][i] + channelValues[2][i];
}

//-----------------------------------------------------------------------------

void computeRGBM(int (*channelValues)[256], int *rgbmValues) {
  int i;
  for (i = 0; i < 256; ++i)
    rgbmValues[i] = channelValues[0][i] + channelValues[1][i] +
                    channelValues[2][i] + channelValues[3][i];
}

}  // namespace

//=============================================================================
// HistogramGraph
//-----------------------------------------------------------------------------

HistogramGraph::HistogramGraph(QWidget *parent, QColor color)
    : QWidget(parent)
    , m_color(color)
    , m_height(120)
    , m_values(0)
    , m_logScale(false) {
  if (m_color.alpha() == 0) m_color = Qt::black;

  setMinimumWidth(256 + drawMargin * 2 + 2);
  setMinimumHeight(m_height + drawMargin);
}

//-----------------------------------------------------------------------------

HistogramGraph::~HistogramGraph() { m_values.clear(); }

//-----------------------------------------------------------------------------

void HistogramGraph::setAlphaMask(int value) { m_color.setAlpha(value); }

//-----------------------------------------------------------------------------

void HistogramGraph::setValues(const int values[]) {
  m_values.clear();
  m_values.resize(256);

  int i;

  double maxValue = 0;
  for (i = 0; i < 256; i++) {
    int count = m_values[i] = values[i];
    if (maxValue < count) maxValue = count;
  }

  m_viewValues.clear();
  m_logViewValues.clear();
  m_viewValues.resize(256);
  m_logViewValues.resize(256);

  double logMaxValue = log(1.0 + maxValue);

  for (i = 0; i < 256; i++) {
    m_viewValues[i] = double(values[i]) * double(m_height) / maxValue;
    m_logViewValues[i] =
        double(log(1.0 + values[i])) * double(m_height) / logMaxValue;
  }
}

//-----------------------------------------------------------------------------

void HistogramGraph::draw(QPainter *p, QPoint translation, int w) {
  int x0 = translation.x() + drawMargin;
  int y0 = translation.y() + drawMargin - 2;
  // in case that the size is not externally specified (i.e. the widget is in
  // some layout)
  if (w <= 0) w = width() - 2 * drawMargin;
  int h = m_height + 1;

  p->setPen(Qt::NoPen);
  p->setBrush(Qt::white);
  p->drawRect(x0, y0, w - 1, h);
  p->setBrush(Qt::NoBrush);

  p->setPen(Qt::gray);
  int step   = w * 0.25;
  int deltaX = x0 + 1 + step;
  int y1     = y0 + h;
  p->drawLine(deltaX, y0 + 1, deltaX, y1);
  deltaX += step;
  p->drawLine(deltaX, y0 + 1, deltaX, y1);
  deltaX += step;
  p->drawLine(deltaX, y0 + 1, deltaX, y1);

  p->drawRect(x0, y0, w - 1, h);

  if (m_values.size() == 0) return;

  const QVector<int> &viewValues = m_logScale ? m_logViewValues : m_viewValues;

  p->setPen(m_color);
  w  = w - 2;
  x0 = x0 + 1;
  y1 = y1 - 1;
  int i;
  double gap = double(viewValues.size()) /
               double(w); /*-- 1チャンネル値あたりのグラフの横幅 --*/
  for (i = 0; i < w; i++) {
    int j = i * gap;
    assert(j >= 0 && j < viewValues.size());
    int v = viewValues[j];
    if (v <= 0) continue;
    int x = x0 + i;
    p->drawLine(x, y1 - v + 1, x, y1);
  }
}

//-----------------------------------------------------------------------------

void HistogramGraph::paintEvent(QPaintEvent *event) {
  QPainter p(this);
  draw(&p);
}

//=============================================================================
// ChannelBar
//-----------------------------------------------------------------------------

ChannelBar::ChannelBar(QWidget *parent, QColor color, bool isHorizontal)
    : QWidget(parent)
    , m_color(color)
    , m_isHorizontal(isHorizontal)
    , m_colorBarLength(13)
    , m_drawNumbers(true)
    , m_range(Range_0_255) {
  int d = 256 + HistogramGraph::drawMargin * 2 + 2;

  if (m_isHorizontal)
    setMinimumWidth(d);
  else
    setFixedHeight(d);  // La barra verticale ha la size fissa

  setDrawNumbers(m_drawNumbers);

  if (color == Qt::black) m_color = Qt::white;
}

//-----------------------------------------------------------------------------

ChannelBar::~ChannelBar() {}

//-----------------------------------------------------------------------------

void ChannelBar::setDrawNumbers(bool onOff) {
  if (m_isHorizontal)
    setFixedHeight(onOff ? m_colorBarLength * 2 + 2 : m_colorBarLength + 2);
  else
    setFixedWidth(onOff ? m_colorBarLength + 20 + 2 : m_colorBarLength + 2);
}

//-----------------------------------------------------------------------------

void ChannelBar::draw(QPainter *p, QPoint translation, int size) {
  // Calcolo i parametri necessari al draw a seconda del caso in cui mi trovo.
  int space = HistogramGraph::drawMargin;
  int w, h, x0, y0;
  QRect rect;
  QPoint initialPoint, finalPoint, delta;
  QColor initialColor, finalColor;

  // in case that the size is not externally specified (i.e. the widget is in
  // some layout)
  if (size <= 0) {
    size = (m_isHorizontal) ? width() - 2 * HistogramGraph::drawMargin
                            : height() - 2 * HistogramGraph::drawMargin;
  }

  if (m_isHorizontal) {
    w            = size;
    h            = m_colorBarLength;
    x0           = translation.x() + space;
    y0           = translation.y();
    initialPoint = QPoint(x0, 0);
    finalPoint   = QPoint(w + x0, 0);
    initialColor = Qt::black;
    finalColor   = m_color;
    rect         = QRect(x0 - space, y0 + h, 20, 20);
    delta        = QPoint(w / 4, 0);
  } else {
    w            = m_colorBarLength;
    h            = size;
    x0           = translation.x() + width() - w;
    y0           = translation.y() + space;
    initialPoint = QPoint(0, y0);
    finalPoint   = QPoint(0, h + y0);
    initialColor = m_color;
    finalColor   = Qt::black;
    rect         = QRect(0, h + y0 - space, 20, 20);
    delta        = QPoint(0, -h / 4);
  }

  if (m_color == QColor(0, 0, 0, 0)) {
    static QPixmap checkboard(":Resources/backg.png");
    p->drawTiledPixmap(x0, y0, w, h, checkboard);
    QColor color = initialColor;
    initialColor = finalColor;
    finalColor   = color;
  }

  QLinearGradient linearGrad(initialPoint, finalPoint);
  linearGrad.setColorAt(0, initialColor);
  linearGrad.setColorAt(1, finalColor);

  p->setBrush(QBrush(linearGrad));

  p->setPen(getTextColor());
  p->drawRect(x0, y0, w - 1, h - 1);

  if (m_drawNumbers) {
    QStringList labels;
    if (m_range == Range_0_255)
      labels << "0"
             << "64"
             << "128"
             << "192"
             << "255";
    else  // m_range == Range_0_1
      labels << "0.0"
             << "0.25"
             << "0.5"
             << "0.75"
             << "1.0";
    for (QString &label : labels) {
      p->drawText(rect, Qt::AlignCenter, label);
      rect.translate(delta);
    }
  }
}

//-----------------------------------------------------------------------------

void ChannelBar::paintEvent(QPaintEvent *event) {
  QPainter p(this);
  draw(&p);
}

//=============================================================================
// HistogramView
//-----------------------------------------------------------------------------

HistogramView::HistogramView(QWidget *parent, QColor color)
    : QWidget(parent), m_drawnWidget(parent) {
  setMinimumWidth(256 + 10 * 2 + 2);   // 10 == margin of internal widget
  setMinimumHeight(120 + 10 * 2 + 2);  // 10 == margin of internal widget

  QVBoxLayout *mainLayout = new QVBoxLayout(this);
  mainLayout->setMargin(0);
  mainLayout->setSpacing(7);

  m_histogramGraph = new HistogramGraph(this, color);
  m_colorBar       = new ChannelBar(this, color);

  mainLayout->addWidget(m_histogramGraph);
  mainLayout->addWidget(m_colorBar);

  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
  setLayout(mainLayout);
}

//-----------------------------------------------------------------------------

void HistogramView::setDrawnWidget(QWidget *widget) { m_drawnWidget = widget; }

//-----------------------------------------------------------------------------

void HistogramView::setValues(const int values[]) {
  m_histogramGraph->setValues(values);
  m_drawnWidget->update();
}

//-----------------------------------------------------------------------------

void HistogramView::draw(QPainter *painter, QPoint translation, int width) {
  m_histogramGraph->draw(painter, translation, width);
  m_colorBar->draw(painter,
                   QPoint(translation.x(),
                          translation.y() + m_histogramGraph->getHeight() + 17),
                   width);
}

//-----------------------------------------------------------------------------

HistogramView::~HistogramView() {}

//=============================================================================
// Histograms
//-----------------------------------------------------------------------------

Histograms::Histograms(QWidget *parent, bool rgba)
    : QStackedWidget(parent)
    , m_raster(0)
    , m_palette(0)
    , m_computeAlsoRGBA(rgba)
    , m_channelsCount(rgba ? 6 : 5) {
  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);

  HistogramView *histogramViews[6];

  int h = 0;
  if (m_computeAlsoRGBA) histogramViews[h++] = new HistogramView(this);

  histogramViews[h++] = new HistogramView(this);
  histogramViews[h++] = new HistogramView(this, Qt::red);
  histogramViews[h++] = new HistogramView(this, Qt::green);
  histogramViews[h++] = new HistogramView(this, Qt::blue);
  histogramViews[h++] = new HistogramView(this, QColor(0, 0, 0, 0));

  int i;
  for (i = 0; i < m_channelsCount; ++i) addWidget(histogramViews[i]);
}

//-----------------------------------------------------------------------------

Histograms::~Histograms() { memset(m_channelValue, 0, sizeof m_channelValue); }

//-----------------------------------------------------------------------------

void Histograms::setRaster(const TRasterP &raster, const TPaletteP &palette) {
  if (palette.getPointer()) m_palette = palette;
  m_raster = raster;
  computeChannelsValue();
  int i;
  for (i = 0; i < count(); i++)
    getHistogramView(i)->setValues(m_channelValue[i]);
}

//-----------------------------------------------------------------------------

void Histograms::computeChannelsValue() {
  m_channelsCount = m_computeAlsoRGBA ? 6 : 5;

  memset(m_channelValue, 0, sizeof m_channelValue);
  if (!m_raster.getPointer()) return;

  int(*rgbChannelValues)[256] = m_channelValue + (m_computeAlsoRGBA ? 1 : 0);
  int(*channelValues)[256]    = rgbChannelValues + 1;

  TRasterCM32P cmRaster = m_raster;
  bool isCmRaster       = !!cmRaster;

  {
    TRaster32P ras32(m_raster);
    if (ras32) {
      ::computeRGBMValues32(ras32, channelValues);
      goto computeChannelsSums;
    }
  }

  {
    TRaster64P ras64(m_raster);
    if (ras64) {
      ::computeRGBMValues64(ras64, channelValues);
      goto computeChannelsSums;
    }
  }

  {
    TRasterGR8P rasGR8(m_raster);
    if (rasGR8) {
      m_channelsCount = 1;
      ::computeGreyValues(rasGR8, m_channelValue[0]);
      return;
    }
  }

  {
    TRasterGR16P rasGR16(m_raster);
    if (rasGR16) {
      m_channelsCount = 1;
      ::computeGreyValues(rasGR16, m_channelValue[0]);
      return;
    }
  }

  return;

computeChannelsSums:

  if (m_computeAlsoRGBA) ::computeRGBM(channelValues, m_channelValue[0]);
  ::computeRGB(channelValues, *rgbChannelValues);
}

//-----------------------------------------------------------------------------

HistogramView *Histograms::getHistogramView(int indexType) const {
  HistogramView *ch = dynamic_cast<HistogramView *>(widget(indexType));
  assert(ch);
  return ch;
}

//=============================================================================
// Histogram
//-----------------------------------------------------------------------------

Histogram::Histogram(QWidget *parent) : QWidget(parent) {
  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);

  QVBoxLayout *mainLayout = new QVBoxLayout(this);
  mainLayout->setMargin(0);
  mainLayout->setSpacing(0);
  setLayout(mainLayout);

  QHBoxLayout *upperLayout = new QHBoxLayout;
  mainLayout->addLayout(upperLayout);

  m_channelsListBox = new QComboBox(this);
  m_channelsListBox->setFixedSize(100, 20);

  upperLayout->addSpacing(HistogramGraph::drawMargin);
  upperLayout->addWidget(m_channelsListBox);
  upperLayout->addStretch(1);

  QIcon icon = createQIcon("histograms");

  QPushButton *logScaleButton = new QPushButton(icon, "", this);
  logScaleButton->setToolTip(tr("Logarithmic Scale"));
  logScaleButton->setFixedSize(20, 20);
  logScaleButton->setCheckable(true);

  upperLayout->addWidget(logScaleButton);
  upperLayout->addSpacing(HistogramGraph::drawMargin);

  m_histograms = new Histograms(this);
  m_histograms->setCurrentIndex(0);
  mainLayout->addWidget(m_histograms);

  connect(m_channelsListBox, SIGNAL(currentIndexChanged(int)), m_histograms,
          SLOT(setCurrentIndex(int)));
  connect(logScaleButton, SIGNAL(toggled(bool)), this, SLOT(setLogScale(bool)));

  updateChannelsList();
}

//-----------------------------------------------------------------------------

void Histogram::updateChannelsList() {
  if (m_histograms->channelsCount() != m_channelsListBox->count()) {
    QStringList channels;
    m_channelsListBox->clear();

    if (m_histograms->channelsCount() == 1)
      channels << tr("Value");
    else
      channels << tr("RGB") << tr("Red") << tr("Green") << tr("Blue")
               << tr("Alpha");

    m_channelsListBox->addItems(channels);
  }
}

//-----------------------------------------------------------------------------

void Histogram::setRaster(const TRasterP &raster, const TPaletteP &palette) {
  m_histograms->setRaster(raster, palette);
  updateChannelsList();
}

//-----------------------------------------------------------------------------

void Histogram::setLogScale(bool onOff) {
  int i, count = m_histograms->channelsCount();
  for (i = 0; i < count; ++i) {
    HistogramGraph *graph = m_histograms->getHistogramView(i)->histogramGraph();
    graph->setLogScale(onOff);
  }
  update();
}
