import { mdiXml } from "@mdi/js";
import Icon from "@mdi/react";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import { IconButton, Tooltip } from "@mui/material";
import jsPDF from "jspdf";
import React from "react";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { Couple } from "../../api/genealogy/CoupleApi";
import { Member } from "../../api/genealogy/MemberApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import { FamilyTreeNode } from "../../utils/family_tree";
import { downloadBlob } from "../../utils/files_utils";
import { getTextWidth } from "../../utils/render_utils";
import "./simpletree.css";
import "./Roboto-normal";
import "svg2pdf.js";

const FACE_WIDTH = 60;
const FACE_HEIGHT = 70;

/**
 * Vertical space between faces and text
 */
const FACE_TEXT_SPACING = 2;

/**
 * Cards height
 */
const CARD_HEIGHT = 114;

/**
 * Space between spouse cards
 */
const SPOUSE_SPACING = 10;

/**
 * Space between two siblings hierachy
 */
const SIBLINGS_SPACING = 15;

/**
 * Vertical space between two generations
 */
const LEVEL_SPACING = 25;

const NAME_FONT = "13px Roboto";
const BIRTH_FONT = "10px Roboto";

interface SimpleTreeSpouseInfo {
  member: Member;
  couple: Couple;
}

interface SimpleTreeNode {
  member: Member;
  spouse?: SimpleTreeSpouseInfo;
  down: SimpleTreeNode[];

  /**
   * The width of the parent and its children
   */
  width: number;

  /**
   * The width of the parents
   */
  parentWidth: number;

  /**
   * The sum of the width of the children
   */
  childrenWidth: number;
}

/**
 * Get the width of a member card
 */
function memberCardWidth(m?: Member): number {
  if (!m) return 0;
  const firstNameWidth = getTextWidth(m.first_name ?? "", NAME_FONT);
  const lastNameWidth = getTextWidth(m.lastNameUpperCase ?? "", NAME_FONT);
  const birthDeathWidth = getTextWidth(m.displayBirthDeath, BIRTH_FONT);
  return Math.max(FACE_WIDTH, firstNameWidth, lastNameWidth, birthDeathWidth);
}

function center(container_width: number, el_width: number): number {
  return Math.floor((container_width - el_width) / 2);
}

function buildSimpleTreeNode(
  tree: FamilyTreeNode,
  depth: number
): SimpleTreeNode {
  if (depth === 0) throw new Error("Too much recursion reached!");

  const lastCoupleId = tree.couples?.length ?? 1;
  const lastCouple = tree.couples?.[lastCoupleId - 1];

  // Preprocess children
  let childrenToProcess = tree.down;
  if (depth > 1)
    tree.couples?.forEach(
      (c) => (childrenToProcess = childrenToProcess?.concat(c.down))
    );
  else childrenToProcess = [];

  const node: SimpleTreeNode = {
    down:
      childrenToProcess?.map((c) => buildSimpleTreeNode(c, depth - 1)) ?? [],
    member: tree.member,
    spouse: lastCouple
      ? {
          couple: lastCouple.couple,
          member: lastCouple.member,
        }
      : undefined,
    parentWidth: -1,
    childrenWidth: -1,
    width: -1,
  };

  // Compute current level width
  let levelWidth: number;
  if (node.spouse) {
    levelWidth =
      SPOUSE_SPACING +
      memberCardWidth(node.member) +
      memberCardWidth(node.spouse.member);
  } else {
    levelWidth = memberCardWidth(node.member);
  }

  // Compute down level width
  const downWidth =
    SIBLINGS_SPACING * node.down.length -
    SIBLINGS_SPACING +
    node.down.reduce((prev, n) => prev + n.width, 0);

  node.parentWidth = levelWidth;
  node.childrenWidth = downWidth;
  node.width = Math.max(levelWidth, downWidth);
  return node;
}

/**
 * Simple family tree
 *
 * Only one couple can be shown in this version
 */

