//------------------------------------------------------------------------------
// CHOLMOD/Check/cholmod_write: write a matrix to a file in Matrix Market format
//------------------------------------------------------------------------------

// CHOLMOD/Check Module.  Copyright (C) 2005-2023, Timothy A. Davis
// All Rights Reserved.
// SPDX-License-Identifier: LGPL-2.1+

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

// Write a matrix to a file in Matrix Market form.
//
// A can be sparse or full.
//
// If present and non-empty, A and Z must have the same dimension.  Z contains
// the explicit zero entries in the matrix (which MATLAB drops).  The entries
// of Z appear as explicit zeros in the output file.  Z is optional.  If it is
// an empty matrix it is ignored.  Z must be sparse or empty, if present.
// It is ignored if A is full.
//
// filename is the name of the output file.  comments is file whose
// contents are include after the Matrix Market header and before the first
// data line.  Ignored if an empty string or not present.
//
// Except for the workspace used by cholmod_symmetry (ncol integers) for
// the sparse case, these routines use no workspace at all.

#ifndef NCHECK

#include "cholmod_internal.h"

#define MMLEN 1024
#define MAXLINE MMLEN+6

//------------------------------------------------------------------------------
// include_comments
//------------------------------------------------------------------------------

// Read in the comments file, if it exists, and copy it to the Matrix Market
// file.  A "%" is prepended to each line.  Returns TRUE if successful, FALSE
// otherwise.

static int include_comments (FILE *f, const char *comments)
{
    FILE *cf = NULL ;
    char buffer [MAXLINE] ;
    int ok = TRUE ;
    if (comments != NULL && comments [0] != '\0')
    {
        cf = fopen (comments, "r") ;
        if (cf == NULL)
        {
            return (FALSE) ;
        }
        while (ok && fgets (buffer, MAXLINE, cf) != NULL)
        {
            // ensure the line is not too long
            buffer [MMLEN-1] = '\0' ;
            buffer [MMLEN-2] = '\n' ;
            ok = ok && (fprintf (f, "%%%s", buffer) > 0) ;
        }
        fclose (cf) ;
    }
    return (ok) ;
}

//------------------------------------------------------------------------------
// get_value
//------------------------------------------------------------------------------

// Get the pth value in a double or float matrix.  Resulting scalar is returned
// as double (x and z).

static void get_value
(
    void *Ax_in,    // real values, or real/imag. for CHOLMOD_COMPLEX type
    void *Az_in,    // imaginary values for CHOLMOD_ZOMPLEX type
    Int p,          // get the pth entry
    int xtype,      // A->xtype: pattern, real, complex, or zomplex
    int dtype,      // A->dtype: double or single
    double *x,      // the real part
    double *z       // the imaginary part
)
{
    #define GET_VALUE(fltype)               \
    {                                       \
        fltype *Ax = (fltype *) Ax_in ;     \
        fltype *Az = (fltype *) Az_in ;     \
        switch (xtype)                      \
        {                                   \
            case CHOLMOD_PATTERN:           \
                *x = 1 ;                    \
                *z = 0 ;                    \
                break ;                     \
            case CHOLMOD_REAL:              \
                *x = (double) Ax [p] ;      \
                *z = 0 ;                    \
                break ;                     \
            case CHOLMOD_COMPLEX:           \
                *x = (double) Ax [2*p] ;    \
                *z = (double) Ax [2*p+1] ;  \
                break ;                     \
            case CHOLMOD_ZOMPLEX:           \
                *x = (double) Ax [p] ;      \
                *z = (double) Az [p] ;      \
                break ;                     \
        }                                   \
    }

    if (dtype == CHOLMOD_DOUBLE)
    {
        GET_VALUE (double) ;
    }
    else
    {
        GET_VALUE (float) ;
    }
}

//------------------------------------------------------------------------------
// print_value
//------------------------------------------------------------------------------

