// Copyright (c) 2023, ETH Zurich and UNC Chapel Hill.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
//     * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
//       its contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)

#include "ui/image_viewer_widget.h"

#include "ui/model_viewer_widget.h"
#include "util/misc.h"

namespace colmap {

const double ImageViewerWidget::kZoomFactor = 1.20;

ImageViewerGraphicsScene::ImageViewerGraphicsScene() {
  setSceneRect(0, 0, 0, 0);
  image_pixmap_item_ = addPixmap(QPixmap::fromImage(QImage()));
  image_pixmap_item_->setZValue(-1);
}

QGraphicsPixmapItem* ImageViewerGraphicsScene::ImagePixmapItem() const {
  return image_pixmap_item_;
}

ImageViewerWidget::ImageViewerWidget(QWidget* parent) : QWidget(parent) {
  setWindowFlags(Qt::Window | Qt::WindowTitleHint |
                 Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint |
                 Qt::WindowCloseButtonHint);

  resize(parent->width() - 20, parent->height() - 20);

  QFont font;
  font.setPointSize(10);
  setFont(font);

  grid_layout_ = new QGridLayout(this);
  grid_layout_->setContentsMargins(5, 5, 5, 5);

  graphics_view_ = new QGraphicsView();
  graphics_view_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

  graphics_view_->setScene(&graphics_scene_);
  graphics_view_->setAlignment(Qt::AlignLeft | Qt::AlignTop);

  grid_layout_->addWidget(graphics_view_, 1, 0);

  button_layout_ = new QHBoxLayout();

  QPushButton* zoom_in_button = new QPushButton("+", this);
  zoom_in_button->setFont(font);
  zoom_in_button->setFixedWidth(50);
  button_layout_->addWidget(zoom_in_button);
  connect(zoom_in_button, &QPushButton::released, this,
          &ImageViewerWidget::ZoomIn);

  QPushButton* zoom_out_button = new QPushButton("-", this);
  zoom_out_button->setFont(font);
  zoom_out_button->setFixedWidth(50);
  button_layout_->addWidget(zoom_out_button);
  connect(zoom_out_button, &QPushButton::released, this,
          &ImageViewerWidget::ZoomOut);

  QPushButton* save_button = new QPushButton("Save image", this);
  save_button->setFont(font);
  button_layout_->addWidget(save_button);
  connect(save_button, &QPushButton::released, this, &ImageViewerWidget::Save);

  grid_layout_->addLayout(button_layout_, 2, 0, Qt::AlignRight);
}

void ImageViewerWidget::resizeEvent(QResizeEvent* event) {
  QWidget::resizeEvent(event);

  graphics_view_->fitInView(graphics_scene_.sceneRect(), Qt::KeepAspectRatio);
}

void ImageViewerWidget::closeEvent(QCloseEvent* event) {
  graphics_scene_.ImagePixmapItem()->setPixmap(QPixmap());
}

void ImageViewerWidget::ShowBitmap(const Bitmap& bitmap) {
  ShowPixmap(QPixmap::fromImage(BitmapToQImageRGB(bitmap)));
}

void ImageViewerWidget::ShowPixmap(const QPixmap& pixmap) {
  graphics_scene_.ImagePixmapItem()->setPixmap(pixmap);
  graphics_scene_.setSceneRect(pixmap.rect());

  show();
  graphics_view_->fitInView(graphics_scene_.sceneRect(), Qt::KeepAspectRatio);

  raise();
}

void ImageViewerWidget::ReadAndShow(const std::string& path) {
  Bitmap bitmap;
  if (!bitmap.Read(path, true)) {
    std::cerr << "ERROR: Cannot read image at path " << path << std::endl;
  }

  ShowBitmap(bitmap);
}

void ImageViewerWidget::ZoomIn() {
  graphics_view_->scale(kZoomFactor, kZoomFactor);
}

void ImageViewerWidget::ZoomOut() {
  graphics_view_->scale(1.0 / kZoomFactor, 1.0 / kZoomFactor);
}

void ImageViewerWidget::Save() {
  QString filter("PNG (*.png)");
  const QString save_path =
      QFileDialog::getSaveFileName(this, tr("Select destination..."), "",
                                   "PNG (*.png);;JPEG (*.jpg);;BMP (*.bmp)",
                                   &filter)
          .toUtf8()
          .constData();

  // Selection canceled?
  if (save_path == "") {
    return;
  }

  graphics_scene_.ImagePixmapItem()->pixmap().save(save_path);
}

FeatureImageViewerWidget::FeatureImageViewerWidget(
    QWidget* parent, const std::string& switch_text)
    : ImageViewerWidget(parent),
      switch_state_(true),
      switch_text_(switch_text) {
  switch_button_ = new QPushButton(tr(("Hide " + switch_text_).c_str()), this);
  switch_button_->setFont(font());
  button_layout_->addWidget(switch_button_);
  connect(switch_button_, &QPushButton::released, this,
          &FeatureImageViewerWidget::ShowOrHide);
}

void FeatureImageViewerWidget::ReadAndShowWithKeypoints(
    const std::string& path, const FeatureKeypoints& keypoints,
    const std::vector<char>& tri_mask) {
  Bitmap bitmap;
  if (!bitmap.Read(path, true)) {
    std::cerr << "ERROR: Cannot read image at path " << path << std::endl;
  }

  image1_ = QPixmap::fromImage(BitmapToQImageRGB(bitmap));
  image2_ = image1_;

  const size_t num_tri_keypoints = std::count_if(
      tri_mask.begin(), tri_mask.end(), [](const bool tri) { return tri; });

  FeatureKeypoints keypoints_tri(num_tri_keypoints);
  FeatureKeypoints keypoints_not_tri(keypoints.size() - num_tri_keypoints);
  size_t i_tri = 0;
  size_t i_not_tri = 0;
  for (size_t i = 0; i < tri_mask.size(); ++i) {
    if (tri_mask[i]) {
      keypoints_tri[i_tri] = keypoints[i];
      i_tri += 1;
    } else {
      keypoints_not_tri[i_not_tri] = keypoints[i];
      i_not_tri += 1;
    }
  }

  DrawKeypoints(&image2_, keypoints_tri, Qt::magenta);
  DrawKeypoints(&image2_, keypoints_not_tri, Qt::red);

  if (switch_state_) {
    ShowPixmap(image2_);
  } else {
    ShowPixmap(image1_);
  }
}

void FeatureImageViewerWidget::ReadAndShowWithMatches(
    const std::string& path1, const std::string& path2,
    const FeatureKeypoints& keypoints1, const FeatureKeypoints& keypoints2,
    const FeatureMatches& matches) {
  Bitmap bitmap1;
  Bitmap bitmap2;
  if (!bitmap1.Read(path1, true) || !bitmap2.Read(path2, true)) {
    std::cerr << "ERROR: Cannot read images at paths " << path1 << " and "
              << path2 << std::endl;
    return;
  }

  const auto image1 = QPixmap::fromImage(BitmapToQImageRGB(bitmap1));
  const auto image2 = QPixmap::fromImage(BitmapToQImageRGB(bitmap2));

  image1_ = ShowImagesSideBySide(image1, image2);
  image2_ = DrawMatches(image1, image2, keypoints1, keypoints2, matches);

  if (switch_state_) {
    ShowPixmap(image2_);
  } else {
    ShowPixmap(image1_);
  }
}

void FeatureImageViewerWidget::ShowOrHide() {
  if (switch_state_) {
    switch_button_->setText(std::string("Show " + switch_text_).c_str());
    ShowPixmap(image1_);
    switch_state_ = false;
  } else {
    switch_button_->setText(std::string("Hide " + switch_text_).c_str());
    ShowPixmap(image2_);
    switch_state_ = true;
  }
}

DatabaseImageViewerWidget::DatabaseImageViewerWidget(
    QWidget* parent, ModelViewerWidget* model_viewer_widget,
    OptionManager* options)
    : FeatureImageViewerWidget(parent, "keypoints"),
      model_viewer_widget_(model_viewer_widget),
      options_(options) {
  setWindowTitle("Image information");

  table_widget_ = new QTableWidget(this);
  table_widget_->setColumnCount(2);
  table_widget_->setRowCount(11);

  QFont font;
  font.setPointSize(10);
  table_widget_->setFont(font);

  table_widget_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

  table_widget_->setEditTriggers(QAbstractItemView::NoEditTriggers);
  table_widget_->setSelectionMode(QAbstractItemView::SingleSelection);
  table_widget_->setShowGrid(true);

  table_widget_->horizontalHeader()->setStretchLastSection(true);
  table_widget_->horizontalHeader()->setVisible(false);
  table_widget_->verticalHeader()->setVisible(false);
  table_widget_->verticalHeader()->setDefaultSectionSize(18);

  int table_row = 0;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("image_id"));
  image_id_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, image_id_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("camera_id"));
  camera_id_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, camera_id_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("camera_model"));
  camera_model_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, camera_model_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("camera_params"));
  camera_params_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, camera_params_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("qw, qx, qy, qz"));
  qvec_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, qvec_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("tx, ty, tz"));
  tvec_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, tvec_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("dims"));
  dimensions_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, dimensions_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("num_points2D"));
  num_points2D_item_ = new QTableWidgetItem();
  num_points2D_item_->setForeground(Qt::red);
  table_widget_->setItem(table_row, 1, num_points2D_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("num_points3D"));
  num_points3D_item_ = new QTableWidgetItem();
  num_points3D_item_->setForeground(Qt::magenta);
  table_widget_->setItem(table_row, 1, num_points3D_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0,
                         new QTableWidgetItem("num_observations"));
  num_obs_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, num_obs_item_);
  table_row += 1;

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("name"));
  name_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, name_item_);
  table_row += 1;

  grid_layout_->addWidget(table_widget_, 0, 0);

  delete_button_ = new QPushButton(tr("Delete"), this);
  delete_button_->setFont(font);
  button_layout_->addWidget(delete_button_);
  connect(delete_button_, &QPushButton::released, this,
          &DatabaseImageViewerWidget::DeleteImage);
}

