/*---------------------------------------------------------------------------*\

    geophase: A C++ template library for geometrical operations on non-convex
    polyhedra with non-planar faces
     
    Copyright (C) 2019 Tomislav Maric, TU Darmstadt 

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Description
    A class that streams geometrical objects into the legacy ASCII VTK 
    file format. 

    Used only for testing/debugging, visualization of bugs, not intended for 
    production code.  

Author
    Tomislav Maric maric@mma.tu-darmstadt.de

\*---------------------------------------------------------------------------*/


#ifndef ReadVtkPolyData_HPP
#define ReadVtkPolyData_HPP

#include <fstream>
#include "tag.hpp"
#include <cassert>
#include <sstream>
#include "Vector.hpp" 

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

namespace geophase { 

template<typename Stream>
class VtkPolyDataIStream 
{
    std::ifstream inFile_; 

    std::vector<vector> points_; 
    std::size_t pointLabel_; 

    using vertex = std::array<std::size_t, 2>; 
    std::vector<vertex> vertices_; 
    std::size_t vertexLabel_; 

    using polygon = std::vector<std::size_t>;
    std::vector<polygon> polygons_; 
    std::size_t polygonLabel_; 

    public: 

        VtkPolyDataIStream() = default; 

        template<typename FileName> 
        VtkPolyDataIStream(FileName const& fileName) 
        :
            inFile_(),
            points_(), 
            pointLabel_(-1), 
            vertices_(), 
            vertexLabel_(-1), 
            polygons_(), 
            polygonLabel_(-1)
        {
            inFile_.open(fileName); 
    
            // Read points
            std::string line; 
            while (std::getline(inFile_, line)) 
            {
                if (line.find("POINTS") != std::string::npos) 
                {
                    // Get number of points.  
                    std::istringstream iss(line); 
                    std::string substr; 
                    // Skip the POINTS word.
                    iss >> substr;  
                    // Read the number of points.
                    iss >> substr; 
                    size_t nPoints = stoi(substr); 

                    // Reset the label of the currently read vertex. 
                    if (nPoints > 0)
                        this->resetPointLabel(); 

                    // The "float" keyword is not read, all components are
                    // assumed to be floating point and are stored as double.
                    
                    // Read points. 
                    points_.reserve(nPoints); 
                    for (std::size_t pointI = 0; pointI < nPoints; ++pointI)
                    {
                        std::string pointLine; 
                        std::getline(inFile_, pointLine); 
                        if (!pointLine.empty())
                        {
                            std::istringstream iss(pointLine); 
                            vector point; 
                            iss >> point[0] >> point[1] >> point[2];
                            points_.push_back(point); 
                        }
                    }

                }
            }

            // Read vertices.
            inFile_.clear();
            inFile_.seekg(0, std::ios::beg);
            while (std::getline(inFile_, line)) 
            {
                if (line.find("VERTICES") != std::string::npos) 
                {
                    // Get number of vertices.  
                    std::istringstream iss(line); 
                    std::string substr; 
                    // Skip the VERTICES keyword.
                    iss >> substr;  
                    // Read the number of points.
                    iss >> substr; 
                    std::size_t nVertices = stoi(substr); 

                    // Reset the label of the currently read vertex. 
                    if (nVertices > 0)
                        this->resetVertexLabel(); 

                    // Read vertices 
                    vertices_.reserve(nVertices); 
                    for (std::size_t vertexI = 0; vertexI < nVertices; ++vertexI)
                    {
                        std::string vertexLine; 
                        std::getline(inFile_, vertexLine); 
                        if (!vertexLine.empty())
                        {
                            std::istringstream iss(vertexLine); 
                            vertex invertex; 
                            iss >> invertex[0] >> invertex[1];
                            vertices_.push_back(invertex);
                        }
                    }
                }
            }

            // Read polygons 
            inFile_.clear();
            inFile_.seekg(0, std::ios::beg);
            while (std::getline(inFile_, line)) 
            {
                if (line.find("POLYGONS") != std::string::npos) 
                {
                    // Get number of vertices.  
                    std::istringstream iss(line); 
                    std::string substr; 
                    // Skip the POLYGONS keyword.
                    iss >> substr;  
                    // Read the number of POLYGONS.
                    iss >> substr; 
                    std::size_t nPolygons = stoi(substr); 

                    if (nPolygons > 0) 
                        this->resetPolygonLabel();

                    // Read polygons 
                    polygons_.reserve(nPolygons); 
                    for (std::size_t polygonI = 0; polygonI < nPolygons; ++polygonI)
                    {
                        std::string polygonLine; 
                        std::getline(inFile_, polygonLine); 
                        if (!polygonLine.empty())
                        {
                            std::istringstream iss(polygonLine); 
                            std::size_t polygonSize; 
                            iss >> polygonSize; 
                            polygon poly(polygonSize); 
                            for(auto& label : poly)
                                iss >> label;
                            polygons_.push_back(poly);
                        }
                    }

                }
            }
        }

        const auto& points() const
        {
            return points_; 
        }

        const auto& polygons() const
        {
            return polygons_; 
        }

        void resetPointLabel() 
        {
            pointLabel_ = 0;  
        }

