//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2016 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef SYSTEMPROCESSSAVE_HPP
#define SYSTEMPROCESSSAVE_HPP

#include <Process.hpp>

#include <QFileDialog>
#include <QRegExp>
#include <GraphUtils.hpp>
#include <QTreeWidget>

/**
 * \file SystemProcessSave.hpp
 * File containing the processes to save data.
 */

class QDialog;
class Ui_SaveMeshDialog;
class Ui_ExportMeshDialog;
class Ui_ExportStackDialog;
class Ui_PlyCellGraphDlg;

namespace mgx 
{
  
  /**
   * \class StackSave SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * Save the data into a MGXS or INRIA format.
   *
   * Description of the MGXS file format
   * ===================================
   *
   * MGXS is a binary file format to store 3D images with all the metadata needed for proper rendering in MorphoGraphX.
   *
   * There are many versions, from 1.0 to 1.3. This version of MorphoGraphX will always generate MGXS files version 1.3.
   *
   * Each MGXS file starts with the ASCII string `MGXS` followed by a space, the version number in ASCII and another
   * space. For instance, for the current version, the file starts with the ASCII string `"MGXS 1.3 "`.
   *
   * A binary header follows the version number. New versions simply added fields to the header. The header is made of:
   *
   *   Field | Size (bytes) | type  | Version | Description
   * --------|--------------|-------|---------|-------------------------------------------------------------
   * isLabel |     1        | bool  |  1.1    | True if the stack's values are labels and not intensities
   * sxum    |     4        | float |  1.3    | Position of the stack's origin on the X axis, in micrometers
   * syum    |     4        | float |  1.3    | Position of the stack's origin on the Y axis, in micrometers
   * szum    |     4        | float |  1.3    | Position of the stack's origin on the Z axis, in micrometers
   * xsz     |     4        | uint  |  1.0    | Number of voxels along the X axis
   * ysz     |     4        | uint  |  1.0    | Number of voxels along the Y axis
   * zsz     |     4        | uint  |  1.0    | Number of voxels along the Z axis
   * xum     |     4        | float |  1.0    | Resolution along the X axis, in micrometers
   * yum     |     4        | float |  1.0    | Resolution along the Y axis, in micrometers
   * zum     |     4        | float |  1.0    | Resolution along the Z axis, in micrometers
   * datasz  |     8        | uint  |  1.0    | Size in bytes of the data. It should be `2*xsz*ysz*zsz`
   * cl      |     1        | uint  |  1.0    | Compression level
   *
   * If the compression level is 0, the header is then followed by the raw, uncompressed, data. Voxels are ordered
   * C-style (e.g. vertices consecutives along the X axis are consecutive in the file, vertices consecutives along the
   * Y axis are separated by `xsz` voxels in the file, and vertices consecutive in the Z axis are separated by `xsz*ysz`
   * voxels in the files).
   *
   * If the compression level is greater than 0, the stack is cut into slice. Each slice is compressed using the gzip
   * algorithm (using the `qCompress` Qt function). In the file, the size of the compressed slice is written as
   * a 4 bytes unsigned integer, followed by the compressed data. A pseudo code to read the compressed data is:
   *
   *     current_size = 0
   *     while current_size < datasz
   *         slice_size = read 4 bytes
   *         compressed_data = read slice_size bytes
   *         data = uncompress compressed_data
   *         store data
   *         current_size += size of data
   *
   * **Note** before version 1.3, if the stack was labeled, the label numbers were store as big endian. Starting 1.3,
   * they are stores as little endian. Stack intensities are always stored as little endian.
   *
   * \ingroup StackProcess
   */
  class mgx_EXPORT StackSave : public Process {
  public:
    StackSave(const Process& proc) : Process(proc)
    {
      setName("Stack/System/Save");
      setDesc("Save a stack into a known 3D image format");

      addParm("Filename","Filename","");		// 0
      addParm("Store","Store","Main", storeChoice());		// 1
      addParm("Stack Number","Stack number","0");		// 2
      addParm("Compression Level","Compression level, from 0 to 9","5");		// 3
      addParm("HDF5 DataSetName","HDF5 DataSetName","/label");    // 4
    }

