// ---------------------------------------------------------------------
//
// Copyright (C) 2010 - 2020 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, 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.
// The full text of the license can be found in the file LICENSE.md at
// the top level directory of deal.II.
//
// ---------------------------------------------------------------------


#ifndef dealii_mesh_worker_output_h
#define dealii_mesh_worker_output_h

#include <deal.II/base/config.h>

#include <deal.II/base/mg_level_object.h>
#include <deal.II/base/smartpointer.h>
#include <deal.II/base/utilities.h>

#include <deal.II/lac/block_vector.h>

#include <deal.II/meshworker/dof_info.h>


DEAL_II_NAMESPACE_OPEN

namespace MeshWorker
{
  namespace Assembler
  {
    /**
     * A class that, instead of assembling into a matrix or vector, outputs
     * the results on a cell to a gnuplot patch.
     *
     * This assembler expects that LocalResults contains quadrature values set
     * with LocalResults::quadrature_value(). When it is initialized with the
     * number of quadrature points in a single (!) space direction and the
     * number of data fields to be displayed, it initializes LocalResults
     * automatically. The number of data fields in local results will be
     * increased by dim in order to accommodate for the coordinates of the
     * data points.
     *
     * While data slots for the space coordinates are allocated automatically,
     * these coordinates are not entered. It is up to the user to enter the
     * coordinates in the first dim data entries at every point. This adds the
     * flexibility to output transformed coordinates or even something
     * completely different.
     *
     * @note In the current implementation, only cell data can be written.
     */
    class GnuplotPatch
    {
    public:
      /**
       * Constructor.
       */
      GnuplotPatch();

      /**
       * Initialize for writing <i>n</i> data vectors. The number of points is
       * the number of quadrature points in a single direction in a tensor
       * product formula. It must match the number in the actual Quadrature
       * used to create the patches. The total number of data vectors produced
       * is <tt>n+dim</tt> and the first dim should be the space coordinates
       * of the points. Nevertheless, it is up to the user to set these values
       * to whatever is desired.
       */
      void
      initialize(const unsigned int n_points, const unsigned int n_vectors);

      /**
       * Set the stream #os to which data is written. If no stream is selected
       * with this function, data goes to @p deallog.
       */
      void
      initialize_stream(std::ostream &stream);

      /**
       * Initialize the local data in the DoFInfo object used later for
       * assembling.
       *
       * The @p info object refers to a cell if <code>!face</code>, or else to an
       * interior or boundary face.
       */
      template <int dim>
      void
      initialize_info(DoFInfo<dim> &info, bool face);

      /**
       * Write the patch to the output stream.
       */
      template <int dim>
      void
      assemble(const DoFInfo<dim> &info);

      /**
       * @warning Not implemented yet
       */
      template <int dim>
      void
      assemble(const DoFInfo<dim> &info1, const DoFInfo<dim> &info2);

    private:
      /**
       * Write the object T either to the stream #os, if initialize_stream()
       * has been called, or to @p deallog if no pointer has been set.
       */
      template <typename T>
      void
      write(const T &t) const;

      /**
       * Write an end-of-line marker either to the stream #os, if
       * initialize_stream has been called, or to @p deallog if no pointer has
       * been set.
       */
      void
      write_endl() const;

      /**
       * The number of output components in each point.
       */
      unsigned int n_vectors;
      /**
       * The number of points in one direction.
       */
      unsigned int n_points;

      /**
       * Stream to which output is to be written. Set by initialize_stream().
       */
      std::ostream *os;
    };

    //----------------------------------------------------------------------//

    template <typename T>
    inline void
    GnuplotPatch::write(const T &d) const
    {
      if (os == nullptr)
        deallog << d;
      else
        (*os) << d;
    }


    inline void
    GnuplotPatch::write_endl() const
    {
      if (os == nullptr)
        deallog << std::endl;
      else
        (*os) << std::endl;
    }


    inline GnuplotPatch::GnuplotPatch()
      : n_vectors(numbers::invalid_unsigned_int)
      , n_points(numbers::invalid_unsigned_int)
      , os(nullptr)
    {}


    inline void
    GnuplotPatch::initialize(const unsigned int np, const unsigned int nv)
    {
      n_vectors = nv;
      n_points  = np;
    }


    inline void
    GnuplotPatch::initialize_stream(std::ostream &stream)
    {
      os = &stream;
    }


    template <int dim>
    inline void
    GnuplotPatch::initialize_info(DoFInfo<dim> &info, bool face)
    {
      if (face)
        info.initialize_quadrature(Utilities::fixed_power<dim - 1>(n_points),
                                   n_vectors + dim);
      else
        info.initialize_quadrature(Utilities::fixed_power<dim>(n_points),
                                   n_vectors + dim);
    }


    template <int dim>
    inline void
    GnuplotPatch::assemble(const DoFInfo<dim> &info)
    {
      const unsigned int np = info.n_quadrature_points();
      const unsigned int nv = info.n_quadrature_values();
      const unsigned int patch_dim =
        (info.face_number == numbers::invalid_unsigned_int) ? dim : (dim - 1);
      const unsigned int row_length = n_points;
      // If patches are 1D, end the
      // patch after a row, else end
      // it after a square
      const unsigned int row_length2 =
        (patch_dim == 1) ? row_length : (row_length * row_length);

      //      AssertDimension(np, Utilities::fixed_power<dim>(n_points));
      AssertDimension(nv, n_vectors + dim);


      for (unsigned int k = 0; k < np; ++k)
        {
          if (k % row_length == 0)
            write_endl();
          if (k % row_length2 == 0)
            write_endl();

          for (unsigned int i = 0; i < nv; ++i)
            {
              write(info.quadrature_value(k, i));
              write('\t');
            }
          write_endl();
        }
    }


    template <int dim>
    inline void
    GnuplotPatch::assemble(const DoFInfo<dim> &info1, const DoFInfo<dim> &info2)
    {
      assemble(info1);
      assemble(info2);
    }
  } // namespace Assembler
} // namespace MeshWorker

DEAL_II_NAMESPACE_CLOSE

#endif
