#ifndef MUPDF_PDF_DOCUMENT_H
#define MUPDF_PDF_DOCUMENT_H

typedef struct pdf_lexbuf_s pdf_lexbuf;
typedef struct pdf_lexbuf_large_s pdf_lexbuf_large;
typedef struct pdf_xref_s pdf_xref;
typedef struct pdf_ocg_descriptor_s pdf_ocg_descriptor;

typedef struct pdf_page_s pdf_page;
typedef struct pdf_annot_s pdf_annot;
typedef struct pdf_annot_s pdf_widget;
typedef struct pdf_js_s pdf_js;

enum
{
	PDF_LEXBUF_SMALL = 256,
	PDF_LEXBUF_LARGE = 65536
};

struct pdf_lexbuf_s
{
	int size;
	int base_size;
	int len;
	int64_t i;
	float f;
	char *scratch;
	char buffer[PDF_LEXBUF_SMALL];
};

struct pdf_lexbuf_large_s
{
	pdf_lexbuf base;
	char buffer[PDF_LEXBUF_LARGE - PDF_LEXBUF_SMALL];
};

/*
	Document event structures are mostly opaque to the app. Only the type
	is visible to the app.
*/
typedef struct pdf_doc_event_s pdf_doc_event;

/*
	the type of function via which the app receives
	document events.
*/
typedef void (pdf_doc_event_cb)(fz_context *ctx, pdf_document *doc, pdf_doc_event *event, void *data);

pdf_document *pdf_open_document(fz_context *ctx, const char *filename);

pdf_document *pdf_open_document_with_stream(fz_context *ctx, fz_stream *file);

void pdf_drop_document(fz_context *ctx, pdf_document *doc);

pdf_document *pdf_keep_document(fz_context *ctx, pdf_document *doc);

pdf_document *pdf_specifics(fz_context *ctx, fz_document *doc);

pdf_document *pdf_document_from_fz_document(fz_context *ctx, fz_document *ptr);
pdf_page *pdf_page_from_fz_page(fz_context *ctx, fz_page *ptr);

int pdf_needs_password(fz_context *ctx, pdf_document *doc);

int pdf_authenticate_password(fz_context *ctx, pdf_document *doc, const char *pw);

int pdf_has_permission(fz_context *ctx, pdf_document *doc, fz_permission p);
int pdf_lookup_metadata(fz_context *ctx, pdf_document *doc, const char *key, char *ptr, int size);

fz_outline *pdf_load_outline(fz_context *ctx, pdf_document *doc);

int pdf_count_layer_configs(fz_context *ctx, pdf_document *doc);

typedef struct
{
	const char *name;
	const char *creator;
} pdf_layer_config;

void pdf_layer_config_info(fz_context *ctx, pdf_document *doc, int config_num, pdf_layer_config *info);

void pdf_select_layer_config(fz_context *ctx, pdf_document *doc, int config_num);

int pdf_count_layer_config_ui(fz_context *ctx, pdf_document *doc);

void pdf_select_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui);

void pdf_deselect_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui);

void pdf_toggle_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui);

typedef enum
{
	PDF_LAYER_UI_LABEL = 0,
	PDF_LAYER_UI_CHECKBOX = 1,
	PDF_LAYER_UI_RADIOBOX = 2
} pdf_layer_config_ui_type;

typedef struct
{
	const char *text;
	int depth;
	pdf_layer_config_ui_type type;
	int selected;
	int locked;
} pdf_layer_config_ui;

void pdf_layer_config_ui_info(fz_context *ctx, pdf_document *doc, int ui, pdf_layer_config_ui *info);

void pdf_set_layer_config_as_default(fz_context *ctx, pdf_document *doc);

int pdf_has_unsaved_changes(fz_context *ctx, pdf_document *doc);

enum pdf_signature_error
{
	PDF_SIGNATURE_ERROR_OKAY,
	PDF_SIGNATURE_ERROR_NO_SIGNATURES,
	PDF_SIGNATURE_ERROR_NO_CERTIFICATE,
	PDF_SIGNATURE_ERROR_DIGEST_FAILURE,
	PDF_SIGNATURE_ERROR_SELF_SIGNED,
	PDF_SIGNATURE_ERROR_SELF_SIGNED_IN_CHAIN,
	PDF_SIGNATURE_ERROR_NOT_TRUSTED,
	PDF_SIGNATURE_ERROR_UNKNOWN
};

