// 12-Dec-24 | caa2627 | ST/EC/1920 | Removed caa2570 and corrected clipping logic (xcomp update required)
// 19-Jul-24 | caa2607 | ST/RC/1467 | Issues with line colors
// 11-Jul-24 | caa2601 | ST/RC/1466 | Opacity issues with rounded rectangles
// 02-Jul-24 | caa2596 | ST/EC/1888 | Limit maximum line height and width only when it's greater than page height or width
// 17-Jun-24 | caa2591 | ST/RC/1464 | Transparency problems
// 15-Apr-24 | caa2570 | ST/EC/1864 | Limit maximum line height and width to no more than page height - starting y point, or page width - starting x point
// 28-Mar-24 | caa2562 | ST/RC/1457 | Added drawImage function
// 13-Feb-24 | caa2546 | ST/EC/1842 | Issues when right clicking linked text
// 22-Jan-24 | caa2541 | ST/EC/1572 | Added support for angled text
// 10-Jul-23 | caa2129 | ST/RC/1417 | Filtering PNGs greens to transparent
// 19-May-23 | caa2123 | ST/RC/1416 | Fill opacity was not being applied to rectangles

const fs = require("fs"); // caa2129
const PNG = require("pngjs").PNG; // caa2129
const fontsEngine = require('fontkit');

// Utils
const inch = 72.0;
const cm = inch / 2.54;
const mm = cm * 0.1;
const pica = 12.0;

const left = 0;
const center = 1;
const right = 2;
const justify = 5;
const noUnderline = 0;
const underline = 1;
const noLineThrough = 0;
const lineThrough = 1;

function cmToPt(cm) {
	return cm * 28.3465;
}

// Font Discovery helpers
const italics = [
	'italic',
	'oblique',
	'inclined'
];

const regulars = [
	'regular',
	'normal',
	'plain',

]

const thins = [
	'light',
	'thin',
	'extralight',
	'extra light',
	'extra-light',
	'ultralight',
	'ultra light',
	'ultra-light'
];

const semi_bolds = [
	'demibold',
	'demi bold',
	'demi-bold',
	'semibold',
	'semi bold',
	'semi-bold',
	'medium'
];

const bolds = [
	'bold'
];

const extra_bolds = [
	'extrabold',
	'extra bold',
	'extra-bold',
	'black',
	'extrablack',
	'extra black',
	'extra-black',
	'ultrabold',
	'ultra bold',
	'ultra-bold',
	'heavy'
];

function get_string(buffer) {
	let string = '';
	if (Buffer.isBuffer(buffer)) {
		string = buffer.toString('utf8');
	} else {
		string = buffer;
	}
	return string;
}

function is_style(font, styles) {
	let outcome = false;
	styles.every((item) => {
		if (get_string(font.postscriptName).toLowerCase().includes(item)) {
			outcome = true;
			return false; // for every loop
		} else return true; // for every loop
	});
	return outcome;
}

// Font collections helpers
function find_bold_and_italic_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, bolds) && is_style(item, italics) &&
			!is_style(item, semi_bolds) && !is_style(item, extra_bolds)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_bold_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, bolds) &&
			!is_style(item, semi_bolds) && !is_style(item, extra_bolds) && !is_style(item, italics)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_italic_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, italics) &&
			!is_style(item, semi_bolds) && !is_style(item, extra_bolds) && !is_style(item, bolds) && !is_style(item, thins)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_semibold_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, semi_bolds) &&
			!is_style(item, italics)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_semibold_and_italic_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, semi_bolds) && is_style(item, italics)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_extrabold_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, extra_bolds) && !is_style(item, italics)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_extrabold_and_italic_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, extra_bolds) && is_style(item, italics)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

function find_regular_font(fonts) {
	let fontname;
	fonts.every(item => {
		if (is_style(item, regulars)) {
			fontname = item.postscriptName;
			return false;
		} else return true;
	})
	return fontname;
}

// Omnis PDF functions

