function savemsh(node, elem, fname, rname)
%
% savemsh(node,elem,fname,rname)
%
% save a tetrahedral mesh to GMSH mesh format
%
% author: Riccardo Scorretti (riccardo.scorretti<at> univ-lyon1.fr)
% date: 2013/07/22
%
% input:
%      node: input, node list, dimension (nn,3)
%      elem: input, tetrahedral mesh element list, dimension (ne,4) or (ne,5) for multi-region meshes
%      fname: output file name
%      rname: name of the regions, cell-array of strings (optional)
%
% -- this function is part of iso2mesh toolbox (http://iso2mesh.sf.net)
%

if nargin < 4
    rname = {};
end
if size(elem, 2) < 5
    elem(:, 5) = 1;
end
fid = fopen(fname, 'wt');
if (fid == -1)
    error('You do not have permission to save mesh files.');
end

% Check that all the elements are correctly oriented
elem(:, 1:4) = meshreorient(node, elem(:, 1:4));

nbNodes = size (node, 1);
reg = unique (elem(:, 5));
reg(reg <= 0) = max(reg) + 1 - reg(reg <= 0); % convert label 0 to max(reg)+1, -1 to max(reg)+2, and so on
nbRegion = length (reg);
nbElements = size (elem, 1);

% Create the skeleton of the mesh structure
M.Info.version = [];

M.Nodes.nb = 0;
M.Nodes.x = [];
M.Nodes.y = [];
M.Nodes.z = [];

M.Elements.nb = 0;
M.Elements.type = zeros (0, 0, 'uint8');
M.Elements.tableOfNodes = zeros (0, 0, 'uint32');
M.Elements.region = zeros (0, 0, 'uint16');

M.Regions.nb = 0;
M.Regions.name = {};
M.Regions.dimension = [];

% Build the table of nodes

M.Nodes.nb = nbNodes;
M.Nodes.x = node(:, 1);
M.Nodes.y = node(:, 2);
M.Nodes.z = node(:, 3);
clear node;

% Build the table of elements

M.Elements.nb = nbElements;
M.Elements.type = uint8(4 * ones(nbElements, 1));
M.Elements.tableOfNodes = uint32(elem(:, 1:4));
M.Elements.region = uint16(elem(:, 5));
clear elem;

% Build the table of regions

M.Regions.nb = max(reg);
for k = 1:nbRegion
    if length(rname) < k
        rname{k} = sprintf('region_%d', k);
    end
    M.Regions.name{reg(k)} = sprintf ('%s', rname{k});
    M.Regions.dimension(reg(k)) = 3;
end

% Writhe the header
fprintf (fid, '$MeshFormat\n2.2 0 8\n$EndMeshFormat\n');

% Write the physical names
if M.Regions.nb > 0
    fprintf (fid, '$PhysicalNames\n');
    fprintf (fid, '%d\n', M.Regions.nb);
    for r = 1:M.Regions.nb
        name = M.Regions.name{r};
        if isempty (name)
            name = sprintf ('Region_%d', r);
        end
        fprintf (fid, '%d %d "%s"\n', M.Regions.dimension(r), r, name);
    end
    fprintf (fid, '$EndPhysicalNames\n');
end

% Write the nodes
fprintf (fid, '$Nodes\n');
fprintf (fid, '%d\n', size(M.Nodes.x, 1));
buffer = [1:M.Nodes.nb; M.Nodes.x'; M.Nodes.y'; M.Nodes.z'];
fprintf (fid, '%d %10.10f %10.10f %10.10f\n', buffer);
fprintf (fid, '$EndNodes\n');

% Write the elements
%
% In order to accelerate the printing, the elements are printed in groups of (blockSize) elements, and
% are grouped by (homogeneous) type. This variable sets the size of each group.
%
blockSize = 100000;

fprintf (fid, '$Elements\n');
fprintf (fid, '%d\n', M.Elements.nb);
for h = 1:blockSize:ceil(length(M.Elements.type) / blockSize) * blockSize
    e = h:min(length(M.Elements.type), h + blockSize - 1);  % = elements being considered
    type = unique (M.Elements.type(e));                    % = types of elements found in this group

    %
    % Process each type of element separately
    %
    for k = 1:length(type)
        if type(k) == 0
            continue
        end
        et = e(find(M.Elements.type(e) == type(k)));  % = elements of the group of the same type

        %
        % Determine the format for printing the elements
        %
        elementFormat = '%d %d %d %d %d %d\n';
        for n = 1:4
            elementFormat = [elementFormat '%d '];
        end
        elementFormat = [elementFormat '\n'];

        %
        % Collect in a buffer all the data of the elements of index (et)
        %
        buffer = zeros (10, length(et));
        buffer(1, :) = et;
        buffer(2, :) = type(k);
        buffer(3, :) = 3;
        buffer(4, :) = M.Elements.region(et);
        buffer(5, :) = M.Elements.region(et);
        buffer(6, :) = 0;
        for n = 1:4
            buffer(6 + n, :) = M.Elements.tableOfNodes(et, n);
        end

        %
        % Print all the homogeneous elements in the group with a single instruction
        %
        fprintf (fid, elementFormat, buffer);
    end
end
fprintf (fid, '$EndElements\n');

fclose(fid);
