/* Copyright (C) 1997, 1998 Aladdin Enterprises.  All rights reserved.
  
  This file is part of GNU Ghostscript.
  
  GNU Ghostscript is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
  to anyone for the consequences of using it or for whether it serves any
  particular purpose or works at all, unless he says so in writing.  Refer
  to the GNU General Public License for full details.
  
  Everyone is granted permission to copy, modify and redistribute GNU
  Ghostscript, but only under the conditions described in the GNU General
  Public License.  A copy of this license is supposed to have been given
  to you along with GNU Ghostscript so you can know your rights and
  responsibilities.  It should be in a file named COPYING.  Among other
  things, the copyright notice and this notice must be preserved on all
  copies.
  
  Aladdin Enterprises supports the work of the GNU Project, but is not
  affiliated with the Free Software Foundation or the GNU Project.  GNU
  Ghostscript, as distributed by Aladdin Enterprises, does not require any
  GNU software to build or run it.
*/

/*$Id: gdevpdfo.c $ */
/* Named object pdfmark processing */
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsutil.h"		/* for bytes_compare */
#include "gdevpdfx.h"
#include "strimpl.h"
#include "sstring.h"

/* GC procedures */

private_st_pdf_named_element();
public_st_pdf_named_object();

#define pne ((pdf_named_element *)vptr)
private 
ENUM_PTRS_BEGIN(pdf_named_elt_enum_ptrs) return 0;

ENUM_PTR(0, pdf_named_element, next);
ENUM_STRING_PTR(1, pdf_named_element, key);
/****** WRONG IF data = 0 ******/
ENUM_STRING_PTR(2, pdf_named_element, value);
ENUM_PTRS_END
private RELOC_PTRS_BEGIN(pdf_named_elt_reloc_ptrs)
{
    RELOC_PTR(pdf_named_element, next);
    if (pne->key.data != 0)
	RELOC_STRING_PTR(pdf_named_element, key);
    RELOC_STRING_PTR(pdf_named_element, value);
}
RELOC_PTRS_END
#undef pne

#define pno ((pdf_named_object *)vptr)
private ENUM_PTRS_BEGIN(pdf_named_obj_enum_ptrs) return 0;
ENUM_PTR(0, pdf_named_object, next);
ENUM_STRING_PTR(1, pdf_named_object, key);
ENUM_PTR(2, pdf_named_object, elements);
case 3:
if (pno->type == named_graphics)
    ENUM_RETURN(pno->graphics.enclosing);
else
    return 0;
ENUM_PTRS_END
private RELOC_PTRS_BEGIN(pdf_named_obj_reloc_ptrs)
{
    RELOC_PTR(pdf_named_object, next);
    RELOC_STRING_PTR(pdf_named_object, key);
    RELOC_PTR(pdf_named_object, elements);
    if (pno->type == named_graphics)
	RELOC_PTR(pdf_named_object, graphics.enclosing);
}
RELOC_PTRS_END
#undef pno

/* ---------------- pdfmark processing ---------------- */

/* Define the pdfmark types implemented here. */
private pdfmark_proc(pdfmark_BP);
private pdfmark_proc(pdfmark_EP);
private pdfmark_proc(pdfmark_SP);
private pdfmark_proc(pdfmark_OBJ);
private pdfmark_proc(pdfmark_PUT);
private pdfmark_proc(pdfmark_PUTDICT);
private pdfmark_proc(pdfmark_PUTINTERVAL);
private pdfmark_proc(pdfmark_CLOSE);
const pdfmark_name pdfmark_names_named[] =
{
    {"BP", pdfmark_BP, pdfmark_nameable},
    {"EP", pdfmark_EP, 0},
    {"SP", pdfmark_SP, pdfmark_odd_ok | pdfmark_keep_name},
    {"OBJ", pdfmark_OBJ, pdfmark_nameable},
    {"PUT", pdfmark_PUT, pdfmark_odd_ok | pdfmark_keep_name},
    {".PUTDICT", pdfmark_PUTDICT, pdfmark_odd_ok | pdfmark_keep_name},
  {".PUTINTERVAL", pdfmark_PUTINTERVAL, pdfmark_odd_ok | pdfmark_keep_name},
    {"CLOSE", pdfmark_CLOSE, pdfmark_odd_ok | pdfmark_keep_name},
    {0, 0}
};

