/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/*===========================================================================*/
/*===============================[ Includes ]================================*/
/*===========================================================================*/
#include "otbGeometriesToGeometriesFilter.h"
#include <cassert>
#include "otbGeometriesSet.h"
#include "otbStopwatch.h"
#include "otbMacro.h"

/*===========================================================================*/
/*============================[ ProcessVisitor ]=============================*/
/*===========================================================================*/
/**\ingroup GeometriesFilters
 * \class ProcessVisitor
 * Internal class used to dispatch filters on either \c ogr::DataSource, or \c
 * ogr::Layer.
 * As \c GeometriesSet is a variant that can contain either a \c ogr::DataSource
 * or a \c ogr::Layer, we need a way to dispatch filtering operations on the
 * right type. This is done thanks to \c ProcessVisitor.
 * \since OTB v 3.14.0
 */
struct otb::internal::ProcessVisitor : boost::static_visitor<>
{
  ProcessVisitor(otb::GeometriesToGeometriesFilter const& filter)
    : m_filter(filter) {}
  /**
   * Processes layers (\em by-copy).
   * \param[in] source  source layer
   * \param[in,out] destination  destination layer.
   * \throw WhatEver the filter throws.
   *
   * This is the \em leaf operation that forwards the call to the polymorphic
   * \c GeometriesToGeometriesFilter::DoProcessLayer() function.
   */
  void operator()(otb::ogr::Layer const& source, otb::ogr::Layer & destination) const
    {
    m_filter.DoProcessLayer(source, destination);
    }

  /**
   * Processes data sources (\em by-copy).
   * \param[in] source  source data source
   * \param[in,out] destination  destination data source
   * \throw WhatEver the filter throws.
   * \throw itk::ExceptionObject if the construction of a new layer fails
   *
   * For each layer in the \c source, this operation:
   * - builds a new layer in the \c destination data source (according to the
   *   choices made in the actual specialization of \c
   *   GeometriesToGeometriesFilter regarding layer options, layer spatial
   *   reference, geometries type, ...);
   * - defines new fileds in the new layer;
   * - and finally processes the layer thanks to the polymorphic \c
   *   GeometriesToGeometriesFilter::DoProcessLayer() function.
   */
  void operator()(otb::ogr::DataSource::Pointer source, otb::ogr::DataSource::Pointer destination) const
    {
    assert(source && "can't filter a nil datasource");
    assert(destination && "can't filter to a nil datasource");
    for (otb::ogr::DataSource::const_iterator b = source->begin(), e = source->end()
; b != e
; ++b
    )
      {
      otb::ogr::Layer const& sourceLayer = *b;
      assert(sourceLayer && "unexpected nil source layer");
      otb::ogr::Layer destLayer = destination->CreateLayer(
        sourceLayer.GetName(),
        m_filter.DoDefineNewLayerSpatialReference(sourceLayer),
        m_filter.DoDefineNewLayerGeometryType(sourceLayer),
        m_filter.DoDefineNewLayerOptions(sourceLayer)
      );
      m_filter.DoDefineNewLayerFields(sourceLayer, destLayer);
      m_filter.DoProcessLayer(sourceLayer, destLayer);
      }
    }

  /**
   * Processes layers (\em in-place).
   * \param[in,out] inout  layer to modify
   * \throw WhatEver the filter throws.
   *
   * This is the \em leaf operation that forwards the call to the polymorphic
   * \c GeometriesToGeometriesFilter::DoProcessLayer() function.
   */
  void operator()(otb::ogr::Layer & inout) const
    {
    m_filter.DoProcessLayer(inout, inout);
    }

  /**
   * Processes data sources (\em in-place).
   * \param[in,out] inout  data source to modify
   * \throw WhatEver the filter throws.
   *
   * This operation finally processes each layer of the \c source thanks to the
   * polymorphic \c GeometriesToGeometriesFilter::DoProcessLayer() function.
   */
  void operator()(otb::ogr::DataSource::Pointer inout) const
    {
    assert(inout && "can't filter a nil datasource");
    for (otb::ogr::DataSource::iterator b = inout->begin(), e = inout->end(); b != e; ++b)
      {
      otb::ogr::Layer layer = *b;
      assert(layer && "unexpected nil source layer");
      m_filter.DoProcessLayer(layer, layer);
      }
    }

