import PDFDocument from './PDFDocument';
import LayoutBuilder from './LayoutBuilder';
import SVGMeasure from './SVGMeasure';
import { normalizePageSize, normalizePageMargin } from './PageSize';
import { tableLayouts } from './tableLayouts';
import Renderer from './Renderer';
import { isNumber, isValue } from './helpers/variableType';
import { convertToDynamicContent } from './helpers/tools';

class PdfPrinter {

	/**
	 * @param {object} fontDescriptors font definition dictionary
	 * @param {object} virtualfs
	 * @param {object} urlResolver
	 */
	constructor(fontDescriptors, virtualfs, urlResolver) {
		this.fontDescriptors = fontDescriptors;
		this.virtualfs = virtualfs;
		this.urlResolver = urlResolver;
	}

	/**
	 * Executes layout engine for the specified document and renders it into a pdfkit document
	 * ready to be saved.
	 *
	 * @param {object} docDefinition
	 * @param {object} options
	 * @returns {Promise<PDFDocument>} resolved promise return a pdfkit document
	 */
	async createPdfKitDocument(docDefinition, options = {}) {
		await this.resolveUrls(docDefinition);

		docDefinition.version = docDefinition.version || '1.3';
		docDefinition.subset = docDefinition.subset || undefined;
		docDefinition.tagged = typeof docDefinition.tagged === 'boolean' ? docDefinition.tagged : false;
		docDefinition.displayTitle = typeof docDefinition.displayTitle === 'boolean' ? docDefinition.displayTitle : false;
		docDefinition.compress = typeof docDefinition.compress === 'boolean' ? docDefinition.compress : true;
		docDefinition.images = docDefinition.images || {};
		docDefinition.attachments = docDefinition.attachments || {};
		docDefinition.pageMargins = isValue(docDefinition.pageMargins) ? docDefinition.pageMargins : 40;
		docDefinition.patterns = docDefinition.patterns || {};

		if (docDefinition.header && typeof docDefinition.header !== 'function') {
			docDefinition.header = convertToDynamicContent(docDefinition.header);
		}

		if (docDefinition.footer && typeof docDefinition.footer !== 'function') {
			docDefinition.footer = convertToDynamicContent(docDefinition.footer);
		}

		let pageSize = normalizePageSize(docDefinition.pageSize, docDefinition.pageOrientation);

		let pdfOptions = {
			size: [pageSize.width, pageSize.height],
			pdfVersion: docDefinition.version,
			subset: docDefinition.subset,
			tagged: docDefinition.tagged,
			displayTitle: docDefinition.displayTitle,
			compress: docDefinition.compress,
			userPassword: docDefinition.userPassword,
			ownerPassword: docDefinition.ownerPassword,
			permissions: docDefinition.permissions,
			lang: docDefinition.language,
			fontLayoutCache: typeof options.fontLayoutCache === 'boolean' ? options.fontLayoutCache : true,
			bufferPages: options.bufferPages || false,
			autoFirstPage: false,
			info: createMetadata(docDefinition),
			font: null
		};

		this.pdfKitDoc = new PDFDocument(this.fontDescriptors, docDefinition.images, docDefinition.patterns, docDefinition.attachments, pdfOptions, this.virtualfs);
		embedFiles(docDefinition, this.pdfKitDoc);

		const builder = new LayoutBuilder(pageSize, normalizePageMargin(docDefinition.pageMargins), new SVGMeasure());

		builder.registerTableLayouts(tableLayouts);
		if (options.tableLayouts) {
			builder.registerTableLayouts(options.tableLayouts);
		}

		let pages = builder.layoutDocument(docDefinition.content, this.pdfKitDoc, docDefinition.styles || {}, docDefinition.defaultStyle || { fontSize: 12, font: 'Roboto' }, docDefinition.background, docDefinition.header, docDefinition.footer, docDefinition.watermark, docDefinition.pageBreakBefore);
		let maxNumberPages = docDefinition.maxPagesNumber || -1;
		if (isNumber(maxNumberPages) && maxNumberPages > -1) {
			pages = pages.slice(0, maxNumberPages);
		}

		// if pageSize.height is set to Infinity, calculate the actual height of the page that
		// was laid out using the height of each of the items in the page.
		pages.forEach(page => {
			if (page.pageSize.height === Infinity) {
				page.pageSize.height = calculatePageHeight(page, page.pageMargins);
			}
		});

		const renderer = new Renderer(this.pdfKitDoc, options.progressCallback);
		renderer.renderPages(pages);

		return this.pdfKitDoc;
	}