/*
 * Predefined objects:
 *      {Catalog}, {DocInfo}
 *      {Page<#>}, {ThisPage}, {PrevPage}, {NextPage}
 */

/* ---------------- Utilities ---------------- */

private int pdfmark_write_named(P2(gx_device_pdf * pdev,
				   const pdf_named_object * pno));
private void pdfmark_free_named(P2(gx_device_pdf * pdev,
				   pdf_named_object * pno));

/* ------ Public ------ */

/*
 * Look up an object name.  Return e_rangecheck if the syntax is invalid.
 * If the object is missing, then:
 *      - If create is false, return gs_error_undefined;
 *      - If create is true, create the object and return 1.
 * Note that there is code in gdevpdf.c that relies on the fact that
 * the named_objects list is sorted by decreasing object number.
 */
private int
pdfmark_find_named(gx_device_pdf * pdev, const gs_param_string * pname,
		   pdf_named_object ** ppno, bool create)
{
    const byte *data = pname->data;
    uint size = pname->size;
    gs_id key_id =
    (size == 0 ? 0 : data[0] + data[size / 2] + data[size - 1]);
    pdf_resource **plist =
    &pdev->resources[resourceNamedObject].
    chains[gs_id_hash(key_id) % num_resource_chains];
    pdf_named_object *pno = (pdf_named_object *) * plist;

    if (!pdfmark_objname_is_valid(data, size))
	return_error(gs_error_rangecheck);
    for (; pno; pno = pno->next)
	if (!bytes_compare(data, size, pno->key.data, pno->key.size)) {
	    *ppno = pno;
	    return 0;
	}
    if (!create)
	return_error(gs_error_undefined);
    {
	gs_memory_t *mem = pdev->pdf_memory;
	byte *key = gs_alloc_string(mem, size, "pdf named object key");
	pdf_resource *pres;
	int code;

	if (key == 0)
	    return_error(gs_error_VMerror);
	code = pdf_alloc_resource(pdev, resourceNamedObject, key_id, &pres);
	if (code < 0) {
	    gs_free_string(mem, key, size, "pdf named object key");
	    return code;
	}
	pno = (pdf_named_object *) pres;
	memcpy(key, data, size);
	pno->id = pdf_obj_ref(pdev);
	pno->type = named_unknown;	/* caller may change */
	pno->key.data = key;
	pno->key.size = size;
	pno->elements = 0;
	pno->open = true;
	/* For now, just link the object onto the private list. */
	pno->next = pdev->named_objects;
	pdev->named_objects = pno;
	*ppno = pno;
	return 1;
    }
}

