/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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.txt
 *
 *  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.
 *
 *=========================================================================*/
/*=========================================================================
 *
 *  Portions of this file are subject to the VTK Toolkit Version 3 copyright.
 *
 *  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 *
 *  For complete copyright, license and disclaimer of warranty information
 *  please refer to the NOTICE file at the top of the ITK source tree.
 *
 *=========================================================================*/
#ifndef itkPointSet_hxx
#define itkPointSet_hxx

#include "itkPointSet.h"
#include "itkProcessObject.h"
#include <algorithm>

namespace itk
{
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "Number Of Points: "
     << this->GetNumberOfPoints()  << std::endl;

  os << indent << "Requested Number Of Regions: "
     << m_RequestedNumberOfRegions << std::endl;
  os << indent << "Requested Region: " << m_RequestedRegion << std::endl;
  os << indent << "Buffered Region: " << m_BufferedRegion << std::endl;
  os << indent << "Maximum Number Of Regions: "
     << m_MaximumNumberOfRegions << std::endl;
  os << indent << "Point Data Container pointer: "
     << ( ( this->m_PointDataContainer ) ?  this->m_PointDataContainer.GetPointer() : nullptr ) << std::endl;
  os << indent << "Size of Point Data Container: "
     << ( ( this->m_PointDataContainer ) ?  this->m_PointDataContainer->Size() : 0 ) << std::endl;
}

/**
 * Access routine to set the points container.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetPoints(PointsContainer *points)
{
  itkDebugMacro("setting Points container to " << points);
  if ( m_PointsContainer != points )
    {
    m_PointsContainer = points;
    this->Modified();
    }
}

/**
 * Access routine to get the points container.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
typename PointSet< TPixelType, VDimension, TMeshTraits >::PointsContainer *
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPoints()
{
  itkDebugMacro("Starting GetPoints()");
  if ( !m_PointsContainer )
    {
    this->SetPoints( PointsContainer::New() );
    }
  itkDebugMacro("returning Points container of " << m_PointsContainer);
  return m_PointsContainer;
}

/**
 * Access routine to get the points container.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
const typename PointSet< TPixelType, VDimension, TMeshTraits >::PointsContainer *
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPoints() const
{
  itkDebugMacro("returning Points container of " << m_PointsContainer);
  return m_PointsContainer.GetPointer();
}

/**
 * Access routine to set the point data container.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetPointData(PointDataContainer *pointData)
{
  itkDebugMacro("setting PointData container to " << pointData);
  if ( m_PointDataContainer != pointData )
    {
    m_PointDataContainer = pointData;
    this->Modified();
    }
}

/**
 * Access routine to get the point data container.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
typename PointSet< TPixelType, VDimension, TMeshTraits >::PointDataContainer *
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPointData()
{
  if ( !m_PointDataContainer )
    {
    this->SetPointData( PointDataContainer::New() );
    }
  itkDebugMacro("returning PointData container of " << m_PointDataContainer);
  return m_PointDataContainer;
}

/**
 * Access routine to get the point data container.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
const typename PointSet< TPixelType, VDimension, TMeshTraits >::PointDataContainer *
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPointData() const
{
  itkDebugMacro("returning PointData container of "
                << m_PointDataContainer);
  return m_PointDataContainer.GetPointer();
}

/**
 * Assign a point to a point identifier.  If a spot for the point identifier
 * does not exist, it will be created automatically.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetPoint(PointIdentifier ptId, PointType point)
{
  /**
   * Make sure a points container exists.
   */
  if ( !m_PointsContainer )
    {
    this->SetPoints( PointsContainer::New() );
    }

  /**
   * Insert the point into the container with the given identifier.
   */
  m_PointsContainer->InsertElement(ptId, point);
}

