//  MUDoc_Portfolio.c  - porting of deprecated/removed  pdf_portfolio 
//
//  Grabbed from mupdf 1.14.x
//  This was part of include/mupdf/pdf/document.h
//  Since the section about pdf-portfolio was removed on 12-Nov-2018,
//   we grabbed the code to be included directly in tclmupdf
//
//  Only a minimal set of functions have been ported ...


#include "asprintf.h"   // WARNING: must be declared BEFORE <stdio.h>
#include <string.h>
#include <mupdf/pdf.h>

#include "MUDoc_Portfolio.h"


typedef int (pdf_name_tree_map_fn)(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg);

static 
int do_name_tree_map(fz_context *ctx, pdf_obj *tree, pdf_name_tree_map_fn *fn, void *arg)
{
	int i;
	int n = 0;
	int m = 0;

	fz_var(n);
	fz_var(m);

	if (pdf_mark_obj(ctx, tree))
		fz_throw(ctx, FZ_ERROR_GENERIC, "Recursive name tree!");

	fz_try(ctx)
	{
		pdf_obj *arr = pdf_dict_get(ctx, tree, PDF_NAME(Kids));
		n = pdf_array_len(ctx, arr);

		for (i = n; i > 0;)
		{
			i--;
			if (do_name_tree_map(ctx, pdf_array_get(ctx, arr, i), fn, arg))
			{
				pdf_array_delete(ctx, arr, i);
				n--;
			}
		}

		arr = pdf_dict_get(ctx, tree, PDF_NAME(Names));
		m = pdf_array_len(ctx, arr);

		if (m & 1)
			fz_throw(ctx, FZ_ERROR_GENERIC, "Malformed Names array");

		for (i = m; i > 0;)
		{
			i -= 2;
			if (fn(ctx, tree, pdf_array_get(ctx, arr, i), pdf_array_get(ctx, arr, i+1), arg))
			{
				pdf_array_delete(ctx, arr, i+1);
				pdf_array_delete(ctx, arr, i);
				m -= 2;
			}
		}
	}
	fz_always(ctx)
		pdf_unmark_obj(ctx, tree);
	fz_catch(ctx)
		fz_rethrow(ctx);

	return n == 0 && m == 0;
}

static
void xpdf_name_tree_map(fz_context *ctx, pdf_obj *tree, pdf_name_tree_map_fn *fn, void *arg)
{
	(void)do_name_tree_map(ctx, tree, fn, arg);
}

static 
int count_nodes(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg)
{
	int *count = (int *)arg;

	*count += 1;

	return 0;
}

/*
	pdf_count_portfolio_entries: Get the number of portfolio entries
	in this document.

	doc: The document in question.
*/
static
int xpdf_count_portfolio_entries(fz_context* ctx, pdf_document* doc)
{
	pdf_obj *s;
	int count;

	s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(Names), PDF_NAME(EmbeddedFiles), NULL);
	count = 0;
	xpdf_name_tree_map(ctx, s, count_nodes, &count);

	return count;
}

struct find_data {
	pdf_obj *key;
	pdf_obj *val;
	int count;
};

static 
int find_entry(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg)
{
	struct find_data *data = (struct find_data *)arg;

	if (data->count == 0)
	{
		data->key = key;
		data->val = val;
	}
	data->count--;

	return 0;
}

static 
pdf_obj *xpdf_portfolio_entry_obj_name(fz_context *ctx, pdf_document* doc, int entry, pdf_obj **name)
{
	struct find_data data;
	pdf_obj *s;

	if (name)
		*name = NULL;

	s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(Names), PDF_NAME(EmbeddedFiles), NULL);
	data.count = entry;
	data.key = NULL;
	data.val = NULL;
	xpdf_name_tree_map(ctx, s, find_entry, &data);

	if (name)
		*name = data.key;
	return data.val;
}

static 
pdf_obj* xpdf_portfolio_entry_obj(fz_context* ctx, pdf_document* doc, int entry)
{
	pdf_obj* name;

	return xpdf_portfolio_entry_obj_name(ctx, doc, entry, &name);
}

/*
	pdf_portfolio_entry_name: Retrieve the name of
	a given portfolio entry.

	doc: The document in question.

	entry: A value in the 0..m-1 range, where m is the
	value returned from pdf_count_portfolio_entries.

	name: Pointer to a place to store the pointer to the
	object representing the name. This is a borrowed
	reference - do not drop it.

	Returns a pointer to the pdf_object representing the
	name of the entry. This is a borrowed reference - do not drop
	it.
*/
static
pdf_obj* xpdf_portfolio_entry_name(fz_context* ctx, pdf_document* doc, int entry)
{
	pdf_obj* name;

	(void)xpdf_portfolio_entry_obj_name(ctx, doc, entry, &name);
	return name;
}

