/*!
  \file NodeWiseMeshPartitioner.h
  \date   2016.05

  \brief  Declare a class to perform node wise mesh partitioning

  \copyright
  Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org)
             Distributed under a Modified BSD License.
               See accompanying file LICENSE.txt or
               http://www.opengeosys.org/project/license

*/

#pragma once

#include <memory>
#include <string>
#include <tuple>
#include <vector>

#include "MeshLib/Elements/Element.h"
#include "MeshLib/IO/MPI_IO/PropertyVectorMetaData.h"
#include "MeshLib/Mesh.h"
#include "MeshLib/Node.h"

namespace ApplicationUtils
{
///  A subdomain mesh.
struct Partition
{
    std::vector<MeshLib::Node*> nodes;  ///< nodes.
    std::size_t number_of_non_ghost_base_nodes;
    std::size_t number_of_non_ghost_nodes;
    std::size_t number_of_base_nodes;
    std::size_t number_of_mesh_base_nodes;
    std::size_t number_of_mesh_all_nodes;
    /// Non ghost elements
    std::vector<const MeshLib::Element*> regular_elements;
    std::vector<const MeshLib::Element*> ghost_elements;

    std::size_t numberOfMeshItems(MeshLib::MeshItemType const item_type) const;

    std::ostream& writeNodesBinary(
        std::ostream& os,
        std::vector<std::size_t> const& global_node_ids) const;

    std::ostream& writeConfigBinary(std::ostream& os) const;
};

/// Creates partitioned mesh properties for nodes and cells.
MeshLib::Properties partitionProperties(
    MeshLib::Properties const& properties,
    std::vector<Partition> const& partitions);

/// Mesh partitioner.
class NodeWiseMeshPartitioner
{
public:
    using IntegerType = long;

public:
    /*!
     * \param num_partitions Number of partitions,
     * \param mesh           Pointer to a mesh object.
     */
    NodeWiseMeshPartitioner(const IntegerType num_partitions,
                            std::unique_ptr<MeshLib::Mesh>&& mesh)
        : _npartitions(num_partitions),
          _partitions(num_partitions),
          _partitioned_properties(),
          _mesh(std::move(mesh)),
          _nodes_global_ids(_mesh->getNumberOfNodes()),
          _nodes_partition_ids(_mesh->getNumberOfNodes())
    {
    }

    /// Partition by node.
    /// \param is_mixed_high_order_linear_elems Flag to indicate whether the
    /// elements of a mesh can be used for both linear and high order
    /// interpolation
    void partitionByMETIS(const bool is_mixed_high_order_linear_elems);

    std::vector<Partition> partitionOtherMesh(
        MeshLib::Mesh const& mesh,
        bool const is_mixed_high_order_linear_elems) const;

    /// Write the partitions into ASCII files
    /// \param file_name_base The prefix of the file name.
    void writeASCII(const std::string& file_name_base);

    /// Write the partitions into binary files
    /// \param file_name_base The prefix of the file name.
    void writeBinary(const std::string& file_name_base);

    void writeOtherMesh(
        std::string const& output_filename_base,
        std::vector<Partition> const& partitions,
        MeshLib::Properties const& partitioned_properties) const;

    void resetPartitionIdsForNodes(
        std::vector<std::size_t>&& node_partition_ids)
    {
        _nodes_partition_ids = std::move(node_partition_ids);
    }

    MeshLib::Mesh const& mesh() const { return *_mesh; }

private:
    /// Number of partitions.
    IntegerType _npartitions;

    /// Data for all  partitions.
    std::vector<Partition> _partitions;

    /// Properties where values at ghost nodes and extra nodes are inserted.
    MeshLib::Properties _partitioned_properties;

    /// Pointer to a mesh object.
    std::unique_ptr<MeshLib::Mesh> _mesh;

    /// Global IDs of all nodes after partitioning.
    std::vector<std::size_t> _nodes_global_ids;

    /// Partition IDs of each nodes.
    std::vector<std::size_t> _nodes_partition_ids;

    // Renumber the global indices of nodes,
    /// \param is_mixed_high_order_linear_elems Flag to indicate whether the
    /// elements of a mesh can be used for both linear and high order
    /// interpolation
    void renumberNodeIndices(const bool is_mixed_high_order_linear_elems);

    void processPartition(std::size_t const part_id,
                          const bool is_mixed_high_order_linear_elems);

    /// Write the configuration data of the partition data in ASCII files.
    /// \param file_name_base The prefix of the file name.
    void writeConfigDataASCII(const std::string& file_name_base);

    ///  Write the element integer variables of all partitions into
    ///  ASCII files.
    /// \param file_name_base The prefix of the file name.
    void writeElementsASCII(const std::string& file_name_base);

    ///  Write the nodes of all partitions into a ASCII file.
    /// \param file_name_base The prefix of the file name.
    void writeNodesASCII(const std::string& file_name_base);

    /*!
        \brief Write local indices of element nodes to a ASCII file
        \param os              Output stream
        \param elem            Element
        \param local_node_ids  Local node indices of a partition
    */
    void writeLocalElementNodeIndices(
        std::ostream& os,
        const MeshLib::Element& elem,
        const std::vector<IntegerType>& local_node_ids);
};

}  // namespace ApplicationUtils