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

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

/*  CCOPYRIGHT  */

#ifndef SHAPE_H
#define SHAPE_H

#include "transformation.h"
#include <vtkSmartPointer.h>
#include <vtkPolyData.h>
#include <vtkCellArray.h>
#include <vtkIdList.h>
#include <memory>
#include <string>
#include <vector>
#include <boost/log/trivial.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/split_member.hpp>
#include "newimage/newimage.h"

#include "armawrap/newmat.h"


class Shape
{
public:
    class ShapeException : public std::runtime_error
    {
    public:
        ShapeException(const std::string& what) : runtime_error(what) { }
    };

    Shape(vtkSmartPointer<vtkPolyData> polydata, const std::string& displayname);
    Shape(const std::string& filename, const std::string& displayname);
    Shape(const std::string& filename, boost::shared_ptr<const Transformation> &transformation,
          const std::string& displayname);

    Shape(const Shape &other);
    Shape(const Shape &other, boost::shared_ptr<const Transformation> &transformation);

    Shape& operator=(const Shape &other);

    std::string GetDisplayName() const;
    vtkSmartPointer<vtkPolyData> GetPolyData() const;
    vtkIdType GetNumberOfVertices() const;
    NEWMAT::ColumnVector GetPoint(vtkIdType vertex) const;
    NEWMAT::ColumnVector GetNormal(vtkIdType vertex) const;
    NEWMAT::ColumnVector GetBounds() const;
    NEWMAT::ColumnVector GetCenter() const;

    std::vector<std::vector<vtkIdType> > GetPolysForVertex(vtkIdType vertex);

    // NB: Distances are in #edges, not mm!
    NEWMAT::Matrix GetDistanceMatrix() const;

    void SetDisplacement(vtkIdType vertex, double amount);
    double GetDisplacement(vtkIdType vertex) const;

    Shape RemoveSelfIntersections(double stepsize) const;

    void ToVolume(NEWIMAGE::volume<char> &vol, bool usevoxelvertices);

    void WritePolyData(const std::string &filename) const;

    virtual ~Shape() { };

protected:
    void Copy(const Shape &other);

private:
    // The VTK code and documentation are slightly unclear and inconsistent about when vtkPolyData::BuildLinks()
    // is called and when the links are invalidated, so I am adding this just to be sure
    bool m_linksBuilt;

    vtkSmartPointer<vtkPolyData> m_polyData;
    std::vector<double> m_displacements;
    std::string m_displayName;

    boost::shared_ptr<const Transformation> m_transformation;
    std::vector<NEWMAT::ColumnVector> m_untransformedPoints;
    std::vector<NEWMAT::ColumnVector> m_untransformedNormals;

    vtkSmartPointer<vtkPolyData> ReadPolyData(const std::string &filename);
    void Init(vtkSmartPointer<vtkPolyData> polydata);
    static vtkSmartPointer<vtkPolyData> ComputeInitialNormals(vtkSmartPointer<vtkPolyData>);

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version);
};

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

    ar & m_displacements;
}

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

        vtkIdType npoints = m->GetPolyData()->GetNumberOfPoints();
        ar << npoints;
        for (vtkIdType i = 0; i < npoints; i++)
        {
            double p[3];
            m->GetPolyData()->GetPoint(i, p);
            ar << p;
        }

        vtkIdType npolys = m->GetPolyData()->GetNumberOfPolys();
        ar << npolys;
        auto idl = vtkSmartPointer<vtkIdList>::New();
        m->GetPolyData()->GetPolys()->InitTraversal();
        while (m->GetPolyData()->GetPolys()->GetNextCell(idl))
        {
            vtkIdType nids = idl->GetNumberOfIds();
            ar << nids;
            for (vtkIdType i = 0; i < nids; i++)
            {
                vtkIdType id = idl->GetId(i);
                ar << id;
            }
        }

        std::string displayname = m->GetDisplayName();
        ar << displayname;
    }

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

        BOOST_LOG_TRIVIAL(debug) << "Deserialising polydata";

        int npoints;
        ar >> npoints;
        BOOST_LOG_TRIVIAL(debug) << " ... points: " << npoints;
        auto points = vtkSmartPointer<vtkPoints>::New();
        points->SetNumberOfPoints(npoints);
        for (vtkIdType i = 0; i < npoints; i++)
        {
            double p[3];
            ar >> p;
            points->SetPoint(i, p);
        }

        int npolys;
        ar >> npolys;
        BOOST_LOG_TRIVIAL(debug) << " ... polys: " << npolys;
        auto polys = vtkSmartPointer<vtkCellArray>::New();
        for (int i = 0; i < npolys; i++)
        {
            vtkIdType nids;
            ar >> nids;
            auto idl = vtkSmartPointer<vtkIdList>::New();
            idl->SetNumberOfIds(nids);
            for (int j = 0; j < nids; j++)
            {
                vtkIdType id;
                ar >> id;
                idl->SetId(j, id);
            }

            polys->InsertNextCell(idl);
        }

        auto polydata = vtkSmartPointer<vtkPolyData>::New();
        polydata->SetPoints(points);
        polydata->SetPolys(polys);

        BOOST_LOG_TRIVIAL(debug) << "Done deserialising polydata";

        std::string displayname;
        ar >> displayname;

        ::new(m)Shape(polydata, displayname);
    }
}
}

#endif // SHAPE_H
