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

export class VentTBreak implements Placeable {
  public debug = false;
  public x = 0;
  public y = 0;
  public angle = 0;
  public ctrl = false;
  public shift = false;
  public selected = false;
  public type = pond.PlaceableType.PLACEABLE_TYPE_SHAFT;
  public direction = 0;
  public subtype = pond.ShaftType.SHAFT_TYPE_T_BREAK;

  private length = 192;
  private diameter = 64;

  private circle = Circle.create(0, 0, 0);
  private break_circle = Circle.create(0, 0, 0);
  private mouseOffsetX = 0;
  private mouseOffsetY = 0;
  private oldAngle = 0;
  private snapped = false;

  private break_length = 64;

  public magnitude(): number {
    return this.length;
  }

  public radius(): number {
    return this.break_length;
  }

  public static fromPond(shaft: pond.Placeable) {
    let v = new VentTBreak();
    v.x = shaft.x;
    v.y = shaft.y;
    v.angle = shaft.angle;
    v.length = shaft.magnitude;
    v.direction = shaft.direction;
    v.break_circle.selected = false;
    v.break_length = shaft.radius;

    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 static create(pb?: pond.Block, img?: HTMLImageElement): VentTBreak {
    let v = new VentTBreak();
    //v.settings = pond.BlockSettings.fromObject(cloneDeep(or(pb?.settings, {})));

    // For some reason, negative x and y values don't clone correctly
    // Same goes for decimal values in angle
    if (pb?.settings) {
      v.x = pb.settings.x;
      v.y = pb.settings.y;
      v.angle = pb.settings.angle;
    }

    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 draw(
    context: CanvasRenderingContext2D,
    offsetX?: number,
    offsetY?: number,
    scale?: number
  ) {
    if (!scale) scale = 1;
    let x = this.x - (offsetX ? offsetX : 0);
    let y = this.y - (offsetY ? offsetY : 0);
    let width = this.length;
    let height = this.diameter;

    //scale it
    x = x * scale;
    y = y * scale;
    width = width * scale;
    height = height * scale;
    let break_length = this.break_length * scale;

    let { x1, y1, x2, y2, x3, y3, x4, y4 } = getRectangularPoints(x, y, width, height, this.angle);

    context.beginPath();

    let { x: bx, y: by } = this.getBreakOrigin(scale, offsetX, offsetY);
    //context.arc(bx, by, 6, 0, Math.PI * 360)

    let break_angle = this.angle + 90;
    if (this.direction === 1) break_angle = this.angle - 90;
    let { x1: x5, y1: y5, x2: x6, y2: y6, x3: x7, y3: y7, x4: x8, y4: y8 } = getRectangularPoints(
      bx,
      by,
      break_length,
      height,
      break_angle
    );

    if (this.direction === 0) {
      context.moveTo(x1, y1);
      context.lineTo(x2, y2);
      context.moveTo(x4, y4);
      context.lineTo(x8, y8);
      context.lineTo(x7, y7);
      context.moveTo(x6, y6);
      context.lineTo(x5, y5);
      context.lineTo(x3, y3);
    } else if (this.direction === 1) {
      context.moveTo(x3, y3);
      context.lineTo(x4, y4);
      context.moveTo(x1, y1);
      context.lineTo(x5, y5);
      context.lineTo(x6, y6);
      context.moveTo(x7, y7);
      context.lineTo(x8, y8);
      context.lineTo(x2, y2);
    }

    context.strokeStyle = "rgba(255, 255, 255, 1)";
    context.stroke();
  }

  public getEndXAndY = (scale = 1, offsetX = 0, offsetY = 0) => {
    let x = this.endX(this.x, this.angle, this.length);
    let y = this.endY(this.y, this.angle, this.length);
    return { x: (x - offsetX) * scale, y: (y - offsetY) * scale };
  };

  public clickCheck = (x: number, y: number, scale = 1) => {
    let width = this.length;
    let height = this.diameter;

    // Check end circle
    ({ x: this.circle.x, y: this.circle.y } = this.getEndXAndY());
    this.circle.radius = height / 4;
    if (this.circle.clickCheck(x, y, scale)) {
      this.break_circle.selected = false;
      return true;
    }

    // Check break end circle
    ({ x: this.break_circle.x, y: this.break_circle.y } = this.getBreakEnd());
    this.break_circle.radius = height / 4;
    if (this.break_circle.clickCheck(x, y, scale)) {
      this.circle.selected = false;
      return true;
    }

    // Check main rectangle
    let p = getRectangularPoints(this.x, this.y, width, height, this.angle);
    if (isPointInRectangle(x, y, p.x1, p.y1, p.x2, p.y2, p.x3, p.y3, p.x4, p.y4)) {
      this.selected = true;
      this.mouseOffsetX = x - this.x;
      this.mouseOffsetY = y - this.y;
      return true;
    }

    // Check extension triangle
    let { x: bx, y: by } = this.getBreakOrigin();
    //bx = bx * scale
    //by = by * scale
    let break_angle = this.angle + 90;
    if (this.direction === 1) break_angle = this.angle - 90;
    p = getRectangularPoints(bx, by, this.break_length, height, break_angle);
    if (isPointInRectangle(x, y, p.x1, p.y1, p.x2, p.y2, p.x3, p.y3, p.x4, p.y4)) {
      this.selected = true;
      this.mouseOffsetX = x - this.x;
      this.mouseOffsetY = y - this.y;
      return true;
    }

    this.selected = false;
    if (this.snapped) {
      this.oldAngle = this.angle;
      this.snapped = false;
    }
    return false;
  };

  public highlight(
    context: CanvasRenderingContext2D,
    offsetX?: number,
    offsetY?: number,
    scale?: number
  ) {
    if (!scale) scale = 1;
    let x = this.x - (offsetX ? offsetX : 0);
    let y = this.y - (offsetY ? offsetY : 0);
    let width = this.length;
    let height = this.diameter;

    //scale it
    x = x * scale;
    y = y * scale;
    width = width * scale;
    height = (height + 3) * scale;
    let break_length = this.break_length * scale;

    let { x1, y1, x2, y2, x3, y3, x4, y4 } = getRectangularPoints(x, y, width, height, this.angle);

    context.beginPath();

    let { x: bx, y: by } = this.getBreakOrigin(scale, offsetX, offsetY);
    //context.arc(bx, by, 6, 0, Math.PI * 360)

    let break_angle = this.angle + 90;
    if (this.direction === 1) break_angle = this.angle - 90;
    let { x1: x5, y1: y5, x2: x6, y2: y6, x3: x7, y3: y7, x4: x8, y4: y8 } = getRectangularPoints(
      bx,
      by,
      break_length,
      height,
      break_angle
    );

    if (this.direction === 0) {
      context.lineTo(x1, y1);
      context.lineTo(x2, y2);
      context.moveTo(x4, y4);
      context.lineTo(x8, y8);
      context.lineTo(x7, y7);
      context.lineTo(x6, y6);
      context.lineTo(x5, y5);
      context.lineTo(x3, y3);
      context.moveTo(x1, y1);
      context.lineTo(x2, y2);
      context.lineTo(x3, y3);
      context.moveTo(x1, y1);
      context.lineTo(x4, y4);
    } else if (this.direction === 1) {
      context.lineTo(x3, y3);
      context.lineTo(x4, y4);
      context.lineTo(x1, y1);
      context.lineTo(x5, y5);
      context.lineTo(x6, y6);
      context.lineTo(x7, y7);
      context.lineTo(x8, y8);
      context.lineTo(x2, y2);
      context.lineTo(x3, y3);
    }

    context.strokeStyle = "rgba(247, 202, 24, 1)";
    context.stroke();

    context.strokeStyle = "rgba(225, 225, 225, 1)";
    let { x: endx, y: endy } = this.getEndXAndY(scale, offsetX, offsetY);
    this.circle.x = endx;
    this.circle.y = endy;
    this.circle.radius = height / 4;
    this.circle.draw(context);

    let { x: cx, y: cy } = this.getBreakEnd(scale, offsetX, offsetY);
    this.break_circle.x = cx;
    this.break_circle.y = cy;
    this.break_circle.radius = height / 4;
    this.break_circle.draw(context);
  }

  public clone() {
    let v = new VentTBreak();
    v.x = this.x;
    v.y = this.y;
    v.angle = this.angle;
    v.circle = this.circle;
    v.break_circle = this.break_circle;
    v.break_length = this.break_length;
    v.ctrl = this.ctrl;
    v.shift = this.shift;
    v.diameter = this.diameter;
    v.length = this.length;
    return v;
  }

  public distance(x1: number, x2: number, y1: number, y2: number) {
    let a = x1 - x2;
    let b = y1 - y2;
    return Math.sqrt(a * a + b * b);
  }

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

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

    let { x: bx, y: by } = this.getBreakEnd();
    let ang = this.angle + 90;
    if (this.direction === 1) ang = this.angle - 90;
    snaps.push({
      x: bx,
      y: by,
      angle: ang,
      occupant: undefined,
      startPoint: false
    });
    if (snaps[2].angle < 0) snaps[2].angle = snaps[2].angle + 360;

    return snaps;
  };

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

  private calcAngle = (x: number, y: number) => {
    var dx = this.x - x;
    var dy = this.y - y;
    var theta = Math.atan2(dy, -dx); // [0, Ⲡ] then [-Ⲡ, 0]; clockwise; 0° = east
    theta *= 180 / Math.PI; // [0, 180] then [-180, 0]; clockwise; 0° = east
    if (theta < 0) theta += 360; // [0, 360]; clockwise; 0° = east
    return theta;
  };

  public drag = (x: number, y: number, scale = 1) => {
    if (this.circle.selected) {
      if (this.shift) {
        let dx = x - this.x;
        let dy = y - this.y;
        this.length = Math.sqrt(dx * dx + dy * dy);

        if (!this.ctrl) this.length = Math.round(this.length / 16) * 16;

        // Rotate 180 instead of having a negative length
        if (dx < 0) {
          if (dy > 0) {
            if (!(this.angle > 180 && this.angle < 270)) {
              this.angle += 180;
              if (this.angle > 360) this.angle -= 360;
            }
          } else if (!(this.angle > 90 && this.angle < 270)) {
            this.angle += 180;
            if (this.angle > 360) this.angle -= 360;
          }
        } else {
          if (dy > 0) {
            if (!(this.angle < 360 && this.angle > 180)) {
              this.angle += 180;
              if (this.angle > 360) this.angle -= 360;
            }
          } else if (!(this.angle > 0 && this.angle < 90)) {
            this.angle += 180;
            if (this.angle > 360) this.angle -= 360;
          }
        }
      } else {
        this.angle = this.calcAngle(x, y);
        let dx = x - this.x;
        let dy = y - this.y;
        this.length = Math.sqrt(dx * dx + dy * dy);
        if (!this.ctrl) {
          this.angle = Math.round(this.angle / 11.25) * 11.25;
          this.length = Math.round(this.length / 16) * 16;
        }
      }
      this.oldAngle = this.angle;
    } else if (this.break_circle.selected) {
      let ang = this.angle;

      let { x: ox, y: oy } = this.getBreakOrigin();

      let a = x - ox;
      let b = y - oy;
      let h = Math.sqrt(a * a + b * b);

      this.break_length = h;
      if (!this.ctrl) this.break_length = Math.round(this.break_length / 16) * 16;

      let ang2 = Math.atan2(y - oy, x - ox) * (180 / Math.PI);
      ang2 = (ang2 + 360) % 360;
      ang2 = (360 - ang2) % 360;
      let diff = (ang - ang2 + 360) % 360;

      // Set break length to 0 if you're not extending outward
      if ((this.direction === 0 && diff < 180) || (this.direction === 1 && diff > 180)) {
        this.break_length = 0;
      }
    } else {
      this.x = x - this.mouseOffsetX;
      this.y = y - this.mouseOffsetY;
    }
  };

  public endX(x = this.x, angle = this.angle, length = this.length): number {
    let rad = angle * (Math.PI / 180);
    return x + length * Math.cos(rad);
  }

  public endY(y = this.y, angle = this.angle, length = this.length): number {
    let rad = angle * (Math.PI / 180);
    return y - length * Math.sin(rad);
  }

  private getBreakEnd(scale = 1, offsetX = 0, offsetY = 0) {
    let rad = this.angle * (Math.PI / 180);
    let rad2 = (this.angle + 90) * (Math.PI / 180);
    if (this.direction === 1) rad2 = (this.angle - 90) * (Math.PI / 180);
    let cx = this.x + (Math.cos(rad) * this.length) / 2;
    let cy = this.y - (Math.sin(rad) * this.length) / 2;
    cx = cx + Math.cos(rad2) * (this.break_length + this.diameter / 2);
    cy = cy - Math.sin(rad2) * (this.break_length + this.diameter / 2);
    return { x: (cx - offsetX) * scale, y: (cy - offsetY) * scale };
  }

  private getBreakOrigin(scale = 1, offsetX = 0, offsetY = 0) {
    let rad = this.angle * (Math.PI / 180);
    let rad2 = (this.angle + 90) * (Math.PI / 180);
    if (this.direction === 1) rad2 = (this.angle - 90) * (Math.PI / 180);
    let cx = this.x + (Math.cos(rad) * this.length) / 2;
    let cy = this.y - (Math.sin(rad) * this.length) / 2;
    cx = cx + Math.cos(rad2) * (this.diameter / 2);
    cy = cy - Math.sin(rad2) * (this.diameter / 2);
    return { x: (cx - offsetX) * scale, y: (cy - offsetY) * scale };
  }

  public getEndAngle(): number {
    return this.angle;
  }

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

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