    bool initialize(QWidget* parent);

    bool run();
    /**
     * Save the stack
     * \param stack Stack containing the data
     * \param store Store containing the data
     * \param filename File in which to save the data, the extension must be
     * .mgxs or .inr
     */
    bool run(Stack* stack, Store* store, const QString& filename, int compressionLevel = 0);
    QIcon icon() const { return QIcon(":/images/save.png"); }
  };
  
  /**
   * \class StackExport SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * Export the stack data into a stack of images.
   *
   * \ingroup StackProcess
   */
  class mgx_EXPORT StackExport : public Process {
    Q_OBJECT
  public:
    StackExport(const Process& proc) : Process(proc)
    {
      setName("Stack/System/Export");
      setDesc("Export a stack into an image sequence.");

      addParm("Filename","Filename","");		// 0
      addParm("Store","Store","Main", storeChoice());		// 1
      addParm("Format","Format","CImg Auto");		// 2
      addParm("Generate Voxel Spacing","Generate Voxel Spacing","No", booleanChoice()); // 3
      addParm("Stack Number","Stack number","0");		// 4
      addParm("Num Digits","Number of digits for file numbering","0");		// 5
    }

    bool initialize(QWidget* parent);

    bool run();
    /**
     * Export the stack
     * \param stack Stack containing the image
     * \param store Store containing the image
     * \param filename Prefix of the file to store the data in
     * \param type Type of the file. Type must be one of 'DML', 'TIF', 'CIMG' or 'CImg Auto'. If 'CImg Auto', the
     * actual type will be deduced by the file extension, and the CImg library will try to save it using its own data
     * formats, Image Magick or Graphics Magick.
     * \param nb_digit Number of digits to use to generate the file numbering. If 0, it will use an optimal number of
     * digits.
     * \param generate_voxel_spacing If true, the process also generate a voxelspacing.txt file for easy import into
     * VolViewer.
     */
    bool run(Stack* stack, Store* store, const QString& filename, const QString& type,
             unsigned int nb_digit, bool generate_voxel_spacing);
    QIcon icon() const { return QIcon(":/images/save.png"); }

  protected slots:
    void selectImageFile();
    void setImageType(const QString& type);

  protected:
    Ui_ExportStackDialog* ui;
    QDialog* dlg;
  };
  