        void incrementPointLabel()
        {
            ++pointLabel_;
        }

        std::size_t pointLabel() const
        {
            return pointLabel_; 
        }

        const auto& vertices() const
        {
            return vertices_; 
        }

        std::size_t vertexLabel() const
        {
            return vertexLabel_; 
        }

        void setVertexLabel(std::size_t n) const
        {
            vertexLabel_ = n;  
        }

        void resetVertexLabel() 
        {
            vertexLabel_ = 0;  
        }

        void incrementVertexLabel()
        {
            ++vertexLabel_;
        }

        std::size_t polygonLabel() const
        {
            return polygonLabel_;
        }

        bool readPolygon() const
        {
            return polygonLabel_ < polygons_.size(); 
        }

        std::size_t incrementPolygonLabel() 
        {
            return ++polygonLabel_;
        }

        std::size_t setPolygonLabel(std::size_t n) 
        {
            return polygonLabel_ = n;
        }

        std::size_t resetPolygonLabel()
        {
            return polygonLabel_ = 0;
        }
};

typedef VtkPolyDataIStream<std::stringstream> vtkPolyDataIStream;  

template<typename Stream, typename Geometry> 
VtkPolyDataIStream<Stream>& operator>>(
    VtkPolyDataIStream<Stream>& is, 
    Geometry& geo
)
{
    return stream(is, geo, typename tag<Geometry>::type());
}

template<typename Stream, typename Vector>  
VtkPolyDataIStream<Stream>& 
stream(VtkPolyDataIStream<Stream>& is, Vector& vec, vector_tag)
{

    const auto& points = is.points(); 
    const auto pointLabel = is.pointLabel();
    assert((pointLabel >= 0 && pointLabel < points.size())); 
    vec = points[pointLabel];
    is.incrementPointLabel();
    return is; 
}

template<typename Stream, typename SequenceContainer>  
VtkPolyDataIStream<Stream>& 
stream(
    VtkPolyDataIStream<Stream>& is, 
    SequenceContainer& cont, 
    sequence_tag
)
{
    const auto& points = is.points(); 
    const auto pointLabel = is.pointLabel();
    assert((pointLabel >= 0 && pointLabel < points.size())); 
    for (auto i = pointLabel; i < cont.size(); ++i)
    {
        cont.push_back(points[pointLabel]);
        is.incrementPointLabel();
    }
    return is;
}

template<typename Stream, typename Polygon>  
VtkPolyDataIStream<Stream>& 
stream(VtkPolyDataIStream<Stream>& is, Polygon& ipoly, sequence_polygon_tag)
{
    const auto& points = is.points();
    const auto& polygons = is.polygons(); 
    const auto polygonLabel = is.polygonLabel();
    if (polygonLabel < polygons.size())
    {
        const auto& polygon = polygons[polygonLabel]; 
        for (const auto polyPointLabel : polygon)
        {
            assert((polyPointLabel < points.size()));
            ipoly.push_back(points[polyPointLabel]);
        }
        is.incrementPolygonLabel();
    }
    return is; 
}

template<typename Stream, typename Polygon>  
VtkPolyDataIStream<Stream>& 
stream(VtkPolyDataIStream<Stream>& is, Polygon& apoly, array_polygon_tag)
{
    const auto& polygons = is.polygons(); 
    const auto& points = is.points(); 
    const auto polygonLabel = is.polygonLabel();
    assert((polygonLabel < polygons.size()));
    if (polygonLabel < polygons.size())
    {
        const auto& polygon = polygons[is.polygonLabel()]; 
        assert((polygon.size() == apoly.size()));
        for (std::size_t i = 0; i < polygon.size(); ++i)
        {
            const auto pointLabel = polygon[i]; 
            assert((pointLabel < points.size()));
            apoly[i] = points[pointLabel]; 
        }
        is.incrementPolygonLabel();
    }
    return is; 
}

template<typename Stream>  
VtkPolyDataIStream<Stream>& 
stream(
    VtkPolyDataIStream<Stream>& is, 
    vectorPolyhedron& iPolyhedron, 
    vector_polyhedron_tag
)
{
    if ((iPolyhedron.size() != 0)) 
    {
        for (decltype(iPolyhedron.size()) polygonI = 0; 
             polygonI < iPolyhedron.size(); ++polygonI)
        {
            vectorPolygon iPolygon; 
            is >> iPolygon;
            iPolyhedron[polygonI] = iPolygon;
        }
    }
    else if ((iPolyhedron.size() == 0)  && (iPolyhedron.capacity() != 0))
    {
        for (decltype(iPolyhedron.capacity()) polygonI = 0; 
             polygonI < iPolyhedron.capacity(); ++polygonI)
        {
            vectorPolygon iPolygon; 
            is >> iPolygon;
            iPolyhedron.push_back(iPolygon);
        }
    }
    else if ((iPolyhedron.size() == 0)  && (iPolyhedron.capacity() == 0))
    {
        while (is.readPolygon())
        {
            vectorPolygon iPolygon; 
            is >> iPolygon;
            iPolyhedron.push_back(iPolygon);
        }
    }

    return is; 
}


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

} // End namespace geophase 

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

#endif

// ************************************************************************* //
