// you may not need all of these but they come up a lot
import fs from "fs";
import logging from '../lib/logging.js';
import path from "path";
import { defer } from "../lib/api.js";
import PDFDocument from "pdfkit";
import assert from "assert";
import { mkdir_to, glob, changed } from "../lib/builderator.js";

const log = logging.create(import.meta.url);

const title_box = { x: 17, y: 415, width: 405, align: "center" };
const summary_box = { width: 405, height: 540 };
const main_title = { align: "center", width: 1196, height: 883, x: 586, y: 99};
const main_box = { width: 928, height: 535, x: 720, y: 323};
const FORCE = false;

export const description = "Generates a PDF presentation from an input .md file and a background template."

// your command uses the npm package commander's options format
export const options = [
  ["--heading-font <path>", "Font file to use for headings.", "static/fonts/VictorMono-Bold.ttf"],
  ["--body-font <path>", "Font file to use for body text.", "static/fonts/VictorMono-Medium.ttf"],
  ["--no-exit", "Normally only used when called directly from other commands."],
  ["--force", "Force the generation no matter what.", FORCE]
]

// put required options in the required variable
export const required = [
  ["--input <path>", "The input .md file _GLOB_ for the source files."],
  ["--template <path>", "The background template image to use."],
]

export const start_pdf = (opts) => {
  const doc = new PDFDocument({
    font: "Courier",
    size: [1920, 1080],
    info: {
      "Title": "Test",
      "Author": "Zed A. Shaw",
      "Subject": "Test subject",
      "Keywords": "test test test",
      "CreationDate": new Date(),
      "ModDate": new Date(),
    }});

  const out_stream = doc.pipe(fs.createWriteStream(opts.output));

  doc.registerFont("heading", opts.headingFont);
  doc.registerFont("body", opts.bodyFont);

  return [doc, out_stream];
}

const valign = (doc, text, options) => {
  assert(options.y !== undefined, "Missing y in options");
  assert(options.height !== undefined, "Missing height in options");

  const h = doc.heightOfString(text, options);
  const start = options.y + (0.5 * (options.height - h));

  return start;
}

const write_page = (doc, template, content) => {
  // place the background image
  doc.image(template, 0, 0, {
    width: 1920,
    height: 1080
  });

  // create the left side constant summary
  doc.fontSize(40);
  doc.font("heading").text(content.title, title_box.x, title_box.y, title_box);
  doc.fontSize(30);
  doc.moveDown();
  // the lesson summary
  doc.font("body").text(content.summary, summary_box);
  doc.fillColor("white");


  if(content.type == "title-bullets") {
    doc.fontSize(120);
    // add the title/body text
    doc.font("heading").text(content.slide_title, main_title.x, main_title.y, main_title);

    doc.fontSize(80);

    doc.list(content.slide_body, main_box.x, main_box.y, main_box);

  } else if(content.type == "title-image") {
    // it's a title only slide
    doc.fontSize(120);
    // add the title/body text
    doc.font("heading").text(content.slide_title, main_title.x, main_title.y, main_title);

    doc.image(content.slide_body, main_box.x, main_box.y, {
      fit: [main_box.width, main_box.height],
      align: "center",
      valign: "center"
    });
  } else if(content.type == "title-only") {
    // it's a title only slide
    doc.fontSize(160);

    const title_start = valign(doc, content.slide_title, main_title);

    doc.font("heading").text(content.slide_title, main_title.x, title_start, main_title);
  } else {
    // this handles title-text slides, and anything else
    // if I put a # I want this to be a subtitle
    doc.fontSize(120);
    doc.font("heading");

    if(content.slide_body.startsWith("#")) {
      // calculate the title height
      let title_start = valign(doc, content.slide_title, main_title);

      // calculate 1 line for the move down
      doc.fontSize(100);
      doc.font("body");
      title_start -= doc.heightOfString("H", main_title);

      doc.fontSize(120);
      // add the title/body text
      doc.font("heading").text(content.slide_title, main_title.x, title_start, main_title);

      doc.fontSize(100);
      const trim_body = content.slide_body.slice(1).trim();
      doc.moveDown(1);

      doc.font("body").text(trim_body, {
        align: "center",
      });
    } else {
      doc.fontSize(120);
      // add the title/body text
      doc.font("heading").text(content.slide_title, main_title.x, main_title.y, main_title);

      doc.moveDown(1);

      doc.fontSize(60);
      doc.font("body").text(content.slide_body, main_box.x, main_box.y, main_box);
    }

  }
}

const next_page = (doc) => {
  doc.addPage();
}

const end_pdf = (doc) => {
  doc.end();
}

const parse_input = (input) => {
  const data = fs.readFileSync(input).toString();
  const result = [];

  const [head_data, slides_data] = data.split("===");
  const head = JSON.parse(head_data);

  for(let slide of slides_data.split("---")) {
    const split = slide.trim().split("\n");
    const title = split.shift();
    const body = split.join("\n").trim();

    if(title || body) {
      const page = {
        slide_title: title,
        slide_body: body,
        ...head
      }

      if(!body) {
        page.type = "title-only";
      } else if(body.startsWith("*")) {
        page.type = "title-bullets";
        page.slide_body = body.split("\n").map(l => l.slice(1).trim());
      } else if(body.startsWith("[")) {
        page.type = "title-image";
        page.slide_body = body.slice(1,-1);
      } else {
        page.type = "title-text";
      }

      result.push(page);
    }
  }

  return result;
}

const make_pdf_path = (input) => {
  const result = path.parse(input);
  const target = path.join(result.dir, `${result.name}.pdf`);
  mkdir_to(target);
  return target;
}

const generate_presentation = (opts, waiting) => {
  try {
    const [doc, out_stream] = start_pdf(opts);

    out_stream.on("finish", () => waiting.resolve());

    const pages = parse_input(opts.input);

    for(let i = 0; i < pages.length; i++) {
      const page = pages[i];

      write_page(doc, opts.template, page);

      // don't add a page when at the end
      if(i !== pages.length - 1) {
        next_page(doc);
      }
    }

    end_pdf(doc);
  } catch(error) {
    console.error(error);
    waiting.resolve();
  }
}

export const main = async (opts) => {
  const in_files = glob(opts.input);

  for(let file of in_files) {
    const settings = {...opts};
    settings.input = file;
    settings.output = make_pdf_path(settings.input);

    if(opts.force || changed(settings.input, settings.output)) {
      const waiting = defer(file);
      generate_presentation(settings, waiting);
      await waiting;
    } else {
      console.log("SKIP", settings.input);
    }
  }

  if(!opts.noExit) process.exit(0);
}
