// 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 "exe/mvs.h"

#include "base/reconstruction.h"
#include "mvs/fusion.h"
#include "mvs/meshing.h"
#include "mvs/patch_match.h"
#include "util/misc.h"
#include "util/option_manager.h"

namespace colmap {

int RunDelaunayMesher(int argc, char** argv) {
#ifndef CGAL_ENABLED
  std::cerr << "ERROR: Delaunay meshing requires CGAL, which is not "
               "available on your system."
            << std::endl;
  return EXIT_FAILURE;
#else   // CGAL_ENABLED
  std::string input_path;
  std::string input_type = "dense";
  std::string output_path;

  OptionManager options;
  options.AddRequiredOption(
      "input_path", &input_path,
      "Path to either the dense workspace folder or the sparse reconstruction");
  options.AddDefaultOption("input_type", &input_type, "{dense, sparse}");
  options.AddRequiredOption("output_path", &output_path);
  options.AddDelaunayMeshingOptions();
  options.Parse(argc, argv);

  StringToLower(&input_type);
  if (input_type == "sparse") {
    mvs::SparseDelaunayMeshing(*options.delaunay_meshing, input_path,
                               output_path);
  } else if (input_type == "dense") {
    mvs::DenseDelaunayMeshing(*options.delaunay_meshing, input_path,
                              output_path);
  } else {
    std::cout << "ERROR: Invalid input type - "
                 "supported values are 'sparse' and 'dense'."
              << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
#endif  // CGAL_ENABLED
}

int RunPatchMatchStereo(int argc, char** argv) {
#ifndef CUDA_ENABLED
  std::cerr << "ERROR: Dense stereo reconstruction requires CUDA, which is not "
               "available on your system."
            << std::endl;
  return EXIT_FAILURE;
#else   // CUDA_ENABLED
  std::string workspace_path;
  std::string workspace_format = "COLMAP";
  std::string pmvs_option_name = "option-all";
  std::string config_path;

  OptionManager options;
  options.AddRequiredOption(
      "workspace_path", &workspace_path,
      "Path to the folder containing the undistorted images");
  options.AddDefaultOption("workspace_format", &workspace_format,
                           "{COLMAP, PMVS}");
  options.AddDefaultOption("pmvs_option_name", &pmvs_option_name);
  options.AddDefaultOption("config_path", &config_path);
  options.AddPatchMatchStereoOptions();
  options.Parse(argc, argv);

  StringToLower(&workspace_format);
  if (workspace_format != "colmap" && workspace_format != "pmvs") {
    std::cout << "ERROR: Invalid `workspace_format` - supported values are "
                 "'COLMAP' or 'PMVS'."
              << std::endl;
    return EXIT_FAILURE;
  }

  mvs::PatchMatchController controller(*options.patch_match_stereo,
                                       workspace_path, workspace_format,
                                       pmvs_option_name, config_path);

  controller.Start();
  controller.Wait();

  return EXIT_SUCCESS;
#endif  // CUDA_ENABLED
}

int RunPoissonMesher(int argc, char** argv) {
  std::string input_path;
  std::string output_path;

  OptionManager options;
  options.AddRequiredOption("input_path", &input_path);
  options.AddRequiredOption("output_path", &output_path);
  options.AddPoissonMeshingOptions();
  options.Parse(argc, argv);

  CHECK(mvs::PoissonMeshing(*options.poisson_meshing, input_path, output_path));

  return EXIT_SUCCESS;
}

int RunStereoFuser(int argc, char** argv) {
  std::string workspace_path;
  std::string input_type = "geometric";
  std::string workspace_format = "COLMAP";
  std::string pmvs_option_name = "option-all";
  std::string output_type = "PLY";
  std::string output_path;
  std::string bbox_path;

  OptionManager options;
  options.AddRequiredOption("workspace_path", &workspace_path);
  options.AddDefaultOption("workspace_format", &workspace_format,
                           "{COLMAP, PMVS}");
  options.AddDefaultOption("pmvs_option_name", &pmvs_option_name);
  options.AddDefaultOption("input_type", &input_type,
                           "{photometric, geometric}");
  options.AddDefaultOption("output_type", &output_type, "{BIN, TXT, PLY}");
  options.AddRequiredOption("output_path", &output_path);
  options.AddDefaultOption("bbox_path", &bbox_path);
  options.AddStereoFusionOptions();
  options.Parse(argc, argv);

  StringToLower(&workspace_format);
  if (workspace_format != "colmap" && workspace_format != "pmvs") {
    std::cout << "ERROR: Invalid `workspace_format` - supported values are "
                 "'COLMAP' or 'PMVS'."
              << std::endl;
    return EXIT_FAILURE;
  }

  StringToLower(&input_type);
  if (input_type != "photometric" && input_type != "geometric") {
    std::cout << "ERROR: Invalid input type - supported values are "
                 "'photometric' and 'geometric'."
              << std::endl;
    return EXIT_FAILURE;
  }

  if (!bbox_path.empty()) {
    std::ifstream file(bbox_path);
    if (file.is_open()) {
      auto& min_bound = options.stereo_fusion->bounding_box.first;
      auto& max_bound = options.stereo_fusion->bounding_box.second;
      file >> min_bound(0) >> min_bound(1) >> min_bound(2);
      file >> max_bound(0) >> max_bound(1) >> max_bound(2);
    } else {
      std::cout << "WARN: Invalid bounds path: \"" << bbox_path
                << "\" - continuing without bounds check" << std::endl;
    }
  }

  mvs::StereoFusion fuser(*options.stereo_fusion, workspace_path,
                          workspace_format, pmvs_option_name, input_type);

  fuser.Start();
  fuser.Wait();

  Reconstruction reconstruction;

  // read data from sparse reconstruction
  if (workspace_format == "colmap") {
    reconstruction.Read(JoinPaths(workspace_path, "sparse"));
  }

  // overwrite sparse point cloud with dense point cloud from fuser
  reconstruction.ImportPLY(fuser.GetFusedPoints());

  std::cout << "Writing output: " << output_path << std::endl;

  // write output
  StringToLower(&output_type);
  if (output_type == "bin") {
    reconstruction.WriteBinary(output_path);
  } else if (output_type == "txt") {
    reconstruction.WriteText(output_path);
  } else if (output_type == "ply") {
    WriteBinaryPlyPoints(output_path, fuser.GetFusedPoints());
    mvs::WritePointsVisibility(output_path + ".vis",
                               fuser.GetFusedPointsVisibility());
  } else {
    std::cerr << "ERROR: Invalid `output_type`" << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

}  // namespace colmap