/*
	pdf_portfolio_entry: Create a buffer containing
	a decoded portfolio entry.

	doc: The document in question.

	entry: A value in the 0..m-1 range, where m is the
	value returned from pdf_count_portfolio_entries.

	Returns a buffer containing the decoded portfolio entry.
    Ownership of the buffer passes to the caller.
*/
static
fz_buffer* xpdf_portfolio_entry(fz_context* ctx, pdf_document* doc, int entry)
{
	pdf_obj* obj = xpdf_portfolio_entry_obj(ctx, doc, entry);

	return pdf_load_stream(ctx, pdf_dict_getl(ctx, obj, PDF_NAME(EF), PDF_NAME(F), NULL));
}


typedef struct
{
	pdf_obj *key;
	pdf_obj *found;
	int found_index;
	pdf_obj *last;
	int last_index;
	int entry;
} find_data;

static int
find_position(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg)
{
	find_data *data = (find_data *)arg;

	if (data->found)
		return 0;
	data->entry++;
	if (data->last != container)
	{
		data->last = container;
		data->last_index = 0;
	}
	else
		data->last_index++;
	if (pdf_objcmp(ctx, key, data->key) > 0)
	{
		data->found = container;
		data->found_index = data->last_index;
	}
	return 0;
}

static 
int xpdf_name_tree_insert(fz_context *ctx, pdf_obj *tree, pdf_obj *key, pdf_obj *val)
{
	find_data data;
	pdf_obj *names, *limits, *limit0, *limit1;

	data.key = key;
	data.found = NULL;
	data.found_index = 0;
	data.last = NULL;
	data.last_index = 0;
	data.entry = 0;
	xpdf_name_tree_map(ctx, tree, find_position, &data);

	if (!data.found)
	{
		data.found = data.last;
		data.found_index = data.last_index;
	}
	if (!data.found)
	{
		/* Completely empty name tree! */
		pdf_dict_put_array(ctx, tree, PDF_NAME(Names), 2);
		pdf_dict_put_array(ctx, tree, PDF_NAME(Limits), 2);
		data.found = tree;
		data.found_index = 0;
	}

	names = pdf_dict_get(ctx, data.found, PDF_NAME(Names));
	if (names == NULL)
		names = pdf_dict_put_array(ctx, data.found, PDF_NAME(Names), 2);
	pdf_array_insert(ctx, names, key, 2*data.found_index);
	pdf_array_insert(ctx, names, val, 2*data.found_index+1);

	limits = pdf_dict_get(ctx, data.found, PDF_NAME(Limits));
	if (limits == NULL)
		limits = pdf_dict_put_array(ctx, data.found, PDF_NAME(Limits), 2);
	limit0 = pdf_array_get(ctx, limits, 0);
	limit1 = pdf_array_get(ctx, limits, 1);
	if (!pdf_is_string(ctx, limit0) || data.found_index == 0)
		pdf_array_put(ctx, limits, 0, key);
	if (!pdf_is_string(ctx, limit1) || 2 * (data.found_index+1) == pdf_array_len(ctx, limits))
		pdf_array_put(ctx, limits, 1, key);

	return data.entry;
}

