/**
 * MOAB, a Mesh-Oriented datABase, is a software component for creating,
 * storing and accessing finite element mesh data.
 *
 * Copyright 2004 Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
 * retains certain rights in this software.
 *
 * This library is free software; 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.1 of the License, or (at your option) any later version.
 *
 */

/*
 * Tim's Quick 'N Dirty Cub File Reader (Tqdcfr)
 *
 */

#ifndef TQDCFR
#define TQDCFR

#include "moab/Forward.hpp"
#include "moab/ReaderIface.hpp"
#include "MBTagConventions.hpp"
#include "moab/Range.hpp"

#include <cstdio>
#include <string>
#include <vector>
#include <map>

namespace moab
{

class ReadUtilIface;
class FEModelHeader;
class GeomHeader;
class GroupHeader;
class BlockHeader;
class NodesetHeader;
class SidesetHeader;

class Tqdcfr : public ReaderIface
{
  public:
    void FSEEK( unsigned offset );                           // set cubFile offset to specified value
    void FREADI( unsigned num_ents );                        // read integers into uint_buf
    void FREADD( unsigned num_ents );                        // read doubles into dbl_buf
    void FREADC( unsigned num_ents );                        // read characters into char_buf
    void FREADIA( unsigned num_ents, unsigned int* array );  // read integers
    void FREADDA( unsigned num_ents, double* array );        // read doubles
    void FREADCA( unsigned num_ents, char* arrat );          // read bytes
    void CONVERT_TO_INTS( unsigned int num_ents );           // convert uint_buf to int_buf in-place

    // class for holding the file table of contents
    class FileTOC
    {
      public:
        unsigned int fileEndian, fileSchema, numModels, modelTableOffset, modelMetaDataOffset, activeFEModel;

        FileTOC();
        void print();
    };

    //
    class FEModelHeader
    {
      public:
        unsigned int feEndian, feSchema, feCompressFlag, feLength;

        class ArrayInfo
        {
          public:
            unsigned numEntities, tableOffset, metaDataOffset;

            ArrayInfo();

            void print();
            void init( const std::vector< unsigned int >& uint_buf_in );
        };

        ArrayInfo geomArray, nodeArray, elementArray, groupArray, blockArray, nodesetArray, sidesetArray;

        void init( const unsigned int offset, Tqdcfr* instance );

        void print();
    };

    class MetaDataContainer
    {
      public:
        unsigned int mdSchema, compressFlag;

        class MetaDataEntry
        {
          public:
            unsigned int mdOwner, mdDataType, mdIntValue;
            std::string mdName, mdStringValue;
            std::vector< unsigned int > mdIntArrayValue;
            double mdDblValue;
            std::vector< double > mdDblArrayValue;

            MetaDataEntry();

            void print();
        };

        void print();

        int get_md_entry( const unsigned int owner, const std::string& name );

        std::vector< MetaDataEntry > metadataEntries;
        MetaDataContainer();
    };

    class GeomHeader
    {
      public:
        unsigned int geomID, nodeCt, nodeOffset, elemCt, elemOffset, elemTypeCt, elemLength;

        int maxDim;

        EntityHandle setHandle;

        void print();

        static ErrorCode read_info_header( const unsigned int model_offset, const FEModelHeader::ArrayInfo& info,
                                           Tqdcfr* instance, GeomHeader*& entity_headers );

        GeomHeader();
    };

    class GroupHeader
    {
      public:
        unsigned int grpID, grpType, memCt, memOffset, memTypeCt, grpLength;

        EntityHandle setHandle;

        void print();

        static ErrorCode read_info_header( const unsigned int model_offset, const FEModelHeader::ArrayInfo& info,
                                           Tqdcfr* instance, GroupHeader*& entity_headers );

        GroupHeader();
    };

    class BlockHeader
    {
      public:
        unsigned int blockID, blockElemType, memCt, memOffset, memTypeCt, attribOrder, blockCol, blockMixElemType,
            blockPyrType, blockMat, blockLength, blockDim;

        EntityHandle setHandle;

        EntityType blockEntityType;

        int hasMidNodes[4];

        void print();

        static ErrorCode read_info_header( const double data_version, const unsigned int model_offset,
                                           const FEModelHeader::ArrayInfo& info, Tqdcfr* instance,
                                           BlockHeader*& block_headers );

        BlockHeader();
    };

    class NodesetHeader
    {
      public:
        unsigned int nsID, memCt, memOffset, memTypeCt, pointSym, nsCol, nsLength;

        EntityHandle setHandle;

        void print();

        static ErrorCode read_info_header( const unsigned int model_offset, const FEModelHeader::ArrayInfo& info,
                                           Tqdcfr* instance, NodesetHeader*& entity_headers );