/* Replace object names with object references in a (parameter) string. */
private const byte *
pdfmark_next_object(const byte * scan, const byte * end, const byte ** pname,
		    pdf_named_object ** ppno, gx_device_pdf * pdev)
{				/*
				 * Starting at scan, find the next object reference, set *pname
				 * to point to it in the string, store the object at *ppno,
				 * and return a pointer to the first character beyond the
				 * reference.  If there are no more object references, set
				 * *pname = end and return end.
				 */
    const byte *left;
    const byte *lit;
    const byte *right;

    *ppno = 0;
  top:left = memchr(scan, '{', end - scan);
    if (left == 0)
	return (*pname = end);
    lit = memchr(scan, '(', left - scan);
    if (lit) {
	/* Skip over the string. */
	byte buf[50];		/* size is arbitrary */
	stream_cursor_read r;
	stream_cursor_write w;
	stream_PSSD_state ss;
	int status;

	s_PSSD_init_inline(&ss);
	r.ptr = lit - 1;
	r.limit = end - 1;
	w.limit = buf + sizeof(buf) - 1;
	do {
	    w.ptr = buf - 1;
	    status = (*s_PSSD_template.process)
		((stream_state *) & ss, &r, &w, true);
	}
	while (status == 1);
	scan = r.ptr + 1;
	goto top;
    }
    right = memchr(left + 1, '}', end - (left + 1));
    if (right == 0)		/* malformed name */
	return (*pname = end);
    *pname = left;
    ++right;
    {
	gs_param_string sname;

	sname.data = left;
	sname.size = right - left;
	pdfmark_find_named(pdev, &sname, ppno, false);
    }
    return right;
}
int
pdfmark_replace_names(gx_device_pdf * pdev, const gs_param_string * from,
		      gs_param_string * to)
{
    const byte *start = from->data;
    const byte *end = start + from->size;
    const byte *scan;
    uint size = 0;
    pdf_named_object *pno;
    bool any = false;
    byte *sto;
    char ref[1 + 10 + 5 + 1];	/* max obj number is 10 digits */

    /* Do a first pass to compute the length of the result. */
    for (scan = start; scan < end;) {
	const byte *sname;
	const byte *next =
	pdfmark_next_object(scan, end, &sname, &pno, pdev);

	size += sname - scan;
	if (pno) {
	    sprintf(ref, " %ld 0 R ", pno->id);
	    size += strlen(ref);
	}
	scan = next;
	any |= next != sname;
    }
    to->persistent = true;	/* ??? */
    if (!any) {
	to->data = start;
	to->size = size;
	return 0;
    }
    sto = gs_alloc_bytes(pdev->pdf_memory, size,
			 "pdfmark_replace_names");
    if (sto == 0)
	return_error(gs_error_VMerror);
    to->data = sto;
    to->size = size;
    /* Do a second pass to do the actual substitutions. */
    for (scan = start; scan < end;) {
	const byte *sname;
	const byte *next =
	pdfmark_next_object(scan, end, &sname, &pno, pdev);
	uint copy = sname - scan;
	int rlen;

	memcpy(sto, scan, copy);
	sto += copy;
	if (pno) {
	    sprintf(ref, " %ld 0 R ", pno->id);
	    rlen = strlen(ref);
	    memcpy(sto, ref, rlen);
	    sto += rlen;
	}
	scan = next;
    }
    return 0;
}

/* Write and free an entire list of named objects. */
int
pdfmark_write_and_free_named(gx_device_pdf * pdev, pdf_named_object ** ppno)
{
    pdf_named_object *pno = *ppno;
    pdf_named_object *next;

    for (; pno; pno = next) {
	next = pno->next;
	pdfmark_write_named(pdev, pno);
	pdfmark_free_named(pdev, pno);
    }
    *ppno = 0;
    return 0;
}

/* ------ Private ------ */

/* Put an element of an array object. */
private int
pdf_named_array_put(gx_device_pdf * pdev, pdf_named_object * pno, int index,
		    const gs_param_string * pvalue)
{
    gs_memory_t *mem = pdev->pdf_memory;
    pdf_named_element **ppne = &pno->elements;
    pdf_named_element *pne;
    pdf_named_element *pnext;
    gs_string value;

    while ((pnext = *ppne) != 0 && pnext->key.size > index)
	ppne = &pnext->next;
    value.data = gs_alloc_string(mem, pvalue->size, "named array value");
    if (value.data == 0)
	return_error(gs_error_VMerror);
    value.size = pvalue->size;
    if (pnext && pnext->key.size == index) {
	/* We're replacing an existing element. */
	gs_free_string(mem, pnext->value.data, pnext->value.size,
		       "named array old value");
	pne = pnext;
    } else {
	/* Create a new element. */
	pne = gs_alloc_struct(mem, pdf_named_element, &st_pdf_named_element,
			      "named array element");
	if (pne == 0) {
	    gs_free_string(mem, value.data, value.size, "named array value");
	    return_error(gs_error_VMerror);
	}
	pne->key.data = 0;
	pne->key.size = index;
	pne->next = pnext;
	*ppne = pne;
    }
    memcpy(value.data, pvalue->data, value.size);
    pne->value = value;
    return 0;
}

