import { DISTANCE_UNIT, MAP_LOCALE } from './CommandTypes';
import type { INSTRUCTION, JIBESTREAM_PATH } from './JibestreamTypes';
const METERS_PER_PIXEL = 0.05962332392785686;

export function getCurrentInstruction(
  userLocation: number[],
  instructions: INSTRUCTION[],
  dist_unit: DISTANCE_UNIT = 'METERS',
  locale: MAP_LOCALE = 'en-US'
) {
  var closestInstruction:
    | { instruction: INSTRUCTION; distance: number }
    | undefined = undefined;
  instructions.forEach(function (instruction) {
    const distance = getPerpendicularDistanceFromPath(
      userLocation,
      instruction.startPoint!,
      instruction.endPoint!
    );
    const distanceAlongPath = getDistanceAlongPath(
      userLocation,
      instruction.startPoint!,
      instruction.endPoint!
    );

    // Ignore matches if not sufficiently within bounds of path
    if (!(distanceAlongPath > 0 && distanceAlongPath < 1)) {
      return;
    }
    let remainingDistance = getDistanceBetweenPoints(
      userLocation,
      instruction.endPoint!
    );
    remainingDistance =
      Math.round(remainingDistance * METERS_PER_PIXEL * 100) / 100;
    const fullText = generateInstructionText(
      {
        ...instruction,
        remainingDistance,
      },
      dist_unit,
      locale
    );

    if (closestInstruction === undefined) {
      closestInstruction = {
        instruction: { ...instruction, remainingDistance, fullText },
        distance,
      };
    } else {
      if (distance < closestInstruction.distance) {
        closestInstruction = {
          instruction: { ...instruction, remainingDistance, fullText },
          distance,
        };
      }
    }
  });
  // @ts-ignore
  return closestInstruction?.instruction;
}

export function getSurroundingPoints(
  path: JIBESTREAM_PATH,
  instructions: INSTRUCTION[],
  waypointId: number
) {
  let instructionIndex = 0;
  let leadWaypointIndex = -1;

  for (let i = 0; i < path.points.length; i++) {
    if (path.points[i] && path.points[i]?.id == waypointId) {
      leadWaypointIndex = instructionIndex;
      break;
    }
    if (
      instructions[instructionIndex] &&
      path.points[i]?.id == instructions[instructionIndex]?.wp
    ) {
      instructionIndex += 1;
    }
  }
  return leadWaypointIndex;
}

export const preProcessPath = (
  path: JIBESTREAM_PATH[],
  instructions: INSTRUCTION[]
) => {
  var pathSegmentIndex = 0;
  var prevPathIndex = 0;
  var prevInstructionIndex: number;

  console.log('Path Length', JSON.stringify(path));
  console.log('Instruction Length' + instructions.length);

  path.forEach(function (floorPath) {
    floorPath.points.forEach(function (pathSegment) {
      console.log(
        'reviewing path segment: ',
        pathSegment.associations?.amenities
      );
      if (pathSegment.associations && pathSegment.associations?.amenities) {
        let foundDoor = false;

        // Check if this waypoint is a door
        pathSegment.associations?.amenities.forEach(function (amenity) {
          if (amenity == 13237) {
            foundDoor = true;
          }
        });

        // if the waypoint has a door find, inject a new nav instruction into the path.
        if (foundDoor) {
          console.log('Path Segment ID' + pathSegment.id);
          const index = getSurroundingPoints(
            floorPath,
            instructions,
            pathSegment.id
          );
          const splitInstruction: INSTRUCTION = {
            fullText: 'Proceed through the Door',
            prepend: '',
            direction: 'Proceed through the Door',
            distance: instructions[index]?.distance || 1,
            remainingDistance: instructions[index]?.remainingDistance || 1,
            wp: pathSegment.id,
            dest: -1,
            startPoint: [],
            endPoint: [],
          };
          instructions.splice(index, 0, splitInstruction);
        }
      }
    });
  });

  instructions.forEach(function (instruction, instructionIndex) {
    for (var i = pathSegmentIndex; i < path.length; i++) {
      const pathPoints = path[i]?.points;
      if (pathPoints === undefined) {
        continue;
      }
      for (var j = prevPathIndex; j < pathPoints.length; j++) {
        const point = pathPoints[j];
        if (point === undefined) {
          continue;
        }
        if (point.id === instruction.wp) {
          instruction.endPoint = [point.x, point.y];
          if (prevInstructionIndex !== undefined) {
            instruction.startPoint =
              instructions[prevInstructionIndex]!.endPoint;
          } else {
            instruction.startPoint = [pathPoints[0]?.x!, pathPoints[0]?.y!];
          }
          prevInstructionIndex = instructionIndex;
          return;
        }
        prevPathIndex = j;
      }
      pathSegmentIndex = i;
      prevPathIndex = 0;
    }
  });
  return instructions;
};

