// Copyright (c), 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.

#include "colmap/ui/image_viewer_widget.h"

#include "colmap/ui/model_viewer_widget.h"
#include "colmap/util/file.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::filesystem::path& path) {
  Bitmap bitmap;
  if (!bitmap.Read(path, /*as_rgb=*/true)) {
    LOG(ERROR) << "Cannot read image at path " << path;
    return;
  }

  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::filesystem::path& path,
    const FeatureKeypoints& keypoints,
    const std::vector<char>& tri_mask) {
  Bitmap bitmap;
  if (!bitmap.Read(path, /*as_rgb=*/true)) {
    LOG(WARNING) << "Cannot read image at path " << path
                 << ", showing metadata only";
    image1_ = QPixmap();
    image2_ = image1_;
    ShowPixmap(image1_);
    return;
  }

  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 idx_tri = 0;
  size_t idx_not_tri = 0;
  for (size_t i = 0; i < tri_mask.size(); ++i) {
    if (tri_mask[i]) {
      keypoints_tri[idx_tri++] = keypoints[i];
    } else {
      keypoints_not_tri[idx_not_tri++] = keypoints[i];
    }
  }

  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::filesystem::path& path1,
    const std::filesystem::path& path2,
    const FeatureKeypoints& keypoints1,
    const FeatureKeypoints& keypoints2,
    const FeatureMatches& matches) {
  Bitmap bitmap1;
  Bitmap bitmap2;
  if (!bitmap1.Read(path1, /*as_rgb=*/true) ||
      !bitmap2.Read(path2, /*as_rgb=*/true)) {
    LOG(ERROR) << "Cannot read images at paths " << path1 << " and " << path2;
    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(8);

  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("frame_id"));
  frame_id_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, frame_id_item_);
  table_row += 1;

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

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

  table_widget_->setItem(table_row, 0, new QTableWidgetItem("cam_from_world"));
  cam_from_world_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, cam_from_world_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("name"));
  name_item_ = new QTableWidgetItem();
  table_widget_->setItem(table_row, 1, name_item_);
  // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
  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);

  image_id_item_->setText(QString::number(image_id));
  frame_id_item_->setText(QString::number(image.FrameId()));
  rig_id_item_->setText(QString::number(image.FramePtr()->RigId()));

  std::ostringstream camera_str;
  camera_str << *image.CameraPtr();
  camera_item_->setText(camera_str.str().c_str());

  if (image.HasPose()) {
    std::ostringstream cam_from_world_str;
    cam_from_world_str << image.CamFromWorld();
    cam_from_world_item_->setText(cam_from_world_str.str().c_str());
  } else {
    cam_from_world_item_->setText("undefined");
  }

  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()));
  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).xy(0));
    keypoints[i].y = static_cast<float>(image.Point2D(i).xy(1));
  }

  const auto path = *options_->image_path / image.Name();
  ReadAndShowWithKeypoints(path, keypoints, tri_mask);
}

void DatabaseImageViewerWidget::ResizeTable() {
  // Set fixed table dimensions. The horizontal header is hidden, so its height
  // must not be included or it leaves an empty strip below the last row.
  table_widget_->resizeColumnsToContents();
  int 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? This will delete other "
         "images in the same frame as well."),
      QMessageBox::Yes | QMessageBox::No);
  if (reply == QMessageBox::Yes) {
    if (model_viewer_widget_->reconstruction->ExistsImage(image_id_)) {
      const Image& image =
          model_viewer_widget_->reconstruction->Image(image_id_);
      model_viewer_widget_->reconstruction->DeRegisterFrame(image.FrameId());
    }
    model_viewer_widget_->ReloadReconstruction();
  }
  hide();
}

}  // namespace colmap
