/* 
  ==========================================================================
  Basic infrastructure providing
    CollectionOf_xFormFields
    CollectionOf_xFormWidgets

  Nov 2020 - A.Buratti fecit
  ==========================================================================
*/

/* See https://en.cppreference.com/w/c/experimental/dynamic/strdup */
#define __STDC_WANT_LIB_EXT2__ 1

#include "asprintf.h"
#include <string.h>
#include "xpdf_extension.h"
#include "xpdf_form.h"

/*
** ----------------------------------------------------------------------------
** xFormField section:
**  * Define xFormField's New/Destroy
**  * Define CollectionOf_xFormFields (set of all the fields of a given pdf_document)
**  * Define Build_CollectionOf_xFormFields()
** ----------------------------------------------------------------------------
*/

xFormField* xFormField_New(fz_context* ctx, char* fullname, pdf_obj* fieldObj) {
    xFormField* rec = malloc(sizeof(xFormField));
    if (rec) {
        rec->ctx = ctx;
        rec->fieldname = strdup(fullname);
        rec->fieldObj = pdf_keep_obj(ctx,fieldObj);    
    }
    return rec;
}

void xFormField_Destroy(xFormField* rec) {
    if (!rec) return;
    
    free(rec->fieldname);
    pdf_drop_obj(rec->ctx,rec->fieldObj);
    free(rec);
}

DEFINE_COLLECTION_OF(xFormField *,xFormFields)

	// extract_fields_imp() 
    // scan the pdf document and adds the FieldEntry in the CollectionOf_Fields map.
    // return the number of fields or -1 on error
	//
	// NOTE that the returned map may contain duplicated fieldnames
	// (it's not a good thing, but it's not prohibited ; a field-name may appear more times .. )
static
int extract_fields_imp( 
    CollectionOf_xFormFields* map,
    fz_context *ctx, 
    pdf_obj *pdfArray, 
    const char *pfx ) {

    int fieldCount = 0;
	fz_var(map);
	fz_var(fieldCount);
	
	fz_try(ctx) {
    	int i,n;
		n = pdf_array_len(ctx, pdfArray );
		for (i = 0; i < n; i++) {
			const char *part;
			pdf_obj *fieldObj;
	
	        fieldObj = pdf_array_get(ctx, pdfArray, i);
			part = pdf_dict_get_text_string(ctx, fieldObj, PDF_NAME(T));	// utf8	
	
			pdf_obj *kids = pdf_dict_get(ctx, fieldObj, PDF_NAME(Kids));
			if ( ! kids ) {
				if( part  &&  strcmp(part,"") != 0 ) {
                    char *fullname = NULL;
                    asprintf(&fullname, "%s%s", pfx, part);
                    xFormField *pRec = xFormField_New(
                        ctx, 
                        fullname, 
                        fieldObj);
                    free(fullname);
                    CollectionOf_xFormFields_Add(map,pRec);
                    ++fieldCount;
				}
			} else {
                char *newPfx = NULL;
                asprintf(&newPfx, "%s%s.", pfx, part);
                int subCount = extract_fields_imp( map, ctx, kids, newPfx );
				free(newPfx);				

                if (subCount < 0 ) {
                    fz_throw(ctx,FZ_ERROR_GENERIC, "field scan error");                
                }
                fieldCount += subCount;
				if (subCount == 0) {			
                    char *fullname = NULL;
                    asprintf(&fullname, "%s%s", pfx, part);

                    xFormField *pRec = xFormField_New(
                        ctx, 
                        fullname, 
                        fieldObj);
                    free(fullname);
                    CollectionOf_xFormFields_Add(map,pRec);
                    ++fieldCount;                    
				}
			}
		}
	} fz_catch(ctx) {
        CollectionOf_xFormFields_Reset(map);
		fieldCount = -1;
	}
	return fieldCount;
}


int Build_CollectionOf_xFormFields( fz_context *ctx, pdf_document *doc, CollectionOf_xFormFields* map ) {
	pdf_obj *acroformFields = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(Fields), NULL);
	if (extract_fields_imp( map, ctx, acroformFields, "" ) < 0 ) {
        return -1;
    }
    return 0;
}

/*
** ----------------------------------------------------------------------------
** xFormWidgets section:
**  * Define xFormWidget's New/Destroy
**  * Define CollectionOf_xFormWidgets   (set of all the fields' widgets of a given pdf_document)
**  * Define Build_CollectionOf_xFormWidgets()
** ----------------------------------------------------------------------------
*/

xFormWidget* xFormWidget_New(fz_context* ctx, pdf_obj* fieldObj, pdf_obj* widget, int pageNumber) {
    xFormWidget* rec = malloc(sizeof(xFormWidget));
    if (rec) {
        rec->ctx = ctx;
        rec->fieldObj = pdf_keep_obj(ctx,fieldObj);    
        rec->widgetObj = pdf_keep_obj(ctx,widget);
        rec->pageNumber = pageNumber;    
    }
    return rec;
}

void xFormWidget_Destroy(xFormWidget* rec) {
    if (!rec) return;
    
    pdf_drop_obj(rec->ctx,rec->fieldObj);
    pdf_drop_obj(rec->ctx,rec->widgetObj);
    free(rec);
}

