/*  Multimodal Image Segmentation Tool (MIST)  */
/*  Eelke Visser  */

/*  Copyright (c) 2016 University of Oxford  */

/*  CCOPYRIGHT  */

#ifndef PROFILEPRIORS_H
#define PROFILEPRIORS_H

#include "armawrap/newmat.h"
#include <boost/log/trivial.hpp>
#include "serialisation.h"

class FlatPrior;
class SimpleEdgePrior;
class BlockPrior;
class RightAngledPrior;
class ExponentialPrior;
class Exponential2Prior;
class DoubleExponentialPrior;
class DoubleExponential2Prior;
class ParabolicPrior;
BOOST_CLASS_EXPORT_KEY(FlatPrior)
BOOST_CLASS_EXPORT_KEY(SimpleEdgePrior)
BOOST_CLASS_EXPORT_KEY(BlockPrior)
BOOST_CLASS_EXPORT_KEY(RightAngledPrior)
BOOST_CLASS_EXPORT_KEY(ExponentialPrior)
BOOST_CLASS_EXPORT_KEY(Exponential2Prior)
BOOST_CLASS_EXPORT_KEY(DoubleExponentialPrior)
BOOST_CLASS_EXPORT_KEY(DoubleExponential2Prior)
BOOST_CLASS_EXPORT_KEY(ParabolicPrior)

namespace boost
{
namespace serialization
{
    template<class Archive>
    inline void save_construct_data(Archive &ar, const FlatPrior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const SimpleEdgePrior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const BlockPrior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const RightAngledPrior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const ExponentialPrior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const Exponential2Prior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const DoubleExponentialPrior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const DoubleExponential2Prior *pp, const unsigned int version);

    template<class Archive>
    inline void save_construct_data(Archive &ar, const ParabolicPrior *pp, const unsigned int version);
}
}


class ProfilePrior
{
public:
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const = 0;
    virtual ~ProfilePrior();

private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising base ProfilePrior";
    }
};

class FlatPrior : public ProfilePrior
{
public:
    FlatPrior(double intensity);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_intensity;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising FlatPrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const FlatPrior *pp, const unsigned int version);
};

class SimpleEdgePrior : public ProfilePrior
{
public:
    SimpleEdgePrior(double intensitya, double intensityb);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_intensityA;
    double m_intensityB;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising SimpleEdgePrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const SimpleEdgePrior *pp, const unsigned int version);
};

class BlockPrior : public ProfilePrior
{
public:
    BlockPrior(double width, double intensitya, double intensityb);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_width;
    double m_intensityA;
    double m_intensityB;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising BlockPrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const BlockPrior *pp, const unsigned int version);
};

class RightAngledPrior : public ProfilePrior
{
public:
    RightAngledPrior(double width, double intensitya, double intensityb);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_width;
    double m_intensityA;
    double m_intensityB;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising RightAngledPrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const RightAngledPrior *pp, const unsigned int version);
};

class ExponentialPrior : public ProfilePrior
{
public:
    ExponentialPrior(double timeconst, double intensitya, double intensityb);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_timeConst;
    double m_intensityA;
    double m_intensityB;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising ExponentialPrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const ExponentialPrior *pp, const unsigned int version);
};

class Exponential2Prior : public ProfilePrior
{
public:
    Exponential2Prior(double timeconst, double intensitya, double intensityb, double intensityc);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_timeConst;
    double m_intensityA;
    double m_intensityB;
    double m_intensityC;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising Exponential2Prior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const Exponential2Prior *pp, const unsigned int version);
};

class DoubleExponentialPrior : public ProfilePrior
{
public:
    DoubleExponentialPrior(double timeconst1, double timeconst2, double constant, double intensitya, double intensityb);

    static double Objective(const std::vector<double> &x, std::vector<double> &grad, void *f_data);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_timeConst1;
    double m_timeConst2;
    double m_intensityA;
    double m_intensityB;
    double m_constant;

    // Temporary that needs to be passed to Objective() - no need to serialise!
    mutable double m_length;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising DoubleExponentialPrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const DoubleExponentialPrior *pp, const unsigned int version);
};

class DoubleExponential2Prior : public ProfilePrior
{
public:
    DoubleExponential2Prior(double timeconst1, double timeconst2, double constant, double intensitya, double intensityb, double intensityc);

    static double Objective(const std::vector<double> &x, std::vector<double> &grad, void *f_data);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    // NB: Names are confusing but consistent with DoubleExponentialPrior; m_constant is 'inside intensity'
    double m_timeConst1;
    double m_timeConst2;
    double m_intensityA;
    double m_intensityB;
    double m_intensityC;
    double m_constant;