  /**
   * \class MeshSave SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * Save a mesh into a INRIA or MGXM file.
   *
   * Description of the MGXM file format
   * ===================================
   * MGXM is a binary file format to store triangular meshes with all the data and meta-data used by MorphoGraphX.
   *
   * There are a few versions, from 1.0 to 1.2. This version of MorphoGraphX will always generate MGXM files version
   * 1.2.
   *
   * Each MGXM file starts with the ASCII string `MGXM` followed by a space, the version number in ASCII and another
   * space. For instance, for the current version, the file starts with the ASCII string `"MGXM 1.2 "`.
   *
   * A binary header follows the version number. The header is made of:
   *
   *   Field       | Size (bytes) | type  | Version | Description
   * --------------|--------------|-------|---------|-------------------------------------------------------------
   *  is_cells     |      1       | bool  |  1.2    | If true, the mesh represent 2D cells (see notes)
   *  scale        |      1       | bool  |  1.1    | If true, the mesh has been scaled when saved
   *  transform    |      1       | bool  |  1.1    | If true, the mesh has been transformed rigidly
   *  signalLow    |      4       | float |  1.1    | Lower value of the signal
   *  signalHigh   |      4       | float |  1.1    | Upper value of the signal
   *  signalUnit   |      *       | utf8  |  1.1    | Text of the signal unit (see notes)
   *  header_size  |      4       | uint  |  1.0    | Specified the size in bytes of extra data placed after the header
   *  vertex_cnt   |      4       | uint  |  1.0    | Number of vertices in the mesh
   *  vertex_size  |      4       | uint  |  1.0    | Size in bytes of the structure describing a vertex, minus 12.
   *  edge_size    |      4       | uint  |  1.0    | Size in bytes of the structure describing an edge.
   *
   * The description of the vertices follows the header. For each vertex, a structure of size `vertex_size+12` is
   * written. Only fields fitting in the given size are actually written, and any other data is ignored. Note that the
   * first four field are always there!
   *
   *   Field       | Size (bytes) | type  |  Description
   * --------------|--------------|-------|----------------------------------------------------------------------
   *   Id          |      4       | uint  | Id of the vertex, used to reference it in other places in the file
   *   X           |      4       | float | X position of the vertex
   *   Y           |      4       | float | Y position of the vertex
   *   Z           |      4       | float | Z position of the vertex
   *   label       |      4       | uint  | Label of the vertex
   *   color       |      4       | float | "Color" of the vertex (e.g. like signal, but scaled from 0 to 1)
   *   signal      |      4       | float | Signal of the vertex
   *   type        |      1       | char  | type of vertex `'c'` for center and `'j'` for junction.
   *   selected    |      1       | bool  | If true, the vertex is selected
   *
   * Currently, the edges have no defined fields. For each vertex, the `Id` of the vertex is written, followed by a 32
   * bits unsigned integer for the number of neighbors, and the list of `Id` for each neighbor. Neighbors of a vertex
   * are always written counter-clockwise.
   *
   * Notes on UTF-8 strings
   * ----------------------
   *
   * UTF-8 strings are written as: their size in bytes as a 32 bits unsigned integer, followed by the bytes themselves.
   *
   * Notes on vertex labels
   * ----------------------
   *
   * Vertex are labels to mark which cells they are part of. Boundary vertices are labeled with -1 or -2. -1 mark
   * vertices between cells, and -2 vertices at the border of the whole mesh.
   *
   * Notes on cell mesh
   * ------------------
   *
   * If the mesh is a cell mesh, then each cell has a unique labeled vertex on its center, surrounded by cell-boundary
   * vertices, labels -1 or -2. In this case, labeled vertices have type `'c'` and boundary vertices `'j'`.
   *
   * \ingroup MeshProcess
   */
  class mgx_EXPORT MeshSave : public Process {
    Q_OBJECT
  public:
    MeshSave(const Process& proc) : Process(proc)
    {
      setName("Mesh/System/Save");
      setDesc("Save a mesh into a known mesh format");

      addParm("Filename","Filename","");		// 0
      addParm("Transform","Transform","No", booleanChoice());		// 1
      addParm("Mesh Number","Mesh number","0", QStringList() << "0" << "1");		// 2
    }

    bool run();
    /**
     * Save a mesh
     * \param mesh Mesh to be saved
     * \param filename File to save the mesh in. Its extension must be either .inr or .mgmx
     * \[aram transform Save the transformed positions
     */
    bool run(Mesh* mesh, const QString& filename, bool transform);
    QIcon icon() const { return QIcon(":/images/save.png"); }
    bool initialize(QWidget* parent);

  protected slots:
    void selectMeshFile();

  protected:
    QString properFile(QString filename) const;
    void setMeshFile(const QString& filename);
    Point3d savedPos(Point3d pos, bool transform, const Stack* stack);

    bool saveMesh(Mesh* mesh, const QString& filename, bool transform);

    QDialog* dlg;
    Ui_SaveMeshDialog* ui;
  };
  

