import * as React from 'react';
import { Orientation } from '../../../Enums/Orientation';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default';
import { MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension } from './Dimension';

interface IDimensionLayerProps {
  root: ContainerModel
  scale: number
}

const MODULE_STROKE_WIDTH = 1;

function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
  const it = MakeRecursionDFSIterator(root, 0, [0, 0]);
  const dimensions: React.ReactNode[] = [];
  const topDim = root.properties.y;
  const leftDim = root.properties.x;
  const rightDim = root.properties.x + root.properties.width;
  const bottomDim = root.properties.y + root.properties.height;

  if (!SHOW_SELF_DIMENSIONS) {
    return [];
  }

  for (const { container, depth, currentTransform } of it) {
    const containerLeftDim = leftDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
    const containerTopDim = topDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
    const containerBottomDim = bottomDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
    const containerRightDim = rightDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
    if (SHOW_SELF_DIMENSIONS && container.properties.showSelfDimensions) {
      AddSelfDimension(container, currentTransform, containerTopDim, containerLeftDim, scale, dimensions);
    }

    if (SHOW_BORROWER_DIMENSIONS && container.properties.isDimensionBorrower) {
      AddBorrowerDimension(containerBottomDim, containerRightDim, depth, scale, container, currentTransform, dimensions);
    }

    if (SHOW_CHILDREN_DIMENSIONS && container.properties.showChildrenDimensions && container.children.length > 1) {
      AddChildrenDimension(container, currentTransform, dimensions, containerBottomDim, containerRightDim, scale);
    }
  }

  return dimensions;
}

function AddChildrenDimension(
  container: IContainerModel,
  currentTransform: [number, number],
  dimensions: React.ReactNode[],
  containerBottomDim: number,
  containerRightDim: number,
  scale: number
): void {
  AddHorizontalChildrenDimension(container, currentTransform, dimensions, containerBottomDim, scale);
  AddVerticalChildrenDimension(container, currentTransform, dimensions, containerRightDim, scale);
}

function AddHorizontalChildrenDimension(
  container: IContainerModel,
  currentTransform: [number, number],
  dimensions: React.ReactNode[],
  containerBottomDim: number,
  scale: number
): void {
  const childrenId = `dim-children-${container.properties.id}`;

  const lastChild = container.children[container.children.length - 1];
  let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
  let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);

  // Find the min and max
  for (let i = container.children.length - 2; i >= 0; i--) {
    const child = container.children[i];
    const left = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
    if (left < xChildrenStart) {
      xChildrenStart = left;
    }
    const right = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
    if (right > xChildrenEnd) {
      xChildrenEnd = right;
    }
  }

  const textChildren = (xChildrenEnd - xChildrenStart)
    .toFixed(2)
    .toString();

  const offset = currentTransform[0] + container.properties.x;
  dimensions.push(<Dimension
    key={childrenId}
    id={childrenId}
    xStart={xChildrenStart + offset}
    xEnd={xChildrenEnd + offset}
    yStart={containerBottomDim}
    yEnd={containerBottomDim}
    strokeWidth={MODULE_STROKE_WIDTH}
    text={textChildren}
    scale={scale} />);
}

function AddVerticalChildrenDimension(
  container: IContainerModel,
  currentTransform: [number, number],
  dimensions: React.ReactNode[],
  containerRightDim: number,
  scale: number
): void {
  const childrenId = `dim-v-children-${container.properties.id}`;

  const lastChild = container.children[container.children.length - 1];
  let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
  let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);

  // Find the min and max
  for (let i = container.children.length - 2; i >= 0; i--) {
    const child = container.children[i];
    const top = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
    if (top < yChildrenStart) {
      yChildrenStart = top;
    }
    const bottom = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
    if (bottom > yChildrenEnd) {
      yChildrenEnd = bottom;
    }
  }

  const textChildren = (yChildrenEnd - yChildrenStart)
    .toFixed(2)
    .toString();

  const offset = currentTransform[0] + container.properties.x;
  dimensions.push(<Dimension
    key={childrenId}
    id={childrenId}
    xStart={containerRightDim}
    yStart={yChildrenStart + offset}
    xEnd={containerRightDim}
    yEnd={yChildrenEnd + offset}
    strokeWidth={MODULE_STROKE_WIDTH}
    text={textChildren}
    scale={scale}
  />);
}

function AddBorrowerDimension(
  bottomDim: number,
  rightDim: number,
  depth: number,
  scale: number,
  container: IContainerModel,
  currentTransform: [number, number],
  dimensions: React.ReactNode[]
): void {
  AddHorizontalBorrowerDimension(bottomDim, container, depth, currentTransform, dimensions, scale);
  AddVerticalBorrowerDimension(rightDim, container, depth, currentTransform, dimensions, scale);
}