/*
	pdf_add_portfolio_entry: Add a new portfolio entry.

	doc: The document in question.

	name: The name to use for this entry (as used in the
	PDF name tree for the collection).

	desc: The description to use for this entry (as used
	in the 'Desc' entry in the Collection entry).

	filename: The filename to use for this entry (as used
	in the 'F' and 'UF' entries in the collection entry).

	buf: The buffer containing the embedded file to add.

	Returns the entry number for this new entry.
*/
static
int xpdf_add_portfolio_entry(fz_context *ctx, pdf_document *doc,
				const char *name,
				const char *desc,
				const char *filename,
				fz_buffer *buf)
{
	size_t len; 
	int entry = 0;
	pdf_obj *ef, *f, *params, *s;
	pdf_obj *key;
	pdf_obj *val = NULL;

	fz_var(val);

	/* Portfolios were introduced in PDF 1.7. */
	if (doc->version < 17)
		doc->version = 17;

	key = pdf_new_text_string(ctx, name);
	fz_try(ctx)
	{
		val = pdf_new_dict(ctx, doc, 6);
		pdf_dict_put_dict(ctx, val, PDF_NAME(CI), 4);
		ef = pdf_dict_put_dict(ctx, val, PDF_NAME(EF), 4);
		pdf_dict_put_string(ctx, val, PDF_NAME(F), filename, strlen(filename));
		pdf_dict_put_text_string(ctx, val, PDF_NAME(UF), filename);
		pdf_dict_put_text_string(ctx, val, PDF_NAME(Desc), desc);
		pdf_dict_put(ctx, val, PDF_NAME(Type), PDF_NAME(Filespec));
		pdf_dict_put_drop(ctx, ef, PDF_NAME(F), (f = pdf_add_stream(ctx, doc, buf, NULL, 0)));
		len = fz_buffer_storage(ctx, buf, NULL);
		pdf_dict_put_int(ctx, f, PDF_NAME(DL), len);
		pdf_dict_put_int(ctx, f, PDF_NAME(Length), len);
		params = pdf_dict_put_dict(ctx, f, PDF_NAME(Params), 4);
		pdf_dict_put_int(ctx, params, PDF_NAME(Size), len);

		s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(Collection), NULL);
		if (s == NULL)
		{
			s = pdf_new_dict(ctx, doc, 4);
			pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), s, PDF_NAME(Root), PDF_NAME(Collection), NULL);
		}

		s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(Names), PDF_NAME(EmbeddedFiles), NULL);
		if (s == NULL)
		{
			s = pdf_new_dict(ctx, doc, 4);
			pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), s, PDF_NAME(Root), PDF_NAME(Names), PDF_NAME(EmbeddedFiles), NULL);
		}
		entry = xpdf_name_tree_insert(ctx, s, key, val);
	}
	fz_always(ctx)
	{
		pdf_drop_obj(ctx, key);
		pdf_drop_obj(ctx, val);
	}
	fz_catch(ctx)
		fz_rethrow(ctx);

	return entry;
}


/*
** ============================================================================
**  mu_pft_*
**
**  Bridge bewtween mupdf and Tcl
** ============================================================================
*/


 // returns a tcl-list ( or NULL on error )
Tcl_Obj *mu_ptf_list(Tcl_Interp* interp, fz_context*ctx, pdf_document* doc) {
    Tcl_Obj *resListObj = Tcl_NewListObj(0,NULL);

    fz_var(resListObj);
    fz_try(ctx) {
    	int n = xpdf_count_portfolio_entries(ctx, doc);
       
    	int i;
    	for (i = 0; i < n; i++)	{
    		pdf_obj *name_obj = xpdf_portfolio_entry_name(ctx, doc, i);
    		char *namestr = pdf_new_utf8_from_pdf_string_obj(ctx, name_obj);
            Tcl_ListObjAppendElement(interp, resListObj,Tcl_NewStringObj(namestr,-1));
    		fz_free(ctx,namestr);
    	}
    } fz_catch(ctx) {
        Tcl_DecrRefCount(resListObj);
        resListObj = NULL;
        
        Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
    }
	return resListObj;
}

 // if outputDir is NULL  --> "."
 // if filename is NULL --> use the embedded filename
TclResCode mu_ptf_extract(Tcl_Interp* interp, fz_context* ctx, pdf_document* doc, int idx, char *outputDir, char *filename) {
	fz_buffer *buf = NULL;
	FILE *file = NULL;
    
	if ( idx >= xpdf_count_portfolio_entries(ctx, doc) ) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("Index out of bounds.", -1));		
		return TCL_ERROR;
	}

	if (!outputDir) outputDir = ".";
	char *fullFileName = NULL;
	if (!filename) {
		char *filename2;
		pdf_obj *name_obj = xpdf_portfolio_entry_name(ctx, doc, idx);
		filename2 = pdf_new_utf8_from_pdf_string_obj(ctx, name_obj);
    	asprintf(&fullFileName, "%s/%s", outputDir, filename2);
		fz_free(ctx,filename2);
	} else {
    	asprintf(&fullFileName, "%s/%s", outputDir, filename);	
	}

	fz_var(file);
	fz_var(buf);
    fz_try(ctx) {
    	size_t len;
		unsigned char *data = NULL;
		buf = xpdf_portfolio_entry(ctx, doc, idx);
		len = fz_buffer_storage(ctx, buf, &data);

		file = fopen(fullFileName, "wb");
		if (file == NULL) {
			fz_throw( ctx, FZ_ERROR_GENERIC, "error writing");
		}
		fwrite(data, 1, len, file);
	} fz_always(ctx) {
		if (file) fclose(file);
		if (buf) fz_drop_buffer(ctx, buf);
	} fz_catch(ctx) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("failed writing to \"%s\".", fullFileName));
        return TCL_ERROR;
	}	
	free(fullFileName);
	return TCL_OK;
}