        NodesetHeader();
    };

    class SidesetHeader
    {
      public:
        unsigned int ssID, memCt, memOffset, memTypeCt, numDF, ssCol, useShell, ssLength;

        EntityHandle setHandle;

        void print();

        static ErrorCode read_info_header( const unsigned int model_offset, const FEModelHeader::ArrayInfo& info,
                                           Tqdcfr* instance, SidesetHeader*& entity_headers );

        SidesetHeader();
    };

    // class to hold model entry data for various kinds of models
    // (acis, free mesh, etc.)
    class ModelEntry
    {
      public:
        ModelEntry();

        ~ModelEntry();

        unsigned int modelHandle, modelOffset, modelLength, modelType, modelOwner, modelPad;

        FEModelHeader feModelHeader;
        GeomHeader* feGeomH;
        GroupHeader* feGroupH;
        BlockHeader* feBlockH;
        NodesetHeader* feNodeSetH;
        SidesetHeader* feSideSetH;

        MetaDataContainer geomMD, nodeMD, elementMD, groupMD, blockMD, nodesetMD, sidesetMD;

        void print();

        void print_geom_headers( const char* prefix, GeomHeader* header, unsigned int num_headers );

        void print_group_headers( const char* prefix, GroupHeader* header, const unsigned int num_headers );

        void print_block_headers( const char* prefix, BlockHeader* header, const unsigned int num_headers );

        void print_nodeset_headers( const char* prefix, NodesetHeader* header, const unsigned int num_headers );

        void print_sideset_headers( const char* prefix, SidesetHeader* header, const unsigned int num_headers );

        ErrorCode read_header_info( Tqdcfr* instance, const double data_version );
        ErrorCode read_metadata_info( Tqdcfr* tqd );
    };

    enum
    {
        aBODY,
        LUMP,
        SHELL,
        FACE,
        LOOP,
        COEDGE,
        aEDGE,
        aVERTEX,
        ATTRIB,
        UNKNOWN
    };

    struct AcisRecord
    {
        unsigned int rec_type;
        std::string att_string;
        bool processed;
        int first_attrib;
        int att_prev, att_next, att_ent_num;
        EntityHandle entity;
    };

    ~Tqdcfr();

    ReadUtilIface* readUtilIface;
    Interface* mdbImpl;
    FILE* cubFile;
    FileTOC fileTOC;
    std::vector< ModelEntry > modelEntries;
    MetaDataContainer modelMetaData;
    long currVHandleOffset;
    Range beforeEnts;
    long currElementIdOffset[MBMAXTYPE];
    Tag globalIdTag, cubIdTag, geomTag, uniqueIdTag, blockTag, nsTag, ssTag, attribVectorTag, entityNameTag,
        categoryTag, hasMidNodesTag;
    std::map< int, EntityHandle > uidSetMap;
    std::map< int, EntityHandle > gidSetMap[6];
    bool swapForEndianness;

    std::vector< unsigned int > uint_buf;
    int* int_buf;
    std::vector< double > dbl_buf;
    std::vector< char > char_buf;

    static ReaderIface* factory( Interface* );

    // read cub file
    ErrorCode load_file( const char* file_name, const EntityHandle* file_set, const FileOptions& opts,
                         const SubsetList* subset_list = 0, const Tag* file_id_tag = 0 );

    ErrorCode read_tag_values( const char* file_name, const char* tag_name, const FileOptions& opts,
                               std::vector< int >& tag_values_out, const SubsetList* subset_list = 0 );

    ErrorCode read_nodeset( const unsigned int nsindex, ModelEntry* model, NodesetHeader* nodeseth );
    ErrorCode read_sideset( const unsigned int ssindex, const double data_version, ModelEntry* model,
                            SidesetHeader* sideseth );
    ErrorCode read_block( const unsigned int blindex, const double data_version, ModelEntry* model,
                          BlockHeader* blockh );
    ErrorCode read_group( const unsigned int gr_index, ModelEntry* model, GroupHeader* grouph );
    ErrorCode read_nodes( const unsigned int gindex, ModelEntry* model, GeomHeader* entity );
    ErrorCode read_elements( ModelEntry* model, GeomHeader* entity );
    ErrorCode read_file_header();
    ErrorCode read_model_entries();
    int find_model( const unsigned int model_type );
    ErrorCode read_meta_data( const unsigned int metadata_offset, MetaDataContainer& mc );
    ErrorCode read_md_string( std::string& name );

    enum
    {
        mesh,
        acist,
        acisb,
        facet,
        exodusmesh
    };
    EntityType type_from_cub_type( const unsigned int cub_type, const unsigned int nodes_per_elem );
    void check_contiguous( const unsigned int num_ents, int& contig, unsigned int& min_id, unsigned int& max_id );