/* Put an element of a dictionary object. */
private int
pdf_named_dict_put(gx_device_pdf * pdev, pdf_named_object * pno,
	       const gs_param_string * pkey, const gs_param_string * pvalue)
{
    gs_memory_t *mem = pdev->pdf_memory;
    pdf_named_element **ppne = &pno->elements;
    pdf_named_element *pne;
    pdf_named_element *pnext;
    gs_string value;

    while ((pnext = *ppne) != 0 &&
	   bytes_compare(pnext->key.data, pnext->key.size,
			 pkey->data, pkey->size)
	)
	ppne = &pnext->next;
    value.data = gs_alloc_string(mem, pvalue->size, "named dict value");
    if (value.data == 0)
	return_error(gs_error_VMerror);
    value.size = pvalue->size;
    if (pnext) {
	/* We're replacing an existing element. */
	gs_free_string(mem, pnext->value.data, pnext->value.size,
		       "named array old value");
	pne = pnext;
    } else {
	/* Create a new element. */
	gs_string key;

	key.data = gs_alloc_string(mem, pkey->size, "named dict key");
	key.size = pkey->size;
	pne = gs_alloc_struct(mem, pdf_named_element, &st_pdf_named_element,
			      "named dict element");
	if (key.data == 0 || pne == 0) {
	    gs_free_object(mem, pne, "named dict element");
	    if (key.data)
		gs_free_string(mem, key.data, key.size, "named dict key");
	    gs_free_string(mem, value.data, value.size, "named dict value");
	    return_error(gs_error_VMerror);
	}
	pne->key = key;
	memcpy(key.data, pkey->data, key.size);
	pne->next = pnext;
	*ppne = pne;
    }
    memcpy(value.data, pvalue->data, value.size);
    pne->value = value;
    return 0;
}

/* Write out the definition of a named object. */
private pdf_named_element *
pdf_reverse_elements(pdf_named_element * pne)
{
    pdf_named_element *prev = NULL;
    pdf_named_element *next;

    for (; pne; pne = next)
	next = pne->next, pne->next = prev, prev = pne;
    return prev;
}
private int
pdfmark_write_named(gx_device_pdf * pdev, const pdf_named_object * pno)
{
    stream *s = pdev->strm;
    pdf_named_element *pne = pno->elements;

    switch (pno->type) {
	case named_array:{
		uint last_index = 0;
		pdf_named_element *last = pne = pdf_reverse_elements(pne);

		pdf_open_obj(pdev, pno->id);
		pputs(s, "[");
		for (; pne; ++last_index, pne = pne->next) {
		    for (; pne->key.size > last_index; ++last_index)
			pputs(s, "null\n");
		    pdf_put_value(pdev, pne->value.data, pne->value.size);
		    pputc(s, '\n');
		}
		pdf_reverse_elements(last);
		pputs(s, "]");
	    }
	    break;
	case named_dict:
	    pdf_open_obj(pdev, pno->id);
	    pputs(s, "<<");
	  dict:for (; pne; pne = pne->next) {
		pdf_put_value(pdev, pne->key.data, pne->key.size);
		pputc(s, ' ');
		pdf_put_value(pdev, pne->value.data, pne->value.size);
		pputc(s, '\n');
	    }
	    pputs(s, ">>");
	    break;
	case named_stream:{
		pdf_named_element *last = pne = pdf_reverse_elements(pne);

/****** NYI ******/
#if 0
		pdf_open_stream(pdev, pno->id);
/****** DOESN'T EXIST ******/
		for (; pne; pne = pne->next)
		    pwrite(s, pne->value.data, pne->value.size);
		pdf_close_stream(pdev);
/****** DITTO ******/
#endif
		pdf_reverse_elements(last);
	    }
	    break;
/****** WHAT TO DO WITH GRAPHICS/UNDEFINED? ******/
	default:
	    return 0;
    }
    pdf_end_obj(pdev);
    return 0;
}