typedef struct pdf_pkcs7_designated_name_s
{
	char *cn;
	char *o;
	char *ou;
	char *email;
	char *c;
}
pdf_pkcs7_designated_name;

/* Object that can perform the cryptographic operation necessary for document signing */
typedef struct pdf_pkcs7_signer_s pdf_pkcs7_signer;

/* Increment the reference count for a signer object */
typedef pdf_pkcs7_signer *(pdf_pkcs7_keep_fn)(pdf_pkcs7_signer *signer);

/* Drop a reference for a signer object */
typedef void (pdf_pkcs7_drop_fn)(pdf_pkcs7_signer *signer);

/* Obtain the designated name information from a signer object */
typedef pdf_pkcs7_designated_name *(pdf_pkcs7_designated_name_fn)(pdf_pkcs7_signer *signer);

/* Free the resources associated with previously obtained designated name information */
typedef void (pdf_pkcs7_drop_designated_name_fn)(pdf_pkcs7_signer *signer, pdf_pkcs7_designated_name *name);

/* Predict the size of the digest. The actual digest returned by create_digest will be no greater in size */
typedef int (pdf_pkcs7_max_digest_size_fn)(pdf_pkcs7_signer *signer);

/* Create a signature based on ranges of bytes drawn from a stream */
typedef int (pdf_pkcs7_create_digest_fn)(pdf_pkcs7_signer *signer, fz_stream *in, unsigned char *digest, int *digest_len);

struct pdf_pkcs7_signer_s
{
	pdf_pkcs7_keep_fn *keep;
	pdf_pkcs7_drop_fn *drop;
	pdf_pkcs7_designated_name_fn *designated_name;
	pdf_pkcs7_drop_designated_name_fn *drop_designated_name;
	pdf_pkcs7_max_digest_size_fn *max_digest_size;
	pdf_pkcs7_create_digest_fn *create_digest;
};

/* Unsaved signature fields */
typedef struct pdf_unsaved_sig_s pdf_unsaved_sig;

struct pdf_unsaved_sig_s
{
	pdf_obj *field;
	int byte_range_start;
	int byte_range_end;
	int contents_start;
	int contents_end;
	pdf_pkcs7_signer *signer;
	pdf_unsaved_sig *next;
};

typedef struct pdf_rev_page_map_s pdf_rev_page_map;
struct pdf_rev_page_map_s
{
	int page;
	int object;
};

typedef struct
{
	int number; /* Page object number */
	int64_t offset; /* Offset of page object */
	int64_t index; /* Index into shared hint_shared_ref */
} pdf_hint_page;

typedef struct
{
	int number; /* Object number of first object */
	int64_t offset; /* Offset of first object */
} pdf_hint_shared;

struct pdf_document_s
{
	fz_document super;

	fz_stream *file;

	int version;
	int64_t startxref;
	int64_t file_size;
	pdf_crypt *crypt;
	pdf_ocg_descriptor *ocg;
	fz_colorspace *oi;

	int max_xref_len;
	int num_xref_sections;
	int saved_num_xref_sections;
	int num_incremental_sections;
	int xref_base;
	int disallow_new_increments;
	pdf_xref *xref_sections;
	pdf_xref *saved_xref_sections;
	int *xref_index;
	int freeze_updates;
	int has_xref_streams;
	int has_old_style_xrefs;

	int rev_page_count;
	pdf_rev_page_map *rev_page_map;

	int repair_attempted;

	/* State indicating which file parsing method we are using */
	int file_reading_linearly;
	int64_t file_length;

	int linear_page_count;
	pdf_obj *linear_obj; /* Linearized object (if used) */
	pdf_obj **linear_page_refs; /* Page objects for linear loading */
	int linear_page1_obj_num;

	/* The state for the pdf_progressive_advance parser */
	int64_t linear_pos;
	int linear_page_num;