  /**
   * Fall-back visiting function for invalid mixed transformations (layer ->
   * datasource).
   * The only transformations accepted are
   * - layer -> layer,
   * - and data source -> data source.
   */
  template <typename GT1, typename GT2> void operator()(GT1 const&, GT2 &) const
    {
    assert(!"You shall not mix DataSources and Layers in GeometriesToGeometriesFilter");
    itkGenericExceptionMacro(<<"You shall not mix DataSources and Layers in GeometriesToGeometriesFilter");
    }
private:
  otb::GeometriesToGeometriesFilter const& m_filter;
};

/*===========================================================================*/
/*=====================[ GeometriesToGeometriesFilter ]======================*/
/*===========================================================================*/

otb::GeometriesToGeometriesFilter::GeometriesToGeometriesFilter()
{
}

/*virtual*/
otb::GeometriesToGeometriesFilter::~GeometriesToGeometriesFilter()
{
}

/*virtual*/ void otb::GeometriesToGeometriesFilter::SetInput(
  const InputGeometriesType * input)
{
  // Process object is not const-correct so the const_cast is required here
  this->itk::ProcessObject::SetNthInput(
    0,
    const_cast<InputGeometriesType *>(input));
}

const otb::GeometriesToGeometriesFilter::InputGeometriesType *
otb::GeometriesToGeometriesFilter::GetInput(void )
{
  return static_cast <InputGeometriesType*>(Superclass::GetInput(0));
}

/*virtual*/ void otb::GeometriesToGeometriesFilter::Process(
  InputGeometriesType const& source, OutputGeometriesType & destination)
{
  // si layer, appelle virt process layer
  // si DS, loop et appelle virt process layer
  source.apply(internal::ProcessVisitor(*this), destination);
}

/*virtual*/ void otb::GeometriesToGeometriesFilter::Process(
  OutputGeometriesType & inout)
{
  // si layer, appelle virt process layer
  // si DS, loop et appelle virt process layer
  inout.apply(internal::ProcessVisitor(*this));
}

/*virtual*/
void otb::GeometriesToGeometriesFilter::GenerateData(void )
{
  this->DoAllocateOutputs();
  this->DoFinalizeInitialization();

  InputGeometriesType::ConstPointer  input  = this->GetInput();
  // assert(input && "Cann't filter to a nil geometries set");
  OutputGeometriesType::Pointer      output = this->GetOutput();
  assert(output && "Cann't filter a nil geometries set");

  // Start recursive processing
  otb::Stopwatch chrono = otb::Stopwatch::StartNew();
  if (input)
    {
    this->Process(*input, *output);
    }
  else
    {
    this->Process(*output);
    }

  chrono.Stop();
  otbMsgDevMacro(<< "GeometriesToGeometriesFilter: geometries processed in " << chrono.GetElapsedMilliseconds() << " ms.");
}

/*virtual*/
OGRSpatialReference* otb::GeometriesToGeometriesFilter::DoDefineNewLayerSpatialReference(
  ogr::Layer const& itkNotUsed(source)) const
{
  return ITK_NULLPTR;
}

/*virtual*/
OGRwkbGeometryType otb::GeometriesToGeometriesFilter::DoDefineNewLayerGeometryType(
  ogr::Layer const& source) const
{
  return source.GetGeomType();
}

/*virtual*/
std::vector<std::string> otb::GeometriesToGeometriesFilter::DoDefineNewLayerOptions(
  ogr::Layer const& itkNotUsed(source)) const
{
  return std::vector<std::string>();
}

/*===========================================================================*/
/*========================[ FieldCopyTransformation ]========================*/
/*===========================================================================*/
void otb::FieldCopyTransformation::DefineFields(
  otb::ogr::Layer const& source, otb::ogr::Layer & dest) const
{
  OGRFeatureDefn & inDefinition = source.GetLayerDefn();
  OGRFeatureDefn & outDefinition = dest.GetLayerDefn();
  for (int i=0,N=inDefinition.GetFieldCount(); i!=N; ++i)
    {
    dest.CreateField(*inDefinition.GetFieldDefn(i));
    // assume the definition is updated automatically
    m_SourceToDestFieldIndicesMap[i] = outDefinition.GetFieldCount()-1;
    }
}


void otb::FieldCopyTransformation::fieldsTransform(
  otb::ogr::Feature const& inFeature, otb::ogr::Feature & outFeature) const
{
  // default => copy all fields for copy transformation

  // The following can't be assumed because of Drivers like KML that always add
  // two fields: "Description" and "Name"
  //assert(inFeature.GetSize() == outFeature.GetSize());

  for (int i=0,N=inFeature.GetSize(); i!=N; ++i)
    {
    int const indexNewField = m_SourceToDestFieldIndicesMap[i];
    outFeature[indexNewField].Assign(inFeature[i]);
    }
}
