////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/// @project        Open Space Toolkit ▸ Core
/// @file           OpenSpaceToolkit/Core/Types/Real.cpp
/// @author         Lucas Brémond <lucas@loftorbital.com>
/// @license        Apache License 2.0

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <OpenSpaceToolkit/Core/Types/Real.hpp>
#include <OpenSpaceToolkit/Core/Error.hpp>

#include <boost/lexical_cast.hpp>

#include <limits>
#include <iomanip>
#include <iostream>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace ostk
{
namespace core
{
namespace types
{

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                                Real::Real                                  (           Real::ValueType             aReal                                       )
                                :   type_(Real::Type::Defined),
                                    value_(aReal)
{

}

Real&                           Real::operator =                            (           Real::ValueType             aReal                                       )
{

    type_ = Real::Type::Defined ;
    value_ = aReal ;

    return *this ;

}

bool                            Real::operator ==                           (   const   Real&                       aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (aReal.type_ == Real::Type::Defined) && (value_ == aReal.value_) ;
}

bool                            Real::operator !=                           (   const   Real&                       aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (aReal.type_ == Real::Type::Defined) && (value_ != aReal.value_) ;
}

bool                            Real::operator <                            (   const   Real&                       aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (aReal.type_ == Real::Type::Defined) && (value_ < aReal.value_) ;
}

bool                            Real::operator <=                           (   const   Real&                       aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (aReal.type_ == Real::Type::Defined) && (value_ <= aReal.value_) ;
}

bool                            Real::operator >                            (   const   Real&                       aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (aReal.type_ == Real::Type::Defined) && (value_ > aReal.value_) ;
}

bool                            Real::operator >=                           (   const   Real&                       aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (aReal.type_ == Real::Type::Defined) && (value_ >= aReal.value_) ;
}

bool                            Real::operator ==                           (   const   Real::ValueType&            aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (value_ == aReal) ;
}

bool                            Real::operator !=                           (   const   Real::ValueType&            aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (value_ != aReal) ;
}

bool                            Real::operator <                            (   const   Real::ValueType&            aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (value_ < aReal) ;
}

bool                            Real::operator <=                           (   const   Real::ValueType&            aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (value_ <= aReal) ;
}

bool                            Real::operator >                            (   const   Real::ValueType&            aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (value_ > aReal) ;
}

bool                            Real::operator >=                           (   const   Real::ValueType&            aReal                                       ) const
{
    return (type_ == Real::Type::Defined) && (value_ >= aReal) ;
}

Real                            Real::operator +                            (   const   Real&                       aReal                                       ) const
{

    if ((type_ != Real::Type::Undefined) && (aReal.type_ != Real::Type::Undefined))
    {

        if ((type_ == Real::Type::PositiveInfinity) && (aReal.type_ == Real::Type::PositiveInfinity))
        {
            return Real::PositiveInfinity() ;
        }
        else if ((type_ == Real::Type::NegativeInfinity) && (aReal.type_ == Real::Type::NegativeInfinity))
        {
            return Real::NegativeInfinity() ;
        }
        else if ((type_ == Real::Type::Defined) || (aReal.type_ == Real::Type::Defined))
        {

            if (type_ != Real::Type::Defined)
            {
                return *this ;
            }
            else if (aReal.type_ != Real::Type::Defined)
            {
                return aReal ;
            }

            // [TBC] Use __builtin_add_overflow instead?

            if ((aReal.value_ > 0.0) && (value_ >= (std::numeric_limits<Real::ValueType>::max() - aReal.value_))) // Addition would overflow
            {
                return (value_ != 0.0) ? Real::PositiveInfinity() : aReal ;
            }

            if ((aReal.value_ < 0.0) && (value_ <= (std::numeric_limits<Real::ValueType>::lowest() - aReal.value_))) // Addition would underflow
            {
                return (value_ != 0.0) ? Real::NegativeInfinity() : aReal ;
            }

            return Real(value_ + aReal.value_) ;

        }

    }

    return Real::Undefined() ;

}

Real                            Real::operator -                            (   const   Real&                       aReal                                       ) const
{

    if ((type_ != Real::Type::Undefined) && (aReal.type_ != Real::Type::Undefined))
    {

        if ((type_ == Real::Type::PositiveInfinity) && (aReal.type_ == Real::Type::NegativeInfinity))
        {
            return Real::PositiveInfinity() ;
        }
        else if ((type_ == Real::Type::NegativeInfinity) && (aReal.type_ == Real::Type::PositiveInfinity))
        {
            return Real::NegativeInfinity() ;
        }
        else if ((type_ == Real::Type::Defined) || (aReal.type_ == Real::Type::Defined))
        {

            if (type_ != Real::Type::Defined)
            {
                return *this ;
            }
            else if (aReal.type_ != Real::Type::Defined)
            {

                if (aReal.type_ == Real::Type::PositiveInfinity)
                {
                    return Real::NegativeInfinity() ;
                }

                return Real::PositiveInfinity() ;

            }

            if ((aReal.value_ < 0.0) && (value_ >= (std::numeric_limits<Real::ValueType>::max() + aReal.value_))) // Subtraction would overflow
            {
                return (value_ != 0.0) ? Real::PositiveInfinity() : -aReal ;
            }

            if ((aReal.value_ > 0.0) && (value_ <= (std::numeric_limits<Real::ValueType>::lowest() + aReal.value_))) // Subtraction would underflow
            {
                return (value_ != 0.0) ? Real::NegativeInfinity() : -aReal ;
            }

            return Real(value_ - aReal.value_) ;

        }

    }

    return Real::Undefined() ;

}

Real                            Real::operator *                            (   const   Real&                       aReal                                       ) const
{

    if ((type_ != Real::Type::Undefined) && (aReal.type_ != Real::Type::Undefined))
    {

        if (type_ == Real::Type::PositiveInfinity)
        {

            if (aReal.isStrictlyPositive())
            {
                return Real::PositiveInfinity() ;
            }
            else if (aReal.isStrictlyNegative())
            {
                return Real::NegativeInfinity() ;
            }

            return Real::Undefined() ;

        }
        else if (type_ == Real::Type::NegativeInfinity)
        {

            if (aReal.isStrictlyPositive())
            {
                return Real::NegativeInfinity() ;
            }
            else if (aReal.isStrictlyNegative())
            {
                return Real::PositiveInfinity() ;
            }

            return Real::Undefined() ;

        }
        else if (aReal.type_ == Real::Type::PositiveInfinity)
        {

            if (this->isStrictlyPositive())
            {
                return Real::PositiveInfinity() ;
            }
            else if (this->isStrictlyNegative())
            {
                return Real::NegativeInfinity() ;
            }

            return Real::Undefined() ;

        }
        else if (aReal.type_ == Real::Type::NegativeInfinity)
        {

            if (this->isStrictlyPositive())
            {
                return Real::NegativeInfinity() ;
            }
            else if (this->isStrictlyNegative())
            {
                return Real::PositiveInfinity() ;
            }

            return Real::Undefined() ;

        }
        else
        {

            if (this->isZero() || aReal.isZero())
            {
                return Real::Zero() ;
            }

            // Check for -1 for two's complement machines

            if ((value_ < 0.0) && (aReal.value_ == std::numeric_limits<Real::ValueType>::lowest())) // Multiplication can overflow
            {
                return Real::PositiveInfinity() ;
            }

            if ((aReal.value_ < 0.0) && (value_ == std::numeric_limits<Real::ValueType>::lowest())) // Multiplication can overflow
            {
                return Real::PositiveInfinity() ;
            }

            if ((this->getSign() == aReal.getSign()) && (std::abs(value_) > (std::numeric_limits<Real::ValueType>::max() / std::abs(aReal.value_)))) // Multiplication would overflow
            {
                return Real::PositiveInfinity() ;
            }

            if ((value_ == +1) && (aReal.value_ == std::numeric_limits<Real::ValueType>::lowest()))
            {
                return Real(std::numeric_limits<Real::ValueType>::lowest()) ;
            }

            if ((value_ == -1) && (aReal.value_ == std::numeric_limits<Real::ValueType>::lowest()))
            {
                return Real::PositiveInfinity() ;
            }

            if ((aReal.value_ != -1) && (this->getSign() != aReal.getSign()) && ((-std::abs(value_)) < (std::numeric_limits<Real::ValueType>::lowest() / std::abs(aReal.value_)))) // Multiplication would underflow
            {
                return Real::NegativeInfinity() ;
            }

            return Real(value_ * aReal.value_) ;

        }

    }

    return Real::Undefined() ;

}

Real                            Real::operator /                            (   const   Real&                       aReal                                       ) const
{

    if (aReal.isZero())
    {
        return Real::Undefined() ;
    }

    if ((type_ != Real::Type::Undefined) && (aReal.type_ != Real::Type::Undefined))
    {

        if (type_ == Real::Type::PositiveInfinity)
        {

            if (aReal.isInfinity())
            {
                return Real::Undefined() ;
            }
            else if (aReal.isStrictlyPositive())
            {
                return Real::PositiveInfinity() ;
            }
            else if (aReal.isStrictlyNegative())
            {
                return Real::NegativeInfinity() ;
            }

            return Real::Undefined() ;

        }
        else if (type_ == Real::Type::NegativeInfinity)
        {

            if (aReal.isInfinity())
            {
                return Real::Undefined() ;
            }
            else if (aReal.isStrictlyPositive())
            {
                return Real::NegativeInfinity() ;
            }
            else if (aReal.isStrictlyNegative())
            {
                return Real::PositiveInfinity() ;
            }

            return Real::Undefined() ;

        }
        else
        {

            if (this->isZero() || aReal.isInfinity())
            {
                return Real::Zero() ;
            }
            else
            {

                if ((value_ == std::numeric_limits<Real::ValueType>::lowest()) && (aReal.value_ == -1))
                {
                    return Real::PositiveInfinity() ;
                }

                return Real(value_ / aReal.value_) ;

            }

        }

    }

    return Real::Undefined() ;

}

Real                            Real::operator +                            (   const   Real::ValueType&            aReal                                       ) const
{
    return (*this) + Real(aReal) ;
}

Real                            Real::operator -                            (   const   Real::ValueType&            aReal                                       ) const
{
    return (*this) - Real(aReal) ;
}

Real                            Real::operator *                            (   const   Real::ValueType&            aReal                                       ) const
{
    return (*this) * Real(aReal) ;
}

Real                            Real::operator /                            (   const   Real::ValueType&            aReal                                       ) const
{
    return (*this) / Real(aReal) ;
}

Real&                           Real::operator +=                           (   const   Real&                       aReal                                       )
{

    (*this) = (*this) + aReal ;

    return *this ;

}

Real&                           Real::operator -=                           (   const   Real&                       aReal                                       )
    {

    (*this) = (*this) - aReal ;

    return *this ;

}

Real&                           Real::operator *=                           (   const   Real&                       aReal                                       )
    {

    (*this) = (*this) * aReal ;

    return *this ;

}

Real&                           Real::operator /=                           (   const   Real&                       aReal                                       )
    {

    (*this) = (*this) / aReal ;

    return *this ;

}

Real&                           Real::operator +=                           (   const   Real::ValueType&            aReal                                       )
{

    (*this) = (*this) + Real(aReal) ;

    return *this ;

}

Real&                           Real::operator -=                           (   const   Real::ValueType&            aReal                                       )
{

    (*this) = (*this) - Real(aReal) ;

    return *this ;

}

Real&                           Real::operator *=                           (   const   Real::ValueType&            aReal                                       )
{

    (*this) = (*this) * Real(aReal) ;

    return *this ;

}

Real&                           Real::operator /=                           (   const   Real::ValueType&            aReal                                       )
{

    (*this) = (*this) / Real(aReal) ;

    return *this ;

}

Real                            operator +                                  (   const   Real::ValueType&            anInt,
                                                                                const   Real&                       aReal                                       )
{
    return Real(anInt) + aReal ;
}

Real                            operator -                                  (   const   Real::ValueType&            anInt,
                                                                                const   Real&                       aReal                                       )
{
    return Real(anInt) - aReal ;
}

Real                            operator *                                  (   const   Real::ValueType&            anInt,
                                                                                const   Real&                       aReal                                       )
{
    return Real(anInt) * aReal ;
}

Real                            operator /                                  (   const   Real::ValueType&            anInt,
                                                                                const   Real&                       aReal                                       )
{
    return Real(anInt) / aReal ;
}

Real                            Real::operator +                            ( ) const
{
    return *this ;
}

Real                            Real::operator -                            ( ) const
{

    switch (type_)
    {

        case Real::Type::Defined:
        {

            if (value_ == std::numeric_limits<Real::ValueType>::lowest())
            {
                return Real::PositiveInfinity() ;
            }

            return Real(-value_) ;

        }

        case Real::Type::Undefined:
            return Real::Undefined() ;

        case Real::Type::PositiveInfinity:
            return Real::NegativeInfinity() ;

        case Real::Type::NegativeInfinity:
            return Real::PositiveInfinity() ;

        default:
            break ;

    }

    return Real::Undefined() ;

}

                                Real::operator Real::ValueType        ( ) const
{

    if (type_ != Real::Type::Defined)
    {
        throw ostk::core::error::runtime::Undefined("Real") ;
    }

    return value_ ;

}

std::ostream&                   operator <<                                 (           std::ostream&               anOutputStream,
                                                                                const   Real&                       aReal                                       )
{

    (void) aReal ;

    switch (aReal.type_)
    {

        case Real::Type::Undefined:
            anOutputStream << "Undefined" ;
            break ;

        case Real::Type::Defined:
            anOutputStream << aReal.value_ ;
            break ;

        case Real::Type::PositiveInfinity:
            anOutputStream << "+Inf" ;
            break ;

        case Real::Type::NegativeInfinity:
            anOutputStream << "-Inf" ;
            break ;

    }

    // ostk::core::utilities::Output::Header(anOutputStream, "Real") ;

    // ostk::core::utilities::Output::Line(anOutputStream) << "Type:" << aReal.type_ ;
    // ostk::core::utilities::Output::Line(anOutputStream) << "Value:" << aReal.value_ ;

    // ostk::core::utilities::Output::Footer(anOutputStream) ;

    return anOutputStream ;

}

bool                            Real::isDefined                             ( ) const
{
    return type_ != Real::Type::Undefined ;
}

bool                            Real::isZero                                ( ) const
{
    return (type_ == Real::Type::Defined) && (value_ == 0.0) ;
}

bool                            Real::isPositive                            ( ) const
{
    return ((type_ == Real::Type::Defined) && (value_ >= 0.0)) || this->isPositiveInfinity() ;
}

bool                            Real::isNegative                            ( ) const
{
    return ((type_ == Real::Type::Defined) && (value_ <= 0.0)) || this->isNegativeInfinity() ;
}

bool                            Real::isStrictlyPositive                    ( ) const
{
    return ((type_ == Real::Type::Defined) && (value_ > 0.0)) || this->isPositiveInfinity() ;
}

bool                            Real::isStrictlyNegative                    ( ) const
{
    return ((type_ == Real::Type::Defined) && (value_ < 0.0)) || this->isNegativeInfinity() ;
}

bool                            Real::isInfinity                            ( ) const
{
    return this->isPositiveInfinity() || this->isNegativeInfinity() ;
}

bool                            Real::isPositiveInfinity                    ( ) const
{
    return type_ == Real::Type::PositiveInfinity ;
}

bool                            Real::isNegativeInfinity                    ( ) const
{
    return type_ == Real::Type::NegativeInfinity ;
}

bool                            Real::isInteger                             ( ) const
{

    if (this->isFinite())
    {

        double intpart ;

        return std::modf(value_, &intpart) == 0.0 ;

    }

    return false ;

}

bool                            Real::isFinite                              ( ) const
{
    return type_ == Real::Type::Defined ;
}

bool                            Real::isNear                                (   const   Real&                       aValue,
                                                                                const   Real&                       aTolerance                                  ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("Real") ;
    }

    if (!aValue.isDefined())
    {
        throw ostk::core::error::runtime::Undefined("Real") ;
    }

    if (!aTolerance.isDefined())
    {
        throw ostk::core::error::runtime::Undefined("Tolerance") ;
    }

    if ((!this->isFinite()) || (!aValue.isFinite()))
    {
        return false ;
    }

    return ((*this) - aValue).abs() <= aTolerance ;

}

types::Sign                     Real::getSign                               ( ) const
{

    switch (type_)
    {

        case Real::Type::Undefined:
            return types::Sign::Undefined ;

        case Real::Type::Defined:
        {

            if (value_ > 0.0)
            {
                return types::Sign::Positive ;
            }
            else if (value_ < 0.0)
            {
                return types::Sign::Negative ;
            }

            return types::Sign::None ;

        }

        case Real::Type::PositiveInfinity:
            return types::Sign::Positive ;

        case Real::Type::NegativeInfinity:
            return types::Sign::Negative ;

        default:
           return types::Sign::Undefined ;

    }

    return types::Sign::Undefined ;

}

types::String                   Real::toString                              (   const   types::Integer&             aPrecision                                  ) const
{

    switch (type_)
    {

        case Real::Type::Undefined:
            return "Undefined" ;

        case Real::Type::Defined:
        {

            if (!aPrecision.isDefined())
            {

                if (this->isInteger())
                {

                    types::String realString = boost::lexical_cast<std::string>(value_) ;

                    if (realString.find('e') == std::string::npos)
                    {
                        realString += ".0" ;
                    }

                    return realString ;

                }

                // types::String realString = std::to_string(value_) ;
                types::String realString = boost::lexical_cast<std::string>(value_) ;

                // std::ostringstream stringStream ;

                // stringStream.precision(14) ;

                // stringStream << std::fixed << value_ ;

                // types::String realString = stringStream.str() ;

                if (realString.find('e') == std::string::npos)
                {
                    realString.erase(realString.find_last_not_of('0') + 1, std::string::npos) ; // Remove trailing zeros if any
                }

                return realString ;

                // return boost::lexical_cast<std::string>(value_) ;
                // return std::to_string(value_) ;

            }

            std::ostringstream stringStream ;

            stringStream.precision(aPrecision) ;

            stringStream << std::fixed << value_ ;

            return stringStream.str() ;

        }

        case Real::Type::PositiveInfinity:
            return "+Inf" ;

        case Real::Type::NegativeInfinity:
            return "-Inf" ;

    }

    return types::String::Empty() ;

}

types::Integer                  Real::toInteger                         ( ) const
{

    if (this->isInteger())
    {
        return types::Integer(static_cast<types::Integer::ValueType>(value_)) ;
    }

    throw ostk::core::error::RuntimeError("Real is not integer.") ;

    return types::Integer::Undefined() ;

}

Real                            Real::abs                                   ( ) const
{

    switch (type_)
    {

        case Real::Type::Undefined:
            return Real::Undefined() ;

        case Real::Type::Defined:
            return Real(Real::Type::Defined, std::abs(value_)) ;

        case Real::Type::PositiveInfinity:
        case Real::Type::NegativeInfinity:
            return Real::PositiveInfinity() ;

        default:
            throw ostk::core::error::runtime::Undefined("Type") ;
            break ;

    }

    return Real::Undefined() ;

}

types::Integer                  Real::floor                                 ( ) const
{

    switch (type_)
    {

        case Real::Type::Undefined:
            return types::Integer::Undefined() ;

        case Real::Type::Defined:
            return types::Integer(static_cast<types::Integer::ValueType>(std::floor(value_))) ;

        case Real::Type::PositiveInfinity:
        case Real::Type::NegativeInfinity:
            return types::Integer::Undefined() ;

        default:
            throw ostk::core::error::runtime::Undefined("Type") ;
            break ;

    }

    return types::Integer::Undefined() ;

}

Real                            Real::sqrt                                  ( ) const
{

    switch (type_)
    {

        case Real::Type::Undefined:
            return Real::Undefined() ;

        case Real::Type::Defined:
        {

            if (this->isStrictlyNegative())
            {
                return Real::Undefined() ;
            }

            return Real(Real::Type::Defined, std::sqrt(value_)) ;

        }

        case Real::Type::PositiveInfinity:
            return Real::PositiveInfinity() ;

        case Real::Type::NegativeInfinity:
            return Real::Undefined() ;

        default:
            throw ostk::core::error::runtime::Undefined("Type") ;
            break ;

    }

    return Real::Undefined() ;

}

Real                            Real::Undefined                             ( )
{
    return Real(Real::Type::Undefined, 0.0) ;
}

Real                            Real::Zero                                  ( )
{
    return Real(Real::Type::Defined, 0.0) ;
}

Real                            Real::Pi                                    ( )
{
    return Real(Real::Type::Defined, M_PI) ;
}

Real                            Real::HalfPi                                ( )
{
    return Real(Real::Type::Defined, M_PI / 2.0) ;
}

Real                            Real::TwoPi                                 ( )
{
    return Real(Real::Type::Defined, 2.0 * M_PI) ;
}

Real                            Real::Epsilon                               ( )
{
    return Real(Real::Type::Defined, 1e-15) ;
}

Real                            Real::PositiveInfinity                      ( )
{
    return Real(Real::Type::PositiveInfinity, std::numeric_limits<Real::ValueType>::max()) ;
}

Real                            Real::NegativeInfinity                      ( )
{
    return Real(Real::Type::NegativeInfinity, std::numeric_limits<Real::ValueType>::lowest()) ;
}

Real                            Real::Integer                               (   const   types::Integer&             anInteger                                   )
{

    if (anInteger.isDefined())
    {
        return Real(Real::Type::Defined, anInteger) ;
    }

    return Real::Undefined() ;

}

Real                            Real::CanParse                              (   const   types::String&              aString                                     )
{

    if (aString.isEmpty())
    {
        return false ;
    }

    if ((aString == "Undefined") || (aString == "NaN") || (aString == "Inf") || (aString == "+Inf") || (aString == "-Inf"))
    {
        return true ;
    }

    Real::ValueType real ;

    return boost::conversion::try_lexical_convert<Real::ValueType>(aString, real) ;

}

Real                            Real::Parse                                 (   const   types::String&              aString                                     )
{

    if (aString.isEmpty())
    {
        throw ostk::core::error::runtime::Undefined("String") ;
    }

    if ((aString == "Undefined") || (aString == "NaN"))
    {
        return Real::Undefined() ;
    }

    if ((aString == "Inf") || (aString == "+Inf"))
    {
        return Real::PositiveInfinity() ;
    }

    if (aString == "-Inf")
    {
        return Real::NegativeInfinity() ;
    }

    try
    {

        const Real::ValueType value = boost::lexical_cast<Real::ValueType>(aString) ;

        if (value != value)
        {
            throw ostk::core::error::RuntimeError("Cannot cast string [" + aString + "] to Real.") ;
        }

        return Real(value) ;

    }
    catch (const boost::bad_lexical_cast&)
    {
        throw ostk::core::error::RuntimeError("Cannot cast string [" + aString + "] to Real.") ;
    }

    return Real::Undefined() ;

}

// Real                             Real::Object                             (   const   ctnr::Object&               anObject                                   )
// {

// }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                                Real::Real                                  (   const   Real::Type&                 aType,
                                                                                const   Real::ValueType&            aReal                                       )
                                :   type_(aType),
                                    value_(aReal)
{

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


/*
 *            Copyright 2009-2018 The VOTCA Development Team
 *                       (http://www.votca.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.
 *
 */

#include <votca/xtp/aomatrix.h>
#include <votca/xtp/dftcoupling.h>

#include <votca/tools/constants.h>
#include <boost/format.hpp>
#include <boost/progress.hpp>

namespace votca { namespace xtp {

using std::flush;
using boost::format;

void DFTcoupling::Initialize(tools::Property& options){

  std::string key="";
  _degeneracy=options.ifExistsReturnElseReturnDefault<bool>(key+"degeneracy",0.0);
  _numberofstatesA=options.ifExistsReturnElseReturnDefault<int>(key+"levA",1);
  _numberofstatesB=options.ifExistsReturnElseReturnDefault<int>(key+"levB",1);

}

void DFTcoupling::WriteToProperty(tools::Property& type_summary, const Orbitals& orbitalsA,
                                  const Orbitals& orbitalsB, int a, int b){
  double J = getCouplingElement(a,b, orbitalsA, orbitalsB);
  tools::Property& coupling = type_summary.add("coupling","");
  double energyA = orbitalsA.getEnergy(a);
  double energyB = orbitalsB.getEnergy(b);
  coupling.setAttribute("levelA", a);
  coupling.setAttribute("levelB", b);
  coupling.setAttribute("j",(format("%1$1.6e") % J).str());
  coupling.setAttribute("eA",(format("%1$1.6e") % energyA).str());
  coupling.setAttribute("eB",(format("%1$1.6e") % energyB).str());
}

 void DFTcoupling::Addoutput(tools::Property & type_summary,const Orbitals& orbitalsA,
                               const Orbitals& orbitalsB){
  tools::Property& dftcoupling= type_summary.add(Identify(),"");
  dftcoupling.setAttribute("homoA",orbitalsA.getHomo());
  dftcoupling.setAttribute("homoB",orbitalsB.getHomo());
   tools::Property &hole_summary = dftcoupling.add("hole","");
   //hole hole
   for (int a=Range_orbA.first;a<=orbitalsA.getHomo();++a){
     for (int b=Range_orbB.first;b<=orbitalsB.getHomo();++b){
          WriteToProperty(hole_summary, orbitalsA, orbitalsB, a, b);
    }
   }
   tools::Property &electron_summary = dftcoupling.add("electron","");
   //electron-//electron
   for (int a=orbitalsA.getLumo();a<=Range_orbA.first+Range_orbA.second;++a){
     for (int b=orbitalsB.getLumo();b<=Range_orbB.first+Range_orbB.second;++b){
          WriteToProperty(electron_summary, orbitalsA, orbitalsB, a, b);
    }
   }
   return;
 }

std::pair<int,int> DFTcoupling::DetermineRangeOfStates(const Orbitals& orbital, int numberofstates)const{
  const Eigen::VectorXd& MOEnergies=orbital.MOEnergies();
  if(std::abs(MOEnergies(orbital.getHomo())-MOEnergies(orbital.getLumo()))<_degeneracy){
    throw std::runtime_error("Homo Lumo Gap is smaller than degeneracy. "
            "Either your degeneracy is too large or your Homo and Lumo are degenerate");
  }

  int minimal=orbital.getHomo()-numberofstates+1;
  int maximal=orbital.getLumo()+numberofstates-1;

  std::vector<int> deg_min=orbital.CheckDegeneracy(minimal,_degeneracy);
  for(int i:deg_min){
    if (i<minimal){
      minimal=i;
    }
  }

  std::vector<int> deg_max=orbital.CheckDegeneracy(maximal,_degeneracy);
  for(int i:deg_max){
    if (i>maximal){
      maximal=i;
    }
  }

  std::pair<int,int> result;
  result.first=minimal;//start
  result.second=maximal-minimal+1;//size

  return result;
}

double DFTcoupling::getCouplingElement( int levelA, int levelB, const Orbitals&orbitalsA,
    const Orbitals& orbitalsB)const {

    int levelsA =Range_orbA.second;

    if (_degeneracy != 0) {
        std::vector<int> list_levelsA = orbitalsA.CheckDegeneracy(levelA,_degeneracy);
        std::vector<int> list_levelsB = orbitalsA.CheckDegeneracy(levelB,_degeneracy);

        double JAB_sq = 0;

        for (int iA : list_levelsA) {
          for (int iB : list_levelsB) {
            double JAB_one_level = JAB(iA - 1, iB - 1 + levelsA);
            JAB_sq += JAB_one_level*JAB_one_level;
          }
        }
        return std::sqrt(JAB_sq / (list_levelsA.size() * list_levelsB.size())) * tools::conv::hrt2ev;
      } else {
        return JAB(levelA - 1, levelB - 1 + levelsA) * tools::conv::hrt2ev;
      }
}

/**
 * \brief evaluates electronic couplings
 * @param _orbitalsA molecular orbitals of molecule A
 * @param _orbitalsB molecular orbitals of molecule B
 * @param _orbitalsAB molecular orbitals of the dimer AB
 */
void DFTcoupling::CalculateCouplings(const Orbitals& orbitalsA, const Orbitals& orbitalsB,
    Orbitals& orbitalsAB) {

    CTP_LOG(ctp::logDEBUG,*_pLog) << "Calculating electronic couplings" << flush;

    CheckAtomCoordinates(orbitalsA, orbitalsB, orbitalsAB);

    // constructing the direct product orbA x orbB
    int basisA = orbitalsA.getBasisSetSize();
    int basisB = orbitalsB.getBasisSetSize();

    if ( ( basisA == 0 ) || ( basisB == 0 ) ) {
       throw std::runtime_error( "Basis set size is not stored in monomers");
    }

    Range_orbA=DetermineRangeOfStates(orbitalsA,_numberofstatesA);
    Range_orbB=DetermineRangeOfStates(orbitalsB,_numberofstatesB);

    int levelsA = Range_orbA.second;
    int levelsB = Range_orbB.second;

    CTP_LOG(ctp::logDEBUG,*_pLog) << "Levels:Basis A[" << levelsA << ":" << basisA << "]"
                                     << " B[" << levelsB << ":" << basisB << "]" << flush;

    if ( ( levelsA == 0 ) || (levelsB == 0) ) {
         throw std::runtime_error("No information about number of occupied/unoccupied levels is stored");
    }

    //       | Orbitals_A          0 |      | Overlap_A |
    //       | 0          Orbitals_B |.T  X   | Overlap_B |  X  ( Orbitals_AB )

    Eigen::MatrixXd psi_AxB=Eigen::MatrixXd::Zero( basisA + basisB, levelsA + levelsB  );

      CTP_LOG(ctp::logDEBUG,*_pLog) << "Constructing direct product AxB ["
            << psi_AxB.rows() << "x"
            << psi_AxB.cols() << "]" << flush;

    // constructing merged orbitals
    psi_AxB.block(0,0, basisA,levelsA) = orbitalsA.MOCoefficients().block(0,Range_orbA.first,basisA,Range_orbA.second);
    psi_AxB.block(basisA,levelsA, basisB,levelsB) =orbitalsB.MOCoefficients().block(0,Range_orbB.first,basisB,Range_orbB.second);

    Eigen::MatrixXd overlap;
    if ( orbitalsAB.hasAOOverlap() ) {
            CTP_LOG(ctp::logDEBUG,*_pLog) << "Reading overlap matrix from orbitals" << flush;
           overlap= orbitalsAB.AOOverlap();
    }else{
        CTP_LOG(ctp::logDEBUG,*_pLog) << "Calculating overlap matrix for basisset: "<< orbitalsAB.getDFTbasis()<< flush;
        overlap=CalculateOverlapMatrix(orbitalsAB);
    }
     CTP_LOG(ctp::logDEBUG,*_pLog) << "Projecting dimer onto monomer orbitals" << flush;
    Eigen::MatrixXd psi_AxB_dimer_basis =psi_AxB.transpose()*overlap*orbitalsAB.MOCoefficients();

    unsigned int LevelsA = levelsA;
    for (unsigned i=0;i<psi_AxB_dimer_basis.rows();i++){
        double mag=psi_AxB_dimer_basis.row(i).squaredNorm();
        if (mag<0.95){
            int monomer = 0;
            int level = 0;
            if ( i < LevelsA ) {
                monomer = 1;
                level   = i;
            } else {
                monomer = 2;
                level   = i -levelsA;

            }
            CTP_LOG(ctp::logERROR,*_pLog) << "\nWarning: " << i << " Projection of orbital " << level
                    << " of monomer " << monomer << " on dimer is insufficient,mag="<<mag
                    <<" maybe the orbital order is screwed up, otherwise increase dimer basis.\n"<<flush;
        }
    }
    CTP_LOG(ctp::logDEBUG,*_pLog) << "Projecting the Fock matrix onto the dimer basis" << flush;
    Eigen::MatrixXd JAB_dimer = psi_AxB_dimer_basis*orbitalsAB.MOEnergies().asDiagonal()*psi_AxB_dimer_basis.transpose();
    CTP_LOG(ctp::logDEBUG,*_pLog) << "Constructing Overlap matrix" << flush;
    Eigen::MatrixXd S_AxB = psi_AxB_dimer_basis*psi_AxB_dimer_basis.transpose();
   Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(S_AxB);
   Eigen::MatrixXd Sm1=es.operatorInverseSqrt();
   CTP_LOG(ctp::logDEBUG,*_pLog) << "Smallest eigenvalue of overlap matrix is "<<es.eigenvalues()(0)<< flush;
   JAB = Sm1*JAB_dimer*Sm1;
    CTP_LOG(ctp::logDEBUG,*_pLog) << "Done with electronic couplings" << flush;

}

}}


#include <memory>
#include <cstdio>
#include <boost/program_options.hpp>
#include "THYAnalysis.h"
#include "TICBjTest.h"
#include "TRNavier3DBj.h"
#include "TDomain3D.h"
#include "TGrid3D.h"
#include "THydro3DBj.h"
#include "TIC3D.h"

using namespace std ;

//! Purpose:
//!
//! To simulate second order hydro for a boost invariant expansion, and
//! to produce output that can be compared to the full hydro evolution.
//!
//! Usage
//!
//! test_icbj [--no-entropy-rewite] nametag
//!
//! Inputs:
//!
//! * The nametag. The program reads nametag.ini and generates the output Of
//! course the input file should be setup with the initial conditions
//! TICBjTest, so that the hydro solution will correspond to a (infinite in
//! transverse extent bjorken solution.
//!
//! * If the command line option is specified. Then the evolution is
//! done without multiplying by the entropy and using ideal EOM.
//!
//! Outputs:
//!
//! On output the program produces a data file with the variables as a function of time. See the fprintf statement below for the variable list.
//!
//! The filename is nametage_icbj_srewrite.out if the entropy rewrite is used,
//! and nametag_icbj_nosrewrite.out if the entropy rewrite is not used.
//!
int main(int argc, char **argv)
{
   bool use_entropy_rewrite = true ;
   std::string nametag;
   if (argc == 2) {
      nametag = argv[1] ;
   } else if (argc ==3) {
      use_entropy_rewrite = false ;
      std::string s = argv[1] ;
      if (s != "--no-entropy-rewrite") {
         cout << "Usage is test_icbj [--no-entropy-rewrite] nametag. " << endl;
      }
      nametag = argv[2] ;
   } else {
      cout << "Usage is test_icbj [--no-entropy-rewrite] nametag. " << endl;
   }

   TRNavier3DBj rn(nametag.c_str(), make_eos_gammalaw) ;
   THydro3DBj hy(&rn, make_ic_icbjtest) ;
   THModel3D *hm = rn.getHModel3D() ;

   // Extract the parameters from the class
   TICBjTest *ic = dynamic_cast<TICBjTest *>(hy.getIC()) ;
   double s0 = ic->getS0() ;
   double t0 = ic->getTau0() ;
   double novers = ic->getNOverS() ;
   double pioverpins = ic->getPiOverPiNS() ;
   double c1 = ic->getCutoffC1() ;

   // Set the time equal to the initial time
   double t = t0 ;

   // determine initial conditions for e and n and other thermo variables
   double s = s0/t;
   double n = novers*s ;
   double e = hm->getEOS()->eofs(s, n) ;
   double p, cs ;
   hm->getEOS()->eos(e, n, p, cs) ;
   double temper, mu ;
   hm->getEOS()->stmu(e, n, s, temper, mu) ;

   // Determine the viscosities needed
   double sigma_overs, kappaT_overs, eta_overs ;
   hm->getEOS()->viscosity(e, n, sigma_overs, kappaT_overs, eta_overs) ;
   double eta = eta_overs*s ;
   double tpi_over_etast, l1, l2 ;
   //l1 and l2 are lambda_1/(eta*tau*pi) and lambda_2/(eta*tau_pi)
   hm->getEOS()->getBRSSSParams(e,n, tpi_over_etast, l1, l2)  ;
   double tpi = tpi_over_etast * eta/(s*temper) ;

   //Determine the initial conditions for pi.
   double pins = -4./3.*eta/t ; //navier stokes value
   double pi   = pioverpins * pins;
   double tr = 3./2.*pi*pi ;    //tr(piij*piij)
   pi = pi/(1. + c1 * tr/(p*p)) ; // regulator used
   double spi = s * pi * t;

   // determine the nonlinear coupling
   double sig = 4./3./t ;
   tr = 3./2.*pi*sig ;
   double pi_sig = 0.5*pi*sig + 0.5*pi*sig - 1./3.*tr ;

   // Set time stepping parameters
   double dt = 0.001 ;
   double tmax =  3. ;
   int nsteps = (tmax - hy.getTime())/dt ;

   // TODO
   // Determine the integration constant c0ns for the analytic
   // solution of first order viscous hydro. For speed of sound^2=1/3.
   // Also set the (analytic) expectations. Add the analytic
   // solution to the printout

   // Open the output file
   std::string name ;
   if (use_entropy_rewrite) {
      name = rn.getNametag() + "_srewrite" + "_icbj.out";
   } else {
      name = rn.getNametag() + "_nosrewrite" + "_icbj.out";
   }
   FILE *fp = fopen(name.c_str(), "w") ;

   // Forward euler
   for (int itime = 0 ;  itime < nsteps ; itime++) {
      // Print out the results and navier stokes expectation for ideal gas.
      fprintf(fp,
              "%15.5e %15.5e %15.5e %15.5e "
              "%15.5e %15.5e %15.5e %15.5e \n",
            t, e, n, p,
            s, temper, pins, pi) ;

      // Take a step
      e +=  -dt * (e + p + pi)/t ;

      n +=  -dt  * n / t ;

      pi += dt * (- 1./tpi*pi  + pins/tpi - 4./3.*pi/t - l1*pi_sig);

      spi +=  -dt * t * s * (1./tpi*pi  - pins/tpi + 4./3.*pi/t + l1*pi_sig);

      t += dt ;

      // determine parameters for next time step
      hm->getEOS()->eos(e, n, p, cs) ;
      hm->getEOS()->stmu(e, n, s, temper, mu) ;
      hm->getEOS()->viscosity(e, n, sigma_overs, kappaT_overs, eta_overs) ;
      eta = eta_overs*s ;
      hm->getEOS()->getBRSSSParams(e, n, tpi_over_etast, l1, l2) ;
      tpi = tpi_over_etast * eta/(s*temper) ;
      pins = -4./3.*eta/t ;
      sig = 4./3./t ;
      tr = 3./2.*pi*sig ;
      pi_sig = 0.5*pi*sig + 0.5*pi*sig - 1./3.*tr ;

      // set spi or pi. Depending on which equations we want to solve
      if (use_entropy_rewrite) {
         pi = spi/(s*t);  // pi is determined by spi
      } else{
         spi = pi*s*t;  // spi is determined by pi
      }
   }
   fclose(fp) ;

}


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_CASS_HPP
#define BOOST_GEOMETRY_PROJECTIONS_CASS_HPP

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_mlfn.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace cass
    {

            //static const double epsilon10 = 1e-10;
            //static const double C1 = .16666666666666666666;
            //static const double C2 = .00833333333333333333;
            //static const double C3 = .04166666666666666666;
            //static const double C4 = .33333333333333333333;
            //static const double C5 = .06666666666666666666;

            template <typename T>
            inline T C1() { return .16666666666666666666666666666666666666; }
            template <typename T>
            inline T C2() { return .00833333333333333333333333333333333333; }
            template <typename T>
            inline T C3() { return .04166666666666666666666666666666666666; }
            template <typename T>
            inline T C4() { return .33333333333333333333333333333333333333; }
            template <typename T>
            inline T C5() { return .06666666666666666666666666666666666666; }

            template <typename T>
            struct par_cass
            {
                T m0;
                detail::en<T> en;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_cass_ellipsoid
                : public base_t_fi<base_cass_ellipsoid<T, Parameters>, T, Parameters>
            {
                par_cass<T> m_proj_parm;

                inline base_cass_ellipsoid(const Parameters& par)
                    : base_t_fi<base_cass_ellipsoid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(e_forward)  ellipsoid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    static const T C1 = cass::C1<T>();
                    static const T C2 = cass::C2<T>();
                    static const T C3 = cass::C3<T>();

                    T n = sin(lp_lat);
                    T c = cos(lp_lat);
                    xy_y = pj_mlfn(lp_lat, n, c, this->m_proj_parm.en);
                    n = 1./sqrt(1. - this->m_par.es * n * n);
                    T tn = tan(lp_lat);
                    T t = tn * tn;
                    T a1 = lp_lon * c;
                    c *= this->m_par.es * c / (1 - this->m_par.es);
                    T a2 = a1 * a1;
                    xy_x = n * a1 * (1. - a2 * t *
                        (C1 - (8. - t + 8. * c) * a2 * C2));
                    xy_y -= this->m_proj_parm.m0 - n * tn * a2 *
                        (.5 + (5. - t + 6. * c) * a2 * C3);
                }

                // INVERSE(e_inverse)  ellipsoid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T C3 = cass::C3<T>();
                    static const T C4 = cass::C4<T>();
                    static const T C5 = cass::C5<T>();

                    T ph1;

                    ph1 = pj_inv_mlfn(this->m_proj_parm.m0 + xy_y, this->m_par.es, this->m_proj_parm.en);
                    T tn = tan(ph1); T t = tn * tn;
                    T n = sin(ph1);
                    T r = 1. / (1. - this->m_par.es * n * n);
                    n = sqrt(r);
                    r *= (1. - this->m_par.es) * n;
                    T dd = xy_x / n;
                    T d2 = dd * dd;
                    lp_lat = ph1 - (n * tn / r) * d2 *
                        (.5 - (1. + 3. * t) * d2 * C3);
                    lp_lon = dd * (1. + t * d2 *
                        (-C4 + (1. + 3. * t) * d2 * C5)) / cos(ph1);
                }

                static inline std::string get_name()
                {
                    return "cass_ellipsoid";
                }

            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_cass_spheroid
                : public base_t_fi<base_cass_spheroid<T, Parameters>, T, Parameters>
            {
                par_cass<T> m_proj_parm;

                inline base_cass_spheroid(const Parameters& par)
                    : base_t_fi<base_cass_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    xy_x = asin(cos(lp_lat) * sin(lp_lon));
                    xy_y = atan2(tan(lp_lat) , cos(lp_lon)) - this->m_par.phi0;
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    T dd = xy_y + this->m_par.phi0;
                    lp_lat = asin(sin(dd) * cos(xy_x));
                    lp_lon = atan2(tan(xy_x), cos(dd));
                }

                static inline std::string get_name()
                {
                    return "cass_spheroid";
                }

            };

            // Cassini
            template <typename Parameters, typename T>
            inline void setup_cass(Parameters& par, par_cass<T>& proj_parm)
            {
                if (par.es) {
                    proj_parm.en = pj_enfn<T>(par.es);
                    proj_parm.m0 = pj_mlfn(par.phi0, sin(par.phi0), cos(par.phi0), proj_parm.en);
                } else {
                }
            }

    }} // namespace detail::cass
    #endif // doxygen

    /*!
        \brief Cassini projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
        \par Example
        \image html ex_cass.gif
    */
    template <typename T, typename Parameters>
    struct cass_ellipsoid : public detail::cass::base_cass_ellipsoid<T, Parameters>
    {
        template <typename Params>
        inline cass_ellipsoid(Params const& , Parameters const& par)
            : detail::cass::base_cass_ellipsoid<T, Parameters>(par)
        {
            detail::cass::setup_cass(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Cassini projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
        \par Example
        \image html ex_cass.gif
    */
    template <typename T, typename Parameters>
    struct cass_spheroid : public detail::cass::base_cass_spheroid<T, Parameters>
    {
        template <typename Params>
        inline cass_spheroid(Params const& , Parameters const& par)
            : detail::cass::base_cass_spheroid<T, Parameters>(par)
        {
            detail::cass::setup_cass(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_cass, cass_spheroid, cass_ellipsoid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI2(cass_entry, cass_spheroid, cass_ellipsoid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(cass_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(cass, cass_entry);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_CASS_HPP


/*=============================================================================

  NifTK: A software platform for medical image computing.

  Copyright (c) University College London (UCL). All rights reserved.

  This software is distributed WITHOUT ANY WARRANTY; without even
  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.

  See LICENSE.txt in the top level directory for details.

=============================================================================*/

#include <niftkConversionUtils.h>
#include <niftkCommandLineParser.h>

#include <itkImage.h>
#include <itkImageFileReader.h>
#include <itkImageFileWriter.h>
#include <itkNifTKImageIOFactory.h>

#include <itkTransformFileWriter.h>
#include <itkTransformFactory.h>

#include <itkGE5000_TomosynthesisGeometry.h>
#include <itkGE6000_TomosynthesisGeometry.h>
#include <itkSiemensMammomat_TomosynthesisGeometry.h>
#include <itkIsocentricConeBeamRotationGeometry.h>

#include <itkImageReconstructionMetric.h>

#include <itkConjugateGradientMaxIterOptimizer.h>
#include <itkConjugateGradientOptimizer.h>
#include <itkRegularStepGradientDescentOptimizer.h>
#include <itkLBFGSOptimizer.h>

#include <itkImageReconstructionMethod.h>

#include <itkCastImageFilter.h>
#include <boost/date_time/posix_time/posix_time.hpp>

#ifndef HZ
  #if defined(__APPLE__)
    #define HZ __DARWIN_CLK_TCK
  #endif
#endif

/* -----------------------------------------------------------------------
   The Command Line Structure
   ----------------------------------------------------------------------- */

struct niftk::CommandLineArgumentDescription clArgList[] = {

  {OPT_SWITCH, "v", NULL,   "Output verbose info"},
  {OPT_SWITCH, "dbg", NULL, "Output debugging info"},

  {OPT_INT, "niters", "n", "Set the maximum number of iterations (set to zero to turn off) [10]"},

  {OPT_INT,  "opt", "n", "The optimizer to use. Options are:\n"
                         "           0    Conjugate gradient with max iterations [default],\n"
                         "           1    Limited Memory BFGS,\n"
                         "           2    Regular step gradient descent,\n"
                         "           3    Conjugate gradient."},

  {OPT_STRING,  "est", "filename", "Input current estimate of the 3D volume"},

  {OPT_INTx3,   "s3D", "nx,ny,nz", "The size of the reconstructed volume [100 x 100 x 100]"},
  {OPT_FLOATx3, "r3D", "rx,ry,rz", "The resolution of the reconstructed volume [1mm x 1mm x 1mm]"},
  {OPT_FLOATx3, "o3D", "ox,oy,oz", "The origin of the reconstructed volume [0mm x 0mm x 0mm]"},

  {OPT_DOUBLE, "FirstAngle", "theta",   "ISOCENTRIC: The angle of the first projection in the sequence [-89]"},
  {OPT_DOUBLE, "AngRange", "range",     "ISOCENTRIC: The full angular range of the sequence [180]"},
  {OPT_DOUBLE, "FocalLength", "length", "ISOCENTRIC: The focal length of the projection [660]"},
  {OPT_INT,    "axis", "number",        "ISOCENTRIC: The axis about which to rotate, 1:'x', 2:'y', 3:'z' [2:'y']"},

  {OPT_DOUBLE,  "transX", "angle", "ISOCENTRIC: Add an additional translation in 'x' [none]"},
  {OPT_DOUBLE,  "transY", "angle", "ISOCENTRIC: Add an additional translation in 'y' [none]"},
  {OPT_DOUBLE,  "transZ", "angle", "ISOCENTRIC: Add an additional translation in 'z' [none]"},

  {OPT_SWITCH,  "GE5000", 0, "Use the 'old' GE-5000, 11 projection geometry [21 projection]"},
  {OPT_SWITCH,  "GE6000", 0, "Use the 'new' GE-6000, 15 projection geometry [21 projection]"},

  {OPT_SWITCH,  "Mammomat", 0, "Use the Siemens Mammomat Inspiration 25 projection geometry [21 projection]"},

  {OPT_DOUBLE,  "thetaX", "angle", "Add an additional rotation in 'x' [none]"},
  {OPT_DOUBLE,  "thetaY", "angle", "Add an additional rotation in 'y' [none]"},
  {OPT_DOUBLE,  "thetaZ", "angle", "Add an additional rotation in 'z' [none]"},

  {OPT_STRING,  "oCurrEstFile", "filestem", "Write the current reconstruction estimate to a file (i.e. filestem_04d%.suffix)"},
  {OPT_STRING,  "oCurrEstSuffix", "suffix", "Write the current reconstruction estimate to a file (i.e. filestem_04d%.suffix) [nii]"},

  {OPT_STRING,  "oGeom", "filestem", "Write out the affine and projection geometries to a set of files"},
  {OPT_STRING,  "oTime", "filename", "Time execution and save value to a file"},

  {OPT_STRING|OPT_REQ,  "o", "filename", "Output 3D reconstructed volume"},

  {OPT_STRING|OPT_REQ,  "projs", "filename", "Input volume of 2D projection images"},

  {OPT_DONE, NULL, NULL,
   "Compute a reconstructed volume from a set of projection images and an initial estimate (or zero).\n"
  }
};

enum {
  O_VERBOSE = 0,
  O_DEBUG,

  O_NITERS,

  O_OPTIMISER,

  O_FILE_ESTIMATE,

  O_RECONSTRUCTION_SIZE,
  O_RECONSTRUCTION_RES,
  O_RECONSTRUCTION_ORIGIN,

  O_FIRST_ANGLE,
  O_ANGULAR_RANGE,
  O_FOCAL_LENGTH,
  O_AXIS_NUMBER,

  O_TRANSX,
  O_TRANSY,
  O_TRANSZ,

  O_GE5000,
  O_GE6000,

  O_MAMMOMAT,

  O_THETAX,
  O_THETAY,
  O_THETAZ,

  O_CURRENT_RECON_FILESTEM,
  O_CURRENT_RECON_SUFFIX,

  O_OUTPUT_GEOMETRY,
  O_TIME,

  O_OUTPUT_RECONSTRUCTION,

  O_INPUT_PROJECTIONS
};

/* -----------------------------------------------------------------------
   Optimizer types
   ----------------------------------------------------------------------- */

typedef enum {
  OPTIMIZER_CONJUGATE_GRADIENT_MAXITER,
  OPTIMIZER_LIMITED_MEMORY_BFGS,
  OPTIMIZER_REGULAR_STEP_GRADIENT_DESCENT,
  OPTIMIZER_CONJUGATE_GRADIENT,
  OPTIMIZER_UNSET
} enumOptimizerType;

const char *nameOptimizer[5] = {
  "Conjugate Gradient (Maximum Iterations)",
  "LBFGS Optimizer",
  "Regular Step Gradient Descent",
  "Conjugate Gradient",
  "Unset"
};

/* -----------------------------------------------------------------------
   main()
   ----------------------------------------------------------------------- */

int main(int argc, char** argv)
{
  itk::NifTKImageIOFactory::Initialize();

  bool flgDebug = false;

  bool flgFirstAngleSet = false; // Has the user set the first angle

  bool flgGE_5000 = false;	// Use the GE 5000 11 projection geometry
  bool flgGE_6000 = false;	// Use the GE 6000 15 projection geometry
  bool flgMammomat = false;	// Use the Siemens Mammomat Inspiration 25 projection geometry

  bool flgTransX = false;	// Translation in 'x' has been set
  bool flgTransY = false;	// Translation in 'y' has been set
  bool flgTransZ = false;	// Translation in 'z' has been set

  char filename[256];

  unsigned int nProjections = 0; // The number of projections in the sequence

  int axis  = 0;		// The axis about which to rotate
  int clo_optimiser = 0;
  int nIterations = 10;		// The maximum number of iterations

  int *clo_size = 0;		// The size of the reconstructed volume

  float *clo_res = 0;		// The resolution of the reconstructed volume
  float *clo_origin = 0;		// The origin of the reconstructed volume

  double firstAngle = 0;         // The angle of the first projection in the sequence
  double angularRange = 0;       // The full angular range of the sequence
  double focalLength = 0;        // The focal length of the projection

  double thetaX = 0;		 // An additional rotation in 'x'
  double thetaY = 0;		 // An additional rotation in 'y'
  double thetaZ = 0;		 // An additional rotation in 'z'

  double transX = 0;		 // An additional translation in 'x'
  double transY = 0;		 // An additional translation in 'y'
  double transZ = 0;		 // An additional translation in 'z'

  enumOptimizerType enumOptimizer =  OPTIMIZER_CONJUGATE_GRADIENT_MAXITER;

  typedef double IntensityType;
  typedef itk::ImageReconstructionMethod<IntensityType> ImageReconstructionMethodType;

  typedef ImageReconstructionMethodType::ReconstructionType        ReconstructionType;

  typedef ImageReconstructionMethodType::InputProjectionVolumeType InputProjectionType;
  typedef ImageReconstructionMethodType::ReconstructionType        ReconstructionType;

  typedef itk::ImageFileReader< ReconstructionType >  ReconEstimateReaderType;
  typedef itk::ImageFileReader< InputProjectionType > InputProjectionReaderType;

  typedef itk::ProjectionGeometry< IntensityType > ProjectionGeometryType;

  std::string fileOutputGeometry;

  std::string fileOutputCurrentEstimate;
  std::string suffixOutputCurrentEstimate;

  std::string fileInputProjectionVolume;
  std::string fileInputCurrentEstimate;
  std::string fileOutputReconstruction;
  std::string fileOutputExecutionTime;

  bool flgInputImage3D_SizeSet = false;	// Has the user specified the 3D image size?
  bool flgInputImage3D_ResSet = false;	// Has the user specified the 3D image resolution?

  ImageReconstructionMethodType::ReconstructionSizeType    nVoxels3D; // The dimensions in voxels of the reconstruction
  ImageReconstructionMethodType::ReconstructionSpacingType spacing3D; // The resolution in mm of the reconstruction
  ImageReconstructionMethodType::ReconstructionPointType   origin3D;  // The origin in mm of the reconstruction

  // Create the command line parser, passing the
  // 'CommandLineArgumentDescription' structure. The final boolean
  // parameter indicates whether the command line options should be
  // printed out as they are parsed.

  niftk::CommandLineParser CommandLineOptions(argc, argv, clArgList, false);

  CommandLineOptions.GetArgument(O_NITERS, nIterations);

  if (CommandLineOptions.GetArgument(O_OPTIMISER, clo_optimiser))
    enumOptimizer = (enumOptimizerType) clo_optimiser;

  CommandLineOptions.GetArgument(O_FILE_ESTIMATE, fileInputCurrentEstimate);

  if (CommandLineOptions.GetArgument(O_RECONSTRUCTION_SIZE, clo_size)) {
    nVoxels3D[0] = clo_size[0];
    nVoxels3D[1] = clo_size[1];
    nVoxels3D[2] = clo_size[2];
  }
  else {
    nVoxels3D[0] = 100;
    nVoxels3D[1] = 100;
    nVoxels3D[2] = 100;
  }

  if (CommandLineOptions.GetArgument(O_RECONSTRUCTION_RES, clo_res)) {
    spacing3D[0] = clo_res[0];
    spacing3D[1] = clo_res[1];
    spacing3D[2] = clo_res[2];
  }
  else {
    spacing3D[0] = 1.;
    spacing3D[1] = 1.;
    spacing3D[2] = 1.;
  }

  if (CommandLineOptions.GetArgument(O_RECONSTRUCTION_ORIGIN, clo_origin)) {
    origin3D[0] = clo_origin[0];
    origin3D[1] = clo_origin[1];
    origin3D[2] = clo_origin[2];
  }
  else {
    origin3D[0] = 0.;
    origin3D[1] = 0.;
    origin3D[2] = 0.;
  }

  flgFirstAngleSet = CommandLineOptions.GetArgument(O_FIRST_ANGLE, firstAngle);

  CommandLineOptions.GetArgument(O_ANGULAR_RANGE, angularRange);
  CommandLineOptions.GetArgument(O_FOCAL_LENGTH, focalLength);
  CommandLineOptions.GetArgument(O_AXIS_NUMBER, axis);

  CommandLineOptions.GetArgument(O_GE5000, flgGE_5000);
  CommandLineOptions.GetArgument(O_GE6000, flgGE_6000);

  CommandLineOptions.GetArgument(O_MAMMOMAT, flgMammomat);

  CommandLineOptions.GetArgument(O_THETAX, thetaX);
  CommandLineOptions.GetArgument(O_THETAY, thetaY);
  CommandLineOptions.GetArgument(O_THETAZ, thetaZ);

  flgTransX = CommandLineOptions.GetArgument(O_TRANSX, transX);
  flgTransY = CommandLineOptions.GetArgument(O_TRANSY, transY);
  flgTransZ = CommandLineOptions.GetArgument(O_TRANSZ, transZ);

  CommandLineOptions.GetArgument(O_CURRENT_RECON_FILESTEM, fileOutputCurrentEstimate);
  CommandLineOptions.GetArgument(O_CURRENT_RECON_SUFFIX, suffixOutputCurrentEstimate);

  CommandLineOptions.GetArgument(O_OUTPUT_GEOMETRY, fileOutputGeometry);
  CommandLineOptions.GetArgument(O_TIME, fileOutputExecutionTime);

  CommandLineOptions.GetArgument(O_OUTPUT_RECONSTRUCTION, fileOutputReconstruction);

  CommandLineOptions.GetArgument(O_INPUT_PROJECTIONS, fileInputProjectionVolume);

  // Validate command line args
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~

  if ( fileInputProjectionVolume.length() == 0 || fileOutputReconstruction.length() == 0 ) {

    CommandLineOptions.PrintUsage();
    return EXIT_FAILURE;
  }

  if ( fileInputCurrentEstimate.length() != 0 &&
       ((flgInputImage3D_SizeSet == true) || (flgInputImage3D_ResSet == true)) ) {

    std::cerr << "Command line options '-est' and '-s3D' or '-r3D' are exclusive.";

    CommandLineOptions.PrintUsage();
    return EXIT_FAILURE;
  }

  if ( ( flgGE_5000  && flgGE_6000 ) ||
       ( flgGE_5000  && flgMammomat ) ||
       ( flgMammomat && flgGE_6000 ) ) {

    std::cerr << "Command line options '-GE5000', '-GE6000'and '-Mammomat' are exclusive."
              << std::endl;

    CommandLineOptions.PrintUsage();
    return EXIT_FAILURE;
  }

  if ( (flgGE_5000 || flgGE_6000 || flgMammomat) &&
       (flgFirstAngleSet || angularRange || focalLength || axis) ) {

    std::cerr << "Command line options '-GE5000' or '-GE6000' and "
              << "'-1stAngle' or '-AngRange' or '-FocalLength' or '-axis' are exclusive.";

    return EXIT_FAILURE;
  }

  if ( (flgGE_5000 || flgGE_6000 || flgMammomat) && (flgTransX || flgTransY || flgTransZ) ) {

    std::cerr <<"Command line options '-transX|Y|Z' can only be used with isocentric geometry.";

    return EXIT_FAILURE;
  }

  // Create the reconstructor
  // ~~~~~~~~~~~~~~~~~~~~~~~~

  ImageReconstructionMethodType::Pointer imReconstructor = ImageReconstructionMethodType::New();

  // Load the volume of 2D projection images
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  InputProjectionReaderType::Pointer inputProjectionReader  = InputProjectionReaderType::New();

  inputProjectionReader->SetFileName( fileInputProjectionVolume );

  try {
    std::cout << "Reading input volume of 2D projection images: " << fileInputProjectionVolume << std::endl;
    inputProjectionReader->Update();
  }
  catch( itk::ExceptionObject & err ) {
    std::cerr << "ERROR: Failed to load input projection volume: " << fileInputProjectionVolume << "; " << err << std::endl;
    return EXIT_FAILURE;
  }

  nProjections = inputProjectionReader->GetOutput()->GetLargestPossibleRegion().GetSize()[2];

  std::cout << "Number of projections: " << niftk::ConvertToString((int) nProjections) << std::endl;

  imReconstructor->SetInputProjectionVolume( inputProjectionReader->GetOutput() );

  // Load the current estimate (or create it)
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  if ( fileInputCurrentEstimate.length() != 0 ) {

    ReconEstimateReaderType::Pointer inputEstimateReader  = ReconEstimateReaderType::New();

    inputEstimateReader->SetFileName( fileInputCurrentEstimate );

    try {
      std::cout << "Reading input 3D estimate: " << fileInputCurrentEstimate << std::endl;
      inputEstimateReader->Update();
    }
    catch( itk::ExceptionObject & err ) {
      std::cerr << "ERROR: Failed to load reconstruction estimate: " << fileInputCurrentEstimate << "; " << err << std::endl;
      return EXIT_FAILURE;
    }

    nVoxels3D = inputEstimateReader->GetOutput()->GetLargestPossibleRegion().GetSize();
    spacing3D = inputEstimateReader->GetOutput()->GetSpacing();
    origin3D  = inputEstimateReader->GetOutput()->GetOrigin();

    imReconstructor->SetReconEstimate(inputEstimateReader->GetOutput());
  }

  imReconstructor->SetReconstructedVolumeSize( nVoxels3D );
  imReconstructor->SetReconstructedVolumeSpacing( spacing3D );
  imReconstructor->SetReconstructedVolumeOrigin( origin3D );

  // Create the tomosynthesis geometry
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  ProjectionGeometryType::Pointer geometry;

  // Create the GE-5000 11 projection geometry

  if (flgGE_5000) {

    if (nProjections != 11) {
      std::cerr << "ERROR: Number of projections in input volume (" << nProjections << ") must equal 11 for GE-5000 geometry" << std::endl;
      return EXIT_FAILURE;
    }

    typedef itk::GE5000_TomosynthesisGeometry< IntensityType > GE5000_TomosynthesisGeometryType;
    geometry = GE5000_TomosynthesisGeometryType::New();

  }

  // Create the GE-6000 15 projection geometry

  else if (flgGE_6000) {

    if (nProjections != 15) {
      std::cerr << "ERROR: Number of projections in input volume (" << nProjections << ") must equal 15 for GE-6000 geometry" << std::endl;
      return EXIT_FAILURE;
    }

    typedef itk::GE6000_TomosynthesisGeometry< IntensityType > GE6000_TomosynthesisGeometryType;
    geometry = GE6000_TomosynthesisGeometryType::New();
  }

  // Siemens Mammomat Inspiration 25 projection geometry

  else if (flgMammomat) {

    if (nProjections != 25) {
      std::cerr << "ERROR: Number of projections in input volume (" << nProjections << ") must equal 25 for Siemens Mammomat geometry" << std::endl;
      return EXIT_FAILURE;
    }

    typedef itk::SiemensMammomat_TomosynthesisGeometry< IntensityType > SiemensMammomat_TomosynthesisGeometryType;
    geometry = SiemensMammomat_TomosynthesisGeometryType::New();
  }

  // Create an isocentric cone bean rotation geometry

  else {

    if (! flgFirstAngleSet) firstAngle = -89.;
    if (! angularRange) angularRange = 180.;
    if (! focalLength) focalLength = 660.;

    typedef itk::IsocentricConeBeamRotationGeometry< IntensityType > IsocentricConeBeamRotationGeometryType;

    IsocentricConeBeamRotationGeometryType::Pointer isoGeometry = IsocentricConeBeamRotationGeometryType::New();

    isoGeometry->SetNumberOfProjections(nProjections);
    isoGeometry->SetFirstAngle(firstAngle);
    isoGeometry->SetAngularRange(angularRange);
    isoGeometry->SetFocalLength(focalLength);

    isoGeometry->SetTranslation(transX, transY, transZ);

    if (axis) {

      switch (axis)
	{

	case 1: {
	  isoGeometry->SetRotationAxis(itk::ISOCENTRIC_CONE_BEAM_ROTATION_IN_X);
	  break;
	}

	case 2: {
	  isoGeometry->SetRotationAxis(itk::ISOCENTRIC_CONE_BEAM_ROTATION_IN_Y);
	  break;
	}

	case 3: {
	  isoGeometry->SetRotationAxis(itk::ISOCENTRIC_CONE_BEAM_ROTATION_IN_Z);
	  break;
	}

	default: {
	  std::cerr << "Command line option '-axis' must be: 1, 2 or 3.";

	  CommandLineOptions.PrintUsage();
	  return EXIT_FAILURE;
	}
	}
    }

    geometry = isoGeometry;
  }

  if (thetaX) geometry->SetRotationInX(thetaX);
  if (thetaY) geometry->SetRotationInY(thetaY);
  if (thetaZ) geometry->SetRotationInZ(thetaZ);

  std::cout << "Projection geometry:" << std::endl;
  geometry->Print(std::cout);

  imReconstructor->SetProjectionGeometry( geometry );

  // Create the optimizer
  // ~~~~~~~~~~~~~~~~~~~~

  std::cout << "Optimiser: " << nameOptimizer[enumOptimizer] << std::endl;

  switch (enumOptimizer)
    {

    case OPTIMIZER_CONJUGATE_GRADIENT_MAXITER: {

      typedef itk::ConjugateGradientMaxIterOptimizer OptimizerType;
      OptimizerType::Pointer optimizer = OptimizerType::New();

      if (nIterations)
	optimizer->SetMaximumNumberOfFunctionEvaluations(nIterations);

      std::cout << "Maximum number of iterations set to: " << niftk::ConvertToString((int) nIterations) << std::endl;

      imReconstructor->SetOptimizer( optimizer );
      break;
    }

    case OPTIMIZER_LIMITED_MEMORY_BFGS: {

      typedef itk::LBFGSOptimizer OptimizerType;
      OptimizerType::Pointer optimizer = OptimizerType::New();

      if (nIterations)
	optimizer->SetMaximumNumberOfFunctionEvaluations(nIterations);

      std::cout << "Maximum number of iterations set to: " << niftk::ConvertToString((int) nIterations) << std::endl;

      imReconstructor->SetOptimizer( optimizer );
      break;
    }

    case OPTIMIZER_REGULAR_STEP_GRADIENT_DESCENT: {

      typedef itk::RegularStepGradientDescentOptimizer OptimizerType;
      OptimizerType::Pointer optimizer = OptimizerType::New();

      imReconstructor->SetOptimizer( optimizer );
      break;
    }

    case OPTIMIZER_CONJUGATE_GRADIENT: {

      typedef itk::ConjugateGradientOptimizer OptimizerType;
      OptimizerType::Pointer optimizer = OptimizerType::New();

      imReconstructor->SetOptimizer( optimizer );
      break;
    }

    default: {
      std::cerr << argv[0]
				     << "Optimizer type: '"
				     << niftk::ConvertToString(nameOptimizer[enumOptimizer])
				     << "' not recognised.";
      return -1;
    }
    }

  // Create the metric
  // ~~~~~~~~~~~~~~~~~

  typedef itk::ImageReconstructionMetric< IntensityType > ImageReconstructionMetricType;
  ImageReconstructionMetricType::Pointer metric = ImageReconstructionMetricType::New();

  if ( fileOutputCurrentEstimate.length() > 0 )
    metric->SetIterativeReconEstimateFile( fileOutputCurrentEstimate );

  if ( suffixOutputCurrentEstimate.length() > 0 )
    metric->SetIterativeReconEstimateSuffix( suffixOutputCurrentEstimate );

  imReconstructor->SetMetric( metric );

  // Initialise the start time
  // ~~~~~~~~~~~~~~~~~~~~~~~~~

  boost::posix_time::ptime startTime = boost::posix_time::second_clock::local_time();

  // Perform the reconstruction
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~

  try {
    std::cout << "Starting reconstruction..." << std::endl;

    if (flgDebug)
      std::cout << "ImageReconstructionMethod: " << imReconstructor << std::endl;

    imReconstructor->Update();
    std::cout << "Reconstruction complete" << std::endl;
  }
  catch( itk::ExceptionObject & err ) {
    std::cerr << "ERROR: Failed to calculate the reconstruction; " << err << std::endl;
    return EXIT_FAILURE;
  }

  // Calculate the execution time
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  boost::posix_time::ptime endTime = boost::posix_time::second_clock::local_time();
  boost::posix_time::time_duration duration = endTime - startTime;

  std::cout << "Execution time: " << boost::posix_time::to_simple_string(duration) << std::endl;

  if (fileOutputExecutionTime.length() != 0) {
    std::ofstream fout(fileOutputExecutionTime.c_str());

    if ((! fout) || fout.bad()) {
      std::cerr << "ERROR: Could not open file: " << fileOutputExecutionTime << std::endl;
      return 1;
    }

    fout << "Execution time: " << boost::posix_time::to_simple_string(duration) << std::endl;

    fout.close();
  }

  // Write the output reconstruction to a file
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  // First cast the image from double to float

  typedef float OutputReconstructionType;
  typedef itk::Image< OutputReconstructionType, 3 > OutputImageType;
  typedef itk::CastImageFilter< ReconstructionType, OutputImageType > CastFilterType;

  CastFilterType::Pointer  caster =  CastFilterType::New();

  caster->SetInput( imReconstructor->GetOutput() );

  // Then write the image

  typedef itk::ImageFileWriter< OutputImageType > OutputImageWriterType;

  OutputImageWriterType::Pointer writer = OutputImageWriterType::New();

  writer->SetFileName( fileOutputReconstruction );
  writer->SetInput( caster->GetOutput() );

  try {
    std::cout << "Writing output to file: " << fileOutputReconstruction << std::endl;
    writer->Update();
  }
  catch( itk::ExceptionObject & err ) {
    std::cerr << "ERROR: Failed to write output to file: " << fileOutputReconstruction << "; " << err << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "Done" << std::endl;

  // Write out the affine and projection geometries to a set of files
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  if ( fileOutputGeometry.length() > 0 ) {

    geometry->Print(std::cout);

    ProjectionGeometryType::EulerAffineTransformPointerType pAffineTransform;
    ProjectionGeometryType::PerspectiveProjectionTransformPointerType pPerspectiveTransform;

    itk::TransformFactory< ProjectionGeometryType::PerspectiveProjectionTransformType >::RegisterTransform();
    itk::TransformFactory< ProjectionGeometryType::EulerAffineTransformType >::RegisterTransform();

    typedef itk::TransformFileWriter TransformFileWriterType;
    TransformFileWriterType::Pointer transformFileWriter = TransformFileWriterType::New();

    unsigned int iProjection;

    for (iProjection=0; iProjection<geometry->GetNumberOfProjections(); iProjection++) {

      // Get and write the perspective transform

      try {
	pPerspectiveTransform = geometry->GetPerspectiveTransform(iProjection);
      }

      catch( itk::ExceptionObject & err ) {
	std::cerr << "Failed: " << err << std::endl;
	return EXIT_FAILURE;
      }

      sprintf(filename, "%s_%02d.tPerspective", fileOutputGeometry.c_str(), iProjection);

      transformFileWriter->SetInput( pPerspectiveTransform );
      transformFileWriter->SetFileName(filename);
      transformFileWriter->Update();

      std::cout << "Writing perspective transform: " << filename << std::endl;

      // Get and write the affine transform

      try {
	pAffineTransform = geometry->GetAffineTransform(iProjection);
	pAffineTransform->SetFullAffine();
      }

      catch( itk::ExceptionObject & err ) {
	std::cerr << "Failed: " << err << std::endl;
	return EXIT_FAILURE;
      }

      sprintf(filename, "%s_%02d.tAffine", fileOutputGeometry.c_str(), iProjection);

      transformFileWriter->SetInput( pAffineTransform );
      transformFileWriter->SetFileName(filename);
      transformFileWriter->Update();

      std::cout << "Writing affine transform: " << filename << std::endl;
    }
  }

  return EXIT_SUCCESS;
}