/**
 * Check if a point exists for a given point identifier.  If a spot for
 * the point identifier exists, "point" is set, and true is returned.
 * Otherwise, false is returned, and "point" is not modified.
 * If "point" is nullptr, then it is never set, but the existence of the point
 * is still returned.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
bool
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPoint(PointIdentifier ptId, PointType *point) const
{
  /**
   * If the points container doesn't exist, then the point doesn't either.
   */
  if ( !m_PointsContainer )
    {
    return false;
    }

  /**
   * Ask the container if the point identifier exists.
   */
  return m_PointsContainer->GetElementIfIndexExists(ptId, point);
}

template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
typename PointSet< TPixelType, VDimension, TMeshTraits >::PointType
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPoint(PointIdentifier ptId) const
{
  /**
   * If the points container doesn't exist, then the point doesn't either.
   */
  if ( !m_PointsContainer )
    {
    itkExceptionMacro("Point container doesn't exist.");
    }

  /**
   * Ask the container if the point identifier exists.
   */
  PointType point;
  bool exist = m_PointsContainer->GetElementIfIndexExists(ptId, &point);
  if( ! exist )
    {
    itkExceptionMacro("Point id doesn't exist: " << ptId);
    }
  return point;
}

/**
 * Assign data to a point identifier.  If a spot for the point identifier
 * does not exist, it will be created automatically.  There is no check if
 * a point with the same identifier exists.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetPointData(PointIdentifier ptId, PixelType data)
{
  /**
   * Make sure a point data container exists.
   */
  if ( !m_PointDataContainer )
    {
    this->SetPointData( PointDataContainer::New() );
    }

  /**
   * Insert the point data into the container with the given identifier.
   */
  m_PointDataContainer->InsertElement(ptId, data);
}

/**
 * Check if point data exists for a given point identifier.  If a spot for
 * the point identifier exists, "data" is set, and true is returned.
 * Otherwise, false is returned, and "data" is not modified.
 * If "data" is nullptr, then it is never set, but the existence of the point
 * data is still returned.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
bool
PointSet< TPixelType, VDimension, TMeshTraits >
::GetPointData(PointIdentifier ptId, PixelType *data) const
{
  /**
   * If the point data container doesn't exist, then the point data doesn't
   * either.
   */
  if ( !m_PointDataContainer )
    {
    return false;
    }

  /**
   * Ask the container if the point identifier exists.
   */
  return m_PointDataContainer->GetElementIfIndexExists(ptId, data);
}

/**
 * Copy the geometric and topological structure of the given input pointSet.
 * The copying is done via reference counting.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::PassStructure(Self *)
{
  // IMPLEMENT ME
}

/**
 * Get the number of points in the PointsContainer.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
typename PointSet< TPixelType, VDimension, TMeshTraits >::PointIdentifier
PointSet< TPixelType, VDimension, TMeshTraits >
::GetNumberOfPoints() const
{
  if ( m_PointsContainer )
    {
    return m_PointsContainer->Size();
    }
  return 0;
}

/**
 * Restore the PointSet to its initial state.  Useful for data pipeline updates
 * without memory re-allocation.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::Initialize()
{
  Superclass::Initialize();

  m_PointsContainer = nullptr;
  m_PointDataContainer = nullptr;
}

/******************************************************************************
 * PROTECTED METHOD DEFINITIONS
 *****************************************************************************/

