/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  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 itkDiffusionTensor3D_hxx
#define itkDiffusionTensor3D_hxx

#include "itkDiffusionTensor3D.h"
#include "itkNumericTraits.h"

namespace itk
{
/**
 * Constructor with initialization
 */
template <typename T>
DiffusionTensor3D<T>::DiffusionTensor3D(const Superclass & r)
  : SymmetricSecondRankTensor<T, 3>(r)
{}

/**
 * Constructor with initialization
 */
template <typename T>
DiffusionTensor3D<T>::DiffusionTensor3D(const ComponentType & r)
  : SymmetricSecondRankTensor<T, 3>(r)
{}

/**
 * Constructor with initialization
 */
template <typename T>
DiffusionTensor3D<T>::DiffusionTensor3D(const ComponentArrayType r)
  : SymmetricSecondRankTensor<T, 3>(r)
{}

/**
 * Assignment Operator
 */
template <typename T>
DiffusionTensor3D<T> &
DiffusionTensor3D<T>::operator=(const ComponentType & r)
{
  Superclass::operator=(r);
  return *this;
}

/**
 * Assignment Operator
 */
template <typename T>
DiffusionTensor3D<T> &
DiffusionTensor3D<T>::operator=(const ComponentArrayType r)
{
  Superclass::operator=(r);
  return *this;
}

/**
 * Assignment Operator
 */
template <typename T>
DiffusionTensor3D<T> &
DiffusionTensor3D<T>::operator=(const Superclass & r)
{
  Superclass::operator=(r);
  return *this;
}

/**
 * Get the Trace, specialized version for 3D.
 *
 * Note that the indices are related to the fact
 * that we store only the upper-right triangle of
 * the matrix. Like
 *
 *       | 0  1  2  |
 *       | X  3  4  |
 *       | X  X  5  |
 *
 * The trace is therefore the sum of the components
 * M[0], M[3] and M[5].
 *
 */
template <typename T>
typename DiffusionTensor3D<T>::AccumulateValueType
DiffusionTensor3D<T>::GetTrace() const
{
  AccumulateValueType trace = (*this)[0];

  trace += (*this)[3];
  trace += (*this)[5];
  return trace;
}

/**
 *  Compute the value of fractional anisotropy
 */
template <typename T>
typename DiffusionTensor3D<T>::RealValueType
DiffusionTensor3D<T>::GetFractionalAnisotropy() const
{
  // Computed as
  // FA = std::sqrt(1.5*sum(sum(N.*N))/sum((sum(D.*D))))
  // where N = D - ((1/3)*trace(D)*eye(3,3))
  // equation (28) in
  // http://lmi.bwh.harvard.edu/papers/pdfs/2002/westinMEDIA02.pdf
  const RealValueType isp = this->GetInnerScalarProduct();

  if (isp > 0.0)
  {
    const RealValueType trace = this->GetTrace();
    const RealValueType anisotropy = 3.0 * isp - trace * trace;
    // sometimes anisotropy has been reported to be a small negative
    // number, and then std::sqrt returns NaN.  If it is a small
    // negative number, the obvious thing is to round to zero. If
    // it is a larger negative number, I'm not sure what the proper
    // result would be.  In either case, returning zero makes as much
    // sense in those cases as any other number.
    if (anisotropy > 0.0)
    {
      const auto fractionalAnisotropy = static_cast<RealValueType>(std::sqrt(anisotropy / (2.0 * isp)));
      return fractionalAnisotropy;
    }
  }

  return 0.0;
}

/**
 *  Compute the value of relative anisotropy
 */
template <typename T>
typename DiffusionTensor3D<T>::RealValueType
DiffusionTensor3D<T>::GetRelativeAnisotropy() const
{
  const RealValueType trace = this->GetTrace();
  const RealValueType isp = this->GetInnerScalarProduct();

  // Avoid negative trace and traces small enough to look like a division by
  // zero.
  if (trace < NumericTraits<RealValueType>::min())
  {
    return NumericTraits<RealValueType>::ZeroValue();
  }

  const RealValueType anisotropy = 3.0 * isp - trace * trace;

  if (anisotropy < NumericTraits<RealValueType>::ZeroValue())
  {
    return NumericTraits<RealValueType>::ZeroValue();
  }

  const auto relativeAnisotropySquared = static_cast<RealValueType>(anisotropy / (std::sqrt(3.0) * trace));

  const auto relativeAnisotropy = static_cast<RealValueType>(std::sqrt(relativeAnisotropySquared));

  return relativeAnisotropy;
}

/**
 *  Compute the inner scalar product
 */
template <typename T>
typename DiffusionTensor3D<T>::RealValueType
DiffusionTensor3D<T>::GetInnerScalarProduct() const
{
  const RealValueType xx = (*this)[0];
  const RealValueType xy = (*this)[1];
  const RealValueType xz = (*this)[2];
  const RealValueType yy = (*this)[3];
  const RealValueType yz = (*this)[4];
  const RealValueType zz = (*this)[5];

  return (xx * xx + yy * yy + zz * zz + 2.0 * (xy * xy + xz * xz + yz * yz));
}
} // end namespace itk

#endif
