/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/
#ifndef itkTriangleHelper_hxx
#define itkTriangleHelper_hxx

#include "itkTriangleHelper.h"
#include "itkMath.h"

namespace itk
{
template< typename TPoint >
bool TriangleHelper< TPoint >::IsObtuse(const PointType & iA, const PointType & iB, const PointType & iC)
{
  VectorType v01 = iB - iA;
  VectorType v02 = iC - iA;
  VectorType v12 = iC - iB;

  if ( v01 * v02 < 0.0 )
    {
    return true;
    }
  else
    {
    if ( v02 * v12 < 0.0 )
      {
      return true;
      }
    else
      {
      if ( v01 * -v12 < 0.0 )
        {
        return true;
        }
      else
        {
        return false;
        }
      }
    }
}

template< typename TPoint >
typename TriangleHelper< TPoint >::VectorType
TriangleHelper< TPoint >::ComputeNormal(const PointType & iA,
                                        const PointType & iB,
                                        const PointType & iC)
{
  CrossVectorType cross;
  VectorType      w = cross (iB - iA, iC - iA);
  CoordRepType    l2 = w.GetSquaredNorm();

  if ( l2 != 0.0 )
    {
    w /= std::sqrt(l2);
    }

  return w;
}

template< typename TPoint >
typename TriangleHelper< TPoint >::CoordRepType
TriangleHelper< TPoint >::Cotangent(const PointType & iA,
                                    const PointType & iB,
                                    const PointType & iC)
{
  VectorType   v21 = iA - iB;

  CoordRepType v21_l2 = v21.GetSquaredNorm();

  if ( Math::NotAlmostEquals( v21_l2, NumericTraits< CoordRepType >::ZeroValue() ) )
    {
    v21 /= std::sqrt(v21_l2);
    }

  VectorType   v23 = iC - iB;
  CoordRepType v23_l2 = v23.GetSquaredNorm();
  if ( Math::NotAlmostEquals( v23_l2, NumericTraits< CoordRepType >::ZeroValue() ) )
    {
    v23 /= std::sqrt(v23_l2);
    }

  CoordRepType bound(0.999999);

  CoordRepType cos_theta = std::max( -bound,
                                         std::min(bound, v21 * v23) );

  return 1.0 / std::tan( std::acos(cos_theta) );
}

template< typename TPoint >
typename TriangleHelper< TPoint >::PointType
TriangleHelper< TPoint >::ComputeBarycenter(
  const CoordRepType & iA1, const PointType & iP1,
  const CoordRepType & iA2, const PointType & iP2,
  const CoordRepType & iA3, const PointType & iP3)
{
  PointType oPt;

  CoordRepType total = iA1 + iA2 + iA3;

  if ( Math::AlmostEquals( total, NumericTraits< CoordRepType >::ZeroValue() ) )
    {
    //in such case there is no barycenter;
    oPt.Fill(0.);
    return oPt;
    }

  CoordRepType inv_total = 1. / total;
  CoordRepType a1 = iA1 * inv_total;
  CoordRepType a2 = iA2 * inv_total;
  CoordRepType a3 = iA3 * inv_total;

  for ( unsigned int dim = 0; dim < PointDimension; ++dim )
    {
    oPt[dim] = a1 * iP1[dim] + a2 * iP2[dim] + a3 * iP3[dim];
    }

  return oPt;
}

template< typename TPoint >
typename TriangleHelper< TPoint >::CoordRepType
TriangleHelper< TPoint >::ComputeAngle(const PointType & iP1,
                                       const PointType & iP2,
                                       const PointType & iP3)
{
  VectorType v21 = iP1 - iP2;
  VectorType v23 = iP3 - iP2;

  CoordRepType v21_l2 = v21.GetSquaredNorm();
  CoordRepType v23_l2 = v23.GetSquaredNorm();

  if ( v21_l2 != 0.0 )
    {
    v21 /= std::sqrt(v21_l2);
    }
  if ( v23_l2 != 0.0 )
    {
    v23 /= std::sqrt(v23_l2);
    }

  CoordRepType bound(0.999999);

  CoordRepType cos_theta = std::max( -bound,
                                         std::min(bound, v21 * v23) );

  return std::acos(cos_theta);
}

template< typename TPoint >
typename TriangleHelper< TPoint >::PointType
TriangleHelper< TPoint >::ComputeGravityCenter(
  const PointType & iP1,
  const PointType & iP2,
  const PointType & iP3)
{
  return ComputeBarycenter(1., iP1, 1., iP2, 1., iP3);
}

template< typename TPoint >
typename TriangleHelper< TPoint >::PointType
TriangleHelper< TPoint >::ComputeCircumCenter(
  const PointType & iP1,
  const PointType & iP2,
  const PointType & iP3)
{
  PointType oPt;

  oPt.Fill (0.0);

  CoordRepType a = iP2.SquaredEuclideanDistanceTo (iP3);
  CoordRepType b = iP1.SquaredEuclideanDistanceTo (iP3);
  CoordRepType c = iP2.SquaredEuclideanDistanceTo (iP1);

  CoordRepType Weight[3];
  Weight[0] = a * ( b + c - a );
  Weight[1] = b * ( c + a - b );
  Weight[2] = c * ( a + b - c );

  return ComputeBarycenter(Weight[0], iP1, Weight[1], iP2, Weight[2], iP3);
}

template< typename TPoint >
typename TriangleHelper< TPoint >::PointType
TriangleHelper< TPoint >::ComputeConstrainedCircumCenter(const PointType & iP1,
                                                         const PointType & iP2, const PointType & iP3)
{
  const CoordRepType a = iP2.SquaredEuclideanDistanceTo (iP3);
  const CoordRepType b = iP1.SquaredEuclideanDistanceTo (iP3);
  const CoordRepType c = iP2.SquaredEuclideanDistanceTo (iP1);

  CoordRepType Weight[3] = {
    a * ( b + c - a ),
    b * ( c + a - b ),
    c * ( a + b - c )
  };

  for (auto & i : Weight)
    {
    if ( i < 0.0 )
      {
      i = 0.;
      }
    }

  return ComputeBarycenter(Weight[0], iP1, Weight[1], iP2, Weight[2], iP3);
}

template< typename TPoint >
typename TriangleHelper< TPoint >::CoordRepType
TriangleHelper< TPoint >::ComputeArea(const PointType & iP1,
                                      const PointType & iP2,
                                      const PointType & iP3)
{
  CoordRepType a = iP2.EuclideanDistanceTo (iP3);
  CoordRepType b = iP1.EuclideanDistanceTo (iP3);
  CoordRepType c = iP2.EuclideanDistanceTo (iP1);

  CoordRepType s = 0.5 * ( a + b + c );

  return static_cast< CoordRepType >( std::sqrt ( s * ( s - a ) * ( s - b ) * ( s - c ) ) );
}

template< typename TPoint >
typename TriangleHelper< TPoint >::CoordRepType
TriangleHelper< TPoint >::ComputeMixedArea(const PointType & iP1,
                                      const PointType & iP2,
                                      const PointType & iP3)
{
  using TriangleType = TriangleHelper< TPoint >;

  if ( !TriangleType::IsObtuse(iP1, iP2, iP3) )
    {
    auto sq_d01 = static_cast< CoordRepType >( iP1.SquaredEuclideanDistanceTo(iP2) );
    auto sq_d02 = static_cast< CoordRepType >( iP1.SquaredEuclideanDistanceTo(iP3) );

    CoordRepType cot_theta_210 = TriangleType::Cotangent(iP3, iP2, iP1);
    CoordRepType cot_theta_021 = TriangleType::Cotangent(iP1, iP3, iP2);

    return 0.125 * ( sq_d02 * cot_theta_210 + sq_d01 * cot_theta_021 );
    }
  else
    {
    auto area = static_cast< CoordRepType >( TriangleType::ComputeArea(iP1, iP2, iP3) );

    if ( ( iP2 - iP1 ) * ( iP3 - iP1 ) < NumericTraits< CoordRepType >::ZeroValue() )
      {
      return 0.5 * area;
      }
    else
      {
      return 0.25 * area;
      }
    }
}
}

#endif