    Tqdcfr( Interface* impl );

    ErrorCode create_set( EntityHandle& h, unsigned int flags = MESHSET_SET );

  private:
    EntityHandle mFileSet;  // set containing read entities.

    bool printedSeqWarning;  // only print acis sequence #'s warning once

    bool printedElemWarning;  // only print element #'s warning once

    ErrorCode convert_nodesets_sidesets();

    ErrorCode read_acis_records( const char* sat_file_name = 0 );

    ErrorCode parse_acis_attribs( const unsigned int entity_rec_num, std::vector< AcisRecord >& records );
    ErrorCode interpret_acis_records( std::vector< AcisRecord >& records );

    ErrorCode reset_record( AcisRecord& this_record );

    ErrorCode process_record( AcisRecord& this_record );

    static const char geom_categories[][CATEGORY_TAG_SIZE];

    FILE* acisDumpFile;

    // map between cub ids and MOAB handles
    std::vector< EntityHandle >* cubMOABVertexMap;

    // enum used to identify element/entity type in groups
    enum
    {
        GROUP = 0,
        BODY,
        VOLUME,
        SURFACE,
        CURVE,
        VERTEX,
        HEX,
        TET,
        PYRAMID,
        QUAD,
        TRI,
        EDGE,
        NODE
    };
    static const EntityType group_type_to_mb_type[];

    enum
    {
        SPHERE_EXO = 0,
        BAR,
        BAR2,
        BAR3,
        BEAM,
        BEAM2,
        BEAM3,
        TRUSS,
        TRUSS2,
        TRUSS3,
        SPRING,
        TRIthree,
        TRI3,
        TRI6,
        TRI7,
        TRISHELL,
        TRISHELL3,
        TRISHELL6,
        TRISHELL7,
        SHEL,
        SHELL4,
        SHELL8,
        SHELL9,
        QUADfour,
        QUAD4,
        QUAD5,
        QUAD8,
        QUAD9,
        TETRAfour,
        TETRA4,
        TETRA8,
        TETRA10,
        TETRA14,
        PYRAMIDfive,
        PYRAMID5,
        PYRAMID8,
        PYRAMID13,
        PYRAMID18,
        HEXeight,
        HEX8,
        HEX9,
        HEX20,
        HEX27,
        HEXSHELL,
        INVALID_ELEMENT_TYPE
    };
    static const EntityType block_type_to_mb_type[];
    static const int cub_elem_num_verts[];
    static const int cub_elem_num_verts_len;

    //! mapping from mesh packet type to moab type
    static const EntityType mp_type_to_mb_type[];

    //! get entities with individually-specified types; if is_group is false,
    //! increment each mem_type by 2 since they're CSOEntityType's and not group types
    ErrorCode get_entities( const unsigned int* mem_types, int* id_buf, const unsigned int id_buf_size,
                            const bool is_group, std::vector< EntityHandle >& entities );

    //! get entities specified by type and ids, append to entities
    ErrorCode get_entities( const unsigned int this_type, int* id_buf, const unsigned int id_buf_size,
                            std::vector< EntityHandle >& entities, std::vector< EntityHandle >& excl_entities );

    //! get ref entity sets with specified type and ids
    ErrorCode get_ref_entities( const unsigned int this_type, int* id_buf, const unsigned id_buf_size,
                                std::vector< EntityHandle >& entities );

    //! get mesh entities with specified type and ids
    ErrorCode get_mesh_entities( const unsigned int this_type, int* id_buf, const unsigned id_buf_size,
                                 std::vector< EntityHandle >& entities, std::vector< EntityHandle >& excl_entities );

    //! process entities in a sideset according to sense flags stored in uint_buf
    //! or char_buf (depending on sense_size)
    ErrorCode process_sideset_10( const int this_type, const int num_ents, const int sense_size,
                                  std::vector< EntityHandle >& ss_entities, Tqdcfr::SidesetHeader* sideseth );

    ErrorCode process_sideset_11( std::vector< EntityHandle >& ss_entities, int num_wrts,
                                  Tqdcfr::SidesetHeader* sideseth );

    // put entities into the specfied set, and excluded entities into a
    // std::vector pointed to by the "Exclude_Entities" tag on that set
    ErrorCode put_into_set( EntityHandle set_handle, std::vector< EntityHandle >& entities,
                            std::vector< EntityHandle >& excl_entities );

    // look in metadatacontainer[set_index] for name data; if found, set name (and extra names,
    // if multiple found) on set handle
    ErrorCode get_names( MetaDataContainer& md, unsigned int set_index, EntityHandle seth );
};

}  // namespace moab

#endif
