/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. You can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either version 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#ifndef _GCTL_STL_IO_H
#define _GCTL_STL_IO_H

#include "../poly/triangle.h"
#include "file_io.h"

namespace gctl
{
    /**
     * @brief Read stl file ascii format
     * 
     * @tparam E vertex attribute type
     * @tparam A element attribute type
     * @param filename File name
     * @param nodes return nodes
     * @param facets return facets
     * @param solid solid name ('null' will read the first solid)
     * @param eps epsilon for node comparison
     */
    template <typename E, typename A>
    void read_stl_ascii(std::string filename, array<vertex<point3dc, E>> &nodes, 
        array<type_triangle<A>> &facets, std::string solid = "null", double eps = 1e-8)
    {
        std::ifstream infile;
        open_infile(infile, filename, ".stl");

        vertex3dc tmp_node;
        std::vector<vertex3dc> tmp_nodes;
        _1i_vector one_tri(3);
        _2i_vector tmp_tris;

        bool inside_solid, new_node;
        point3dc nor, vt[3];
        char solid_name[256], solid_endname[256];
        std::string line;
        while (std::getline(infile, line))
        {
            if (sscanf(line.c_str(), "solid %s", solid_name) == 1 && (!strcmp(solid_name, solid.c_str()) || solid == "null"))
            {
                inside_solid = true;
                while (inside_solid)
                {
                    std::getline(infile, line);
                    if (sscanf(line.c_str(), "endsolid %s", solid_endname) == 1)
                    {
                        if (strcmp(solid_endname, solid_name))
                            throw std::runtime_error("[gctl::read_stl_ascii] Invalid solid names.");

                        inside_solid = false;
                    }
                    else
                    {
                        sscanf(line.c_str(), "facet normal %lf %lf %lf", &nor.x, &nor.y, &nor.z);
                        std::getline(infile, line); // outer loop
                        for (int i = 0; i < 3; i++)
                        {
                            std::getline(infile, line);
                            line.erase(0, line.find_first_not_of(" \t"));
                            line.erase(line.find_last_not_of(" \t") + 1);
                            sscanf(line.c_str(), "vertex %lf %lf %lf", &vt[i].x, &vt[i].y, &vt[i].z);
                        }
                        std::getline(infile, line); // endloop
                        std::getline(infile, line); // endfacet

                        for (size_t i = 0; i < 3; i++)
                        {
                            tmp_node.id = tmp_nodes.size();
                            tmp_node.x = vt[i].x;
                            tmp_node.y = vt[i].y;
                            tmp_node.z = vt[i].z;

                            new_node = true;
                            for (size_t v = 0; v < tmp_nodes.size(); v++)
                            {
                                if (isequal(tmp_nodes[v], tmp_node, eps))
                                {
                                    tmp_node.id = tmp_nodes[v].id;
                                    new_node = false;
                                    break;
                                }
                            }

                            if (new_node) tmp_nodes.push_back(tmp_node);
                            one_tri[i] = tmp_node.id;
                        }
                        tmp_tris.push_back(one_tri);
                    }
                }
            }
        }
        infile.close();

        nodes.resize(tmp_nodes.size());
        for (size_t i = 0; i < tmp_nodes.size(); i++)
        {
            nodes[i].id = tmp_nodes[i].id;
            nodes[i].x = tmp_nodes[i].x;
            nodes[i].y = tmp_nodes[i].y;
            nodes[i].z = tmp_nodes[i].z;
        }

        facets.resize(tmp_tris.size());
        for (size_t i = 0; i < tmp_tris.size(); i++)
        {
            facets[i].set(nodes[tmp_tris[i][0]], nodes[tmp_tris[i][1]], nodes[tmp_tris[i][2]], i);
        }

        destroy_vector(tmp_nodes);
        destroy_vector(tmp_tris);
        destroy_vector(one_tri);
        return;
    }

    /**
     * @brief Save stl file ascii format
     * 
     * @tparam E Vertex attribute type
     * @tparam A Element attribute type
     * @param filename File name
     * @param facets Input facets
     * @param solid Solid name
     */
    template <typename A>
    void save_stl_ascii(std::string filename, const array<type_triangle<A>> &facets, 
        std::string solid = "STL generated by GCTL")
    {
        std::ofstream outfile;
        open_outfile(outfile, filename, ".stl");

        point3dc nor;
        outfile << "solid " << solid << "\n";
        for (size_t i = 0; i < facets.size(); i++)
        {
            nor = cross(*facets[i].vert[1] - *facets[i].vert[0], *facets[i].vert[2] - *facets[i].vert[0]).normal();
            outfile << "facet normal " << nor.x << " " << nor.y << " " << nor.z << "\n";
            outfile << "  outer loop\n";
            outfile << "    vertex " << facets[i].vert[0]->x << " " << facets[i].vert[0]->y << " " << facets[i].vert[0]->z << "\n";
            outfile << "    vertex " << facets[i].vert[1]->x << " " << facets[i].vert[1]->y << " " << facets[i].vert[1]->z << "\n";
            outfile << "    vertex " << facets[i].vert[2]->x << " " << facets[i].vert[2]->y << " " << facets[i].vert[2]->z << "\n";
            outfile << "  endloop\n";
            outfile << "endfacet\n";
        }
        outfile << "endsolid " << solid << "\n";
        outfile.close();
        return;
    }