function drawJustifiedText(doc, x, y, width, height, spareSpace, ltWidth, textArray, angle) {
	var spaces = 0;
	var wordSpace = 0;

	textArray.forEach(element => {
		if (!element.img) {
			spaces += element.text.split(' ').length - 1;
		}
	})
	if (spaces > 0) {
		wordSpace = spareSpace / (spaces * 1.0);
	}

	textArray.forEach(element => {
		if (element.img) {
			doc.image(element.img, x, y, {
				fit: [element.width, element.height]
			});
			x += element.width + 1;
		} else {
			doc.font(element.fontname)
				.fontSize(element.fontsize)
				.fillColor(element.rgb);
      
      start_angle(doc, angle, { origin: [x, y]}); // caa2541
      
			var s = element.text;
			if (s.length !== 1 && s != ' ') {
				doc.text(element.text, x, y, {
					underline: element.underlineText,
					strike: element.lineThroughText,
					align: 'justify',
					wordSpacing: wordSpace
				}, lineBreak = false);
			} else {
				doc.text(element.text, x, y, {
					underline: element.underlineText,
					strike: element.lineThroughText,
					align: 'justify'
				}, lineBreak = false);
			}

      end_angle(doc, angle); // caa2541 // caa2546

			x += doc.widthOfString(element.text);
		}
	})
}

// caa2541 start
function start_angle(doc, angle, options = {})
{
  if (angle==0) return;
  doc.save();
  doc.rotate(angle, options);
}

function end_angle(doc, angle) // caa2546
{
  if (angle==0) return; // caa2546
  doc.restore();
}
// caa2541 end

function drawText(doc, x, y, width, height, jst, ltWidth, textArray, angle) {
	var widthOfRuns = 0;
  
	textArray.forEach(element => {
		if (element.img) {
			widthOfRuns += (element.width + 1);
		} else {
			doc.font(element.fontname)
				.fontSize(element.fontsize);
			widthOfRuns += doc.widthOfString(element.text);
		}
	})

	switch (jst) {
		case center:
			x += (width - widthOfRuns) / 2;
			break;
		case right:
			x += width - widthOfRuns;
			break;
		case justify:
			if ((widthOfRuns / width) * 100 >= 75) {
				drawJustifiedText(doc, x, y, width, height, width - widthOfRuns, ltWidth, textArray, angle); // caa2541
				return;
			}
			break;
		default:
			break;
	}

	textArray.forEach(element => {
		if (element.img) {
			doc.image(element.img, x, y, {
				fit: [element.width, element.height]
			});
			x += (element.width + 1);
		} else {
      start_angle(doc, angle, { origin: [x, y]}); // caa2541
			doc.font(element.fontname)
				.fontSize(element.fontsize)
				.fillColor(element.rgb)
				.text(element.text, x, y, {
					underline: element.underlineText,
					strike: element.lineThroughText
				}, lineBreak = false);
      end_angle(doc, angle); // caa2541 // caa2546
			var widthOfRun = doc.widthOfString(element.text);
			x += widthOfRun;
		}
	})
}

function clipRectangle(doc, x, y, width, height) {
	doc.save()
		.rect(x, y, width, height)
		.clip(); // caa2627: removed .restore()
}

function restoreClip(doc) {
  doc.restore();
}

function drawRectangle(doc, x, y, width, height, borderColor, fillColor, strokeWidth, hasFill) {
	doc.save() // caa2562
		.rect(x, y, width, height)
		.strokeColor(borderColor)
		.strokeOpacity(strokeWidth ? 1 : 0)
		.fillColor(fillColor)
		.fillOpacity(hasFill ? 1 : 0) // caa2123
		.lineWidth(strokeWidth / 2)
		.fillAndStroke()
		.restore();
}

function drawRoundedRectangle(doc, x, y, width, height, borderColor, fillColor, strokeWidth, hasFill) {
	doc.save() // caa2562
		.roundedRect(x, y, width, height, 6)
		.strokeColor(borderColor)
		.strokeOpacity(strokeWidth ? 1 : 0)
		.fillColor(fillColor)
		.fillOpacity(hasFill ? 1 : 0) // caa2601
		.lineWidth(strokeWidth / 2)
		.fillAndStroke()
		.restore();
}

