//
// PDF array functions for PDFio.
//
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

#include "pdfio-private.h"


//
// Local functions...
//

static bool	append_value(pdfio_array_t *a, _pdfio_value_t *v);


//
// 'pdfioArrayAppendArray()' - Add an array value to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendArray(
    pdfio_array_t *a,			// I - Array
    pdfio_array_t *value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a || !value)
    return (false);

  // Add an array...
  v.type        = PDFIO_VALTYPE_ARRAY;
  v.value.array = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendBinary()' - Add a binary string value to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendBinary(
    pdfio_array_t       *a,		// I - Array
    const unsigned char *value,		// I - Value
    size_t              valuelen)	// I - Length of value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a || !value || !valuelen)
    return (false);

  // Add a binary string...
  v.type                 = PDFIO_VALTYPE_BINARY;
  v.value.binary.datalen = valuelen;

  if ((v.value.binary.data = (unsigned char *)malloc(valuelen)) == NULL)
  {
    _pdfioFileError(a->pdf, "Unable to allocate memory for binary string - %s", strerror(errno));
    return (false);
  }

  memcpy(v.value.binary.data, value, valuelen);

  if (!append_value(a, &v))
  {
    free(v.value.binary.data);
    return (false);
  }

  return (true);
}

//
// 'pdfioArrayAppendBoolean()' - Add a boolean value to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendBoolean(
    pdfio_array_t *a,			// I - Array
    bool          value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a)
    return (false);

  // Add a boolean...
  v.type          = PDFIO_VALTYPE_BOOLEAN;
  v.value.boolean = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendDate()' - Add a date value to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendDate(
    pdfio_array_t *a,			// I - Array
    time_t        value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a)
    return (false);

  // Add a dictionary...
  v.type       = PDFIO_VALTYPE_DATE;
  v.value.date = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendDict()' - Add a dictionary to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendDict(
    pdfio_array_t *a,			// I - Array
    pdfio_dict_t  *value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a || !value)
    return (false);

  // Add a dictionary...
  v.type       = PDFIO_VALTYPE_DICT;
  v.value.dict = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendName()' - Add a name to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendName(
    pdfio_array_t *a,			// I - Array
    const char    *value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a || !value)
    return (false);

  // Add a name string...
  v.type       = PDFIO_VALTYPE_NAME;
  v.value.name = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendNumber()' - Add a number to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendNumber(
    pdfio_array_t *a,			// I - Array
    double         value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a)
    return (false);

  // Add a number...
  v.type         = PDFIO_VALTYPE_NUMBER;
  v.value.number = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendObj()' - Add an indirect object reference to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendObj(
    pdfio_array_t *a,			// I - Array
    pdfio_obj_t   *value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a || !value || a->pdf != value->pdf)
    return (false);

  // Add an indirect reference...
  v.type                      = PDFIO_VALTYPE_INDIRECT;
  v.value.indirect.number     = value->number;
  v.value.indirect.generation = value->generation;

  return (append_value(a, &v));
}


//
// 'pdfioArrayAppendString()' - Add a string to an array.
//

bool					// O - `true` on success, `false` on failure
pdfioArrayAppendString(
    pdfio_array_t *a,			// I - Array
    const char    *value)		// I - Value
{
  _pdfio_value_t	v;		// Value for array


  // Range check input
  if (!a || !value)
    return (false);

  // Add a string...
  v.type         = PDFIO_VALTYPE_STRING;
  v.value.string = value;

  return (append_value(a, &v));
}


//
// 'pdfioArrayCopy()' - Copy an array.
//

pdfio_array_t *				// O - New array or `NULL` on error
pdfioArrayCopy(pdfio_file_t  *pdf,	// I - PDF file
               pdfio_array_t *a)	// I - Original array
{
  pdfio_array_t		*na;		// New array
  size_t		i;		// Looping var
  _pdfio_value_t	*vsrc,		// Current source value
			vdst;		// Current destination value


  PDFIO_DEBUG("pdfioArrayCopy(pdf=%p, a=%p(%p))\n", pdf, a, a ? a->pdf : NULL);

  // Create the new array...
  if ((na = pdfioArrayCreate(pdf)) == NULL)
    return (NULL);

  // Pre-allocate the values array to make this a little faster...
  if ((na->values = (_pdfio_value_t *)malloc(a->num_values * sizeof(_pdfio_value_t))) == NULL)
    return (NULL);			// Let pdfioFileClose do the cleanup...

  na->alloc_values = a->num_values;

  // Copy and add each of the source array's values...
  for (i = a->num_values, vsrc = a->values; i > 0; i --, vsrc ++)
  {
    if (!_pdfioValueCopy(pdf, &vdst, a->pdf, vsrc))
      return (NULL);			// Let pdfioFileClose do the cleanup...

    // Cannot fail since we already allocated memory...
    append_value(na, &vdst);
  }

  // Successfully copied the array, so return it...
  return (na);
}


//
// 'pdfioArrayCreate()' - Create an empty array.
//