// Print a numeric value to the file, using the shortest format that ensures
// the value is written precisely.  Returns TRUE if successful, FALSE otherwise.

static int print_value
(
    FILE *f,        // file to print to
    double x,       // value to print
    Int is_integer  // TRUE if printing as an integer
)
{
    double y ;
    char s [MAXLINE], *p ;
    Int i, dest = 0, src = 0 ;
    int width, ok ;

    if (is_integer)
    {
        i = (Int) x ;
        ok = (fprintf (f, ID, i) > 0) ;
        return (ok) ;
    }

    //--------------------------------------------------------------------------
    // handle Inf and NaN
    //--------------------------------------------------------------------------

    // change -inf to -HUGE_DOUBLE, and change +inf and nan to +HUGE_DOUBLE
    if (isnan (x) || x >= HUGE_DOUBLE)
    {
        x = HUGE_DOUBLE ;
    }
    else if (x <= -HUGE_DOUBLE)
    {
        x = -HUGE_DOUBLE ;
    }

    //--------------------------------------------------------------------------
    // find the smallest acceptable precision
    //--------------------------------------------------------------------------

    for (width = 6 ; width < 20 ; width++)
    {
        sprintf (s, "%.*g", width, x) ;
        sscanf (s, "%lg", &y) ;
        if (x == y) break ;
    }

    //--------------------------------------------------------------------------
    // shorten the string
    //--------------------------------------------------------------------------

    // change "e+0" to "e", change "e+" to "e", and change "e-0" to "e-"
    for (i = 0 ; i < MAXLINE && s [i] != '\0' ; i++)
    {
        if (s [i] == 'e')
        {
            if (s [i+1] == '+')
            {
                dest = i+1 ;
                if (s [i+2] == '0')
                {
                    // delete characters s[i+1] and s[i+2]
                    src = i+3 ;
                }
                else
                {
                    // delete characters s[i+1]
                    src = i+2 ;
                }
            }
            else if (s [i+1] == '-')
            {
                dest = i+2 ;
                if (s [i+2] == '0')
                {
                    // delete character s[i+2]
                    src = i+3 ;
                }
                else
                {
                    // no change
                    break ;
                }
            }
            while (s [src] != '\0')
            {
                s [dest++] = s [src++] ;
            }
            s [dest] = '\0' ;
            break ;
        }
    }

    // delete the leading "0" if present and not necessary
    p = s ;
    s [MAXLINE-1] = '\0' ;
    i = strlen (s) ;
    if (i > 2 && s [0] == '0' && s [1] == '.')
    {
        // change "0.x" to ".x"
        p = s + 1 ;
    }
    else if (i > 3 && s [0] == '-' && s [1] == '0' && s [2] == '.')
    {
        // change "-0.x" to "-.x"
        s [1] = '-' ;
        p = s + 1 ;
    }

    //--------------------------------------------------------------------------
    // print the value to the file
    //--------------------------------------------------------------------------

    ok = (fprintf (f, "%s", p) > 0) ;
    return (ok) ;
}

//------------------------------------------------------------------------------
// print_triplet
//------------------------------------------------------------------------------

// Print a triplet, converting it to one-based.  Returns TRUE if successful,
// FALSE otherwise.

static int print_triplet
(
    FILE *f,            // file to print to
    Int is_binary,      // TRUE if file is "pattern"
    Int is_complex,     // TRUE if file is "complex"
    Int is_integer,     // TRUE if file is "integer"
    Int i,              // row index (zero-based)
    Int j,              // column index (zero-based)
    double x,           // real part
    double z            // imaginary part
)
{
    int ok ;
    ok = (fprintf (f, ID " " ID, 1+i, 1+j) > 0) ;
    if (!is_binary)
    {
        fprintf (f, " ") ;
        ok = ok && print_value (f, x, is_integer) ;
        if (is_complex)
        {
            fprintf (f, " ") ;
            ok = ok && print_value (f, z, is_integer) ;
        }
    }
    ok = ok && (fprintf (f, "\n") > 0) ;
    return (ok) ;
}