	int hint_object_offset;
	int hint_object_length;
	int hints_loaded; /* Set to 1 after the hints loading has completed,
			   * whether successful or not! */
	/* Page n references shared object references:
	 *   hint_shared_ref[i]
	 * where
	 *      i = s to e-1
	 *	s = hint_page[n]->index
	 *	e = hint_page[n+1]->index
	 * Shared object reference r accesses objects:
	 *   rs to re-1
	 * where
	 *   rs = hint_shared[r]->number
	 *   re = hint_shared[r]->count + rs
	 * These are guaranteed to lie within the region starting at
	 * hint_shared[r]->offset of length hint_shared[r]->length
	 */
	pdf_hint_page *hint_page;
	int *hint_shared_ref;
	pdf_hint_shared *hint_shared;
	int hint_obj_offsets_max;
	int64_t *hint_obj_offsets;

	int resources_localised;

	pdf_lexbuf_large lexbuf;

	pdf_js *js;

	int recalculate;
	int dirty;
	int redacted;

	pdf_doc_event_cb *event_cb;
	void *event_cb_data;

	int num_type3_fonts;
	int max_type3_fonts;
	fz_font **type3_fonts;

	struct {
		fz_hash_table *images;
		fz_hash_table *fonts;
	} resources;

	int orphans_max;
	int orphans_count;
	pdf_obj **orphans;
};

pdf_document *pdf_create_document(fz_context *ctx);

typedef struct pdf_graft_map_s pdf_graft_map;

pdf_obj *pdf_graft_object(fz_context *ctx, pdf_document *dst, pdf_obj *obj);

pdf_graft_map *pdf_new_graft_map(fz_context *ctx, pdf_document *dst);

pdf_graft_map *pdf_keep_graft_map(fz_context *ctx, pdf_graft_map *map);
void pdf_drop_graft_map(fz_context *ctx, pdf_graft_map *map);

pdf_obj *pdf_graft_mapped_object(fz_context *ctx, pdf_graft_map *map, pdf_obj *obj);

fz_device *pdf_page_write(fz_context *ctx, pdf_document *doc, fz_rect mediabox, pdf_obj **presources, fz_buffer **pcontents);

pdf_obj *pdf_add_page(fz_context *ctx, pdf_document *doc, fz_rect mediabox, int rotate, pdf_obj *resources, fz_buffer *contents);

void pdf_insert_page(fz_context *ctx, pdf_document *doc, int at, pdf_obj *page);

void pdf_delete_page(fz_context *ctx, pdf_document *doc, int number);

void pdf_delete_page_range(fz_context *ctx, pdf_document *doc, int start, int end);

void pdf_finish_edit(fz_context *ctx, pdf_document *doc);

int pdf_recognize(fz_context *doc, const char *magic);

typedef struct pdf_write_options_s pdf_write_options;

/*
	In calls to fz_save_document, the following options structure can be used
	to control aspects of the writing process. This structure may grow
	in the future, and should be zero-filled to allow forwards compatibility.
*/
struct pdf_write_options_s
{
	int do_incremental; /* Write just the changed objects. */
	int do_pretty; /* Pretty-print dictionaries and arrays. */
	int do_ascii; /* ASCII hex encode binary streams. */
	int do_compress; /* Compress streams. */
	int do_compress_images; /* Compress (or leave compressed) image streams. */
	int do_compress_fonts; /* Compress (or leave compressed) font streams. */
	int do_decompress; /* Decompress streams (except when compressing images/fonts). */
	int do_garbage; /* Garbage collect objects before saving; 1=gc, 2=re-number, 3=de-duplicate. */
	int do_linear; /* Write linearised. */
	int do_clean; /* Clean content streams. */
	int do_sanitize; /* Sanitize content streams. */
	int do_appearance; /* (Re)create appearance streams. */
	int do_encrypt; /* Encryption method to use: keep, none, rc4-40, etc. */
	int permissions; /* Document encryption permissions. */
	char opwd_utf8[128]; /* Owner password. */
	char upwd_utf8[128]; /* User password. */
};

extern const pdf_write_options pdf_default_write_options;

pdf_write_options *pdf_parse_write_options(fz_context *ctx, pdf_write_options *opts, const char *args);

int pdf_has_unsaved_sigs(fz_context *ctx, pdf_document *doc);

void pdf_write_document(fz_context *ctx, pdf_document *doc, fz_output *out, pdf_write_options *opts);

void pdf_save_document(fz_context *ctx, pdf_document *doc, const char *filename, pdf_write_options *opts);

int pdf_can_be_saved_incrementally(fz_context *ctx, pdf_document *doc);

#endif
