/*
 * Copyright (C) 2005-2020 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mvdMultiResolutionPyramidWidget.h"
#include "ui_mvdMultiResolutionPyramidWidget.h"


/*****************************************************************************/
/* INCLUDE SECTION                                                           */

//
// Qt includes (sorted by alphabetic order)
//// Must be included before system/custom includes.

//
// System includes (sorted by alphabetic order)
#include <cassert>
#include <cmath>

//
// ITK includes (sorted by alphabetic order)

//
// OTB includes (sorted by alphabetic order)

//
// Monteverdi includes (sorted by alphabetic order)
#include "mvdCore.h"
#include "mvdGui.h"
#include "mvdI18nCoreApplication.h"

namespace mvd
{

/*
  TRANSLATOR mvd::MultiResolutionPyramidWidget

  Necessary for lupdate to be aware of C++ namespaces.

  Context comment for translator.
*/


/*****************************************************************************/
/* CONSTANTS                                                                 */

char const* const GDAL_RESAMPLING_NAMES[otb::GDAL_RESAMPLING_COUNT] = {
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "None"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "Nearest"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "Gauss"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "Cubic"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "Average"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "Mode"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "Average Mag/Phase"),
};


char const* const GDAL_COMPRESSION_NAMES[otb::GDAL_COMPRESSION_COUNT] = {
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "None"),    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "JPEG"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "LZW"),     QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "PACKBITS"),
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "DEFLATE"),
};


char const* const GDAL_FORMAT_NAMES[otb::GDAL_FORMAT_COUNT] = {
    QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "ERDAS"), QT_TRANSLATE_NOOP("mvd::MultiResolutionPyramidWidget", "GeoTIFF"),
};

/*****************************************************************************/
/* STATIC IMPLEMENTATION SECTION                                             */

/*****************************************************************************/
/* CLASS IMPLEMENTATION SECTION                                              */
/*****************************************************************************/
MultiResolutionPyramidWidget::MultiResolutionPyramidWidget(QWidget* p, Qt::WindowFlags flags)
  : QWidget(p, flags), m_UI(new mvd::Ui::MultiResolutionPyramidWidget())
{
  m_UI->setupUi(this);

  {
    QItemSelectionModel* ism = m_UI->resolutionsListView->selectionModel();

    m_UI->resolutionsListView->setModel(new QStandardItemModel(m_UI->resolutionsListView));

    delete ism;
    ism = NULL;
  }

  AddItemsInto(m_UI->formatComboBox, "mvd::MultiResolutionPyramidWidget", GDAL_FORMAT_NAMES, otb::GDAL_FORMAT_COUNT);

  AddItemsInto(m_UI->algorithmComboBox, "mvd::MultiResolutionPyramidWidget", GDAL_RESAMPLING_NAMES, otb::GDAL_RESAMPLING_COUNT);

  AddItemsInto(m_UI->compressionComboBox, "mvd::MultiResolutionPyramidWidget", GDAL_COMPRESSION_NAMES, otb::GDAL_COMPRESSION_COUNT);

  m_UI->baseSpinBox->setValue(2);

  m_UI->levelsSpinBox->setValue(1);

  m_UI->sizeSpinBox->setValue(2);
}

/*****************************************************************************/
MultiResolutionPyramidWidget::~MultiResolutionPyramidWidget()
{
  delete m_UI;
  m_UI = NULL;
}

/*****************************************************************************/
void MultiResolutionPyramidWidget::ClearResolutions()
{
  QStandardItemModel* itemModel = qobject_cast<QStandardItemModel*>(m_UI->resolutionsListView->model());

  assert(itemModel != NULL);

  itemModel->clear();
}

/*****************************************************************************/
void MultiResolutionPyramidWidget::SetBuilder(const otb::GDALOverviewsBuilder::Pointer& p)
{
  m_UI->formatComboBox->setCurrentIndex(otb::GDAL_FORMAT_GEOTIFF);
  m_UI->algorithmComboBox->setCurrentIndex(otb::GDAL_RESAMPLING_AVERAGE);
  m_UI->compressionComboBox->setCurrentIndex(otb::GDAL_COMPRESSION_NONE);


  {
    bool prevSignalsBlocked = m_UI->baseSpinBox->blockSignals(true);

    m_UI->baseSpinBox->setValue(2);

    m_UI->baseSpinBox->blockSignals(prevSignalsBlocked);
  }

  {
    bool prevSignalsBlocked = m_UI->levelsSpinBox->blockSignals(true);

    m_UI->levelsSpinBox->setValue(1);

    m_UI->levelsSpinBox->blockSignals(prevSignalsBlocked);
  }

  {
    bool prevSignalsBlocked = m_UI->sizeSpinBox->blockSignals(true);

    m_UI->sizeSpinBox->setRange(1, std::numeric_limits<int>::max());
    m_UI->sizeSpinBox->setValue(1);

    m_UI->sizeSpinBox->blockSignals(prevSignalsBlocked);
  }


  ClearResolutions();


  m_GDALOverviewsBuilder = p;

  if (p.IsNull())
    return;

  assert(!m_GDALOverviewsBuilder.IsNull());


  m_UI->formatComboBox->setCurrentIndex(m_GDALOverviewsBuilder->GetFormat());

  m_UI->algorithmComboBox->setCurrentIndex(m_GDALOverviewsBuilder->GetResamplingMethod());

  m_UI->compressionComboBox->setCurrentIndex(m_GDALOverviewsBuilder->GetCompressionMethod());


  unsigned int minSize = std::min(m_GDALOverviewsBuilder->GetWidth(), m_GDALOverviewsBuilder->GetHeight());


  {
    bool prevSignalsBlocked = m_UI->baseSpinBox->blockSignals(true);

    m_UI->baseSpinBox->setRange(2, minSize);
    m_UI->baseSpinBox->setValue(m_GDALOverviewsBuilder->GetResolutionFactor());

    m_UI->baseSpinBox->blockSignals(prevSignalsBlocked);
  }

  unsigned int maxResolutions = m_GDALOverviewsBuilder->CountResolutions();

  {
    bool prevSignalsBlocked = m_UI->levelsSpinBox->blockSignals(true);

    m_UI->levelsSpinBox->setRange(0, maxResolutions);
    m_UI->levelsSpinBox->setValue(m_GDALOverviewsBuilder->GetNbResolutions());

    m_UI->levelsSpinBox->blockSignals(prevSignalsBlocked);
  }

  {
    bool prevSignalsBlocked = m_UI->sizeSpinBox->blockSignals(true);

    m_UI->sizeSpinBox->setRange(1, minSize);

    assert(maxResolutions >= m_GDALOverviewsBuilder->GetNbResolutions());

    // Force C++ implicit cast by declaring local variables to force compiler
    // to find correct std::pow() signature.
    //
    // It fixes compile-time:
    // error: call of pow(unsigned int, unsigned int) is ambiguous
    double rf = m_GDALOverviewsBuilder->GetResolutionFactor();

    int m = maxResolutions - m_GDALOverviewsBuilder->GetNbResolutions();

    m_UI->sizeSpinBox->setValue(static_cast<int>(std::pow(rf, m)));

    m_UI->sizeSpinBox->blockSignals(prevSignalsBlocked);
  }


  UpdateResolutions();
}

