import { pond } from "protobuf-ts/pond";
import { Direction, Placeable } from "./Placeable";
import { SnapPoint } from "./geometry";

export class VentCorner implements Placeable {
  x: number = 0;
  y: number = 0;
  direction = Direction.CLOCKWISE;
  angle: number = 0;
  ctrl: boolean = false;
  shift: boolean = false;
  selected: boolean = false;
  type: pond.PlaceableType = pond.PlaceableType.PLACEABLE_TYPE_SHAFT;
  subtype = pond.ShaftType.SHAFT_TYPE_CORNER;

  // Unique to VentCorner
  private corner_radius: number = 128;
  private width: number = 64;
  private mouseOffsetX = 0;
  private mouseOffsetY = 0;
  private oldAngle = 0;
  private snapped = false;

  public static create = (x?: number, y?: number, radius?: number, startAngle?: number) => {
    let v = new VentCorner();
    v.x = x ? x : 0;
    v.y = y ? y : 0;
    v.angle = startAngle ? startAngle : 0;
    v.oldAngle = startAngle ? startAngle : 0;
    v.corner_radius = radius ? radius : 128;
    return v;
  };

  public static fromPond(shaft: pond.Placeable) {
    let v = new VentCorner();
    v.x = shaft.x;
    v.y = shaft.y;
    v.angle = shaft.angle;
    v.corner_radius = shaft.magnitude;
    v.direction = shaft.direction;

    document.body.addEventListener("keydown", function(event) {
      if (event.key === "Control") {
        v.ctrl = true;
      } else if (event.key === "Shift") {
        v.shift = true;
      }
    });

    document.body.addEventListener("keyup", function(event) {
      if (event.key === "Control") {
        v.ctrl = false;
      } else if (event.key === "Shift") {
        v.shift = false;
      }
    });

    return v;
  }

  public magnitude = (): number => {
    return this.radius();
  };

  public radius = (): number => {
    return this.corner_radius;
  };

  public clone = () => {
    let v = new VentCorner();
    v.x = this.x;
    v.y = this.y;
    v.angle = this.angle;
    v.radius = this.radius;
    return v;
  };

  private distance(x: number, y: number, x2?: number, y2?: number) {
    let a = this.x - x;
    let b = this.y - y;
    if (x2) {
      a = x2 - x;
    }
    if (y2) {
      b = y2 - y;
    }
    return Math.sqrt(a * a + b * b);
  }

  public clickCheck(x: number, y: number, scale = 1): boolean {
    // Check angle of click relative to the radius
    let radians = this.angle * (Math.PI / 180);

    //scale it
    let radius = this.radius();
    let width = this.width;

    let centerx = 0;
    let centery = 0;
    if (this.direction === Direction.CLOCKWISE) {
      centerx = this.x + Math.cos(radians - 0.5 * Math.PI) * radius;
      centery = this.y - Math.sin(radians - 0.5 * Math.PI) * radius;
    } else {
      centerx = this.x + Math.cos(radians - 1.5 * Math.PI) * radius;
      centery = this.y - Math.sin(radians - 1.5 * Math.PI) * radius;
    }

    let { x: endx, y: endy } = this.getEndXAndY(scale);

    // Check that distance from radial center is greater than
    // inner radius and less than outer radius
    let d = this.distance(centerx, centery, x, y);
    if (d > radius - width / 2 && d < radius + width / 2) {
      let dx = x - centerx;
      let dy = y - centery;
      let radians = Math.atan2(dy, dx);
      if (radians < 0) {
        radians = radians + 2 * Math.PI;
      }
      let ratio = (2 * (radians / (Math.PI / 180))) / 360;
      dx = this.x - centerx;
      dy = this.y - centery;
      radians = Math.atan2(dy, dx);
      if (radians < 0) {
        radians = radians + 2 * Math.PI;
      }
      let ratioLow = (2 * (radians / (Math.PI / 180))) / 360;
      dx = endx - centerx;
      dy = endy - centery;
      radians = Math.atan2(dy, dx);
      if (radians < 0) {
        radians = radians + 2 * Math.PI;
      }
      let ratioHigh = (2 * (radians / (Math.PI / 180))) / 360;
      if (this.direction === Direction.COUNTERCLOCKWISE) {
        if (ratioHigh > ratioLow) {
          ratioLow = ratioLow + 2;
          if (ratio < ratioHigh) ratio = ratio + 2;
        }
        if (ratio > ratioHigh && ratio < ratioLow) {
          this.mouseOffsetX = x - this.x;
          this.mouseOffsetY = y - this.y;
          this.selected = true;
          return true;
        } else {
          return false;
        }
      }

      if (ratioHigh < ratioLow) {
        ratioHigh = ratioHigh + 2;
        if (ratio < ratioLow) ratio = ratio + 2;
      }

      // If the angle is within the start and end of the arc relativ eto the radial center
      if (ratio > ratioLow && ratio < ratioHigh) {
        this.mouseOffsetX = x - this.x;
        this.mouseOffsetY = y - this.y;
        this.selected = true;
        return true;
      }
    }
    this.selected = false;
    if (this.snapped) {
      this.oldAngle = this.angle;
    }
    return false;
  }