  /**
   * \class MeshExport SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * Export a mesh on various formats: \ref PLY, \ref VTK, \ref text, \ref cells, \ref MeshEdit, \ref STL or \ref OBJ.
   *
   * We will here describe the file formats, or at least how MorphoGraphX uses them.
   *
   * \section PLY PLY format
   *
   * The PLY format is fully described on the website of the Paul Bourke: http://paulbourke.net/dataformats/ply/
   *
   * It is also available on Wikipedia: https://en.wikipedia.org/wiki/PLY_(file_format)
   *
   * MorphoGraphX exports only the two kind of elements described as mandatory: vertex and face.
   *
   * In the case of a cell mesh, each vertex contains only its position (x,y,z). Otherwise, vertices also have their
   * label.
   *
   * In any case, each face contains the list of 0-based index into the vertex elements that form the face, and the
   * label marking which cell the face is a part of. Vertices are oriented counter-clockwise.
   *
   * \section VTK VTK format
   *
   * MorphoGraphX save VTU format, which are XML files for unstructure grids.
   *
   * The file format is quite complex, and fully described in the VTK book and website.
   *
   * MorphoGraphX stores the following properties in addition to the mandatory ones:
   *
   * For each vertex: 'Signal' (float32), 'Normals' (3 floats32), 'Color' (float32) and 'Label' (int32)
   *
   * For each "cell" (e.g. triangles or 2D cells): 'Label' (int32)
   *
   * MorphoGraphX also adds a DataArray to store the heat per VTU "cell". The name is either "Cell Heat" or "Wall Heat".
   *
   * At last, an extra DataArray is created, store the heat per label instead of per cell.
   *
   * \section text "text" format
   *
   * The text format is a trivial ASCII file format. The file contains:
   *
   *     n
   *     vertex 0
   *     vertex 1
   *     ...
   *     vertex n-1
   *     neighbors 0
   *     neighbors 1
   *     ...
   *     neighbors n-1
   *
   * Each vertex is described as:
   *
   *     id x y z label
   *
   * Each neighborhood is described as:
   *
   *     id m id_1 id_2 ... id_m
   *
   * Where `id` is the id of the current vertex, `m` the number of neighbors, and `id_1`, `id_2` are vertex ids as
   * described before. The vertex must be ordered counter-clockwise.
   *
   * \section cells "cells" format
   *
   * This format is very similar to the "text" format. Only each vertex is described as:
   *
   *     id x z y label type
   *
   * Where `type` is `j` for junctions and `c` for cell centers.
   *
   * \section MeshEdit MeshEdit format
   *
   * This file format is the one used by the MeshEdit software. In MorphoGraphX, the file has the following format
   *
   *     MeshVersionFormatted 1
   *     Dimension 3
   *     Vertices
   *     vertex 1
   *     vertex 2
   *     ...
   *     vertex n
   *     Triangles
   *     triangle 1
   *     triangle 2
   *     ...
   *     triangle m
   *     End
   *
   * Each vertex is described as:
   *
   *     x y z label
   *
   * And each triangle is described as:
   *
   *     v0 v1 v2 label
   *
   * Again, the vertices must be ordered counter-clockwise.
   *
   * \section STL STL format
   *
   * The STL file format is the one used in many CAD software, including Abaqus.
   *
   * In MorphoGraphX, the mesh is stored as a single solid called "mgx". The file structure is:
   *
   *     solid mgx
   *     facet 1
   *     facet 2
   *     ...
   *     facet n
   *     endsolid mgx
   *
   * Where each facet is described as:
   *
   *     facet normal nx ny nz
   *       outer loop
   *         vertex vx1 vy1 vz1
   *         vertex vx2 vy2 vz2
   *         vertex vx3 vy3 vz3
   *       endloop
   *     endfacet
   *
   * \section OBJ OBJ format
   *
   * MorphoGraphX can also export files in the WaveFront's OBJ file format.
   *
   * The file has the following structure:
   *
   *     # Triangular mesh created by MorphoGraphX
   *     # Length unit: µm
   *     # Vertices
   *     vertex 1
   *     vertex 2
   *     ...
   *     vertex n
   *     # Triangles
   *     triangle 1
   *     triangle 2
   *     ...
   *     triangle m
   *
   * Each vertex is stored as:
   *
   *     v x y z
   *     vn nx ny nz
   *
   * And each triangle as:
   *
   *     f v1 v2 v3
   *
   * where `v1`, `v2` and `v3` are 1-based index in the vertex array. Also, the vertices are oriented counter-clockwise.
   *
   * \ingroup MeshProcess
   */