/*****************************************************************************/
void MultiResolutionPyramidWidget::UpdateResolutions()
{
  assert(!m_GDALOverviewsBuilder.IsNull());

  QStandardItemModel* itemModel = qobject_cast<QStandardItemModel*>(m_UI->resolutionsListView->model());

  assert(itemModel != NULL);

  otb::GDALOverviewsBuilder::SizeVector resolutions;

  m_GDALOverviewsBuilder->ListResolutions(resolutions);

  for (otb::GDALOverviewsBuilder::SizeVector::const_iterator it(resolutions.begin()); it != resolutions.end(); ++it)
    itemModel->appendRow(new QStandardItem(QString("%1x%2").arg((*it)[0]).arg((*it)[1])));
}

/*****************************************************************************/
void MultiResolutionPyramidWidget::UpdateSize()
{
  assert(!m_GDALOverviewsBuilder.IsNull());

  bool prevSignalsBlocked = m_UI->sizeSpinBox->blockSignals(true);

  unsigned int count = m_GDALOverviewsBuilder->CountResolutions();

  // Force C++ implicit cast by declaring local variables to force compiler
  // to find correct std::pow() signature.
  //
  // It fixes compile-time:
  // error: call of pow(unsigned int, unsigned int) is ambiguous
  double rf = m_GDALOverviewsBuilder->GetResolutionFactor();

  int m = count - std::min(m_GDALOverviewsBuilder->GetNbResolutions(), count);

  m_UI->sizeSpinBox->setValue(static_cast<int>(std::pow(rf, m)));

  m_UI->sizeSpinBox->blockSignals(prevSignalsBlocked);
}

/*****************************************************************************/
/* SLOTS                                                                     */
/*****************************************************************************/
void MultiResolutionPyramidWidget::on_baseSpinBox_valueChanged(int value)
{
  // qDebug() << this << "::on_baseSpinBox_valueChanged(" << value << ")";

  ClearResolutions();

  if (m_GDALOverviewsBuilder.IsNull())
    return;

  m_GDALOverviewsBuilder->SetResolutionFactor(value);

  m_UI->levelsSpinBox->setRange(0, m_GDALOverviewsBuilder->CountResolutions());

  UpdateResolutions();

  UpdateSize();

  emit BaseValueChanged(value);
}

/*****************************************************************************/
void MultiResolutionPyramidWidget::on_levelsSpinBox_valueChanged(int value)
{
  // qDebug() << this << "::on_levelsSpinBox_valueChanged(" << value << ")";

  ClearResolutions();

  if (m_GDALOverviewsBuilder.IsNull())
    return;

  m_GDALOverviewsBuilder->SetNbResolutions(value);

  UpdateResolutions();

  UpdateSize();

  emit LevelsValueChanged(value);
}

/*****************************************************************************/
void MultiResolutionPyramidWidget::on_sizeSpinBox_valueChanged(int value)
{
  // qDebug() << this << "::on_sizeSpinBox_valueChanged(" << value << ")";

  if (m_GDALOverviewsBuilder.IsNull())
    return;

  unsigned int count = m_GDALOverviewsBuilder->CountResolutions(m_GDALOverviewsBuilder->GetResolutionFactor(), value);

  if (static_cast<unsigned int>(m_UI->levelsSpinBox->value()) <= count)
    return;

  {
    bool prevSignalsBlocked = m_UI->levelsSpinBox->blockSignals(true);

    m_UI->levelsSpinBox->setValue(count);
    m_GDALOverviewsBuilder->SetNbResolutions(count);

    m_UI->levelsSpinBox->blockSignals(prevSignalsBlocked);
  }

  ClearResolutions();

  UpdateResolutions();

  emit SizeValueChanged(value);
}

} // end namespace 'mvd'