DEFINE_COLLECTION_OF(xFormWidget *,xFormWidgets)


 // ... some auxiliary internal functions 
 //     before defining Build_CollectionOf_xFormWidgets()


 /*
 ** xxpdf_scan_pages()
 ** scan all the pages of a document  applying an external callback
 **  (in this case we apply ScanPageObjWidgets() for collecting all the widgets of a page )
 **
 **  NOTE: this is a deep-level scan function, working on the basic pdf_obj blocks.
 *   We choose not to work on pdf_pages because pdf_load_page() is an heavy function
 
 ** xxpdf_scan_pages() applies a callback function
 ** to every element of the page-tree.
 **  the callback is called with the following params:  ctx, doc, pageNumber, pageObj, pClientData
 **     pClientData (if not NULL) is the address where to store specific results.
 **  If the callback returns -1 , 
 **    then the scan loop is stopped, and xx_pdfscan_pages() returns -1
 **  Note that -1 does not denote a missing result;on the contrary !
 */

typedef void *GenericPtr;

 // Prototype for the xxpdf_scan_pages() callback.
 // Typically, clientData points to a data structure containing application-specific data 
typedef int (*pOperationalFun)(fz_context *ctx, int pagenum, pdf_obj *pageobj, GenericPtr pClientData);  


 /*
 **  xxpdf_scan_pages() params:
 **   ctx  - the document's context
 **   node - the starting node of the documents' page-tree
 **   Func - the callback to be applied to every leaf of the page-tree
 **   pPageCount - where to save the count of the processed pages.
 ** xxpdfscan_pages() returns an exit-code
 **    0 : all pages have been processed
 **    1 :
 ** Warning: xxpdf_scan_pages() MAY do fz_throw() ..
 */
static
int xxpdf_scan_pages(fz_context *ctx, pdf_obj *node, pOperationalFun Func, GenericPtr pClientData, int *pPageCount) {
	int res = 0;
 
	// pdf_mark/unmark has been removed ... if page.tree is corrupted, you are in serious troubles.
	
	// do NOT catch errors here ; it is responsability of the caller .... 
	pdf_obj *kids = pdf_dict_get(ctx, node, PDF_NAME(Kids));
	int len = pdf_array_len(ctx, kids);
	int i;		
	for ( i= 0; (i < len) && (res == 0) ; ++i ) {
		pdf_obj *kid = pdf_array_get(ctx, kids, i);
		pdf_obj *type = pdf_dict_get(ctx, kid, PDF_NAME(Type));
		// expected type : /Pages or /Page
		if (!type) { 
			// this is a weird error  ...
			fz_throw(ctx,FZ_ERROR_GENERIC, "Found un-typed element of a tree-node");
		}
		if ( pdf_name_eq(ctx, type, PDF_NAME(Pages)) ) {
			res = xxpdf_scan_pages(ctx,kid,Func,pClientData, pPageCount);
		} else {
			// assert: kid is a simple indirect ref to a dictionary of type /Page

			// ASSERT : pPageCount != NULL
			res = Func( ctx, *pPageCount, kid, pClientData );
			++(*pPageCount);
		}
	}
	return res;
}
 
// ========================================================================== 

/* Find the point in a field hierarchy where all descendants
 * share the same name
 * Duplicated from pdf-form.c  (it's private)
 */
static pdf_obj *find_head_of_field_group(fz_context *ctx, pdf_obj *obj)
{
	if (obj == NULL || pdf_dict_get(ctx, obj, PDF_NAME(T)))
		return obj;
	else
		return find_head_of_field_group(ctx, pdf_dict_get(ctx, obj, PDF_NAME(Parent)));
}

static
int ScanPageObjWidgets( fz_context *ctx, int pageNumber, pdf_obj *pageObj, GenericPtr pClientData ) {
    CollectionOf_xFormWidgets* map = pClientData;
	pdf_obj *annots = pdf_dict_get(ctx, pageObj, PDF_NAME(Annots));
	if ( ! annots ) { return 0; }

	int i;
	int n = pdf_array_len(ctx,annots);	
	for (i = 0 ; i < n ; ++i ) {
		pdf_obj *annotObj = pdf_array_get(ctx, annots, i);

		pdf_obj *subtype = pdf_dict_get(ctx, annotObj, PDF_NAME(Subtype));
		if ( pdf_annot_type_from_string(ctx, pdf_to_name(ctx, subtype)) != PDF_ANNOT_WIDGET ) continue;

        pdf_obj* fieldObj = find_head_of_field_group(ctx, annotObj);
		if (fieldObj == NULL ) continue ;  //  weird case ...         

        xFormWidget *pRec = xFormWidget_New(
            ctx, 
            fieldObj, 
            annotObj, 
            pageNumber);
         CollectionOf_xFormWidgets_Add(map, pRec);

	}
	return 0;
}


int Build_CollectionOf_xFormWidgets( fz_context *ctx, pdf_document *doc, CollectionOf_xFormWidgets* map ) {
	pdf_obj *rootNodeObj = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/Pages");

    fz_try(ctx) {
	   int dummyCount = 0;
	   xxpdf_scan_pages(ctx, rootNodeObj, ScanPageObjWidgets, map, &dummyCount );
    } fz_catch(ctx) {
        return -1;
    }
    return 0;
}

