// Copyright (c) 2018, 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 inf.ethz.ch)

#include "util/option_manager.h"

#include <boost/filesystem/operations.hpp>
#include <boost/property_tree/ini_parser.hpp>

#include "base/image_reader.h"
#include "controllers/incremental_mapper.h"
#include "feature/extraction.h"
#include "feature/matching.h"
#include "feature/sift.h"
#include "mvs/fusion.h"
#include "mvs/meshing.h"
#include "mvs/patch_match.h"
#include "optim/bundle_adjustment.h"
#include "ui/render_options.h"
#include "util/misc.h"
#include "util/version.h"

namespace config = boost::program_options;

namespace colmap {

OptionManager::OptionManager() {
  project_path.reset(new std::string());
  database_path.reset(new std::string());
  image_path.reset(new std::string());

  image_reader.reset(new ImageReaderOptions());
  sift_extraction.reset(new SiftExtractionOptions());
  sift_matching.reset(new SiftMatchingOptions());
  exhaustive_matching.reset(new ExhaustiveMatchingOptions());
  sequential_matching.reset(new SequentialMatchingOptions());
  vocab_tree_matching.reset(new VocabTreeMatchingOptions());
  spatial_matching.reset(new SpatialMatchingOptions());
  transitive_matching.reset(new TransitiveMatchingOptions());
  bundle_adjustment.reset(new BundleAdjustmentOptions());
  mapper.reset(new IncrementalMapperOptions());
  patch_match_stereo.reset(new mvs::PatchMatchOptions());
  stereo_fusion.reset(new mvs::StereoFusionOptions());
  poisson_meshing.reset(new mvs::PoissonMeshingOptions());
  delaunay_meshing.reset(new mvs::DelaunayMeshingOptions());
  render.reset(new RenderOptions());

  Reset();

  desc_->add_options()("help,h", "");
  desc_->add_options()("project_path", config::value<std::string>());
}

void OptionManager::ModifyForIndividualData() {
  mapper->max_extra_param = std::numeric_limits<double>::max();
}

void OptionManager::ModifyForVideoData() {
  const bool kResetPaths = false;
  ResetOptions(kResetPaths);
  mapper->mapper.init_min_tri_angle /= 2;
  mapper->ba_global_images_ratio = 1.4;
  mapper->ba_global_points_ratio = 1.4;
  mapper->max_extra_param = std::numeric_limits<double>::max();
  stereo_fusion->min_num_pixels = 15;
}

void OptionManager::ModifyForInternetData() {
  stereo_fusion->min_num_pixels = 10;
}

void OptionManager::ModifyForLowQuality() {
  sift_extraction->max_image_size = 1000;
  sequential_matching->loop_detection_num_images /= 2;
  vocab_tree_matching->num_images /= 2;
  mapper->ba_local_max_num_iterations /= 2;
  mapper->ba_global_max_num_iterations /= 2;
  mapper->ba_global_images_ratio *= 1.2;
  mapper->ba_global_points_ratio *= 1.2;
  mapper->ba_global_max_refinements = 2;
  patch_match_stereo->max_image_size = 1000;
  patch_match_stereo->window_radius = 4;
  patch_match_stereo->window_step = 2;
  patch_match_stereo->num_samples /= 2;
  patch_match_stereo->num_iterations = 3;
  patch_match_stereo->geom_consistency = false;
  stereo_fusion->check_num_images /= 2;
  stereo_fusion->max_image_size = 1000;
}

void OptionManager::ModifyForMediumQuality() {
  sift_extraction->max_image_size = 1600;
  sequential_matching->loop_detection_num_images /= 1.5;
  vocab_tree_matching->num_images /= 1.5;
  mapper->ba_local_max_num_iterations /= 1.5;
  mapper->ba_global_max_num_iterations /= 1.5;
  mapper->ba_global_images_ratio *= 1.1;
  mapper->ba_global_points_ratio *= 1.1;
  mapper->ba_global_max_refinements = 2;
  patch_match_stereo->max_image_size = 1600;
  patch_match_stereo->window_radius = 4;
  patch_match_stereo->window_step = 2;
  patch_match_stereo->num_samples /= 1.5;
  patch_match_stereo->num_iterations = 5;
  patch_match_stereo->geom_consistency = false;
  stereo_fusion->check_num_images /= 1.5;
  stereo_fusion->max_image_size = 1600;
}

void OptionManager::ModifyForHighQuality() {
  sift_extraction->max_image_size = 2400;
  patch_match_stereo->max_image_size = 2400;
  stereo_fusion->max_image_size = 2400;
}

void OptionManager::ModifyForExtremeQuality() {
  // Extreme quality is the default.
}

void OptionManager::AddAllOptions() {
  AddLogOptions();
  AddDatabaseOptions();
  AddImageOptions();
  AddExtractionOptions();
  AddMatchingOptions();
  AddExhaustiveMatchingOptions();
  AddSequentialMatchingOptions();
  AddVocabTreeMatchingOptions();
  AddSpatialMatchingOptions();
  AddTransitiveMatchingOptions();
  AddBundleAdjustmentOptions();
  AddMapperOptions();
  AddPatchMatchStereoOptions();
  AddStereoFusionOptions();
  AddPoissonMeshingOptions();
  AddDelaunayMeshingOptions();
  AddRenderOptions();
}

void OptionManager::AddLogOptions() {
  if (added_log_options_) {
    return;
  }
  added_log_options_ = true;

  AddAndRegisterDefaultOption("log_to_stderr", &FLAGS_logtostderr);
  AddAndRegisterDefaultOption("log_level", &FLAGS_v);
}

void OptionManager::AddDatabaseOptions() {
  if (added_database_options_) {
    return;
  }
  added_database_options_ = true;

  AddAndRegisterRequiredOption("database_path", database_path.get());
}

void OptionManager::AddImageOptions() {
  if (added_image_options_) {
    return;
  }
  added_image_options_ = true;

  AddAndRegisterRequiredOption("image_path", image_path.get());
}

void OptionManager::AddExtractionOptions() {
  if (added_extraction_options_) {
    return;
  }
  added_extraction_options_ = true;

  AddAndRegisterDefaultOption("ImageReader.camera_model",
                              &image_reader->camera_model);
  AddAndRegisterDefaultOption("ImageReader.single_camera",
                              &image_reader->single_camera);
  AddAndRegisterDefaultOption("ImageReader.single_camera_per_folder",
                              &image_reader->single_camera_per_folder);
  AddAndRegisterDefaultOption("ImageReader.existing_camera_id",
                              &image_reader->existing_camera_id);
  AddAndRegisterDefaultOption("ImageReader.camera_params",
                              &image_reader->camera_params);
  AddAndRegisterDefaultOption("ImageReader.default_focal_length_factor",
                              &image_reader->default_focal_length_factor);

  AddAndRegisterDefaultOption("SiftExtraction.num_threads",
                              &sift_extraction->num_threads);
  AddAndRegisterDefaultOption("SiftExtraction.use_gpu",
                              &sift_extraction->use_gpu);
  AddAndRegisterDefaultOption("SiftExtraction.gpu_index",
                              &sift_extraction->gpu_index);
  AddAndRegisterDefaultOption("SiftExtraction.max_image_size",
                              &sift_extraction->max_image_size);
  AddAndRegisterDefaultOption("SiftExtraction.max_num_features",
                              &sift_extraction->max_num_features);
  AddAndRegisterDefaultOption("SiftExtraction.first_octave",
                              &sift_extraction->first_octave);
  AddAndRegisterDefaultOption("SiftExtraction.num_octaves",
                              &sift_extraction->num_octaves);
  AddAndRegisterDefaultOption("SiftExtraction.octave_resolution",
                              &sift_extraction->octave_resolution);
  AddAndRegisterDefaultOption("SiftExtraction.peak_threshold",
                              &sift_extraction->peak_threshold);
  AddAndRegisterDefaultOption("SiftExtraction.edge_threshold",
                              &sift_extraction->edge_threshold);
  AddAndRegisterDefaultOption("SiftExtraction.estimate_affine_shape",
                              &sift_extraction->estimate_affine_shape);
  AddAndRegisterDefaultOption("SiftExtraction.max_num_orientations",
                              &sift_extraction->max_num_orientations);
  AddAndRegisterDefaultOption("SiftExtraction.upright",
                              &sift_extraction->upright);
  AddAndRegisterDefaultOption("SiftExtraction.domain_size_pooling",
                              &sift_extraction->domain_size_pooling);
  AddAndRegisterDefaultOption("SiftExtraction.dsp_min_scale",
                              &sift_extraction->dsp_min_scale);
  AddAndRegisterDefaultOption("SiftExtraction.dsp_max_scale",
                              &sift_extraction->dsp_max_scale);
  AddAndRegisterDefaultOption("SiftExtraction.dsp_num_scales",
                              &sift_extraction->dsp_num_scales);
}

void OptionManager::AddMatchingOptions() {
  if (added_match_options_) {
    return;
  }
  added_match_options_ = true;

  AddAndRegisterDefaultOption("SiftMatching.num_threads",
                              &sift_matching->num_threads);
  AddAndRegisterDefaultOption("SiftMatching.use_gpu", &sift_matching->use_gpu);
  AddAndRegisterDefaultOption("SiftMatching.gpu_index",
                              &sift_matching->gpu_index);
  AddAndRegisterDefaultOption("SiftMatching.max_ratio",
                              &sift_matching->max_ratio);
  AddAndRegisterDefaultOption("SiftMatching.max_distance",
                              &sift_matching->max_distance);
  AddAndRegisterDefaultOption("SiftMatching.cross_check",
                              &sift_matching->cross_check);
  AddAndRegisterDefaultOption("SiftMatching.max_error",
                              &sift_matching->max_error);
  AddAndRegisterDefaultOption("SiftMatching.max_num_matches",
                              &sift_matching->max_num_matches);
  AddAndRegisterDefaultOption("SiftMatching.confidence",
                              &sift_matching->confidence);
  AddAndRegisterDefaultOption("SiftMatching.max_num_trials",
                              &sift_matching->max_num_trials);
  AddAndRegisterDefaultOption("SiftMatching.min_inlier_ratio",
                              &sift_matching->min_inlier_ratio);
  AddAndRegisterDefaultOption("SiftMatching.min_num_inliers",
                              &sift_matching->min_num_inliers);
  AddAndRegisterDefaultOption("SiftMatching.multiple_models",
                              &sift_matching->multiple_models);
  AddAndRegisterDefaultOption("SiftMatching.guided_matching",
                              &sift_matching->guided_matching);
}

void OptionManager::AddExhaustiveMatchingOptions() {
  if (added_exhaustive_match_options_) {
    return;
  }
  added_exhaustive_match_options_ = true;

  AddMatchingOptions();

  AddAndRegisterDefaultOption("ExhaustiveMatching.block_size",
                              &exhaustive_matching->block_size);
}

void OptionManager::AddSequentialMatchingOptions() {
  if (added_sequential_match_options_) {
    return;
  }
  added_sequential_match_options_ = true;

  AddMatchingOptions();

  AddAndRegisterDefaultOption("SequentialMatching.overlap",
                              &sequential_matching->overlap);
  AddAndRegisterDefaultOption("SequentialMatching.quadratic_overlap",
                              &sequential_matching->quadratic_overlap);
  AddAndRegisterDefaultOption("SequentialMatching.loop_detection",
                              &sequential_matching->loop_detection);
  AddAndRegisterDefaultOption("SequentialMatching.loop_detection_period",
                              &sequential_matching->loop_detection_period);
  AddAndRegisterDefaultOption("SequentialMatching.loop_detection_num_images",
                              &sequential_matching->loop_detection_num_images);
  AddAndRegisterDefaultOption(
      "SequentialMatching.loop_detection_num_nearest_neighbors",
      &sequential_matching->loop_detection_num_nearest_neighbors);
  AddAndRegisterDefaultOption("SequentialMatching.loop_detection_num_checks",
                              &sequential_matching->loop_detection_num_checks);
  AddAndRegisterDefaultOption(
      "SequentialMatching.loop_detection_num_images_after_verification",
      &sequential_matching->loop_detection_num_images_after_verification);
  AddAndRegisterDefaultOption(
      "SequentialMatching.loop_detection_max_num_features",
      &sequential_matching->loop_detection_max_num_features);
  AddAndRegisterDefaultOption("SequentialMatching.vocab_tree_path",
                              &sequential_matching->vocab_tree_path);
}

void OptionManager::AddVocabTreeMatchingOptions() {
  if (added_vocab_tree_match_options_) {
    return;
  }
  added_vocab_tree_match_options_ = true;

  AddMatchingOptions();

  AddAndRegisterDefaultOption("VocabTreeMatching.num_images",
                              &vocab_tree_matching->num_images);
  AddAndRegisterDefaultOption("VocabTreeMatching.num_nearest_neighbors",
                              &vocab_tree_matching->num_nearest_neighbors);
  AddAndRegisterDefaultOption("VocabTreeMatching.num_checks",
                              &vocab_tree_matching->num_checks);
  AddAndRegisterDefaultOption(
      "VocabTreeMatching.num_images_after_verification",
      &vocab_tree_matching->num_images_after_verification);
  AddAndRegisterDefaultOption("VocabTreeMatching.max_num_features",
                              &vocab_tree_matching->max_num_features);
  AddAndRegisterDefaultOption("VocabTreeMatching.vocab_tree_path",
                              &vocab_tree_matching->vocab_tree_path);
  AddAndRegisterDefaultOption("VocabTreeMatching.match_list_path",
                              &vocab_tree_matching->match_list_path);
}

void OptionManager::AddSpatialMatchingOptions() {
  if (added_spatial_match_options_) {
    return;
  }
  added_spatial_match_options_ = true;

  AddMatchingOptions();

  AddAndRegisterDefaultOption("SpatialMatching.is_gps",
                              &spatial_matching->is_gps);
  AddAndRegisterDefaultOption("SpatialMatching.ignore_z",
                              &spatial_matching->ignore_z);
  AddAndRegisterDefaultOption("SpatialMatching.max_num_neighbors",
                              &spatial_matching->max_num_neighbors);
  AddAndRegisterDefaultOption("SpatialMatching.max_distance",
                              &spatial_matching->max_distance);
}

void OptionManager::AddTransitiveMatchingOptions() {
  if (added_transitive_match_options_) {
    return;
  }
  added_transitive_match_options_ = true;

  AddMatchingOptions();

  AddAndRegisterDefaultOption("TransitiveMatching.batch_size",
                              &transitive_matching->batch_size);
  AddAndRegisterDefaultOption("TransitiveMatching.num_iterations",
                              &transitive_matching->num_iterations);
}

void OptionManager::AddBundleAdjustmentOptions() {
  if (added_ba_options_) {
    return;
  }
  added_ba_options_ = true;

  AddAndRegisterDefaultOption(
      "BundleAdjustment.max_num_iterations",
      &bundle_adjustment->solver_options.max_num_iterations);
  AddAndRegisterDefaultOption(
      "BundleAdjustment.max_linear_solver_iterations",
      &bundle_adjustment->solver_options.max_linear_solver_iterations);
  AddAndRegisterDefaultOption(
      "BundleAdjustment.function_tolerance",
      &bundle_adjustment->solver_options.function_tolerance);
  AddAndRegisterDefaultOption(
      "BundleAdjustment.gradient_tolerance",
      &bundle_adjustment->solver_options.gradient_tolerance);
  AddAndRegisterDefaultOption(
      "BundleAdjustment.parameter_tolerance",
      &bundle_adjustment->solver_options.parameter_tolerance);
  AddAndRegisterDefaultOption("BundleAdjustment.refine_focal_length",
                              &bundle_adjustment->refine_focal_length);
  AddAndRegisterDefaultOption("BundleAdjustment.refine_principal_point",
                              &bundle_adjustment->refine_principal_point);
  AddAndRegisterDefaultOption("BundleAdjustment.refine_extra_params",
                              &bundle_adjustment->refine_extra_params);
}

void OptionManager::AddMapperOptions() {
  if (added_mapper_options_) {
    return;
  }
  added_mapper_options_ = true;

  AddAndRegisterDefaultOption("Mapper.min_num_matches",
                              &mapper->min_num_matches);
  AddAndRegisterDefaultOption("Mapper.ignore_watermarks",
                              &mapper->ignore_watermarks);
  AddAndRegisterDefaultOption("Mapper.multiple_models",
                              &mapper->multiple_models);
  AddAndRegisterDefaultOption("Mapper.max_num_models", &mapper->max_num_models);
  AddAndRegisterDefaultOption("Mapper.max_model_overlap",
                              &mapper->max_model_overlap);
  AddAndRegisterDefaultOption("Mapper.min_model_size", &mapper->min_model_size);
  AddAndRegisterDefaultOption("Mapper.init_image_id1", &mapper->init_image_id1);
  AddAndRegisterDefaultOption("Mapper.init_image_id2", &mapper->init_image_id2);
  AddAndRegisterDefaultOption("Mapper.init_num_trials",
                              &mapper->init_num_trials);
  AddAndRegisterDefaultOption("Mapper.extract_colors", &mapper->extract_colors);
  AddAndRegisterDefaultOption("Mapper.num_threads", &mapper->num_threads);
  AddAndRegisterDefaultOption("Mapper.min_focal_length_ratio",
                              &mapper->min_focal_length_ratio);
  AddAndRegisterDefaultOption("Mapper.max_focal_length_ratio",
                              &mapper->max_focal_length_ratio);
  AddAndRegisterDefaultOption("Mapper.max_extra_param",
                              &mapper->max_extra_param);
  AddAndRegisterDefaultOption("Mapper.ba_refine_focal_length",
                              &mapper->ba_refine_focal_length);
  AddAndRegisterDefaultOption("Mapper.ba_refine_principal_point",
                              &mapper->ba_refine_principal_point);
  AddAndRegisterDefaultOption("Mapper.ba_refine_extra_params",
                              &mapper->ba_refine_extra_params);
  AddAndRegisterDefaultOption("Mapper.ba_local_num_images",
                              &mapper->ba_local_num_images);
  AddAndRegisterDefaultOption("Mapper.ba_local_max_num_iterations",
                              &mapper->ba_local_max_num_iterations);
  AddAndRegisterDefaultOption("Mapper.ba_global_use_pba",
                              &mapper->ba_global_use_pba);
  AddAndRegisterDefaultOption("Mapper.ba_global_pba_gpu_index",
                              &mapper->ba_global_pba_gpu_index);
  AddAndRegisterDefaultOption("Mapper.ba_global_images_ratio",
                              &mapper->ba_global_images_ratio);
  AddAndRegisterDefaultOption("Mapper.ba_global_points_ratio",
                              &mapper->ba_global_points_ratio);
  AddAndRegisterDefaultOption("Mapper.ba_global_images_freq",
                              &mapper->ba_global_images_freq);
  AddAndRegisterDefaultOption("Mapper.ba_global_points_freq",
                              &mapper->ba_global_points_freq);
  AddAndRegisterDefaultOption("Mapper.ba_global_max_num_iterations",
                              &mapper->ba_global_max_num_iterations);
  AddAndRegisterDefaultOption("Mapper.ba_global_max_refinements",
                              &mapper->ba_global_max_refinements);
  AddAndRegisterDefaultOption("Mapper.ba_global_max_refinement_change",
                              &mapper->ba_global_max_refinement_change);
  AddAndRegisterDefaultOption("Mapper.ba_local_max_refinements",
                              &mapper->ba_local_max_refinements);
  AddAndRegisterDefaultOption("Mapper.ba_local_max_refinement_change",
                              &mapper->ba_local_max_refinement_change);
  AddAndRegisterDefaultOption("Mapper.snapshot_path", &mapper->snapshot_path);
  AddAndRegisterDefaultOption("Mapper.snapshot_images_freq",
                              &mapper->snapshot_images_freq);

  // IncrementalMapper.
  AddAndRegisterDefaultOption("Mapper.init_min_num_inliers",
                              &mapper->mapper.init_min_num_inliers);
  AddAndRegisterDefaultOption("Mapper.init_max_error",
                              &mapper->mapper.init_max_error);
  AddAndRegisterDefaultOption("Mapper.init_max_forward_motion",
                              &mapper->mapper.init_max_forward_motion);
  AddAndRegisterDefaultOption("Mapper.init_min_tri_angle",
                              &mapper->mapper.init_min_tri_angle);
  AddAndRegisterDefaultOption("Mapper.init_max_reg_trials",
                              &mapper->mapper.init_max_reg_trials);
  AddAndRegisterDefaultOption("Mapper.abs_pose_max_error",
                              &mapper->mapper.abs_pose_max_error);
  AddAndRegisterDefaultOption("Mapper.abs_pose_min_num_inliers",
                              &mapper->mapper.abs_pose_min_num_inliers);
  AddAndRegisterDefaultOption("Mapper.abs_pose_min_inlier_ratio",
                              &mapper->mapper.abs_pose_min_inlier_ratio);
  AddAndRegisterDefaultOption("Mapper.filter_max_reproj_error",
                              &mapper->mapper.filter_max_reproj_error);
  AddAndRegisterDefaultOption("Mapper.filter_min_tri_angle",
                              &mapper->mapper.filter_min_tri_angle);
  AddAndRegisterDefaultOption("Mapper.max_reg_trials",
                              &mapper->mapper.max_reg_trials);

  // IncrementalTriangulator.
  AddAndRegisterDefaultOption("Mapper.tri_max_transitivity",
                              &mapper->triangulation.max_transitivity);
  AddAndRegisterDefaultOption("Mapper.tri_create_max_angle_error",
                              &mapper->triangulation.create_max_angle_error);
  AddAndRegisterDefaultOption("Mapper.tri_continue_max_angle_error",
                              &mapper->triangulation.continue_max_angle_error);
  AddAndRegisterDefaultOption("Mapper.tri_merge_max_reproj_error",
                              &mapper->triangulation.merge_max_reproj_error);
  AddAndRegisterDefaultOption("Mapper.tri_complete_max_reproj_error",
                              &mapper->triangulation.complete_max_reproj_error);
  AddAndRegisterDefaultOption("Mapper.tri_complete_max_transitivity",
                              &mapper->triangulation.complete_max_transitivity);
  AddAndRegisterDefaultOption("Mapper.tri_re_max_angle_error",
                              &mapper->triangulation.re_max_angle_error);
  AddAndRegisterDefaultOption("Mapper.tri_re_min_ratio",
                              &mapper->triangulation.re_min_ratio);
  AddAndRegisterDefaultOption("Mapper.tri_re_max_trials",
                              &mapper->triangulation.re_max_trials);
  AddAndRegisterDefaultOption("Mapper.tri_min_angle",
                              &mapper->triangulation.min_angle);
  AddAndRegisterDefaultOption("Mapper.tri_ignore_two_view_tracks",
                              &mapper->triangulation.ignore_two_view_tracks);
}

void OptionManager::AddPatchMatchStereoOptions() {
  if (added_patch_match_stereo_options_) {
    return;
  }
  added_patch_match_stereo_options_ = true;

  AddAndRegisterDefaultOption("PatchMatchStereo.max_image_size",
                              &patch_match_stereo->max_image_size);
  AddAndRegisterDefaultOption("PatchMatchStereo.gpu_index",
                              &patch_match_stereo->gpu_index);
  AddAndRegisterDefaultOption("PatchMatchStereo.depth_min",
                              &patch_match_stereo->depth_min);
  AddAndRegisterDefaultOption("PatchMatchStereo.depth_max",
                              &patch_match_stereo->depth_max);
  AddAndRegisterDefaultOption("PatchMatchStereo.window_radius",
                              &patch_match_stereo->window_radius);
  AddAndRegisterDefaultOption("PatchMatchStereo.window_step",
                              &patch_match_stereo->window_step);
  AddAndRegisterDefaultOption("PatchMatchStereo.sigma_spatial",
                              &patch_match_stereo->sigma_spatial);
  AddAndRegisterDefaultOption("PatchMatchStereo.sigma_color",
                              &patch_match_stereo->sigma_color);
  AddAndRegisterDefaultOption("PatchMatchStereo.num_samples",
                              &patch_match_stereo->num_samples);
  AddAndRegisterDefaultOption("PatchMatchStereo.ncc_sigma",
                              &patch_match_stereo->ncc_sigma);
  AddAndRegisterDefaultOption("PatchMatchStereo.min_triangulation_angle",
                              &patch_match_stereo->min_triangulation_angle);
  AddAndRegisterDefaultOption("PatchMatchStereo.incident_angle_sigma",
                              &patch_match_stereo->incident_angle_sigma);
  AddAndRegisterDefaultOption("PatchMatchStereo.num_iterations",
                              &patch_match_stereo->num_iterations);
  AddAndRegisterDefaultOption("PatchMatchStereo.geom_consistency",
                              &patch_match_stereo->geom_consistency);
  AddAndRegisterDefaultOption(
      "PatchMatchStereo.geom_consistency_regularizer",
      &patch_match_stereo->geom_consistency_regularizer);
  AddAndRegisterDefaultOption("PatchMatchStereo.geom_consistency_max_cost",
                              &patch_match_stereo->geom_consistency_max_cost);
  AddAndRegisterDefaultOption("PatchMatchStereo.filter",
                              &patch_match_stereo->filter);
  AddAndRegisterDefaultOption("PatchMatchStereo.filter_min_ncc",
                              &patch_match_stereo->filter_min_ncc);
  AddAndRegisterDefaultOption(
      "PatchMatchStereo.filter_min_triangulation_angle",
      &patch_match_stereo->filter_min_triangulation_angle);
  AddAndRegisterDefaultOption("PatchMatchStereo.filter_min_num_consistent",
                              &patch_match_stereo->filter_min_num_consistent);
  AddAndRegisterDefaultOption(
      "PatchMatchStereo.filter_geom_consistency_max_cost",
      &patch_match_stereo->filter_geom_consistency_max_cost);
  AddAndRegisterDefaultOption("PatchMatchStereo.cache_size",
                              &patch_match_stereo->cache_size);
  AddAndRegisterDefaultOption("PatchMatchStereo.write_consistency_graph",
                              &patch_match_stereo->write_consistency_graph);
}

void OptionManager::AddStereoFusionOptions() {
  if (added_stereo_fusion_options_) {
    return;
  }
  added_stereo_fusion_options_ = true;

  AddAndRegisterDefaultOption("StereoFusion.max_image_size",
                              &stereo_fusion->max_image_size);
  AddAndRegisterDefaultOption("StereoFusion.min_num_pixels",
                              &stereo_fusion->min_num_pixels);
  AddAndRegisterDefaultOption("StereoFusion.max_num_pixels",
                              &stereo_fusion->max_num_pixels);
  AddAndRegisterDefaultOption("StereoFusion.max_traversal_depth",
                              &stereo_fusion->max_traversal_depth);
  AddAndRegisterDefaultOption("StereoFusion.max_reproj_error",
                              &stereo_fusion->max_reproj_error);
  AddAndRegisterDefaultOption("StereoFusion.max_depth_error",
                              &stereo_fusion->max_depth_error);
  AddAndRegisterDefaultOption("StereoFusion.max_normal_error",
                              &stereo_fusion->max_normal_error);
  AddAndRegisterDefaultOption("StereoFusion.check_num_images",
                              &stereo_fusion->check_num_images);
  AddAndRegisterDefaultOption("StereoFusion.cache_size",
                              &stereo_fusion->cache_size);
}

void OptionManager::AddPoissonMeshingOptions() {
  if (added_poisson_meshing_options_) {
    return;
  }
  added_poisson_meshing_options_ = true;

  AddAndRegisterDefaultOption("PoissonMeshing.point_weight",
                              &poisson_meshing->point_weight);
  AddAndRegisterDefaultOption("PoissonMeshing.depth", &poisson_meshing->depth);
  AddAndRegisterDefaultOption("PoissonMeshing.color", &poisson_meshing->color);
  AddAndRegisterDefaultOption("PoissonMeshing.trim", &poisson_meshing->trim);
  AddAndRegisterDefaultOption("PoissonMeshing.num_threads",
                              &poisson_meshing->num_threads);
}

void OptionManager::AddDelaunayMeshingOptions() {
  if (added_delaunay_meshing_options_) {
    return;
  }
  added_delaunay_meshing_options_ = true;

  AddAndRegisterDefaultOption("DelaunayMeshing.max_proj_dist",
                              &delaunay_meshing->max_proj_dist);
  AddAndRegisterDefaultOption("DelaunayMeshing.max_depth_dist",
                              &delaunay_meshing->max_depth_dist);
  AddAndRegisterDefaultOption("DelaunayMeshing.distance_sigma_factor",
                              &delaunay_meshing->distance_sigma_factor);
  AddAndRegisterDefaultOption("DelaunayMeshing.quality_regularization",
                              &delaunay_meshing->quality_regularization);
  AddAndRegisterDefaultOption("DelaunayMeshing.max_side_length_factor",
                              &delaunay_meshing->max_side_length_factor);
  AddAndRegisterDefaultOption("DelaunayMeshing.max_side_length_percentile",
                              &delaunay_meshing->max_side_length_percentile);
  AddAndRegisterDefaultOption("DelaunayMeshing.num_threads",
                              &delaunay_meshing->num_threads);
}

void OptionManager::AddRenderOptions() {
  if (added_render_options_) {
    return;
  }
  added_render_options_ = true;

  AddAndRegisterDefaultOption("Render.min_track_len", &render->min_track_len);
  AddAndRegisterDefaultOption("Render.max_error", &render->max_error);
  AddAndRegisterDefaultOption("Render.refresh_rate", &render->refresh_rate);
  AddAndRegisterDefaultOption("Render.adapt_refresh_rate",
                              &render->adapt_refresh_rate);
  AddAndRegisterDefaultOption("Render.image_connections",
                              &render->image_connections);
  AddAndRegisterDefaultOption("Render.projection_type",
                              &render->projection_type);
}

void OptionManager::Reset() {
  FLAGS_logtostderr = false;
  FLAGS_v = 2;

  const bool kResetPaths = true;
  ResetOptions(kResetPaths);

  desc_.reset(new boost::program_options::options_description());

  options_bool_.clear();
  options_int_.clear();
  options_double_.clear();
  options_string_.clear();

  added_log_options_ = false;
  added_database_options_ = false;
  added_image_options_ = false;
  added_extraction_options_ = false;
  added_match_options_ = false;
  added_exhaustive_match_options_ = false;
  added_sequential_match_options_ = false;
  added_vocab_tree_match_options_ = false;
  added_spatial_match_options_ = false;
  added_transitive_match_options_ = false;
  added_ba_options_ = false;
  added_mapper_options_ = false;
  added_patch_match_stereo_options_ = false;
  added_stereo_fusion_options_ = false;
  added_poisson_meshing_options_ = false;
  added_delaunay_meshing_options_ = false;
  added_render_options_ = false;
}

void OptionManager::ResetOptions(const bool reset_paths) {
  if (reset_paths) {
    *project_path = "";
    *database_path = "";
    *image_path = "";
  }
  *image_reader = ImageReaderOptions();
  *sift_extraction = SiftExtractionOptions();
  *sift_matching = SiftMatchingOptions();
  *exhaustive_matching = ExhaustiveMatchingOptions();
  *sequential_matching = SequentialMatchingOptions();
  *vocab_tree_matching = VocabTreeMatchingOptions();
  *spatial_matching = SpatialMatchingOptions();
  *transitive_matching = TransitiveMatchingOptions();
  *bundle_adjustment = BundleAdjustmentOptions();
  *mapper = IncrementalMapperOptions();
  *patch_match_stereo = mvs::PatchMatchOptions();
  *stereo_fusion = mvs::StereoFusionOptions();
  *poisson_meshing = mvs::PoissonMeshingOptions();
  *delaunay_meshing = mvs::DelaunayMeshingOptions();
  *render = RenderOptions();
}

bool OptionManager::Check() {
  bool success = true;

  if (added_database_options_) {
    const auto database_parent_path = GetParentDir(*database_path);
    success = success && CHECK_OPTION_IMPL(!ExistsDir(*database_path)) &&
              CHECK_OPTION_IMPL(database_parent_path == "" ||
                                ExistsDir(database_parent_path));
  }

  if (added_image_options_)
    success = success && CHECK_OPTION_IMPL(ExistsDir(*image_path));

  if (image_reader) success = success && image_reader->Check();
  if (sift_extraction) success = success && sift_extraction->Check();

  if (sift_matching) success = success && sift_matching->Check();
  if (exhaustive_matching) success = success && exhaustive_matching->Check();
  if (sequential_matching) success = success && sequential_matching->Check();
  if (vocab_tree_matching) success = success && vocab_tree_matching->Check();
  if (spatial_matching) success = success && spatial_matching->Check();

  if (bundle_adjustment) success = success && bundle_adjustment->Check();
  if (mapper) success = success && mapper->Check();

  if (patch_match_stereo) success = success && patch_match_stereo->Check();
  if (stereo_fusion) success = success && stereo_fusion->Check();
  if (poisson_meshing) success = success && poisson_meshing->Check();
  if (delaunay_meshing) success = success && delaunay_meshing->Check();

  if (render) success = success && render->Check();

  return success;
}

void OptionManager::Parse(const int argc, char** argv) {
  config::variables_map vmap;

  try {
    config::store(config::parse_command_line(argc, argv, *desc_), vmap);

    if (vmap.count("help")) {
      std::cout << StringPrintf("%s (%s)", GetVersionInfo().c_str(),
                                GetBuildInfo().c_str())
                << std::endl
                << std::endl;
      std::cout
          << "Options can either be specified via command-line or by defining"
          << std::endl
          << "them in a .ini project file passed to `--project_path`."
          << std::endl
          << std::endl;
      std::cout << *desc_ << std::endl;
      exit(EXIT_SUCCESS);
    }

    if (vmap.count("project_path")) {
      *project_path = vmap["project_path"].as<std::string>();
      if (!Read(*project_path)) {
        exit(EXIT_FAILURE);
      }
    } else {
      vmap.notify();
    }
  } catch (std::exception& exc) {
    std::cerr << "ERROR: Failed to parse options - " << exc.what() << "."
              << std::endl;
    exit(EXIT_FAILURE);
  } catch (...) {
    std::cerr << "ERROR: Failed to parse options for unknown reason."
              << std::endl;
    exit(EXIT_FAILURE);
  }

  if (!Check()) {
    std::cerr << "ERROR: Invalid options provided." << std::endl;
    exit(EXIT_FAILURE);
  }
}

bool OptionManager::Read(const std::string& path) {
  config::variables_map vmap;

  if (!ExistsFile(path)) {
    std::cout << "ERROR: Configuration file does not exist." << std::endl;
    return false;
  }

  try {
    std::ifstream file(path);
    CHECK(file.is_open()) << path;
    config::store(config::parse_config_file(file, *desc_), vmap);
    vmap.notify();
  } catch (std::exception& e) {
    std::cout << "ERROR: Failed to parse options " << e.what() << "."
              << std::endl;
    return false;
  } catch (...) {
    std::cout << "ERROR: Failed to parse options for unknown reason."
              << std::endl;
    return false;
  }

  return Check();
}

bool OptionManager::ReRead(const std::string& path) {
  Reset();
  AddAllOptions();
  return Read(path);
}

void OptionManager::Write(const std::string& path) const {
  boost::property_tree::ptree pt;

  // First, put all options without a section and then those with a section.
  // This is necessary as otherwise older Boost versions will write the
  // options without a section in between other sections and therefore
  // the errors will be assigned to the wrong section if read later.

  for (const auto& option : options_bool_) {
    if (!StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_int_) {
    if (!StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_double_) {
    if (!StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_string_) {
    if (!StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_bool_) {
    if (StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_int_) {
    if (StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_double_) {
    if (StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  for (const auto& option : options_string_) {
    if (StringContains(option.first, ".")) {
      pt.put(option.first, *option.second);
    }
  }

  boost::property_tree::write_ini(path, pt);
}

}  // namespace colmap
