/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * 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_MESH_IO_H
#define _GCTL_MESH_IO_H

// library's head files
#include "../core.h"
#include "../geometry.h"
#include "../utility.h"

#include "triangle_io.h"
#include "tetgen_io.h"
#include "gmsh_io.h"
#include "map"


namespace gctl
{
    /**
     * @brief 无效的索引缺省值。
     */
    #define DEFAULT_INVALID_TAG -9999

    /**
     * @brief 网格单元体名称枚举类型。
     */
    enum element_type_enum
    {
        NotSet,
        _2NodeLine,
        _3NodeTriangle,
        _4NodeQuadrangle,
        _4NodeTetrahedron,
        _8NodeHexahedron,
        _6NodePrism,
        _5NodePyramid,
        _3NodeSecondOrderLine,
        _6NdoeSecondOrderLine,
        _9NodeSecondOrderQuadrangle,
        _10NodeSecondOrderTetrahedron,
        _27NodeSecondOrderHexahedron,
        _18NodeSecondOrderPrism,
        _14NodeSecondOrderPyramid,
        _1NodePoint,
        _8NodeSecondOrderQuadrangle,
        _20NdoeSecondOrderHexahedron,
        _15NodeSecondOrderPrism,
        _13NodeSecondOrderPyramid,
        _9NodeThirdOrderIncompleteTriangle,
        _10NdoeThirdOrderTriangle,
        _12NodeFourthOrderIncompleteTriangle,
        _15NodeFourthOrderTriangle,
        _15NodeFifthOrderCompleteTriangle,
        _21NodeFifthOrderCompleteTriangle,
        _4NodeThirdOrderEdge,
        _5NodeFourthOrderEdge,
        _6NodeFifthOrderEdge,
        _20NodeThirdOrderTetrahedron,
        _35NodeFourthOrderTetrahedron,
        _56NodeFifithOrderTetrahedron,
        _64NodeThirdOrderHexahedron,
        _125NodeFourthOrderHexahedron,
    };

    /**
     * @brief 网格单元体标签类型枚举类型
     * 
     */
    enum element_tag_enum
    {
        PhysicalTag,
        GeometryTag,
        PartitionTag,
        NodeTag,
    };

    /**
     * @brief 网格单元体结构体
     * 
     */
    struct mesh_element
    {
        bool enabled;
        int id;
        element_type_enum type;
        array<vertex3dc*> vert_ptrs;
        array<mesh_element*> neigh_ptrs;

        mesh_element();
    };

    /**
     * @brief 网格数据结构体
     * 
     */
    struct mesh_data
    {
        bool enabled;
        mesh_data_type_e d_type;
        array<std::string> str_tag;
        array<double> real_tag;
        array<int> int_tag;
        array<vertex3dc*> vert_ptrs; // 两者只能存在一个
        array<mesh_element*> elem_ptrs; // 两者只能存在一个
        array<double> val;

        mesh_data();

        /**
         * @brief 清空数组并重置变量。
         * 
         */
        void clear();

        /**
         * @brief 检查数据体是否合规
         * 
         */
        bool pass_check();
    };

    /**
     * @brief 网格单元体分组结构体。
     * 
     */
    struct mesh_element_group
    {
        bool enabled;
        element_type_enum type;
        int phys_group;
        int geom_group;
        int part_group;
        std::string name;
        std::vector<mesh_element*> elem_ptrs;

        mesh_element_group();

        /**
         * @brief 将组内所有单元体设置为有效状态。
         * 
         */
        void enable_elements();

        /**
         * @brief 将组内所有单元体设置为无效状态。
         * 
         */
        void disable_elements();
    };
    
    /**
     * @brief 网格读写类，这个类实现了多种数据格式的网格文件的读写操作。并具备简单的单元体操作功能。
     * 
     */
    class mesh_io
    {
    public:
        mesh_io();
        virtual ~mesh_io();

        /**
         * @brief 重置（清空）网格数据。
         * 
         */
        void reset();

        /**
         * @brief 输出网格数据信息至指定流。
         * 
         * @param ss 指定流（默认为clog）
         */
        void info(std::ostream &ss = std::clog);

        /**
         * @brief 编辑网格数据状态。
         * 
         * @param swt 使能类型（Enable或Disable）
         * @param dataname 数据名称（缺省值为null，表示对所有数据进行操作）。
         */
        void edit_data(switch_type_e swt, std::string dataname = "null");