void DatabaseImageViewerWidget::ShowImageWithId(const image_t image_id) {
  if (model_viewer_widget_->images.count(image_id) == 0) {
    return;
  }

  image_id_ = image_id;

  const Image& image = model_viewer_widget_->images.at(image_id);
  const Camera& camera = model_viewer_widget_->cameras.at(image.CameraId());

  image_id_item_->setText(QString::number(image_id));
  camera_id_item_->setText(QString::number(image.CameraId()));
  camera_model_item_->setText(QString::fromStdString(camera.ModelName()));
  camera_params_item_->setText(QString::fromStdString(camera.ParamsToString()));
  qvec_item_->setText(QString::number(image.Qvec(0)) + ", " +
                      QString::number(image.Qvec(1)) + ", " +
                      QString::number(image.Qvec(2)) + ", " +
                      QString::number(image.Qvec(3)));
  tvec_item_->setText(QString::number(image.Tvec(0)) + ", " +
                      QString::number(image.Tvec(1)) + ", " +
                      QString::number(image.Tvec(2)));
  dimensions_item_->setText(QString::number(camera.Width()) + "x" +
                            QString::number(camera.Height()));
  num_points2D_item_->setText(QString::number(image.NumPoints2D()));

  std::vector<char> tri_mask(image.NumPoints2D());
  for (size_t i = 0; i < image.NumPoints2D(); ++i) {
    tri_mask[i] = image.Point2D(i).HasPoint3D();
  }

  num_points3D_item_->setText(QString::number(image.NumPoints3D()));
  num_obs_item_->setText(QString::number(image.NumObservations()));
  name_item_->setText(QString::fromStdString(image.Name()));

  ResizeTable();

  FeatureKeypoints keypoints(image.NumPoints2D());
  for (point2D_t i = 0; i < image.NumPoints2D(); ++i) {
    keypoints[i].x = static_cast<float>(image.Point2D(i).X());
    keypoints[i].y = static_cast<float>(image.Point2D(i).Y());
  }

  const std::string path = JoinPaths(*options_->image_path, image.Name());
  ReadAndShowWithKeypoints(path, keypoints, tri_mask);
}

void DatabaseImageViewerWidget::ResizeTable() {
  // Set fixed table dimensions.
  table_widget_->resizeColumnsToContents();
  int height = table_widget_->horizontalHeader()->height() +
               2 * table_widget_->frameWidth();
  for (int i = 0; i < table_widget_->rowCount(); i++) {
    height += table_widget_->rowHeight(i);
  }
  table_widget_->setFixedHeight(height);
}

void DatabaseImageViewerWidget::DeleteImage() {
  QMessageBox::StandardButton reply = QMessageBox::question(
      this, "", tr("Do you really want to delete this image?"),
      QMessageBox::Yes | QMessageBox::No);
  if (reply == QMessageBox::Yes) {
    if (model_viewer_widget_->reconstruction->ExistsImage(image_id_)) {
      model_viewer_widget_->reconstruction->DeRegisterImage(image_id_);
    }
    model_viewer_widget_->ReloadReconstruction();
  }
  hide();
}

}  // namespace colmap