//------------------------------------------------------------------------------
// ntriplets
//------------------------------------------------------------------------------

// Compute the number of triplets that will be printed to the file
// from the matrix A.

static Int ntriplets
(
    cholmod_sparse *A,      // matrix that will be printed
    Int is_sym              // TRUE if the file is symmetric (lower part only)
)
{
    Int *Ap, *Ai, *Anz, packed, i, j, p, pend, ncol, stype, nz = 0 ;
    if (A == NULL)
    {
        // the Z matrix is NULL
        return (0) ;
    }
    stype = A->stype ;
    Ap = A->p ;
    Ai = A->i ;
    Anz = A->nz ;
    packed = A->packed ;
    ncol = A->ncol ;
    for (j = 0 ; j < ncol ; j++)
    {
        p = Ap [j] ;
        pend = (packed) ? Ap [j+1] : p + Anz [j] ;
        for ( ; p < pend ; p++)
        {
            i = Ai [p] ;
            if ((stype < 0 && i >= j) || (stype == 0 && (i >= j || !is_sym)))
            {
                // CHOLMOD matrix is symmetric-lower (and so is the file);
                // or CHOLMOD matrix is unsymmetric and either A(i,j) is in
                // the lower part or the file is unsymmetric.
                nz++ ;
            }
            else if (stype > 0 && i <= j)
            {
                // CHOLMOD matrix is symmetric-upper, but the file is
                // symmetric-lower.  Need to transpose the entry.
                nz++ ;
            }
        }
    }
    return (nz) ;
}

//------------------------------------------------------------------------------
// cholmod_write_sparse
//------------------------------------------------------------------------------

// Write a sparse matrix to a file in Matrix Market format.   Optionally include
// comments, and print explicit zero entries given by the pattern of the Z
// matrix.  If not NULL, the Z matrix must have the same dimensions and stype
// as A.
//
// Returns the symmetry in which the matrix was printed (1 to 7, see the
// CHOLMOD_MM_* codes in CHOLMOD/Include/cholmod.h), or -1 on failure.
//
// If A and Z are sorted on input, and either unsymmetric (stype = 0) or
// symmetric-lower (stype < 0), and if A and Z do not overlap, then the triplets
// are sorted, first by column and then by row index within each column, with
// no duplicate entries.  If all the above holds except stype > 0, then the
// triplets are sorted by row first and then column.