        /**
         * @brief 按单元体类型编辑网格单元体组。
         * 
         * @param swt 使能类型（Enable或Disable）
         * @param e_type 单元体类型（缺省值值NotSet，表示对所有单元体组进行操作）。
         */
        void edit_group(switch_type_e swt, element_type_enum e_type = NotSet);

        /**
         * @brief 按单元体组名称编辑网格单元体组。
         * 
         * @param swt 使能类型（Enable或Disable）。
         * @param grp_name 单元体组名称。
         */
        void edit_group(switch_type_e swt, std::string grp_name);

        /**
         * @brief 按单元体组标签编辑网格单元体组。
         * 
         * @param swt 使能类型（Enable或Disable）。
         * @param tag_type 标签类型（PhysicalTag，GeometryTag或者PartitionTag）。
         * @param tag 标签值。
         */
        void edit_group(switch_type_e swt, element_tag_enum tag_type, int tag);

        /**
         * @brief 按单元体组标签编辑网格单元体组的名称。
         * 
         * @param anchor_type 搜索的标签类型（PhysicalTag，GeometryTag或者PartitionTag）。
         * @param anchor_group 搜索的标签值。
         * @param new_name 单元体组的新名称。
         */
        void edit_group(element_tag_enum anchor_type, int anchor_group, std::string new_name);

        /**
         * @brief 按单元体组标签搜索并编辑网格单元体组的标签。
         * 
         * @param anchor_type 搜索的标签类型（PhysicalTag，GeometryTag或者PartitionTag）。
         * @param anchor_group 搜索的标签值
         * @param tar_type 更改的标签类型（PhysicalTag，GeometryTag或者PartitionTag）。
         * @param tar_group 更改的标签值
         */
        void edit_group(element_tag_enum anchor_type, int anchor_group, element_tag_enum tar_type, int tar_group);

        /**
         * @brief 返回顶点标签数组的引用。
         * 
         * @return 整型数组的引用。
         */
        const array<int> &get_node_tag();

        /**
         * @brief 返回顶点数组的引用。
         * 
         * @return 顶点数组的引用。
         */
        const array<vertex3dc> &get_nodes();

        /**
         * @brief 返回单元体数组的引用。
         * 
         * @return 单元体数组的引用。
         */
        const array<mesh_element> &get_elems();

        /**
         * @brief 返回对应类型单元体的数量。
         * 
         * @param e_type 单元体类型（缺省为NotSet，返回所有单元体类型的总和）。
         * @return 整型大小。
         */
        size_t element_size(element_type_enum e_type = NotSet);

        /**
         * @brief 返回对应标签类型与标签值的单元体数量。
         * 
         * @param tag_type 标签类型。
         * @param tag 标签值。
         * @return 整型大小。
         */
        size_t element_size(element_tag_enum tag_type, int tag);

        /**
         * @brief 返回对应名称的单元体数量。
         * 
         * @param phys_name 单元体组名称
         * @return 整型大小。
         */
        size_t element_size(std::string phys_name);

        /**
         * @brief 将对应标签类型转换为网格数据类型
         * 
         * @param tag_type 标签类型。
         */
        void convert_tags_to_data(element_tag_enum tag_type);

        /**
         * @brief 输出对应名称的单元体组到三角形数组。
         * 
         * @param tris 输出的三角形数组的引用。
         * @param phys_name 单元体组的名称。
         */
        void export_elements_to(array<triangle> &tris, std::string phys_name = "All");

        /**
         * @brief 输出对应标签类型与标签值的单元体组到三角形数组。
         * 
         * @param tris 输出的三角形数组的引用。
         * @param tag_type 标签类型。
         * @param tag 标签值。
         */
        void export_elements_to(array<triangle> &tris, element_tag_enum tag_type, int tag);

        /**
         * @brief 输出对应名称的单元体组到四面体数组。
         * 
         * @param tets 输出的四面体数组的引用。
         * @param phys_name 单元体组的名称。
         */
        void export_elements_to(array<tetrahedron> &tets, std::string phys_name = "All");

        /**
         * @brief 输出对应标签类型与标签值的单元体组到四面体数组。
         * 
         * @param tris 输出的四面体数组的引用。
         * @param tag_type 标签类型。
         * @param tag 标签值。
         */
        void export_elements_to(array<tetrahedron> &tets, element_tag_enum tag_type, int tag);

