//======================================================================================================================
//
//  This file is part of waLBerla. waLBerla 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.
//
//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file MeshIO.h
//! \ingroup mesh
//! \author Christian Godenschwager <christian.godenschwager@fau.de>
//! \author Lukas Werner
//
//======================================================================================================================

#pragma once

#include "core/DataTypes.h"
#include "core/mpi/Broadcast.h"
#include "core/mpi/BufferDataTypeExtensions.h"

#include <fstream>
#include <string>

#include "core/Filesystem.h"

#ifdef _MSC_VER
#  pragma warning(push)
#  pragma warning( disable : 4456 )
#endif //_MSC_VER
#include <OpenMesh/Core/IO/MeshIO.hh>
#ifdef _MSC_VER
#  pragma warning(pop)
#endif //_MSC_VER

namespace walberla {
namespace mesh {

/**
 * \brief Reads a mesh from a generic input stream.
 *
 * \tparam MeshType     The type of the OpenMesh
 *
 * \param inputStream   The input stream from which the mesh should be read
 * \param mesh          The mesh data structure to be written to
 * \param extension     The mesh file's extension
 *
 * \return Whether the read operation was successful.
 */
template< typename MeshType >
bool readFromStream( std::istream & inputStream, MeshType & mesh, const std::string & extension,
                     bool binaryFile = false )
{
   OpenMesh::IO::Options options;
   if( mesh.has_face_colors() )
      options += OpenMesh::IO::Options::FaceColor;
   if ( binaryFile )
      options += OpenMesh::IO::Options::Binary;
   if( mesh.has_vertex_colors() )
      options += OpenMesh::IO::Options::VertexColor;
   return OpenMesh::IO::read_mesh( mesh, inputStream, extension, options );
}

/**
* \brief Loads an OpenMesh in parallel
*
* The mesh is read from disk by a single process and then broadcasted. This ensures a scalable load process that does
* not put to much pressure on the file system at large scale.
*
* \tparam MeshType The type of the OpenMesh
*
* \param filename filename of the mesh to be loaded
* \param mesh     The mesh data structure to be written to
*/
template< typename MeshType >
void readAndBroadcast( const std::string & filename, MeshType & mesh, bool binaryFile = false )
{
   if( !filesystem::exists( filename ) )
      WALBERLA_ABORT( "The mesh file \"" << filename << "\" does not exist!" );

   std::string extension = filesystem::path( filename ).extension().string();

   std::string str;

   WALBERLA_ROOT_SECTION()
   {
      std::ifstream t( filename.c_str() );
      if ( binaryFile )
         t = std::ifstream( filename.c_str(), std::ifstream::in | std::ifstream::binary );

      if( !t )
         WALBERLA_ABORT( "Error while reading file \"" << filename << "\"!" );
      t.seekg( 0, std::ios::end );
      str.reserve( static_cast<std::string::size_type>( t.tellg() ) );
      t.seekg( 0, std::ios::beg );

      str.assign( ( std::istreambuf_iterator<char>(t) ),
                    std::istreambuf_iterator<char>() );
   }

   mpi::broadcastObject( str );

   std::istringstream iss( str );
   if ( binaryFile )
      iss = std::istringstream( str, std::ifstream::in | std::ifstream::binary );

   if (!readFromStream<MeshType>(iss, mesh, extension, binaryFile)) {
      WALBERLA_ABORT( "Error while reading file \"" << filename << "\"!" );
   }
}

/**
 * \brief Read a mesh from a file.
 * \attention If reading in parallel, readAndBroadcast should be preferred!
 *
 * \tparam MeshType     The type of the OpenMesh
 *
 * \param filename      Filename of the mesh to be loaded
 * \param mesh          The mesh data structure to be written to
 */
template< typename MeshType >
void readFromFile( const std::string & filename, MeshType & mesh, bool binaryFile = false )
{
   if (!filesystem::exists(filename)) {
      WALBERLA_ABORT( "The mesh file \"" << filename << "\" does not exist!" );
   }

   std::string extension = filesystem::path(filename).extension().string();

   std::ios_base::openmode openMode = std::ifstream::in;
   if (binaryFile) {
      openMode |= std::ifstream::binary;
   }
   std::ifstream inputFileStream = std::ifstream(filename, openMode);

   if (!readFromStream<MeshType>(inputFileStream, mesh, extension, binaryFile)) {
      WALBERLA_ABORT( "Error while reading file \"" << filename << "\"!" );
   }
   inputFileStream.close();
}


} // namespace mesh
} // namespace walberla