pdfio_array_t *				// O - New array or `NULL` on error
pdfioArrayCreate(pdfio_file_t *pdf)	// I - PDF file
{
  pdfio_array_t	*a;			// Array


  if (!pdf)
    return (NULL);

  if ((a = (pdfio_array_t *)calloc(1, sizeof(pdfio_array_t))) == NULL)
    return (NULL);

  a->pdf = pdf;

  if (pdf->num_arrays >= pdf->alloc_arrays)
  {
    pdfio_array_t **temp = realloc(pdf->arrays, (pdf->alloc_arrays + 16) * sizeof(pdfio_array_t *));

    if (!temp)
    {
      free(a);
      return (NULL);
    }

    pdf->arrays       = temp;
    pdf->alloc_arrays += 16;
  }

  pdf->arrays[pdf->num_arrays ++] = a;

  return (a);
}


//
// '_pdfioArrayDecrypt()' - Decrypt values in an array.
//

bool					// O - `true` on success, `false` on error
_pdfioArrayDecrypt(pdfio_file_t  *pdf,	// I - PDF file
                   pdfio_obj_t   *obj,	// I - Object
                   pdfio_array_t *a,	// I - Array
                   size_t        depth)	// I - Depth
{
  size_t		i;		// Looping var
  _pdfio_value_t	*v;		// Current value


  for (i = a->num_values, v = a->values; i > 0; i --, v ++)
  {
    if (!_pdfioValueDecrypt(pdf, obj, v, depth))
      return (false);
  }

  return (true);
}


//
// '_pdfioArrayDebug()' - Print the contents of an array.
//

void
_pdfioArrayDebug(pdfio_array_t *a,	// I - Array
                 FILE          *fp)	// I - Output file
{
  size_t		i;		// Looping var
  _pdfio_value_t	*v;		// Current value


  if (!a)
    return;

  putc('[', fp);
  for (i = a->num_values, v = a->values; i > 0; i --, v ++)
    _pdfioValueDebug(v, fp);
  putc(']', fp);
}


//
// '_pdfioArrayDelete()' - Free the memory used by an array.
//

void
_pdfioArrayDelete(pdfio_array_t *a)	// I - Array
{
  size_t	i;			// Looping var


  for (i = 0; i < a->num_values; i ++)
  {
    if (a->values[i].type == PDFIO_VALTYPE_BINARY)
      free(a->values[i].value.binary.data);
  }

  free(a->values);
  free(a);
}


//
// 'pdfioArrayGetArray()' - Get an array value from an array.
//

pdfio_array_t *				// O - Value
pdfioArrayGetArray(pdfio_array_t *a,	// I - Array
                   size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_ARRAY)
    return (NULL);
  else
    return (a->values[n].value.array);
}


//
// 'pdfioArrayGetBinary()' - Get a binary string value from an array.
//

unsigned char *				// O - Value
pdfioArrayGetBinary(
    pdfio_array_t *a,			// I - Array
    size_t        n,			// I - Index
    size_t        *length)		// O - Length of string
{
  if (!a || n >= a->num_values || (a->values[n].type != PDFIO_VALTYPE_BINARY && a->values[n].type != PDFIO_VALTYPE_STRING))
  {
    if (length)
      *length = 0;

    return (NULL);
  }
  else if (a->values[n].type == PDFIO_VALTYPE_BINARY)
  {
    if (length)
      *length = a->values[n].value.binary.datalen;

    return (a->values[n].value.binary.data);
  }
  else
  {
    if (length)
      *length = strlen(a->values[n].value.string);

    return ((unsigned char *)a->values[n].value.string);
  }
}


//
// 'pdfioArrayGetBoolean()' - Get a boolean value from an array.
//

bool					// O - Value
pdfioArrayGetBoolean(pdfio_array_t *a,	// I - Array
                     size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_BOOLEAN)
    return (false);
  else
    return (a->values[n].value.boolean);
}


//
// 'pdfioArrayGetDate()' - Get a date value from an array.
//

time_t					// O - Value
pdfioArrayGetDate(pdfio_array_t *a,	// I - Array
		  size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_DATE)
    return (0);
  else
    return (a->values[n].value.date);
}


//
// 'pdfioArrayGetDict()' - Get a dictionary value from an array.
//

pdfio_dict_t *				// O - Value
pdfioArrayGetDict(pdfio_array_t *a,	// I - Array
		  size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_DICT)
    return (NULL);
  else
    return (a->values[n].value.dict);
}


//
// 'pdfioArrayGetName()' - Get a name value from an array.
//

const char *				// O - Value
pdfioArrayGetName(pdfio_array_t *a,	// I - Array
		  size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_NAME)
    return (NULL);
  else
    return (a->values[n].value.name);
}


//
// 'pdfioArrayGetNumber()' - Get a number from an array.
//

double					// O - Value
pdfioArrayGetNumber(pdfio_array_t *a,	// I - Array
                    size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_NUMBER)
    return (0.0);
  else
    return (a->values[n].value.number);
}


//
// 'pdfioArrayGetObject()' - Get an indirect object reference from an array.
//