export function SimpleFamilyTree(p: {
  tree: FamilyTreeNode;
  depth: number;
}): React.ReactElement {
  const darkTheme = useDarkTheme();

  const tree = React.useMemo(
    () => buildSimpleTreeNode(p.tree, p.depth),
    [p.tree, p.depth]
  );

  const height = p.depth * (CARD_HEIGHT + LEVEL_SPACING) - LEVEL_SPACING;

  console.info(`tree width=${tree.width} height=${height}`);

  const doExport = async (onlySVG: boolean) => {
    const el: HTMLElement = document.querySelector(".simpletree")!;
    const svg = el.outerHTML;

    // Download in SVG format
    if (onlySVG) {
      const blob = new Blob([svg], {
        type: "image/svg+xml",
      });

      downloadBlob(blob, "ArbreGenealogique.svg");

      return;
    }

    const PDF_MARGIN = 10;

    const PDF_MAX_SIZE = 14400 - PDF_MARGIN * 2;

    const tree_width = Math.min(tree.width, PDF_MAX_SIZE);
    const tree_height = Math.min(height, PDF_MAX_SIZE);

    console.info(`pdf tree w=${tree_width} h=${tree_height}`);

    // Download in PDF format
    const doc = new jsPDF({
      orientation: "l",
      unit: "px",
      format: [tree_height + PDF_MARGIN * 2, tree_width + PDF_MARGIN * 2],
    });

    doc.setFont("Roboto", "normal");

    await doc.svg(el, {
      x: PDF_MARGIN,
      y: PDF_MARGIN,
      height: tree_height,
      width: tree_width,
    });

    // Save the created pdf
    doc.save("ArbreGenealogique.pdf");
  };

  const exportPDF = () => doExport(false);

  const exportSVG = () => doExport(true);

  return (
    <div>
      <div style={{ textAlign: "right" }}>
        <Tooltip title="Exporter le graphique au format PDF">
          <IconButton onClick={exportPDF}>
            <PictureAsPdfIcon />
          </IconButton>
        </Tooltip>
        <Tooltip title="Exporter le graphique au format SVG">
          <IconButton onClick={exportSVG}>
            <Icon path={mdiXml} size={1} />
          </IconButton>
        </Tooltip>
      </div>
      <TransformWrapper maxScale={15} minScale={0.2}>
        <TransformComponent wrapperStyle={{ width: "100%", flex: "1" }}>
          <svg
            className={`simpletree ${
              darkTheme.enabled ? "simpletree-dark" : ""
            }`}
            width={tree.width}
            height={height}
            xmlns="http://www.w3.org/2000/svg"
          >
            <NodeArea node={tree} x={0} y={0} />
          </svg>
        </TransformComponent>
      </TransformWrapper>
    </div>
  );
}

function NodeArea(p: {
  x: number;
  y: number;
  childrenLinkDestX?: number;
  childrenLinkDestY?: number;
  node: SimpleTreeNode;
}): React.ReactElement {
  let parent_x_offset: number;

  let pers1 = p.node.member;
  let pers2 = p.node.spouse?.member;
  let didSwap = false;

  // Show male of the left (all the time)
  if (pers2?.sex === "M") {
    let s = pers1;
    pers1 = pers2;
    pers2 = s;
    didSwap = true;
  }

  parent_x_offset = p.x + center(p.node.width, p.node.parentWidth);

  let unusedChildrenWidth = p.node.width - p.node.childrenWidth;
  let downXOffset = p.x + Math.floor(unusedChildrenWidth / 2);

  let endFirstFaceX =
    parent_x_offset +
    Math.floor((memberCardWidth(pers1) - FACE_WIDTH) / 2) +
    FACE_WIDTH;

  let beginingOfSecondCardX =
    parent_x_offset + p.node.parentWidth - memberCardWidth(pers2);

  let beginSecondFaceX =
    p.node.spouse &&
    beginingOfSecondCardX + (memberCardWidth(pers2) - FACE_WIDTH) / 2;

  let middleParentFaceY = p.y + Math.floor(FACE_HEIGHT / 2);

  // Compute points for link between children and parent
  let parentLinkX = didSwap
    ? beginSecondFaceX! + Math.floor(FACE_WIDTH / 2)
    : parent_x_offset + Math.floor(memberCardWidth(pers1) / 2);
  let parentLinkY = p.y;

  // Remove ugly little shifts
  if (Math.abs(parentLinkX - (p.childrenLinkDestX ?? 0)) < 10)
    parentLinkX = p.childrenLinkDestX!;

  let childrenLinkX: number;
  let childrenLinkY: number;

  // If the father is the father of all the children, while the
  // mother is not the mother of any of the children
  if (
    pers2 &&
    p.node.down.every(
      (n) => n.member.father === pers1.id && n.member.mother !== pers2!.id
    )
  ) {
    childrenLinkX = parent_x_offset + Math.floor(memberCardWidth(pers1) / 2);
    childrenLinkY = p.y + CARD_HEIGHT + 2;
  }
  // If the mother is the mother of all the children, while the
  // father is not the father of any of the children
  else if (
    pers2 &&
    p.node.down.every(
      (n) => n.member.father !== pers1.id && n.member.mother === pers2!.id
    )
  ) {
    childrenLinkX = beginSecondFaceX! + Math.floor(memberCardWidth(pers2) / 2);
    childrenLinkY = p.y + CARD_HEIGHT + 2;
  }

  // Normal couple
  else if (p.node.spouse) {
    childrenLinkX = Math.floor((endFirstFaceX + beginSecondFaceX!) / 2);
    childrenLinkY = middleParentFaceY;
  }

  // Single person
  else {
    childrenLinkX = parent_x_offset + Math.floor(memberCardWidth(pers1) / 2);
    childrenLinkY = p.y + CARD_HEIGHT + 2;
  }

  return (
    <>
      {/* Parent link */}
      {p.childrenLinkDestX && (
        <path
          className="link"
          fill="none"
          stroke="#000"
          d={`M${p.childrenLinkDestX} ${p.childrenLinkDestY} V ${
            parentLinkY - Math.floor(LEVEL_SPACING / 2)
          } H${parentLinkX} V${parentLinkY}`}
        ></path>
      )}

      <MemberCard x={parent_x_offset} y={p.y} member={pers1} />

      {p.node.spouse && (
        <>
          {/* Couple link */}
          <path
            className="link"
            fill="none"
            stroke="#000"
            d={`M${endFirstFaceX} ${middleParentFaceY} H ${beginSecondFaceX}`}
          ></path>

          <MemberCard x={beginingOfSecondCardX} y={p.y} member={pers2!} />
        </>
      )}

      {p.node.down.map((n) => {
        const el = (
          <NodeArea
            x={downXOffset}
            y={p.y + CARD_HEIGHT + LEVEL_SPACING}
            childrenLinkDestX={childrenLinkX}
            childrenLinkDestY={childrenLinkY}
            node={n}
          />
        );
        downXOffset += n.width + SIBLINGS_SPACING;
        return el;
      })}

      {/*<circle cx={childrenLinkX} cy={childrenLinkY} r="2" fill="red" />
      <circle cx={parentLinkX} cy={parentLinkY} r="2" fill="green" />*/}
    </>
  );
}