// get distance along path as proportion of total path length
export function getDistanceAlongPath(
  point: number[],
  startPathPoint: number[],
  endPathPoint: number[]
) {
  const x1 = startPathPoint[0]!;
  const y1 = startPathPoint[1]!;
  const x2 = endPathPoint[0]!;
  const y2 = endPathPoint[1]!;
  const x0 = point[0]!;
  const y0 = point[1]!;
  const numerator = (x2 - x1) * (x0 - x1) + (y2 - y1) * (y0 - y1); // dot product
  const denominator = Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2);
  return numerator / denominator;
}

// Get perpendicular distance from point to path
export function getPerpendicularDistanceFromPath(
  point: number[],
  startPathPoint: number[],
  endPathPoint: number[]
) {
  const x1 = startPathPoint[0]!;
  const y1 = startPathPoint[1]!;
  const x2 = endPathPoint[0]!;
  const y2 = endPathPoint[1]!;
  const x0 = point[0]!;
  const y0 = point[1]!;
  const numerator = Math.abs(
    (y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1
  );
  const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));
  return numerator / denominator;
}

export function getDistanceBetweenPoints(point1: number[], point2: number[]) {
  const x1 = point1[0]!;
  const y1 = point1[1]!;
  const x2 = point2[0]!;
  const y2 = point2[1]!;
  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}

export function generateInstructionText(
  instruction: INSTRUCTION,
  dist_unit: DISTANCE_UNIT,
  locale: MAP_LOCALE
) {
  const distance = Math.round(
    convertDistanceUnit(instruction.remainingDistance, dist_unit)
  );
  const prepend = convertPrepend(instruction.prepend, locale);
  const direction = convertDirection(instruction.direction, locale);
  const atTranslated = locale === 'es-ES' ? 'en' : 'at';
  const destinationName = instruction.destinationName;
  const inTranslated = locale === 'es-ES' ? 'en' : 'in';
  const unitName = convertUnitName(dist_unit, locale, distance != 1);
  if (destinationName)
    return `${prepend}${direction} ${atTranslated} ${destinationName} ${inTranslated} ${distance} ${unitName}`;
  else return `${prepend}${direction} ${inTranslated} ${distance} ${unitName}`;
}

export function convertDistanceUnit(
  distance: number,
  dist_unit: DISTANCE_UNIT
): number {
  switch (dist_unit) {
    case 'METERS':
      return distance;
    case 'FEET':
      return distance * 3.28084;
    case 'INCHES':
      return distance * 39.3701;
    default:
      return distance;
  }
}

export function convertPrepend(prepend: string, locale: MAP_LOCALE): string {
  switch (prepend) {
    case 'Turn ':
      switch (locale) {
        case 'es-ES':
          return 'Gire ';
        default:
          return prepend;
      }
    case 'Arrive ':
      switch (locale) {
        case 'es-ES':
          return 'Llegue ';
        default:
          return prepend;
      }
    default:
      return prepend;
  }
}

export function convertDirection(prepend: string, locale: MAP_LOCALE): string {
  switch (prepend) {
    case 'Left':
      switch (locale) {
        case 'es-ES':
          return 'Izquierdo';
        default:
          return prepend;
      }
    case 'Right':
      switch (locale) {
        case 'es-ES':
          return 'Derecho';
        default:
          return prepend;
      }
    case 'Forward':
      switch (locale) {
        case 'es-ES':
          return 'hacia adelante';
        default:
          return prepend;
      }
    default:
      return prepend;
  }
}

export function convertUnitName(
  unit: DISTANCE_UNIT,
  locale: MAP_LOCALE,
  plural: boolean
): string {
  switch (unit) {
    case 'METERS':
      switch (locale) {
        case 'es-ES':
          return plural ? 'metros' : 'metro';
        default:
          return plural ? 'meters' : 'meter';
      }
    case 'FEET':
      switch (locale) {
        case 'es-ES':
          return plural ? 'pies' : 'pie';
        default:
          return plural ? 'feet' : 'foot';
      }
    case 'INCHES':
      switch (locale) {
        case 'es-ES':
          return plural ? 'pulgada' : 'pulgadas';
        default:
          return plural ? 'inches' : 'inch';
      }
    default:
      return unit!;
  }
}