function AddHorizontalBorrowerDimension(
  bottomDim: number,
  container: IContainerModel,
  depth: number,
  currentTransform: [number, number],
  dimensions: React.ReactNode[],
  scale: number
): void {
  const yDim = bottomDim;
  const it = MakeRecursionDFSIterator(container, depth, currentTransform);
  const marks = []; // list of vertical lines for the dimension
  for (const {
    container: childContainer, currentTransform: childCurrentTransform
  } of it) {
    const isHidden = !childContainer.properties.markPositionToDimensionBorrower.includes(Orientation.Horizontal);
    if (isHidden) {
      continue;
    }

    const x = TransformX(
      childContainer.properties.x,
      childContainer.properties.width,
      childContainer.properties.positionReference
    );

    const restoredX = x + childCurrentTransform[0];

    marks.push(
      restoredX
    );
  }

  const restoredX = container.properties.x + currentTransform[0];
  marks.push(restoredX);
  marks.push(restoredX + container.properties.width);
  marks.sort((a, b) => a - b);
  let count = 0;
  for (const { cur, next } of Pairwise(marks)) {
    const id = `dim-borrow-${container.properties.id}-{${count}}`;
    dimensions.push(<Dimension
      key={id}
      id={id}
      xStart={cur}
      xEnd={next}
      yStart={yDim}
      yEnd={yDim}
      strokeWidth={MODULE_STROKE_WIDTH}
      text={(next - cur).toFixed(0).toString()}
      scale={scale} />);
    count++;
  }
}

function AddVerticalBorrowerDimension(
  rightDim: number,
  container: IContainerModel,
  depth: number,
  currentTransform: [number, number],
  dimensions: React.ReactNode[],
  scale: number
): void {
  const xDim = rightDim;
  const it = MakeRecursionDFSIterator(container, depth, currentTransform);
  const marks = []; // list of vertical lines for the dimension
  for (const {
    container: childContainer, currentTransform: childCurrentTransform
  } of it) {
    const isHidden = !childContainer.properties.markPositionToDimensionBorrower.includes(Orientation.Vertical);
    if (isHidden) {
      continue;
    }

    const y = TransformY(
      childContainer.properties.y,
      childContainer.properties.height,
      childContainer.properties.positionReference
    );

    const restoredy = y + childCurrentTransform[1];

    marks.push(
      restoredy
    );
  }

  const restoredY = container.properties.y + currentTransform[1];
  marks.push(restoredY);
  marks.push(restoredY + container.properties.height);
  marks.sort((a, b) => a - b);
  let count = 0;
  for (const { cur, next } of Pairwise(marks)) {
    const id = `dim-v-borrow-${container.properties.id}-{${count}}`;
    dimensions.push(<Dimension
      key={id}
      id={id}
      xStart={xDim}
      xEnd={xDim}
      yStart={cur}
      yEnd={next}
      strokeWidth={MODULE_STROKE_WIDTH}
      text={(next - cur).toFixed(0).toString()}
      scale={scale} />);
    count++;
  }
}

function AddSelfDimension(
  container: IContainerModel,
  currentTransform: [number, number],
  topDim: number,
  leftDim: number,
  scale: number,
  dimensions: React.ReactNode[]
): void {
  AddHorizontalSelfDimension(container, currentTransform, topDim, dimensions, scale);
  AddVerticalSelfDimension(container, currentTransform, leftDim, dimensions, scale);
}

function AddVerticalSelfDimension(container: IContainerModel, currentTransform: [number, number], leftDim: number, dimensions: React.ReactNode[], scale: number): void {
  const height = container.properties.height;
  const idVert = `dim-v-${container.properties.id}`;
  const yStart = container.properties.y + currentTransform[1];
  const yEnd = yStart + height;
  const x = leftDim;
  const textVert = height
    .toFixed(0)
    .toString();
  dimensions.push(
    <Dimension
      key={idVert}
      id={idVert}
      xStart={x}
      yStart={yStart}
      xEnd={x}
      yEnd={yEnd}
      strokeWidth={MODULE_STROKE_WIDTH}
      text={textVert}
      scale={scale} />
  );
}

function AddHorizontalSelfDimension(
  container: IContainerModel,
  currentTransform: [number, number],
  topDim: number,
  dimensions: React.ReactNode[],
  scale: number
): void {
  const width = container.properties.width;
  const id = `dim-${container.properties.id}`;
  const xStart = container.properties.x + currentTransform[0];
  const xEnd = xStart + width;
  const y = topDim;
  const text = width
    .toFixed(0)
    .toString();
  dimensions.push(
    <Dimension
      key={id}
      id={id}
      xStart={xStart}
      yStart={y}
      xEnd={xEnd}
      yEnd={y}
      strokeWidth={MODULE_STROKE_WIDTH}
      text={text}
      scale={scale} />
  );
}

/**
 * A layer containing all dimension
 * @param props
 * @returns
 */
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
  return (
    <g>
      { Dimensions(props) }
    </g>
  );
}