    // Temporary that needs to be passed to Objective() - no need to serialise!
    mutable double m_length;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising DoubleExponential2Prior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const DoubleExponential2Prior *pp, const unsigned int version);
};

class ParabolicPrior : public ProfilePrior
{
public:
    ParabolicPrior(double minintensity, double maxintensity);
    virtual NEWMAT::ColumnVector Create(int points, double spacing) const;

private:
    double m_minIntensity;
    double m_maxIntensity;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "(De)serialising ParabolicPrior";

        ar & boost::serialization::base_object<ProfilePrior>(*this);
    }

    template<class Archive>
    friend void boost::serialization::save_construct_data(Archive &ar, const ParabolicPrior *pp, const unsigned int version);
};


namespace boost
{
namespace serialization
{
    template<class Archive>
    inline void save_construct_data(Archive &ar, const FlatPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for FlatPrior";

        ar << pp->m_intensity;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, FlatPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for FlatPrior";

        double intensity;
        ar >> intensity;

        ::new(pp)FlatPrior(intensity);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const SimpleEdgePrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for SimpleEdgePrior";

        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, SimpleEdgePrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for SimpleEdgePrior";

        double intensitya, intensityb;
        ar >> intensitya;
        ar >> intensityb;

        ::new(pp)SimpleEdgePrior(intensitya, intensityb);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const BlockPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for BlockPrior";

        ar << pp->m_width;
        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, BlockPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for BlockPrior";

        double width, intensitya, intensityb;
        ar >> width;
        ar >> intensitya;
        ar >> intensityb;

        ::new(pp)BlockPrior(width, intensitya, intensityb);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const RightAngledPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for RightAngledPrior";

        ar << pp->m_width;
        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, RightAngledPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for RightAngledPrior";

        double width, intensitya, intensityb;
        ar >> width;
        ar >> intensitya;
        ar >> intensityb;

        ::new(pp)RightAngledPrior(width, intensitya, intensityb);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const ExponentialPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for ExponentialPrior";

        ar << pp->m_timeConst;
        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, ExponentialPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for ExponentialPrior";

        double timeconst, intensitya, intensityb;
        ar >> timeconst;
        ar >> intensitya;
        ar >> intensityb;

        ::new(pp)ExponentialPrior(timeconst, intensitya, intensityb);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const Exponential2Prior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for Exponential2Prior";

        ar << pp->m_timeConst;
        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
        ar << pp->m_intensityC;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, Exponential2Prior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for Exponential2Prior";

        double timeconst, intensitya, intensityb, intensityc;
        ar >> timeconst;
        ar >> intensitya;
        ar >> intensityb;
        ar >> intensityc;

        ::new(pp)Exponential2Prior(timeconst, intensitya, intensityb, intensityc);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const DoubleExponentialPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for DoubleExponentialPrior";

        ar << pp->m_timeConst1;
        ar << pp->m_timeConst2;
        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
        ar << pp->m_constant;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, DoubleExponentialPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for DoubleExponentialPrior";

        double timeconst1, timeconst2, constant, intensitya, intensityb;
        ar >> timeconst1;
        ar >> timeconst2;
        ar >> intensitya;
        ar >> intensityb;
        ar >> constant;

        ::new(pp)DoubleExponentialPrior(timeconst1, timeconst2, intensitya, intensityb, constant);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const DoubleExponential2Prior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for DoubleExponential2Prior";

        ar << pp->m_timeConst1;
        ar << pp->m_timeConst2;
        ar << pp->m_intensityA;
        ar << pp->m_intensityB;
        ar << pp->m_intensityC;
        ar << pp->m_constant;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, DoubleExponential2Prior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for DoubleExponential2Prior";

        double timeconst1, timeconst2, constant, intensitya, intensityb, intensityc;
        ar >> timeconst1;
        ar >> timeconst2;
        ar >> intensitya;
        ar >> intensityb;
        ar >> intensityc;
        ar >> constant;

        ::new(pp)DoubleExponential2Prior(timeconst1, timeconst2, intensitya, intensityb, intensityc, constant);
    }

    template<class Archive>
    inline void save_construct_data(Archive &ar, const ParabolicPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Saving construction info for ParabolicPrior";

        ar << pp->m_minIntensity;
        ar << pp->m_maxIntensity;
    }

    template<class Archive>
    inline void load_construct_data(Archive &ar, ParabolicPrior *pp, const unsigned int version)
    {
        BOOST_LOG_TRIVIAL(debug) << "Loading construction info for ParabolicPrior";

        double minintensity, maxintensity;
        ar >> minintensity;
        ar >> maxintensity;

        ::new(pp)ParabolicPrior(minintensity, maxintensity);
    }
}
}

#endif