/* Free the definition of a named object. */
private void
pdfmark_free_named(gx_device_pdf * pdev, pdf_named_object * pno)
{
    pdf_named_element *pne = pno->elements;
    pdf_named_element *next;

    for (; pne; pne = next) {
	next = pne->next;
	gs_free_string(pdev->pdf_memory, pne->value.data, pne->value.size,
		       "named object element value");
	if (pne->key.data)
	    gs_free_string(pdev->pdf_memory, pne->key.data, pne->key.size,
			   "named object element key");
    }
    gs_free_string(pdev->pdf_memory, pno->key.data, pno->key.size,
		   "named object key");
    gs_free_object(pdev->pdf_memory, pno, "named object");
}

/* ---------------- Individual pdfmarks ---------------- */

/* [ /BBox [llx lly urx ury] /_objdef {obj} /BP pdfmark */
private int
pdfmark_BP(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
	   const gs_matrix * pctm, const gs_param_string * objname)
{
    gs_rect bbox;
    pdf_named_object *pno;
    int code;

    if (objname == 0 || count != 2 || !pdf_key_eq(&pairs[0], "BBox"))
	return_error(gs_error_rangecheck);
    if (sscanf((const char *)pairs[1].data, "[%lg %lg %lg %lg]",
	       &bbox.p.x, &bbox.p.y, &bbox.q.x, &bbox.q.y) != 4
	)
	return_error(gs_error_rangecheck);
    code = pdfmark_find_named(pdev, objname, &pno, true);
    if (code < 0)
	return code;
    if (pno->type != named_unknown)
	return_error(gs_error_rangecheck);
    code = pdf_named_dict_put(pdev, pno, &pairs[0], &pairs[1]);
    if (code < 0)
	return code;
    pno->type = named_graphics;
    pno->graphics.enclosing = pdev->open_graphics;
    pdev->open_graphics = pno;
/****** NYI ******/
    return 0;
}

/* [ /EP pdfmark */
private int
pdfmark_EP(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
	   const gs_matrix * pctm, const gs_param_string * no_objname)
{
    pdf_named_object *pno = pdev->open_graphics;

    if (count != 0 || pno == 0 || pno->type != named_graphics ||
	!pno->open
	)
	return_error(gs_error_rangecheck);
    pno->open = false;
    pdev->open_graphics = pno->graphics.enclosing;
/****** NYI ******/
    return 0;
}

/* [ {obj} /SP pdfmark */
private int
pdfmark_SP(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
	   const gs_matrix * pctm, const gs_param_string * no_objname)
{
    pdf_named_object *pno;
    int code;

    if (count != 1)
	return_error(gs_error_rangecheck);
    if ((code = pdfmark_find_named(pdev, &pairs[0], &pno, false)) < 0)
	return code;
    if (pno->type != named_graphics || pno->open)
	return_error(gs_error_rangecheck);
    code = pdf_open_contents(pdev, pdf_in_stream);
    if (code < 0)
	return code;
    pdf_put_matrix(pdev, "q ", pctm, "cm\n");
    pprintld1(pdev->strm, "/R%ld Do Q\n", pno->id);
    return 0;
}

