/**
 * This program has been developed by students from the bachelor Computer Science at
 * Utrecht University within the Software Project course.
 * © Copyright Utrecht University (Department of Information and Computing Sciences)
 */
import { Position } from 'reactflow';
import { BoundingBox } from '../../vis/common'; // TODO: MOVE ELSEWHERE

/** This is the interface to get the center of an edge */
export interface GetCenterParams {
  sourceX: number;
  sourceY: number;
  targetX: number;
  targetY: number;
  offset: number;
  sourcePosition?: Position;
  targetPosition?: Position;
}

const LeftOrRight = [Position.Left, Position.Right];

/**
 * Gets the center of an edge using the source position and the target position of the edge.
 * The offset is used in order to correct for the displacement created by the schema positioning algorithm.
 * @param {GetCenterParams} param0 The coordinates of the start and end of the line, and its offset.
 * @returns {[number, number, number, number]} The coordinates of the center of the edge.
 */
export const getCenter = ({
  sourceX,
  sourceY,
  targetX,
  targetY,
  offset,
  sourcePosition = Position.Bottom,
  targetPosition = Position.Top,
}: GetCenterParams): [number, number, number, number] => {
  const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition);
  const targetIsLeftOrRight = LeftOrRight.includes(targetPosition);

  // we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom)
  // a mixed edge is when one the source is on the left and the target is on the top for example.
  const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight);

  if (mixedEdge) {
    const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0;
    const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset;

    const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY);
    const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset;

    return [centerX, centerY, xOffset, yOffset];
  }

  // Add the offset to the position parameters
  const xOffset = Math.abs(targetX - sourceX) / 2;
  const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;

  const yOffset = Math.abs(targetY - sourceY) / 2;
  const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;

  return [centerX + offset, centerY, xOffset, yOffset];
};

// /**
//  * Calculate the width of the specified text.
//  * @param text Text input as string.
//  * @param fontname Name of the font.
//  * @param fontsize Size of the fond in px.
//  * @param fontWeight The weight of the font.
//  * @returns {number} Width of the textfield in px.
//  */
// export const getWidthOfText = (text: string, fontname: string, fontsize: string, fontWeight = 'normal'): number => {
//   let canvas = document.createElement('canvas');
//   let canvasText = canvas.getContext('2d') as CanvasRenderingContext2D;
//   return canvasText.measureText(text).width;
// };

/**
 * This calculates the width of the attributesBox and nodesBox of an entityNode
 * @param attributeCount Number of attributes of the entityNode
 * @param nodeCount Amount of nodes the entityNode has
 * @returns {number} the width of the largest box (attributesBox or nodesBox) of an entityNode in ch
 */
export const calcWidthEntityNodeBox = (attributeCount: number, nodeCount: number): number => {
  if (attributeCount >= nodeCount) return (attributeCount?.toString()?.length || 0) + 5.5;
  else return (nodeCount?.toString()?.length || 0) + 5.5;
};

/**
 * This calculates the width of the attributesBox and nodesBox of an relationNode
 * @param attributeCount Number of attributes of the relationNode
 * @param nodeCount Amount of nodes the entityNode has
 * @returns {number} the width of the largest box (attributesBox or nodesBox) of an relationNode in px
 */
export const calcWidthRelationNodeBox = (attributeCount: number, nodeCount: number): number => {
  if (attributeCount >= nodeCount) return attributeCount.toString().length * 5 + 60;
  else return nodeCount.toString().length * 5 + 60;
};

/**
 * Creates a bounding box for a Shema component in order to check for collisions.
 * @param x Top left x position of the component.
 * @param y Top left y position of the component.
 * @param width Width of the component.
 * @param height Height of the component.
 * @returns {BoundingBox} The bounding box of the component.
 */
export const makeBoundingBox = (x: number, y: number, width: number, height: number): BoundingBox => {
  return {
    topLeft: { x: x, y: y },
    bottomRight: { x: x + width, y: y + height },
  };
};

/**
 * Check if two bounding boxes overlap in order to avoid overlapping components.
 * @param boundingBoxOne bounding box of the first component.
 * @param boundingBoxTwo bounding box of the second component.
 * @returns {boolean} True if boxes overlap and false if there is no overlap.
 */
export const doBoxesOverlap = (boundingBoxOne: BoundingBox, boundingBoxTwo: BoundingBox): boolean => {
  if (boundingBoxOne.topLeft.x >= boundingBoxTwo.bottomRight.x || boundingBoxTwo.topLeft.x >= boundingBoxOne.bottomRight.x) return false;

  if (boundingBoxOne.topLeft.y >= boundingBoxTwo.bottomRight.y || boundingBoxTwo.topLeft.y >= boundingBoxOne.bottomRight.y) return false;

  return true;
};

/**
 * Capitalize the first letter of a string.
 * @param string This is the given string.
 * @returns {string} This is the modified string.
 */
export const capitalizeFirstLetter = (string: string) => {
  return string.charAt(0).toUpperCase() + string.slice(1);
};

/**
 * This calculates the quality of the attributes from the given data in percentages.
 * @param data This is the data of which the quality of the attributes must be calculated.
 * @returns {number} This is the percentage of the quality of the attributes from the given data.
 */
export const calculateAttributeQuality = (data: any) => {
  const maxPossibleNullValues = data.nodeCount * data.attributes.length;
  if (data.summedNullAmount == 0) return 0;
  return (data.summedNullAmount / maxPossibleNullValues) * 100;
};

/**
 * This calculates the quality of the entity from the given data in percentages, based on the connectedRatio.
 * @param data This is the data of which the quality of the entity must be calculated.
 * @returns {number} This is the percentage of the quality of the entity from the given data.
 */
export const calculateEntityQuality = (data: any) => {
  const nodeQuality = (1 - data.connectedRatio) * 100;
  return nodeQuality;
};

/**
 * This calculates the quality of the relation from the given data in percentages, based on the connectedRatio.
 * @param data This is the data of which the quality of the relation must be calculated.
 * @returns {number} This is the percentage of the quality of the relation from the given data.
 */
export const calculateRelationQuality = (data: any) => {
  const nodeQuality = (1 - (data.fromRatio + data.toRatio) / 2) * 100;
  return nodeQuality;
};

type NumberPredicate = (a1: number, a2: number) => boolean;

/**
 * Determines the true-value of two values, based on the predicate.
 */
export const numberPredicates: Record<string, NumberPredicate> = {
  Equal: (a1, a2) => a1 == a2,
  NotEqual: (a1, a2) => a1 != a2,
  Smaller: (a1, a2) => a1 < a2,
  SmallerOrEqual: (a1, a2) => a1 <= a2,
  Bigger: (a1, a2) => a1 > a2,
  BiggerOrEqual: (a1, a2) => a1 >= a2,
};