	/**
	 * @param {object} docDefinition
	 * @returns {Promise}
	 */
	async resolveUrls(docDefinition) {
		const getExtendedUrl = url => {
			if (typeof url === 'object') {
				return { url: url.url, headers: url.headers };
			}

			return { url: url, headers: {} };
		};

		for (let font in this.fontDescriptors) {
			if (this.fontDescriptors.hasOwnProperty(font)) {
				if (this.fontDescriptors[font].normal) {
					if (Array.isArray(this.fontDescriptors[font].normal)) { // TrueType Collection
						let url = getExtendedUrl(this.fontDescriptors[font].normal[0]);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].normal[0] = url.url;
					} else {
						let url = getExtendedUrl(this.fontDescriptors[font].normal);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].normal = url.url;
					}
				}
				if (this.fontDescriptors[font].bold) {
					if (Array.isArray(this.fontDescriptors[font].bold)) { // TrueType Collection
						let url = getExtendedUrl(this.fontDescriptors[font].bold[0]);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].bold[0] = url.url;
					} else {
						let url = getExtendedUrl(this.fontDescriptors[font].bold);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].bold = url.url;
					}
				}
				if (this.fontDescriptors[font].italics) {
					if (Array.isArray(this.fontDescriptors[font].italics)) { // TrueType Collection
						let url = getExtendedUrl(this.fontDescriptors[font].italics[0]);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].italics[0] = url.url;
					} else {
						let url = getExtendedUrl(this.fontDescriptors[font].italics);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].italics = url.url;
					}
				}
				if (this.fontDescriptors[font].bolditalics) {
					if (Array.isArray(this.fontDescriptors[font].bolditalics)) { // TrueType Collection
						let url = getExtendedUrl(this.fontDescriptors[font].bolditalics[0]);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].bolditalics[0] = url.url;
					} else {
						let url = getExtendedUrl(this.fontDescriptors[font].bolditalics);
						this.urlResolver.resolve(url.url, url.headers);
						this.fontDescriptors[font].bolditalics = url.url;
					}
				}
			}
		}

		if (docDefinition.images) {
			for (let image in docDefinition.images) {
				if (docDefinition.images.hasOwnProperty(image)) {
					let url = getExtendedUrl(docDefinition.images[image]);
					this.urlResolver.resolve(url.url, url.headers);
					docDefinition.images[image] = url.url;
				}
			}
		}

		if (docDefinition.attachments) {
			for (let attachment in docDefinition.attachments) {
				if (docDefinition.attachments.hasOwnProperty(attachment) && docDefinition.attachments[attachment].src) {
					let url = getExtendedUrl(docDefinition.attachments[attachment].src);
					this.urlResolver.resolve(url.url, url.headers);
					docDefinition.attachments[attachment].src = url.url;
				}
			}
		}

		if (docDefinition.files) {
			for (let file in docDefinition.files) {
				if (docDefinition.files.hasOwnProperty(file) && docDefinition.files[file].src) {
					let url = getExtendedUrl(docDefinition.files[file].src);
					this.urlResolver.resolve(url.url, url.headers);
					docDefinition.files[file].src = url.url;
				}
			}
		}

		await this.urlResolver.resolved();
	}
}

function createMetadata(docDefinition) {
	// PDF standard has these properties reserved: Title, Author, Subject, Keywords,
	// Creator, Producer, CreationDate, ModDate, Trapped.
	// To keep the pdfmake api consistent, the info field are defined lowercase.
	// Custom properties don't contain a space.
	function standardizePropertyKey(key) {
		let standardProperties = ['Title', 'Author', 'Subject', 'Keywords',
			'Creator', 'Producer', 'CreationDate', 'ModDate', 'Trapped'];
		let standardizedKey = key.charAt(0).toUpperCase() + key.slice(1);
		if (standardProperties.includes(standardizedKey)) {
			return standardizedKey;
		}

		return key.replace(/\s+/g, '');
	}

	let info = {
		Producer: 'pdfmake',
		Creator: 'pdfmake'
	};

	if (docDefinition.info) {
		for (let key in docDefinition.info) {
			let value = docDefinition.info[key];
			if (value) {
				key = standardizePropertyKey(key);
				info[key] = value;
			}
		}
	}
	return info;
}

function embedFiles(docDefinition, pdfKitDoc) {
	if (docDefinition.files) {
		for (const key in docDefinition.files) {
			const file = docDefinition.files[key];

			if (!file.src) return;

			if (pdfKitDoc.virtualfs && pdfKitDoc.virtualfs.existsSync(file.src)) {
				file.src = pdfKitDoc.virtualfs.readFileSync(file.src);
			}

			file.name = file.name || key;
			pdfKitDoc.file(file.src, file);
		}
	}
}

function calculatePageHeight(page, margins) {
	function getItemHeight(item) {
		if (typeof item.item.getHeight === 'function') {
			return item.item.getHeight();
		} else if (item.item._height) {
			return item.item._height;
		} else if (item.type === 'vector') {
			if (typeof item.item.y1 !== 'undefined') {
				return item.item.y1 > item.item.y2 ? item.item.y1 : item.item.y2;
			} else {
				return item.item.h;
			}
		} else {
			// TODO: add support for next item types
			return 0;
		}
	}

	function getBottomPosition(item) {
		let top = item.item.y || 0;
		let height = getItemHeight(item);
		return top + height;
	}

	let fixedMargins = normalizePageMargin(margins || 40);
	let height = fixedMargins.top;

	page.items.forEach(item => {
		let bottomPosition = getBottomPosition(item);
		if (bottomPosition > height) {
			height = bottomPosition;
		}
	});

	height += fixedMargins.bottom;

	return height;
}

export default PdfPrinter;