/* [ /_objdef {array} /type /array /OBJ pdfmark */
/* [ /_objdef {dict} /type /dict /OBJ pdfmark */
/* [ /_objdef {stream} /type /stream /OBJ pdfmark */
private int
pdfmark_OBJ(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
	    const gs_matrix * pctm, const gs_param_string * objname)
{
    pdf_named_object_type type;
    pdf_named_object *pno;
    int code;

    if (objname == 0 || count != 2 || !pdf_key_eq(&pairs[0], "type"))
	return_error(gs_error_rangecheck);
    if (pdf_key_eq(&pairs[1], "/array"))
	type = named_array;
    else if (pdf_key_eq(&pairs[1], "/dict"))
	type = named_dict;
    else if (pdf_key_eq(&pairs[1], "/stream"))
	type = named_stream;
    else
	return_error(gs_error_rangecheck);
    if ((code = pdfmark_find_named(pdev, objname, &pno, true)) < 0)
	return code;
    if (pno->type != named_unknown)
	return_error(gs_error_rangecheck);
    pno->type = type;
    return 0;
}

/* [ {array} index value /PUT pdfmark */
/* [ {stream} string|file /PUT pdfmark */
/* Dictionaries are converted to .PUTDICT */
private int
pdfmark_PUT(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
	    const gs_matrix * pctm, const gs_param_string * no_objname)
{
    pdf_named_object *pno;
    int code;

    if ((code = pdfmark_find_named(pdev, &pairs[0], &pno, false)) < 0)
	return code;
    switch (pno->type) {
	case named_array:{
		int index;

		if (count != 3)
		    return_error(gs_error_rangecheck);
		if ((code = pdfmark_scan_int(&pairs[1], &index)) < 0)
		    return code;
		if (index < 0)
		    return_error(gs_error_rangecheck);
		code = pdf_named_array_put(pdev, pno, index, pairs + 2);
		break;
	    }
	case named_stream:
	    if (count != 2)
		return_error(gs_error_rangecheck);
/****** NYI ******/
	    break;
	default:
	    return_error(gs_error_rangecheck);
    }
    return code;
}

/* [ {dict} key value ... /.PUTDICT pdfmark */
private int
pdfmark_PUTDICT(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
		const gs_matrix * pctm, const gs_param_string * no_objname)
{
    pdf_named_object *pno;
    int code, i;

    if ((code = pdfmark_find_named(pdev, &pairs[0], &pno, false)) < 0)
	return code;
    if (!(count & 1) || pno->type != named_dict)
	return_error(gs_error_rangecheck);
    for (i = 1; code >= 0 && i < count; i += 2)
	code = pdf_named_dict_put(pdev, pno, pairs + i, pairs + i + 1);
    return code;
}

/* [ {array} index value ... /.PUTINTERVAL pdfmark */
private int
pdfmark_PUTINTERVAL(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
		 const gs_matrix * pctm, const gs_param_string * no_objname)
{
    pdf_named_object *pno;
    int code, index, i;

    if ((code = pdfmark_find_named(pdev, &pairs[0], &pno, false)) < 0)
	return code;
    if (count < 2 || pno->type != named_array)
	return_error(gs_error_rangecheck);
    if ((code = pdfmark_scan_int(&pairs[1], &index)) < 0)
	return code;
    if (index < 0)
	return_error(gs_error_rangecheck);
    for (i = 2; code >= 0 && i < count; ++i)
	code = pdf_named_array_put(pdev, pno, index + i - 2, &pairs[i]);
    return code;
}

/* [ {stream} /CLOSE pdfmark */
private int
pdfmark_CLOSE(gx_device_pdf * pdev, gs_param_string * pairs, uint count,
	      const gs_matrix * pctm, const gs_param_string * no_objname)
{
    pdf_named_object *pno;
    int code;

    if (count != 1)
	return_error(gs_error_rangecheck);
    if ((code = pdfmark_find_named(pdev, &pairs[0], &pno, false)) < 0)
	return code;
    if (pno->type != named_stream || !pno->open)
	return_error(gs_error_rangecheck);
    /* Currently we don't do anything special when closing a stream. */
    pno->open = false;
    return 0;
}
