/* libppm2.c - ppm utility library part 2
**
** Copyright (C) 1989 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include <string.h>
#include <errno.h>


#include "netpbm/pm_c_util.h"
#include "netpbm/mallocvar.h"
#include "ppm.h"

void
ppm_writeppminit(FILE*  const fileP, 
                 int    const cols, 
                 int    const rows, 
                 pixval const maxval, 
                 int    const forceplain) {

    bool const plainFormat = forceplain || pm_plain_output;

    if (maxval > PPM_OVERALLMAXVAL && !plainFormat) 
        pm_error("too-large maxval passed to ppm_writeppminit(): %d."
                 "Maximum allowed by the PPM format is %d.",
                 maxval, PPM_OVERALLMAXVAL);

    fprintf(fileP, "%c%c\n%d %d\n%d\n", 
            PPM_MAGIC1, 
            plainFormat || maxval >= 1<<16 ? PPM_MAGIC2 : RPPM_MAGIC2, 
            cols, rows, maxval );
}



static void
putus(unsigned short const n,
      FILE *         const fileP) {

    if (n >= 10)
        putus(n / 10, fileP);
    putc('0' + n % 10, fileP);
}



static void
format1bpsRow(const pixel *   const pixelrow,
              unsigned int    const cols,
              unsigned char * const rowBuffer) {

    /* single byte samples. */

    unsigned int col;
    unsigned int bufferCursor;

    bufferCursor = 0;

    for (col = 0; col < cols; ++col) {
        rowBuffer[bufferCursor++] = PPM_GETR(pixelrow[col]);
        rowBuffer[bufferCursor++] = PPM_GETG(pixelrow[col]);
        rowBuffer[bufferCursor++] = PPM_GETB(pixelrow[col]);
    }
}




static void
format2bpsRow(const pixel *   const pixelrow,
              unsigned int    const cols,
              unsigned char * const rowBuffer) {
    
    /* two byte samples. */

    unsigned int col;
    unsigned int bufferCursor;

    bufferCursor = 0;

    for (col = 0; col < cols; ++col) {
        pixval const r = PPM_GETR(pixelrow[col]);
        pixval const g = PPM_GETG(pixelrow[col]);
        pixval const b = PPM_GETB(pixelrow[col]);
        
        rowBuffer[bufferCursor++] = r >> 8;
        rowBuffer[bufferCursor++] = (unsigned char)r;
        rowBuffer[bufferCursor++] = g >> 8;
        rowBuffer[bufferCursor++] = (unsigned char)g;
        rowBuffer[bufferCursor++] = b >> 8;
        rowBuffer[bufferCursor++] = (unsigned char)b;
    }
}



static void
ppm_writeppmrowraw(FILE *        const fileP,
                   const pixel * const pixelrow,
                   unsigned int  const cols,
                   pixval        const maxval ) {

    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
    unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;

    unsigned char * rowBuffer;
    ssize_t rc;

    MALLOCARRAY(rowBuffer, bytesPerRow);

    if (rowBuffer == NULL)
        pm_error("Unable to allocate memory for row buffer "
                 "for %u columns", cols);

    if (maxval < 256)
        format1bpsRow(pixelrow, cols, rowBuffer);
    else
        format2bpsRow(pixelrow, cols, rowBuffer);

    rc = fwrite(rowBuffer, 1, bytesPerRow, fileP);

    if (rc < 0)
        pm_error("Error writing row.  fwrite() errno=%d (%s)",
                 errno, strerror(errno));
    else {
        size_t const bytesWritten = rc;

        if (bytesWritten != bytesPerRow)
            pm_error("Error writing row.  Short write of %u bytes "
                     "instead of %u", (unsigned)bytesWritten, bytesPerRow);
    }
    free(rowBuffer);
}




static void
ppm_writeppmrowplain(FILE *        const fileP,
                     const pixel * const pixelrow,
                     unsigned int  const cols,
                     pixval        const maxval) {

    unsigned int col;
    unsigned int charcount;

    charcount = 0;

    for (col = 0; col < cols; ++col) {
        if (charcount >= 65) {
            putc('\n', fileP);
            charcount = 0;
        } else if (charcount > 0) {
            putc(' ', fileP);
            putc(' ', fileP);
            charcount += 2;
        }
        putus(PPM_GETR(pixelrow[col]), fileP);
        putc(' ', fileP);
        putus(PPM_GETG(pixelrow[col]), fileP);
        putc(' ', fileP);
        putus(PPM_GETB(pixelrow[col]), fileP);
        charcount += 11;
    }
    if (charcount > 0)
        putc('\n', fileP);
}



void
ppm_writeppmrow(FILE *        const fileP, 
                const pixel * const pixelrow, 
                int           const cols, 
                pixval        const maxval, 
                int           const forceplain) {

    if (forceplain || pm_plain_output || maxval >= 1<<16) 
        ppm_writeppmrowplain(fileP, pixelrow, cols, maxval);
    else 
        ppm_writeppmrowraw(fileP, pixelrow, cols, maxval);
}



void
ppm_writeppm(FILE *  const file, 
             pixel** const pixels, 
             int     const cols, 
             int     const rows, 
             pixval  const maxval, 
             int     const forceplain)  {
    int row;
    
    ppm_writeppminit(file, cols, rows, maxval, forceplain);
    
    for (row = 0; row < rows; ++row)
        ppm_writeppmrow(file, pixels[row], cols, maxval, forceplain);
}