  bool savePLYCellGraph(Mesh* mesh, const QString& filename, bool binary);

  class mgx_EXPORT MeshExport : public Process {
    Q_OBJECT
  public:
    MeshExport(const Process& proc) : Process(proc)
    {
      setName("Mesh/System/Export");
      setDesc("Export a mesh into a known mesh format");

      addParm("Filename","Filename","");		// 0
      addParm("Kind","Kind","Text", QStringList() << "PLY Binary" << "PLY Ascii" << "VTK Mesh Binary"
              << "VTK Mesh Ascii" << "Text" << "Cells" << "MeshEdit" << "STL" << "OBJ");		// 1
      addParm("Transform","Transform","no", booleanChoice());		// 2
      addParm("Mesh Number","Mesh number","0", booleanChoice());		// 3
      addParm("Extended PLY","If the PLY format is chosen, save a mesh file with extended information","No", booleanChoice());    // 2
    }

    bool run();
    /**
     * Export a mesh
     * \param mesh Mesh to be exported
     * \param filename File to save the mesh into
     * \param type Type (format) of the export.
     * \param transform Save the transformed positions
     */
    bool run(Mesh* mesh, const QString& filename, const QString& type, bool transform);
    QIcon icon() const { return QIcon(":/images/save.png"); }
    bool initialize(QWidget* parent);

    bool savePLY(Mesh* mesh, const QString& filename, bool transform, bool binary = false); // Soeren: made public to be accessible by new 2-file Ply process in HeatMap

  protected slots:
    void selectMeshFile();
    void selectMeshType(const QString& type);

  protected:
    QString properFile(QString filename, const QString& type) const;
    void setMeshFile(const QString& filename);
    Point3d savedPos(Point3d pos, bool transform, const Stack* stack);

    bool saveText(Mesh* mesh, const QString& filename, bool transform);
    bool saveCells(Mesh* mesh, const QString& filename, bool transform);
    bool saveMeshEdit(Mesh* mesh, const QString& filename, bool transform);
    bool saveMeshSTL(Mesh* mesh, const QString& filename, bool transform);
    bool saveVTU(Mesh* mesh, const QString& filename, bool transform, bool binary = false);
    bool saveOBJ(Mesh* mesh, const QString& filename, bool transform);

    QDialog* dlg;
    Ui_ExportMeshDialog* ui;

    bool extendedPLY = false;
  };
  
  /**
   * \class SaveViewFile SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * Save the view file
   *
   * \ingroup MiscellaneousProcess
   */
  class mgx_EXPORT SaveViewFile : public Process {
  public:
    SaveViewFile(const Process& proc) : Process(proc)
    {
      setName("Misc/System/Save View");
      setDesc("Save the view file for the current configuration.");

      addParm("Filename","Filename","");		// 0
    }

    bool run();
    /**
     * Save the view file
     * \param filename File to save the parameters in
     */
    bool run(const QString& filename);

    bool initialize(QWidget* parent);

    QIcon icon() const { return QIcon(":/images/save.png"); }
  };
  
  /**
   * \class QuickSaveFile SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * Save all active files
   *
   * \ingroup MiscellaneousProcess
   */
  class mgx_EXPORT QuickSaveFile : public Process {
  public:
    QuickSaveFile(const Process& proc) : Process(proc)
    {
      setName("Misc/System/Quick Save");
      setDesc("Save all active mesh, stack and view files.");

      addParm("Save with File Extensions","Save with File Extensions","No", booleanChoice());		// 0
      addParm("Extension Counter","Extension Counter","1");		// 1
    }

    void saveStore(int stackNr, Store* store, int compressionLevel);
    void saveMesh(int meshNr);

    QString addIncSuffix(QString filename, QString fileExtension);

    bool saveWithExt;
    int extCounter;

    bool initialize(QWidget* parent);

    /**
     * Save the view file
     * \param filename File to save the parameters in
     */
    bool run();