pdfio_obj_t *				// O - Value
pdfioArrayGetObj(pdfio_array_t *a,	// I - Array
                 size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_INDIRECT)
    return (NULL);
  else
    return (pdfioFileFindObj(a->pdf, a->values[n].value.indirect.number));
}


//
// 'pdfioArrayGetSize()' - Get the length of an array.
//

size_t					// O - Length of array
pdfioArrayGetSize(pdfio_array_t *a)	// I - Array
{
  return (a ? a->num_values : 0);
}


//
// 'pdfioArrayGetString()' - Get a string value from an array.
//

const char *				// O - Value
pdfioArrayGetString(pdfio_array_t *a,	// I - Array
                    size_t        n)	// I - Index
{
  if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_STRING)
    return (NULL);
  else
    return (a->values[n].value.string);
}


//
// 'pdfioArrayGetType()' - Get a value type from an array.
//

pdfio_valtype_t				// O - Value type
pdfioArrayGetType(pdfio_array_t *a,	// I - Array
                  size_t        n)	// I - Index
{
  if (!a || n >= a->num_values)
    return (PDFIO_VALTYPE_NONE);
  else
    return (a->values[n].type);
}


//
// '_pdfioArrayGetValue()' - Get a value from an array.
//

_pdfio_value_t *			// O - Value
_pdfioArrayGetValue(pdfio_array_t *a,	// I - Array
                    size_t        n)	// I - Index
{
  if (!a || n >= a->num_values)
    return (NULL);
  else
    return (a->values + n);
}


//
// '_pdfioArrayRead()' - Read an array from a file.
//
// At this point the initial "[" has been seen...
//

pdfio_array_t *				// O - New array
_pdfioArrayRead(pdfio_file_t   *pdf,	// I - PDF file
		pdfio_obj_t    *obj,	// I - Object, if any
                _pdfio_token_t *tb,	// I - Token buffer/stack
                size_t         depth)	// I - Depth of array
{
  pdfio_array_t		*array;		// New array
  char			token[8192];	// Token from file
  _pdfio_value_t	value;		// Value


  PDFIO_DEBUG("_pdfioArrayRead(pdf=%p, tb=%p)\n", pdf, tb);

  // Create an array...
  if ((array = pdfioArrayCreate(pdf)) == NULL)
    return (NULL);

  // Read until we get "]" to end the array...
  while (_pdfioTokenGet(tb, token, sizeof(token)))
  {
    if (!strcmp(token, "]"))
    {
      // End of array...
      return (array);
    }

    // Push the token and decode the value...
    _pdfioTokenPush(tb, token);
    if (!_pdfioValueRead(pdf, obj, tb, &value, depth))
      break;

//    PDFIO_DEBUG("_pdfioArrayRead(%p): Appending ", (void *)array);
//    PDFIO_DEBUG_VALUE(&value);
//    PDFIO_DEBUG("\n");

    append_value(array, &value);
  }

  return (NULL);
}


//
// 'pdfioArrayRemove()' - Remove an array entry.
//

bool					// O - `true` on success, `false` otherwise
pdfioArrayRemove(pdfio_array_t *a,	// I - Array
                 size_t        n)	// I - Index
{
  if (!a || n >= a->num_values)
    return (false);

  if (a->values[n].type == PDFIO_VALTYPE_BINARY)
    free(a->values[n].value.binary.data);

  a->num_values --;
  if (n < a->num_values)
    memmove(a->values + n, a->values + n + 1, (a->num_values - n) * sizeof(_pdfio_value_t));

  return (true);
}


//
// '_pdfioArrayWrite()' - Write an array to a PDF file.
//

bool					// O - `true` on success, `false` otherwise
_pdfioArrayWrite(pdfio_array_t *a,	// I - Array
		 pdfio_obj_t   *obj)	// I - Object, if any
{
  pdfio_file_t	*pdf = a->pdf;		// PDF file
  size_t	i;			// Looping var
  _pdfio_value_t *v;			// Current value


  // Arrays are surrounded by square brackets ([ ... ])
  if (!_pdfioFilePuts(pdf, "["))
    return (false);

  // Write each value...
  for (i = a->num_values, v = a->values; i > 0; i --, v ++)
  {
    if (!_pdfioValueWrite(pdf, obj, v, NULL))
      return (false);
  }

  // Closing bracket...
  return (_pdfioFilePuts(pdf, "]"));
}


//
// 'append_value()' - Append a value.
//

static bool				// O - `true` on success, `false` otherwise
append_value(pdfio_array_t  *a,		// I - Array
             _pdfio_value_t *v)		// I - Value
{
  if (a->num_values >= a->alloc_values)
  {
    _pdfio_value_t *temp = (_pdfio_value_t *)realloc(a->values, (a->alloc_values + 16) * sizeof(_pdfio_value_t));

    if (!temp)
      return (false);

    a->values       = temp;
    a->alloc_values += 16;
  }

  a->values[a->num_values ++] = *v;

  return (true);
}