function MemberCard(p: {
  x: number;
  y: number;
  member: Member;
}): React.ReactElement {
  const w = memberCardWidth(p.member);
  return (
    <g transform={`translate(${p.x} ${p.y})`} width={w} height={CARD_HEIGHT}>
      {/* Member image */}
      {p.member.hasPhoto ? (
        <image
          x={center(w, FACE_WIDTH)}
          href={p.member.thumbnailURL!}
          height={FACE_HEIGHT}
          width={FACE_WIDTH}
          preserveAspectRatio="xMidYMin slice"
        ></image>
      ) : (
        <GenderlessIcon
          x={center(w, FACE_WIDTH)}
          width={FACE_WIDTH}
          height={FACE_HEIGHT}
        />
      )}

      {/* Member text */}
      <text y={FACE_HEIGHT + FACE_TEXT_SPACING}>
        <tspan
          x={center(w, getTextWidth(p.member.first_name ?? "", NAME_FONT))}
          dy="14"
          font-size="13"
          fontFamily="Roboto"
        >
          {p.member.first_name ?? ""}
        </tspan>
        <tspan
          x={center(
            w,
            getTextWidth(p.member.lastNameUpperCase ?? "", NAME_FONT)
          )}
          dy="14"
          font-size="13"
          fontFamily="Roboto"
        >
          {p.member.lastNameUpperCase ?? ""}
        </tspan>
        <tspan
          x={center(
            w,
            getTextWidth(p.member.displayBirthDeathShort, BIRTH_FONT)
          )}
          dy="14"
          font-size="10"
          fontFamily="Roboto"
        >
          {p.member.displayBirthDeathShort}
        </tspan>
      </text>
    </g>
  );
}

function GenderlessIcon(p: {
  x?: number;
  width: number;
  height: number;
}): React.ReactElement {
  return (
    <g transform={`translate(${p.x ?? 0} 0)`}>
      <rect height={p.height} width={p.width} fill="rgb(59, 85, 96)" />
      <g transform={`scale(${p.width * 0.001616})`}>
        <path
          transform="translate(50,40)"
          fill="lightgrey"
          d="M256 288c79.5 0 144-64.5 144-144S335.5 0 256 0 112 
          64.5 112 144s64.5 144 144 144zm128 32h-55.1c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16H128C57.3 320 0 377.3 
          0 448v16c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48v-16c0-70.7-57.3-128-128-128z"
        />
      </g>
    </g>
  );
}