    QIcon icon() const { return QIcon(":/images/save.png"); }
  };

  /**
   * \class TakeSnapshot SystemProcessSave.hpp <SystemProcessSave.hpp>
   *
   * This process allows scripts and other processes to take snapshots of the current result.
   *
   * Parameter       | Description
   * ----------------|------------
   * Filename        | Name of the output file. The extension will define will file format is used.
   * Expend Frustrum | If true and the widht/height ratio is not the same as the screen, the frustrum will be expended
   * Width           | Width of the image
   * Height          | Height of the image
   * Oversampling    | Oversampling factor (see Notes)
   * Quality         | For lossy file formats, quality of the image on a scale from 0 to 100.
   *
   * Note on oversampling
   * ====================
   *
   * If oversampling is not 1, then the scene is rendered on an image with width and height multiplied by the
   * oversampling. The result is then scaled back to the wanted size, using bilinear filtering.
   *
   * \ingroup MiscellaneousProcess
   */
  class mgx_EXPORT TakeSnapshot : public Process {
  public:
    TakeSnapshot(const Process& proc) : Process(proc)
    {
      setName("Misc/System/Snapshot");
      setDesc("Take a snapshot of the current view");

      addParm("Filename","Filename","");		// 0
      addParm("Expand Frustum","Expand Frustum","false");		// 1
      addParm("Width","Width","0");		// 2
      addParm("Height","Height","0");		// 3
      addParm("Oversampling","Oversampling","1.0");		// 4
      addParm("Quality","Quality","95");		// 5
    }

    bool run()
    {
      QString filename = parm("Filename");
      if(filename.isEmpty())
        return setErrorMessage("Error, no file name specified.");
      int width = parm("Width").toInt();
      int height = parm("Height").toInt();
      float overSampling = parm("Oversampling").toFloat();
      int quality = parm("Quality").toInt();
      bool expand_frustum = stringToBool(parm("Expand Frustum"));
      return run(filename, overSampling, width, height, quality, expand_frustum);
    }

    bool run(QString filename, float overSampling, int width, int height, int quality, bool expand_frustum);

    QIcon icon() const { return QIcon(":/images/SaveScreenshot.png"); }
  };


  /**
   * \class StackOpenSaveFolder
   *
   * Opens and saves a compressed TIFF for all tif stacks of a folder
   */
  class mgx_EXPORT StackOpenSaveFolder : public Process {
  public:
    StackOpenSaveFolder(const Process& proc) : Process(proc)
    {
      setName("Stack/System/Batch Open and Save");
      setDesc("Opens and saves a compressed TIFF for all tif stacks of a folder");

      addParm("Folder","Folder","");		// 0
      addParm("File Prefix","File Prefix for new files","MGX_");		// 1
      addParm("Compression Level","Compression Level (-1 to 9)","9");		// 2
    }
  
    bool run();

    QStringList images;

    bool initialize(QWidget* parent);
    QIcon icon() const { return QIcon(":/images/open.png"); }
  };

  class CellGraph3D : public Process
  {
  public:
    CellGraph3D(const Process& process) : Process(process) 
    {
      setName("Mesh/Export/Cell Graph 3D");
      setDesc("Export the 3D cell graph");
      setIcon(QIcon(":/images/CellGraph3D.png"));
    
      addParm("File Name", "File name to save cell graph","CellGraph3D.csv");
      addParm("Min Area", "Minimum area of cell-cell interfaces to include in CSV file, 0 for all", "0.0");
      addParm("Tolerance", "Tolerance for collapsing shared vertices", "0.0001");
    }

    bool run()
    {
        if(!checkState().mesh(MESH_NON_EMPTY))
          return false;

        Mesh *m = currentMesh();
        bool res = run(m, parm("File Name"), parm("Min Area").toDouble(), parm("Tolerance").toDouble());
        if(res)
          m->showHeat();
        return res;
    }

    bool run(Mesh* mesh, const QString &fileName, double minArea, double tolerance);
  };
}

#endif
