// 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/feature/extractor.h"

#include "colmap/feature/aliked.h"
#include "colmap/feature/sift.h"
#include "colmap/util/misc.h"

namespace colmap {
namespace {

void ThrowUnknownFeatureExtractorType(FeatureExtractorType type) {
  std::ostringstream error;
  error << "Unknown feature extractor type: " << type;
  throw std::runtime_error(error.str());
}

}  // namespace

FeatureExtractionTypeOptions::FeatureExtractionTypeOptions()
    : sift(std::make_shared<SiftExtractionOptions>()),
      aliked(std::make_shared<AlikedExtractionOptions>()) {}

FeatureExtractionTypeOptions::FeatureExtractionTypeOptions(
    const FeatureExtractionTypeOptions& other) {
  if (other.sift) {
    sift = std::make_shared<SiftExtractionOptions>(*other.sift);
  }
  if (other.aliked) {
    aliked = std::make_shared<AlikedExtractionOptions>(*other.aliked);
  }
}

FeatureExtractionTypeOptions& FeatureExtractionTypeOptions::operator=(
    const FeatureExtractionTypeOptions& other) {
  if (this == &other) {
    return *this;
  }
  if (other.sift) {
    sift = std::make_shared<SiftExtractionOptions>(*other.sift);
  } else {
    sift.reset();
  }
  if (other.aliked) {
    aliked = std::make_shared<AlikedExtractionOptions>(*other.aliked);
  } else {
    aliked.reset();
  }
  return *this;
}

FeatureExtractionOptions::FeatureExtractionOptions(FeatureExtractorType type)
    : FeatureExtractionTypeOptions(), type(type) {}

bool FeatureExtractionOptions::RequiresRGB() const {
  switch (type) {
    case FeatureExtractorType::SIFT:
      return false;
    case FeatureExtractorType::ALIKED_N16ROT:
    case FeatureExtractorType::ALIKED_N32:
      return true;
    default:
      ThrowUnknownFeatureExtractorType(type);
  }
  return false;
}

bool FeatureExtractionOptions::RequiresOpenGL() const {
  switch (type) {
    case FeatureExtractorType::SIFT: {
      // Sync with logic in CreateSiftFeatureExtractor().
      if (sift->estimate_affine_shape || sift->domain_size_pooling ||
          sift->force_covariant_extractor) {
        return false;
      }
#ifdef COLMAP_CUDA_ENABLED
      return false;
#else
      return use_gpu;
#endif
    }
    case FeatureExtractorType::ALIKED_N16ROT:
    case FeatureExtractorType::ALIKED_N32:
      return false;
    default:
      ThrowUnknownFeatureExtractorType(type);
  }
  return false;
}

int FeatureExtractionOptions::EffMaxImageSize() const {
  if (max_image_size > 0) {
    return max_image_size;
  } else {
    switch (type) {
      case FeatureExtractorType::SIFT:
        return 3200;
      case FeatureExtractorType::ALIKED_N16ROT:
      case FeatureExtractorType::ALIKED_N32:
        return 1600;
      default:
        ThrowUnknownFeatureExtractorType(type);
    }
  }
  return 0;
}

bool FeatureExtractionOptions::Check() const {
  CHECK_OPTION_GT(EffMaxImageSize(), 0);
  if (use_gpu) {
    CHECK_OPTION_GT(CSVToVector<int>(gpu_index).size(), 0);
#if !defined(COLMAP_GPU_ENABLED) && !defined(COLMAP_CUDA_ENABLED)
    LOG(ERROR) << "Cannot use GPU feature extraction without CUDA or OpenGL "
                  "support. Consider setting use_gpu to false.";
    return false;
#endif
  }
  switch (type) {
    case FeatureExtractorType::SIFT:
      return THROW_CHECK_NOTNULL(sift)->Check();
    case FeatureExtractorType::ALIKED_N16ROT:
    case FeatureExtractorType::ALIKED_N32:
      return THROW_CHECK_NOTNULL(aliked)->Check();
    default:
      LOG(ERROR) << "Unknown feature extractor type: " << type;
      return false;
  }
}

std::unique_ptr<FeatureExtractor> FeatureExtractor::Create(
    const FeatureExtractionOptions& options) {
  switch (options.type) {
    case FeatureExtractorType::SIFT:
      return CreateSiftFeatureExtractor(options);
    case FeatureExtractorType::ALIKED_N16ROT:
    case FeatureExtractorType::ALIKED_N32:
      return CreateAlikedFeatureExtractor(options);
    default:
      ThrowUnknownFeatureExtractorType(options.type);
  }
  return nullptr;
}

}  // namespace colmap