function drawEllipse(doc, x, y, width, height, borderColor, fillColor, strokeWidth, hasFill) {
	radiusX = width / 2;
	radiusY = height / 2;
	centerX = x + radiusX;
	centerY = y + radiusY;
	doc.save() // caa2562
		.ellipse(centerX, centerY, radiusX, radiusY)
		.strokeColor(borderColor)
		.strokeOpacity(strokeWidth ? 1 : 0)
		.fillColor(fillColor)
		.fillOpacity(hasFill ? 1 : 0) // caa2601
		.lineWidth(strokeWidth / 2)
		.fillAndStroke()
		.restore();
}

// caa2562 start
function drawImage(doc, path, x, y, options = {}, opacity=1) {
	doc.save()
		.opacity(opacity); // caa2591
	doc.image(path, x, y, options);
	doc.restore();
}
// caa2562 end

function drawLine(doc, x1, y1, x2, y2, colour, width, dash1, dash2) {
  /* caa2627 disabled the following logic to limit line lengths
  // caa2570 start
	if (Math.abs(y1 - y2) <= 1 & x2 > doc.page.width) { // caa2596
		// Horizontal line
		const maxLineWidth = doc.page.width - x1;
		if (x2 > maxLineWidth)
			x2 = maxLineWidth;
	} else if (Math.abs(x1 - x2) <= 1 & y2 > doc.page.height) { // caa2596
		// Vertical line
		const maxLineHeight = doc.page.height - y1;
		if (y2 > maxLineHeight)
			y2 = maxLineHeight;
	}
	// caa2570 end
  */
	doc.save()
    .moveTo(x1, y1)
		.lineTo(x2, y2)
		.lineWidth(width)
		.strokeColor(colour) // caa2607
		.fillColor(colour);
	if (dash1 && dash2 !== 0)
		doc.dash(dash1, {
			space: dash2
		});
	doc.fillAndStroke();
	doc.restore();
}

function registerFont(doc, name, path, bold, semibold, italic) {
	const font = fontsEngine.openSync(path);

	if (path.toLowerCase().endsWith('.ttc') ||
		path.toLowerCase().endsWith(".dfont")) {
		// We're loading from a collection of fonts
		const names = font.fonts.map((item) => ({
			postscriptName: item.postscriptName,
			subfamilyName: item.subfamilyName
		}));

		var fontname = 'Regular';

		if (bold && semibold && italic)
			fontname = find_extrabold_and_italic_font(names);
		else if (bold && semibold)
			fontname = find_extrabold_font(names);
		else if (semibold && italic)
			fontname = find_semibold_and_italic_font(names);
		else if (bold && italic)
			fontname = find_bold_and_italic_font(names);
		else if (semibold)
			fontname = find_semibold_font(names);
		else if (bold)
			fontname = find_bold_font(names);
		else if (italic)
			fontname = find_italic_font(names);
		else
			fontname = find_regular_font(names);

		if (!fontname) fontname = names[0].postscriptName; // in case no font gets found, default to first available

		doc.registerFont(name, path, fontname);
	} else
		// We're loading from a single file
		doc.registerFont(name, path);

}

// caa2129 start
function makeGreensTransparent(imagePath, maskFrom, maskTo) {
	const image = PNG.sync.read(fs.readFileSync(imagePath));
	for (let y = 0; y < image.height; y++) {
		for (let x = 0; x < image.width; x++) {
		  	const idx = (image.width * y + x) << 2;
		  	if ( image.data[idx + 1] >= maskFrom && image.data[idx + 1] <= maskTo )
				image.data[idx + 3] = 0;
		}
	}
	fs.writeFileSync(imagePath, PNG.sync.write(image));
}
// caa2129 end

module.exports = {
	// Constants
	inch,
	cm,
	mm,
	pica,
	left,
	center,
	right,
	justify,
	noUnderline,
	underline,
	noLineThrough,
	lineThrough,
	// Functions
	drawJustifiedText,
	drawText,
	clipRectangle,
	drawRectangle,
	drawRoundedRectangle,
	drawEllipse,
	drawLine,
	registerFont,
	makeGreensTransparent,
	drawImage,
  restoreClip // caa2627
}