/*
 * pnmout.c
 * PNM file output.
 *
 * Copyright (C) 2002-2025 Cosmin Truta.
 *
 * Use, modification and distribution are subject
 * to the Boost Software License, Version 1.0.
 * See the accompanying file LICENSE_BSL_1_0.txt
 * or visit https://www.boost.org/LICENSE_1_0.txt
 *
 * SPDX-License-Identifier: BSL-1.0
 */


#include "pnmio.h"

#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>


/*
 * Validates a PNM header structure and writes it to a file stream.
 * Returns 1 on success, 0 on validation failure, or -1 on output failure.
 */
int pnm_fput_header(const pnm_struct *pnm_ptr, FILE *stream)
{
   unsigned int format = pnm_ptr->format;
   unsigned int depth = pnm_ptr->depth;
   unsigned int width = pnm_ptr->width;
   unsigned int height = pnm_ptr->height;
   unsigned int maxval = pnm_ptr->maxval;
   int result;

   /* Validate the info structure. */
   if (!pnm_is_valid(pnm_ptr))
      return 0;

   /* Write the PNM file header. */
   switch (format)
   {
   case PNM_P1:
   case PNM_P4:
      result = fprintf(stream,
                       "P%c\n"
                       "%u %u\n",
                       (int)(format + '0'), width, height);
      break;
   case PNM_P2:
   case PNM_P3:
   case PNM_P5:
   case PNM_P6:
      result = fprintf(stream,
                       "P%c\n"
                       "%u %u\n%u\n",
                       (int)(format + '0'), width, height, maxval);
      break;
   case PNM_P7:
      result = fprintf(stream,
                       "P7\n"
                       "DEPTH %u\nWIDTH %u\nHEIGHT %u\nMAXVAL %u\nENDHDR\n",
                       depth, width, height, maxval);
      break;
   default:
      errno = EINVAL;
      return 0;
   }

   /* Check the result. */
   return (result > 0) ? 1 : -1;
}


/*
 * Writes an array of PNM sample values to a file stream.
 * The values are written in the format specified by pnm_ptr->format.
 * The array length is pnm_ptr->depth * pnm_ptr->width * num_rows.
 * The validity check performed on the PNM structure is only partial.
 * Returns 1 on success, 0 on validation failure, or -1 on output failure.
 */
int pnm_fput_values(const pnm_struct *pnm_ptr,
                    const unsigned int *sample_values,
                    unsigned int num_rows,
                    FILE *stream)
{
   unsigned int format = pnm_ptr->format;
   unsigned int depth = pnm_ptr->depth;
   unsigned int width = pnm_ptr->width;
   unsigned int maxval = pnm_ptr->maxval;
   size_t row_length = (size_t)depth * (size_t)width;
   size_t num_samples = num_rows * row_length;
   int ch, mask;
   size_t i, j;

   /* Write the sample values. */
   switch (format)
   {
   case PNM_P1:
      for (i = j = 0; i < num_samples; ++i)
      {
         if (putc(((sample_values[i] != 0) ? '0' : '1'), stream) == EOF)
            break;
         if (++j == row_length)
         {
            j = 0;
            if (putc('\n', stream) == EOF)
               break;
         }
      }
      break;
   case PNM_P2:
   case PNM_P3:
      for (i = j = 0; i < num_samples; ++i)
      {
         if (++j == row_length)
            j = 0;
         if (fprintf(stream, (j == 0) ? "%u\n" : "%u ", sample_values[i]) <= 0)
            break;
      }
      break;
   case PNM_P4:
      for (i = j = 0; i < num_samples;)
      {
         ch = 0;
         for (mask = 0x80; mask != 0; mask >>= 1)
         {
            if (sample_values[i++] == 0)
               ch |= mask;
            if (++j == row_length)
            {
               j = 0;
               break;
            }
         }
         if (putc(ch, stream) == EOF)
            break;
      }
      break;
   case PNM_P5:
   case PNM_P6:
   case PNM_P7:
      if (maxval <= 0xffU)
      {
         /* Write 1 byte per sample. */
         for (i = 0; i < num_samples; ++i)
         {
            if (putc(sample_values[i] & 0xff, stream) == EOF)
               break;
         }
      }
      else if (maxval <= 0xffffU)
      {
         /* Write 2 bytes per sample. */
         for (i = 0; i < num_samples; ++i)
         {
            if (putc((sample_values[i] >> 8) & 0xff, stream) == EOF
                || putc((sample_values[i]) & 0xff, stream) == EOF)
               break;
         }
      }
#if UINT_MAX > PNM_VALUE_MAX
      else if (maxval > PNM_VALUE_MAX)
      {
         errno = EINVAL;
         return 0;
      }
#endif
      else
      {
         /* Write 3 or 4 bytes per sample. */
         for (i = 0; i < num_samples; ++i)
         {
            if (maxval > 0xffffffU)
               if (putc((sample_values[i] >> 24) & 0xff, stream) == EOF)
                  break;
            if (putc((sample_values[i] >> 16) & 0xff, stream) == EOF
                || putc((sample_values[i] >> 8) & 0xff, stream) == EOF
                || putc((sample_values[i]) & 0xff, stream) == EOF)
               break;
         }
      }
      break;
   default:
      errno = EINVAL;
      return 0;
   }

   /* Check the result. */
   return (i == num_samples) ? 1 : -1;
}


/*
 * Writes an array of sample bytes to a raw PNM file stream.
 * Multi-byte samples are stored in network order, as in the PNM stream.
 * The byte count is sample_size * pnm_ptr->depth * pnm_ptr->width * num_rows.
 * The validity check performed on the PNM structure is only partial.
 * Returns 1 on success, 0 on validation failure, or -1 on output failure.
 */
int pnm_fput_bytes(const pnm_struct *pnm_ptr,
                   const unsigned char *sample_bytes,
                   size_t sample_size,
                   unsigned int num_rows,
                   FILE *stream)
{
   unsigned int format = pnm_ptr->format;
   unsigned int depth = pnm_ptr->depth;
   unsigned int width = pnm_ptr->width;
   unsigned int maxval = pnm_ptr->maxval;
   size_t row_length = (size_t)depth * (size_t)width;
   size_t num_samples = num_rows * row_length;
   size_t raw_sample_size;
   int ch, mask;
   size_t i, j;

   /* Validate the given sample size. */
   if (maxval <= 0xffU)
      raw_sample_size = 1;
   else if (maxval <= 0xffffU)
      raw_sample_size = 2;
   else if (maxval <= 0xffffffU)
      raw_sample_size = 3;
#if UINT_MAX > PNM_VALUE_MAX
   else if (maxval > PNM_VALUE_MAX)
      raw_sample_size = !sample_size;
#endif
   else
      raw_sample_size = 4;
   if (raw_sample_size != sample_size)
   {
      errno = EINVAL;
      return 0;
   }

   /* Write the raw sample bytes. */
   switch (format)
   {
   case PNM_P4:
      for (i = j = 0; i < num_samples;)
      {
         ch = 0;
         for (mask = 0x80; mask != 0; mask >>= 1)
         {
            if (sample_bytes[i++] == 0)
               ch |= mask;
            if (++j == row_length)
            {
               j = 0;
               break;
            }
         }
         if (putc(ch, stream) == EOF)
            break;
      }
      break;
   case PNM_P5:
   case PNM_P6:
   case PNM_P7:
      i = fwrite(sample_bytes, sample_size, num_samples, stream);
      break;
   default:
      errno = EINVAL;
      return 0;
   }

   /* Check the result. */
   return (i == num_samples) ? 1 : -1;
}