  public draw(
    context: CanvasRenderingContext2D,
    offsetX?: number,
    offsetY?: number,
    scale = 1
  ): void {
    let x = this.x - (offsetX ? offsetX : 0);
    let y = this.y - (offsetY ? offsetY : 0);

    //scale it
    x = x * scale;
    y = y * scale;
    let radius = this.radius() * scale;
    let width = this.width * scale;

    let radians = this.angle * (Math.PI / 180);
    let ratio = (2 * this.angle) / 360;
    ratio = 2 - ratio;

    let angleOffset = 0.5;
    let c = false;
    if (this.direction === Direction.COUNTERCLOCKWISE) {
      x = x - Math.cos(radians - 0.5 * Math.PI) * radius;
      y = y + Math.sin(radians - 0.5 * Math.PI) * radius;
      angleOffset = 1.5;
      c = true;
    } else {
      x = x + Math.cos(radians - 0.5 * Math.PI) * radius;
      y = y - Math.sin(radians - 0.5 * Math.PI) * radius;
    }

    context.strokeStyle = "white";
    context.beginPath();
    context.arc(x, y, radius - width / 2, (ratio - angleOffset) * Math.PI, ratio * Math.PI, c);
    context.stroke();

    context.beginPath();
    context.arc(x, y, radius + width / 2, (ratio - angleOffset) * Math.PI, ratio * Math.PI, c);
    context.stroke();
  }

  highlight(
    context: CanvasRenderingContext2D,
    offsetX?: number,
    offsetY?: number,
    scale = 1
  ): void {
    let x = this.x - (offsetX ? offsetX : 0);
    let y = this.y - (offsetY ? offsetY : 0);
    let radians = this.angle * (Math.PI / 180);
    let ratio = (2 * this.angle) / 360;
    ratio = 2 - ratio;

    //scale it
    x = x * scale;
    y = y * scale;
    let radius = this.radius() * scale;
    let width = this.width * scale;

    context.strokeStyle = "yellow";
    context.beginPath();
    context.arc(x, y, 6, 0, 2 * Math.PI);
    context.stroke();

    let angleOffset = 0.5;
    let c = false;
    if (this.direction === Direction.COUNTERCLOCKWISE) {
      x = x - Math.cos(radians - 0.5 * Math.PI) * radius;
      y = y + Math.sin(radians - 0.5 * Math.PI) * radius;
      angleOffset = 1.5;
      c = true;
    } else {
      x = x + Math.cos(radians - 0.5 * Math.PI) * radius;
      y = y - Math.sin(radians - 0.5 * Math.PI) * radius;
    }

    //x = x + Math.cos(radians - 0.5 * Math.PI) * this.radius;
    //y = y - Math.sin(radians - 0.5 * Math.PI) * this.radius;

    context.beginPath();
    context.arc(
      x,
      y,
      radius - (width + 2) / 2,
      (ratio - angleOffset) * Math.PI,
      ratio * Math.PI,
      c
    );
    context.stroke();

    context.beginPath();
    context.arc(
      x,
      y,
      radius + (width + 2) / 2,
      (ratio - angleOffset) * Math.PI,
      ratio * Math.PI,
      c
    );
    context.stroke();
  }

  drag(x: number, y: number, scale = 1): void {
    this.x = x - this.mouseOffsetX;
    this.y = y - this.mouseOffsetY;
  }

  getEndXAndY(scale = 1): { x: number; y: number } {
    let ratio = (2 * this.angle) / 360;

    //scale it
    let x = this.x;
    let y = this.y;
    let radius = this.radius();
    //let width = this.width*scale

    if (this.direction === Direction.COUNTERCLOCKWISE) {
      x = x - Math.cos((ratio - 0.5) * Math.PI) * radius;
      y = y + Math.sin((ratio - 0.5) * Math.PI) * radius;
      x = x + Math.sin((ratio - 1.5) * Math.PI) * radius;
      y = y + Math.cos((ratio - 1.5) * Math.PI) * radius;
      return { x: x, y: y };
    }
    x = x + Math.cos((ratio - 0.5) * Math.PI) * radius;
    y = y - Math.sin((ratio - 0.5) * Math.PI) * radius;
    x = x - Math.sin((ratio - 0.5) * Math.PI) * radius;
    y = y - Math.cos((ratio - 0.5) * Math.PI) * radius;
    return { x: x, y: y };
  }

  getSnapPoints() {
    let snaps: SnapPoint[] = [];

    let { x: endx, y: endy } = this.getEndXAndY();
    snaps.push({
      x: endx,
      y: endy,
      angle: this.getEndAngle(),
      occupant: undefined,
      startPoint: false
    } as SnapPoint);

    snaps.push({
      x: this.x,
      y: this.y,
      angle: this.angle + 180,
      occupant: undefined,
      startPoint: true
    } as SnapPoint);
    if (snaps[1].angle > 360) snaps[0].angle = snaps[0].angle - 360;

    return snaps;
  }

  snap(p: Placeable, d?: number): void {
    if (!d) d = 12;
    p.getSnapPoints().forEach(point => {
      if (!point.startPoint && this.distance(this.x, this.y, point.x, point.y) < d!) {
        this.x = point.x;
        this.y = point.y;
        this.angle = point.angle;
      }
    });
  }

  getEndAngle(): number {
    if (this.direction === Direction.COUNTERCLOCKWISE) {
      return this.angle + 90;
    }
    let ang = this.angle - 90;
    return ang;
  }

  setDirection(direction: number | Direction): void {
    this.direction = direction;
  }

  setAngle(angle: number, oldAngle?: number) {
    this.angle = angle;
    if (oldAngle) this.oldAngle = oldAngle;
  }
}