/**
 * A protected default constructor allows the New() routine to create an
 * instance of PointSet.  All the containers are initialized to non-existent.
 */
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
PointSet< TPixelType, VDimension, TMeshTraits >
::PointSet():
  m_PointsContainer(nullptr),
  m_PointDataContainer(nullptr)
{

  // If we used unstructured regions instead of structured regions, then
  // assume this object was created by the user and this is region 0 of
  // 1 region.
  m_MaximumNumberOfRegions = 1;
  m_NumberOfRegions = 1;
  m_BufferedRegion  = -1;
  m_RequestedNumberOfRegions = 0;
  m_RequestedRegion = -1;
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::UpdateOutputInformation()
{
  if ( this->GetSource() )
    {
    this->GetSource()->UpdateOutputInformation();
    }

  // Now we should know what our largest possible region is. If our
  // requested region was not set yet, (or has been set to something
  // invalid - with no data in it ) then set it to the largest
  // possible region.
  if ( m_RequestedRegion == -1 && m_RequestedNumberOfRegions == 0 )
    {
    this->SetRequestedRegionToLargestPossibleRegion();
    }
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetRequestedRegionToLargestPossibleRegion()
{
  m_RequestedNumberOfRegions     = 1;
  m_RequestedRegion           = 0;
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::CopyInformation(const DataObject *data)
{
  const auto * pointSet = dynamic_cast< const PointSet * >( data );

  if ( !pointSet )
    {
    // pointer could not be cast back down
    itkExceptionMacro( << "itk::PointSet::CopyInformation() cannot cast "
                       << typeid( data ).name() << " to "
                       << typeid( PointSet * ).name() );
    }

  m_MaximumNumberOfRegions = pointSet->GetMaximumNumberOfRegions();

  m_NumberOfRegions = pointSet->m_NumberOfRegions;
  m_RequestedNumberOfRegions = pointSet->m_RequestedNumberOfRegions;
  m_BufferedRegion  = pointSet->m_BufferedRegion;
  m_RequestedRegion = pointSet->m_RequestedRegion;
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::Graft(const DataObject *data)
{
  // Copy Meta Data
  this->CopyInformation(data);

  const auto * pointSet = dynamic_cast< const Self * >( data );

  if ( !pointSet )
    {
    // pointer could not be cast back down
    itkExceptionMacro( << "itk::PointSet::CopyInformation() cannot cast "
                       << typeid( data ).name() << " to "
                       << typeid( Self * ).name() );
    }

  this->SetPoints(pointSet->m_PointsContainer);
  this->SetPointData(pointSet->m_PointDataContainer);
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetRequestedRegion(const DataObject *data)
{
  const auto * pointSet = dynamic_cast< const Self * >( data );

  if ( pointSet )
    {
    // only copy the RequestedRegion if the parameter is another PointSet
    m_RequestedRegion = pointSet->m_RequestedRegion;
    m_RequestedNumberOfRegions = pointSet->m_RequestedNumberOfRegions;
    }
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetRequestedRegion(const RegionType & region)
{
  if ( m_RequestedRegion != region )
    {
    m_RequestedRegion = region;
    }
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
void
PointSet< TPixelType, VDimension, TMeshTraits >
::SetBufferedRegion(const RegionType & region)
{
  if ( m_BufferedRegion != region )
    {
    m_BufferedRegion = region;
    this->Modified();
    }
}

//----------------------------------------------------------------------------
template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
bool
PointSet< TPixelType, VDimension, TMeshTraits >
::RequestedRegionIsOutsideOfTheBufferedRegion()
{
  if ( m_RequestedRegion != m_BufferedRegion
       || m_RequestedNumberOfRegions != m_NumberOfRegions )
    {
    return true;
    }

  return false;
}

template< typename TPixelType, unsigned int VDimension, typename TMeshTraits >
bool
PointSet< TPixelType, VDimension, TMeshTraits >
::VerifyRequestedRegion()
{
  bool retval = true;

  // Are we asking for more regions than we can get?
  if ( m_RequestedNumberOfRegions > m_MaximumNumberOfRegions )
    {
    itkExceptionMacro(<< "Cannot break object into "
                      << m_RequestedNumberOfRegions << ". The limit is "
                      << m_MaximumNumberOfRegions);
    }

  if ( m_RequestedRegion >= m_RequestedNumberOfRegions
       || m_RequestedRegion < 0 )
    {
    itkExceptionMacro(<< "Invalid update region " << m_RequestedRegion
                      << ". Must be between 0 and "
                      << m_RequestedNumberOfRegions - 1);
    }

  return retval;
}
} // end namespace itk

#endif
