import axios from 'axios';
import cheerio from "cheerio";
import fs from "fs";
import Handlebars from 'handlebars';
import i18next from "i18next";
import Backend from 'i18next-fs-backend';
import mime from "mime-types";
import path from 'path';
import { PDFDocument } from 'pdf-lib';
import puppeteer from "puppeteer";
import { Runner } from './models/Runner';
import { RunnerGroup } from './models/RunnerGroup';
/**
 * This class is responsible for all things pdf creation.
 * This uses the html templates from src/templates.
 */
export class PdfCreator {
    private templateDir = path.join(__dirname, '/templates');
    private browser;
    private static interpolations = { eventname: "Lauf für Kaya! 2021", sponsoring_receipt_minimum_amount: '10', currency_symbol: "€" }

    /**
     * Main constructor.
     * Initializes i18n(ext), Handlebars and puppeteer.
     */
    constructor() {
        this.init();
    }

    /**
     * Main constructor.
     * Initializes i18n(ext), Handlebars and puppeteer.
     */
    public async init() {
        const minimal_args = [
            '--autoplay-policy=user-gesture-required',
            '--disable-background-networking',
            '--disable-background-timer-throttling',
            '--disable-backgrounding-occluded-windows',
            '--disable-breakpad',
            '--disable-client-side-phishing-detection',
            '--disable-component-update',
            '--disable-default-apps',
            '--disable-dev-shm-usage',
            '--disable-domain-reliability',
            '--disable-extensions',
            '--disable-features=AudioServiceOutOfProcess',
            '--disable-hang-monitor',
            '--disable-ipc-flooding-protection',
            '--disable-notifications',
            '--disable-offer-store-unmasked-wallet-cards',
            '--disable-popup-blocking',
            '--disable-print-preview',
            '--disable-prompt-on-repost',
            '--disable-renderer-backgrounding',
            '--disable-speech-api',
            '--disable-sync',
            '--hide-scrollbars',
            '--ignore-gpu-blacklist',
            '--metrics-recording-only',
            '--mute-audio',
            '--no-default-browser-check',
            '--no-first-run',
            '--no-pings',
            '--no-zygote',
            '--password-store=basic',
            '--use-gl=swiftshader'
        ];

        await i18next
            .use(Backend)
            .init({
                fallbackLng: 'en',
                lng: 'en',
                backend: {
                    loadPath: path.join(__dirname, '/locales/{{lng}}.json')
                }
            });
        await Handlebars.registerHelper('__',
            function (str) {
                return i18next.t(str, PdfCreator.interpolations).toString();
            }
        );
        this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
    }

    /**
     * Generate sponsoring contract pdfs.
     * @param runner The runner you want to generate the contracts for.
     * @param locale The locale used for the contracts (default:en)
     */
    public async generateSponsoringContract(runners: Runner[], locale: string = "en"): Promise<Buffer> {
        if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
            runners[0] = this.generateEmptyRunner();
        }
        if (runners.length > 50) {
            let pdf_promises = new Array<Promise<Buffer>>();
            let i, j;
            for (i = 0, j = runners.length; i < j; i += 50) {
                let chunk = runners.slice(i, i + 50);
                pdf_promises.push(this.generateSponsoringContract(chunk, locale));
            }
            const pdfs = await Promise.all(pdf_promises);
            return await this.mergePdfs(pdfs);
        }
        await i18next.changeLanguage(locale);
        const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
        const template = Handlebars.compile(template_source);
        const result = template({ runners })
        const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
        return pdf
    }

    /**
     * Converts all images in html to base64.
     * Works with image files in the template directory or images from urls.
     * @param html The html string whoms images shall get replaced.
     */
    public async imgToBase64(html): Promise<string> {
        const $ = cheerio.load(html)
        $('img').each(async (index, element) => {
            let imgsrc = $(element).attr("src");
            const img_type = mime.lookup(imgsrc);

            if (!(img_type.includes("image"))) {
                throw new Error("File is not image mime type");
            }

            let image;
            if (imgsrc.startsWith("http")) {
                image = (await axios.get(imgsrc)).data;
                image = Buffer.from(image).toString('base64');
            }
            else {
                if (imgsrc.startsWith("./")) {
                    imgsrc = imgsrc.replace("./", "");
                }
                image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
            }

            image = `data:${img_type};base64,${image}`
            $(element).attr("src", image)
        })

        return $.html();
    }

    /**
     * This method manages the creation of pdfs via puppeteer.
     * @param html The HTML that should get rendered.
     * @param options Puppeteer PDF option (eg: {format: "A4"})
     */
    public async renderPdf(html: string, options): Promise<any> {
        html = await this.imgToBase64(html);
        let page = await this.browser.newPage();
        await page.setContent(html);
        const pdf = await page.pdf(options);
        await page.close();
        return pdf;
    }

    /**
     * Merges multiple pdfs into one.
     * @param pdfs The pdfs you want to merge as an buffer array.
     * @returns The merged pdf as a buffer.
     */
    private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
        const mergedPdf = await PDFDocument.create();

        for (const pdfBuffer of pdfs) {
            const pdf = await PDFDocument.load(pdfBuffer);
            const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
            copiedPages.forEach((page) => {
                mergedPdf.addPage(page);
            });
        }

        return <Buffer>(await mergedPdf.save());
    }

    /**
     * Generates a new dummy runner with halfspaces for all strings.
     * Can be used to generate empty sponsoring contracts.
     * @returns A new runner object that apears to be empty.
     */
    private generateEmptyRunner(): Runner {
        let group = new RunnerGroup();
        group.id = 0;
        group.name = " ";
        let runner = new Runner();
        runner.id = 0;
        runner.firstname = " ";
        runner.lastname = " ";
        runner.group = group;
        return runner;
    }
}