int CHOLMOD(write_sparse)
(
    // input:
    FILE *f,                // file to write to, must already be open
    cholmod_sparse *A,      // matrix to print
    cholmod_sparse *Z,      // optional matrix with pattern of explicit zeros
    const char *comments,   // optional filename of comments to include
    cholmod_common *Common
)
{
    double x = 0, z = 0 ;
    void *Ax, *Az ;
    Int *Ap, *Ai, *Anz, *Zp, *Zi, *Znz ;
    Int nrow, ncol, is_complex, symmetry, i, j, q, iz, p, nz, is_binary, stype,
        is_integer, asym, is_sym, apacked, zpacked, pend, qend, zsym ;
    int ok, xtype, dtype ;

    //--------------------------------------------------------------------------
    // check inputs
    //--------------------------------------------------------------------------

    RETURN_IF_NULL_COMMON (EMPTY) ;
    RETURN_IF_NULL (f, EMPTY) ;
    RETURN_IF_NULL (A, EMPTY) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
    if (Z != NULL && (Z->nrow == 0 || Z->ncol == 0))
    {
        // Z is non-NULL but empty, so treat it as a NULL matrix
        Z = NULL ;
    }
    if (Z != NULL)
    {
        RETURN_IF_XTYPE_INVALID (Z, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
        if (Z->nrow != A->nrow || Z->ncol != A->ncol || Z->stype != A->stype)
        {
            ERROR (CHOLMOD_INVALID, "dimension or type of A and Z mismatch") ;
            return (EMPTY) ;
        }
    }
    Common->status = CHOLMOD_OK ;

    //--------------------------------------------------------------------------
    // get the A matrix
    //--------------------------------------------------------------------------

    Ap = A->p ;
    Ai = A->i ;
    Ax = A->x ;
    Az = A->z ;
    Anz = A->nz ;
    nrow = A->nrow ;
    ncol = A->ncol ;
    xtype = A->xtype ;
    dtype = A->dtype ;
    apacked = A->packed ;

    if (xtype == CHOLMOD_PATTERN)
    {
        // a CHOLMOD pattern matrix is printed as "pattern" in the file
        is_binary = TRUE ;
        is_integer = FALSE ;
        is_complex = FALSE ;
    }
    else if (xtype == CHOLMOD_REAL)
    {
        // determine if a real matrix is in fact binary or integer
        is_binary = TRUE ;
        is_integer = TRUE ;
        is_complex = FALSE ;
        for (j = 0 ; (is_binary || is_integer) && j < ncol ; j++)
        {
            p = Ap [j] ;
            pend = (apacked) ? Ap [j+1] : p + Anz [j] ;
            for ( ; (is_binary || is_integer) && p < pend ; p++)
            {
                get_value (Ax, Az, p, xtype, dtype, &x, &z) ;
                if (x != 1)
                {
                    is_binary = FALSE ;
                }
                // convert to Int and then back to double
                i = (Int) x ;
                z = (double) i ;
                if (z != x)
                {
                    is_integer = FALSE ;
                }
            }
        }
    }
    else
    {
        // a CHOLMOD complex matrix is printed as "complex" in the file
        is_binary = FALSE ;
        is_integer = FALSE ;
        is_complex = TRUE ;
    }

    //--------------------------------------------------------------------------
    // get the Z matrix (only consider the pattern)
    //--------------------------------------------------------------------------

    Zp = NULL ;
    Zi = NULL ;
    Znz = NULL ;
    zpacked = TRUE ;
    if (Z != NULL)
    {
        Zp = Z->p ;
        Zi = Z->i ;
        Znz = Z->nz ;
        zpacked = Z->packed ;
    }

    //--------------------------------------------------------------------------
    // determine the symmetry of A and Z
    //--------------------------------------------------------------------------

    stype = A->stype ;
    if (A->nrow != A->ncol)
    {
        asym = CHOLMOD_MM_RECTANGULAR ;
    }
    else if (stype != 0)
    {
        // CHOLMOD's A and Z matrices have a symmetric (and matching) stype.
        // Note that the diagonal is not checked.
        asym = is_complex ? CHOLMOD_MM_HERMITIAN : CHOLMOD_MM_SYMMETRIC ;
    }
    else if (!A->sorted)
    {
        // A is in unsymmetric storage, but unsorted
        asym = CHOLMOD_MM_UNSYMMETRIC ;
    }
    else
    {
        // CHOLMOD's stype is zero (stored in unsymmetric form)
        asym = EMPTY ;
        zsym = EMPTY ;

#ifndef NMATRIXOPS
        // determine if the matrices are in fact symmetric or Hermitian
        asym = CHOLMOD(symmetry) (A, 1, NULL, NULL, NULL, NULL, Common) ;
        zsym = (Z == NULL) ? 999 :
               CHOLMOD(symmetry) (Z, 1, NULL, NULL, NULL, NULL, Common) ;
#endif

        if (asym == EMPTY || zsym <= CHOLMOD_MM_UNSYMMETRIC)
        {
            // not computed, out of memory, or Z is unsymmetric
            asym = CHOLMOD_MM_UNSYMMETRIC ;
        }
    }

    //--------------------------------------------------------------------------
    // write the Matrix Market header
    //--------------------------------------------------------------------------

    ok = fprintf (f, "%%%%MatrixMarket matrix coordinate") > 0 ;

    if (is_complex)
    {
        ok = ok && (fprintf (f, " complex") > 0) ;
    }
    else if (is_binary)
    {
        ok = ok && (fprintf (f, " pattern") > 0) ;
    }
    else if (is_integer)
    {
        ok = ok && (fprintf (f, " integer") > 0) ;
    }
    else
    {
        ok = ok && (fprintf (f, " real") > 0) ;
    }

    is_sym = FALSE ;

    switch (asym)
    {
        case CHOLMOD_MM_RECTANGULAR:
        case CHOLMOD_MM_UNSYMMETRIC:
            // A is rectangular or unsymmetric
            ok = ok && (fprintf (f, " general\n") > 0) ;
            is_sym = FALSE ;
            symmetry = CHOLMOD_MM_UNSYMMETRIC ;
            break ;

        case CHOLMOD_MM_SYMMETRIC:
        case CHOLMOD_MM_SYMMETRIC_POSDIAG:
            // A is symmetric
            ok = ok && (fprintf (f, " symmetric\n") > 0) ;
            is_sym = TRUE ;
            symmetry = CHOLMOD_MM_SYMMETRIC ;
            break ;

        case CHOLMOD_MM_HERMITIAN:
        case CHOLMOD_MM_HERMITIAN_POSDIAG:
            // A is Hermitian
            ok = ok && (fprintf (f, " Hermitian\n") > 0) ;
            is_sym = TRUE ;
            symmetry = CHOLMOD_MM_HERMITIAN ;
            break ;

        case CHOLMOD_MM_SKEW_SYMMETRIC:
            // A is skew symmetric
            ok = ok && (fprintf (f, " skew-symmetric\n") > 0) ;
            is_sym = TRUE ;
            symmetry = CHOLMOD_MM_SKEW_SYMMETRIC ;
            break ;
    }

    //--------------------------------------------------------------------------
    // include the comments if present
    //--------------------------------------------------------------------------

    ok = ok && include_comments (f, comments) ;

    //--------------------------------------------------------------------------
    // write a sparse matrix (A and Z)
    //--------------------------------------------------------------------------

    nz = ntriplets (A, is_sym) + ntriplets (Z, is_sym) ;

    // write the first data line, with nrow, ncol, and # of triplets
    ok = ok && (fprintf (f, ID " " ID " " ID "\n", nrow, ncol, nz) > 0) ;

    for (j = 0 ; ok && j < ncol ; j++)
    {
        // merge column of A and Z
        p = Ap [j] ;
        pend = (apacked) ? Ap [j+1] : p + Anz [j] ;
        q = (Z == NULL) ? 0 : Zp [j] ;
        qend = (Z == NULL) ? 0 : ((zpacked) ? Zp [j+1] : q + Znz [j]) ;
        while (ok)
        {
            // get the next row index from A and Z
            i  = (p < pend) ? Ai [p] : (nrow+1) ;
            iz = (q < qend) ? Zi [q] : (nrow+2) ;
            if (i <= iz)
            {
                // get A(i,j), or quit if both A and Z are exhausted
                if (i == nrow+1) break ;
                get_value (Ax, Az, p, xtype, dtype, &x, &z) ;
                p++ ;
            }
            else
            {
                // get Z(i,j)
                i = iz ;
                x = 0 ;
                z = 0 ;
                q++ ;
            }
            if ((stype < 0 && i >= j) || (stype == 0 && (i >= j || !is_sym)))
            {
                // CHOLMOD matrix is symmetric-lower (and so is the file);
                // or CHOLMOD matrix is unsymmetric and either A(i,j) is in
                // the lower part or the file is unsymmetric.
                ok = ok && print_triplet (f, is_binary, is_complex, is_integer,
                    i,j, x,z) ;
            }
            else if (stype > 0 && i <= j)
            {
                // CHOLMOD matrix is symmetric-upper, but the file is
                // symmetric-lower.  Need to transpose the entry.   If the
                // matrix is real, the complex part is ignored.  If the matrix
                // is complex, it Hermitian.
                ASSERT (IMPLIES (is_complex, asym == CHOLMOD_MM_HERMITIAN)) ;
                if (z != 0)
                {
                    z = -z ;
                }
                ok = ok && print_triplet (f, is_binary, is_complex, is_integer,
                    j,i, x,z) ;
            }
        }
    }

    if (!ok)
    {
        ERROR (CHOLMOD_INVALID, "error reading/writing file") ;
        return (EMPTY) ;
    }

    return (asym) ;
}

//------------------------------------------------------------------------------
// cholmod_write_dense
//------------------------------------------------------------------------------

// Write a dense matrix to a file in Matrix Market format.   Optionally include
// comments.  Returns > 0 if successful, -1 otherwise (1 if rectangular, 2 if
// square).  Future versions may return 1 to 7 on success (a CHOLMOD_MM_* code,
// just as cholmod_write_sparse does).
//
// A dense matrix is written in "general" format; symmetric formats in the
// Matrix Market standard are not exploited.

int CHOLMOD(write_dense)
(
    // input:
    FILE *f,                // file to write to, must already be open
    cholmod_dense *X,       // matrix to print
    const char *comments,   // optional filename of comments to include
    cholmod_common *Common
)
{
    double x = 0, z = 0 ;
    void *Xx, *Xz ;
    Int nrow, ncol, is_complex, i, j, p ;
    int ok, xtype, dtype ;

    //--------------------------------------------------------------------------
    // check inputs
    //--------------------------------------------------------------------------

    RETURN_IF_NULL_COMMON (EMPTY) ;
    RETURN_IF_NULL (f, EMPTY) ;
    RETURN_IF_NULL (X, EMPTY) ;
    RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, EMPTY) ;
    Common->status = CHOLMOD_OK ;

    //--------------------------------------------------------------------------
    // get the X matrix
    //--------------------------------------------------------------------------

    nrow = X->nrow ;
    ncol = X->ncol ;
    xtype = X->xtype ;
    dtype = X->dtype ;
    is_complex = (xtype == CHOLMOD_COMPLEX) || (xtype == CHOLMOD_ZOMPLEX) ;

    //--------------------------------------------------------------------------
    // write the Matrix Market header
    //--------------------------------------------------------------------------

    ok = (fprintf (f, "%%%%MatrixMarket matrix array") > 0) ;
    if (is_complex)
    {
        ok = ok && (fprintf (f, " complex general\n") > 0) ;
    }
    else
    {
        ok = ok && (fprintf (f, " real general\n") > 0) ;
    }

    //--------------------------------------------------------------------------
    // include the comments if present
    //--------------------------------------------------------------------------

    ok = ok && include_comments (f, comments) ;

    //--------------------------------------------------------------------------
    // write a dense matrix
    //--------------------------------------------------------------------------

    // write the first data line, with nrow and ncol
    ok = ok && (fprintf (f, ID " " ID "\n", nrow, ncol) > 0) ;

    Xx = X->x ;
    Xz = X->z ;
    for (j = 0 ; ok && j < ncol ; j++)
    {
        for (i = 0 ; ok && i < nrow ; i++)
        {
            p = i + j*nrow ;
            get_value (Xx, Xz, p, xtype, dtype, &x, &z) ;
            ok = ok && print_value (f, x, FALSE) ;
            if (is_complex)
            {
                ok = ok && (fprintf (f, " ") > 0) ;
                ok = ok && print_value (f, z, FALSE) ;
            }
            ok = ok && (fprintf (f, "\n") > 0) ;
        }
    }

    if (!ok)
    {
        ERROR (CHOLMOD_INVALID, "error reading/writing file") ;
        return (EMPTY) ;
    }

    return ((nrow == ncol) ? CHOLMOD_MM_UNSYMMETRIC : CHOLMOD_MM_RECTANGULAR) ;
}
#endif