        /**
         * @brief 创建一个顶点数据对象。
         * 
         * @param data 输入的数据数组。
         * @param name 新建的数据名称。
         */
        void create_node_data(const array<double> &data, std::string name);

        /**
         * @brief 按单元体类型筛选创建一个单元体数据对象。
         * 
         * @param data 输入的数据数组。
         * @param name 新建数据名称。
         * @param e_type 新建数据的单元体类型（缺省值为NotSet，表示选择所有有效的单元体）。
         */
        void create_element_data(const array<double> &data, std::string name, element_type_enum e_type = NotSet);

        /**
         * @brief 按单元体标签值筛选创建一个单元体数据对象。
         * 
         * @param data 输入的数据数组。
         * @param name 新建数据名称。
         * @param tag_type 标签类型。
         * @param tag 标签值。
         */
        void create_element_data(const array<double> &data, std::string name, element_tag_enum tag_type, int tag);

        /**
         * @brief 按单元体组的名称筛选创建一个单元体数据对象。
         * 
         * @param data 输入的数据数组。
         * @param name 新建数据名称。
         * @param phys_name 单元体组的名称。
         */
        void create_element_data(const array<double> &data, std::string name, std::string phys_name);

        /**
         * @brief 读入triangle软件输出的网格剖分文件。
         * 
         * @param filename 文件名（.node和.ele文件必须在同一路径下，.neigh文件不是必须的，文件名不包含后缀名）。
         * @param is_packed 输入文件的索引值是否从0开始。
         */
        void read_triangle_ascii(std::string filename, index_packed_e is_packed = Packed);

        /**
         * @brief 读入tetgen软件输出的网格剖分文件。
         * 
         * @param filename 文件名（.node和.ele文件必须在同一路径下，.neigh文件不是必须的，文件名不包含后缀名）。
         * @param is_packed 输入文件的索引值是否从0开始。
         */
        void read_tetgen_ascii(std::string filename, index_packed_e is_packed = Packed);

        /**
         * @brief 读入Gmsh软件输出的网格剖分文件（只支持v2.2的ASCII文件）。
         * 
         * @param filename 文件名
         * @param is_packed 输入文件的索引值是否从0开始。
         */
        void read_gmsh_v2_ascii(std::string filename, index_packed_e is_packed = NotPacked);

        /**
         * @brief 保存Gmsh软件格式的网格剖分文件（只支持v2.2的ASCII文件）。
         * 
         * @param filename 文件名
         * @param is_packed 输入文件的索引值是否从0开始。
         */
        void save_gmsh_v2_ascii(std::string filename, index_packed_e is_packed = NotPacked);

        /**
         * @brief 保存Paraview软件格式的网格剖分文件
         * 
         * @param filename 文件名
         */
        void save_vtk_legacy_ascii(std::string filename);

        /**
         * @brief 导出数据到点云文件
         * 
         * @param filename 输出文件名
         * @param dataname 数据名称
         * @param out_coor 数据的坐标系（Cartesian或Spherical）
         * @param refr 参考椭球的短半径（out_coor为Cartesian时无效）
         * @param refR 参考椭球的长半径（out_coor为Cartesian时无效）
         */
        void save_data_to_xyz(std::string filename, std::string dataname = "null", coordinate_system_e out_coor = Cartesian, double refr = GCTL_Earth_Radius, double refR = GCTL_Earth_Radius);
    
    private:
        bool initialized_;

        size_t valid_node_size_, valid_elem_size_, valid_group_size_;
        array<vertex3dc> nodes_;
        array<mesh_element> elems_;
        std::vector<mesh_data> datas_;
        std::vector<mesh_element_group> groups_;
        array<int> nodes_tag_;
        
        element_type_enum elem_gmshcode2type_[94];
        element_type_enum elem_vtkcode2type_[14];
        std::map<element_type_enum, int> elem_type2gmshcode_;
        std::map<element_type_enum, int> elem_type2vtkcode_;
        std::map<element_type_enum, int> elem_type2size_;
        std::map<element_type_enum, std::string> elem_type2name_;

        std::string elem_name(element_type_enum e_type);
        int elem_gmsh_code(element_type_enum e_type);
        int elem_vtk_code(element_type_enum e_type);
        int elem_size(element_type_enum e_type);
        element_type_enum elem_gmsh_type(int code);
        element_type_enum elem_vtk_type(int code);
        void update_indexing();
        void sort_groups();
    };
}

#endif // _GCTL_MESH_IO_H