    /**
     * @brief Read stl file binary format
     * 
     * @tparam E vertex attribute type
     * @tparam A element attribute type
     * @param filename File name
     * @param nodes return nodes
     * @param facets return facets
     * @param eps epsilon for node comparison
     * 
     * @return solid name
     */
    template <typename E, typename A>
    std::string read_stl_binary(std::string filename, array<vertex<point3dc, E>> &nodes, 
        array<type_triangle<A>> &facets, double eps = 1e-8)
    {
        std::ifstream infile;
        open_infile(infile, filename, ".stl", std::ios::binary);

        char blank[2], header[80];
        infile.read(header, 80);

        int n_facets;
        infile.read((char*)&n_facets, 4);

        vertex3dc tmp_node;
        std::vector<vertex3dc> tmp_nodes;
        _1i_vector one_tri(3);
        _2i_vector tmp_tris;
        tmp_tris.reserve(n_facets);

        bool new_node;
        point3fc nor, vt[3];
        for (size_t i = 0; i < n_facets; i++)
        {
            infile.read((char*)&nor.x, 4);
            infile.read((char*)&nor.y, 4);
            infile.read((char*)&nor.z, 4);
            for (size_t j = 0; j < 3; j++)
            {
                infile.read((char*)&vt[j].x, 4);
                infile.read((char*)&vt[j].y, 4);
                infile.read((char*)&vt[j].z, 4);
            }
            infile.read((char*)&blank, 2);

            for (size_t j = 0; j < 3; j++)
            {
                tmp_node.id = tmp_nodes.size();
                tmp_node.x = vt[j].x;
                tmp_node.y = vt[j].y;
                tmp_node.z = vt[j].z;

                new_node = true;
                for (size_t v = 0; v < tmp_nodes.size(); v++)
                {
                    if (isequal(tmp_nodes[v], tmp_node, eps))
                    {
                        tmp_node.id = tmp_nodes[v].id;
                        new_node = false;
                        break;
                    }
                }

                if (new_node) tmp_nodes.push_back(tmp_node);
                one_tri[j] = tmp_node.id;
            }
            tmp_tris.push_back(one_tri);
        }
        infile.close();

        nodes.resize(tmp_nodes.size());
        for (size_t i = 0; i < tmp_nodes.size(); i++)
        {
            nodes[i].id = tmp_nodes[i].id;
            nodes[i].x = tmp_nodes[i].x;
            nodes[i].y = tmp_nodes[i].y;
            nodes[i].z = tmp_nodes[i].z;
        }

        facets.resize(tmp_tris.size());
        for (size_t i = 0; i < tmp_tris.size(); i++)
        {
            facets[i].set(nodes[tmp_tris[i][0]], nodes[tmp_tris[i][1]], nodes[tmp_tris[i][2]], i);
        }

        destroy_vector(tmp_nodes);
        destroy_vector(tmp_tris);
        destroy_vector(one_tri);

        std::string solid_name(header);
        return solid_name;
    }

    /**
     * @brief Save stl file binary format
     * 
     * @tparam E vertex attribute type
     * @tparam A element attribute type
     * @param filename File name
     * @param facets return facets
     * @param solid solid name
     */
    template <typename A>
    void save_stl_binary(std::string filename, const array<type_triangle<A>> &facets, 
        std::string solid = "STL generated by GCTL")
    {
        std::ofstream outfile;
        open_outfile(outfile, filename, ".stl", std::ios::binary);

        char blank[2] = {'\0', '\0'};
        char header[80];
        for (size_t i = 0; i < 80; i++) header[i] = '\0';
        for (size_t i = 0; i < solid.size(); i++) header[i] = solid[i];
        outfile.write(header, 80);

        int n_facets = facets.size();
        outfile.write((char*)&n_facets, 4);
        point3dc nor;
        point3fc nor_fl;
        point3fc vt[3];
        for (size_t i = 0; i < facets.size(); i++)
        {
            nor = cross(*facets[i].vert[1] - *facets[i].vert[0], *facets[i].vert[2] - *facets[i].vert[0]).normal();
            // convert from double to float
            nor_fl.x = nor.x;
            nor_fl.y = nor.y;
            nor_fl.z = nor.z;
            for (size_t j = 0; j < 3; j++)
            {
                vt[j].x = facets[i].vert[j]->x;
                vt[j].y = facets[i].vert[j]->y;
                vt[j].z = facets[i].vert[j]->z;
            }

            outfile.write((char*)&nor_fl.x, 4);
            outfile.write((char*)&nor_fl.y, 4);
            outfile.write((char*)&nor_fl.z, 4);
            for (size_t j = 0; j < 3; j++)
            {
                outfile.write((char*)&vt[j].x, 4);
                outfile.write((char*)&vt[j].y, 4);
                outfile.write((char*)&vt[j].z, 4);
            }
            outfile.write(blank, 2);
        }
        outfile.close();
    }
};

#endif // _GCTL_STL_IO_H