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

export class VentShaft 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_DEFAULT;

  private length = 128;
  private diameter = 64;

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

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

  public radius(): number {
    return this.diameter / 2;
  }

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

    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): VentShaft {
    let v = new VentShaft();
    //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 cos = Math.cos;
    let sin = Math.sin;

    let ang = this.angle;
    let rad = ang * (Math.PI / 180);

    y = y - cos(rad) * (height / 2);
    x = x - sin(rad) * (height / 2);

    let x2 = x + cos(rad) * width;
    let y2 = y - sin(rad) * width;

    if (this.debug) {
      context.beginPath();
      context.arc(x, y, 6, 0, 2 * Math.PI);
      context.stroke();

      context.strokeStyle = "rgb(0, 0, 255)";
      context.beginPath();
      context.arc(x2, y2, 6, 0, 2 * Math.PI);
      context.stroke();

      context.strokeStyle = "rgb(0, 255, 0)";
      context.beginPath();
      context.arc(
        x + cos((ang - 90) * (Math.PI / 180)) * height,
        y - sin((ang - 90) * (Math.PI / 180)) * height,
        6,
        0,
        2 * Math.PI
      );
      context.stroke();

      context.strokeStyle = "rgb(255, 0, 0)";
      context.beginPath();
      context.arc(
        x2 + cos((ang - 90) * (Math.PI / 180)) * height,
        y2 - sin((ang - 90) * (Math.PI / 180)) * height,
        6,
        0,
        2 * Math.PI
      );
      context.stroke();
    }
    context.beginPath();
    context.moveTo(x, y);
    context.lineTo(x2, y2);
    context.closePath();
    context.moveTo(
      x + cos((ang - 90) * (Math.PI / 180)) * height,
      y - sin((ang - 90) * (Math.PI / 180)) * height
    );
    context.lineTo(
      x2 + cos((ang - 90) * (Math.PI / 180)) * height,
      y2 - sin((ang - 90) * (Math.PI / 180)) * height
    );
    context.closePath();
    context.strokeStyle = "rgba(255, 255, 255, 1)";
    context.stroke();
  }

  /*public static clone(p: Placeable): VentShaft {
    if (p) {
      let b = VentShaft.create(
        pond.Block.fromObject({
          settings: cloneDeep(other.settings),
          img: new Image()
        })
      );
      let v = new VentShaft()
      v.x = p.x;
      v.y = p.y;
      v.angle = p.angle;
      v.circle = Circle.create(0, 0, 0);
      v.ctrl = p.ctrl;
      v.shift = p.shift;
      v.diameter = p.diameter;
      v.length = p.length;
      if (b.img)
        switch (b.settings.type) {
          case pond.BlockType.BLOCK_TYPE_VENT:
            b.img.src = StandardDuctIcon;
            break;
          case pond.BlockType.BLOCK_TYPE_VENT_CORNER:
            b.img.src = ConnectionDuctIcon;
            break;
          default:
            b.img = undefined;
        }
      return b;
    }
    return VentShaft.create();
  }*/

  public getEndXAndY = () => {
    return { x: this.endX(), y: this.endY() };
  };

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

    let cos = Math.cos;
    let sin = Math.sin;

    let ang = this.angle;
    let rad = ang * (Math.PI / 180);

    let cx = x1 + Math.cos(rad) * width;
    let cy = y1 - Math.sin(rad) * width;
    this.circle.x = cx;
    this.circle.y = cy;
    this.circle.radius = height / 4;
    if (this.circle.clickCheck(x, y, scale)) return true;

    y1 = y1 - cos(rad) * (height / 2);
    x1 = x1 - sin(rad) * (height / 2);

    let x2 = x1 + cos(rad) * width;
    let y2 = y1 - sin(rad) * width;

    let x3 = x1 + cos((ang - 90) * (Math.PI / 180)) * height;
    let y3 = y1 - sin((ang - 90) * (Math.PI / 180)) * height;

    let x4 = x2 + cos((ang - 90) * (Math.PI / 180)) * height;
    let y4 = y2 - sin((ang - 90) * (Math.PI / 180)) * height;

    let D1 = (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1);
    let D2 = (x4 - x2) * (y - y2) - (x - x2) * (y4 - y2);
    let D3 = (x3 - x4) * (y - y4) - (x - x4) * (y3 - y4);
    let D4 = (x1 - x3) * (y - y3) - (x - x3) * (y1 - y3);

    if (D1 > 0 && D2 > 0 && D3 > 0 && D4 > 0) {
      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 * scale;

    let ang = this.angle;
    let rad = ang * (Math.PI / 180);

    // Draw selection circle
    let cx = x + Math.cos(rad) * width;
    let cy = y - Math.sin(rad) * width;
    this.circle.x = cx;
    this.circle.y = cy;
    this.circle.radius = height / 4;
    this.circle.draw(context);

    let x1 = x - Math.sin(rad) * ((height + 3) / 2);
    let y1 = y - Math.cos(rad) * ((height + 3) / 2);

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

    // Make the point of rotation the objects x and y before making its rotation
    context.translate(x1, y1);
    context.rotate(-rad);
    context.translate(-x1, -y1);

    // Draw the rectangle and then rotate back to original
    context.strokeRect(x1, y1, width, height + 3);
    context.translate(x1, y1);
    context.rotate(rad);
    context.translate(-x1, -y1);
  }

  public clone() {
    let v = new VentShaft();
    v.x = this.x;
    v.y = this.y;
    v.angle = this.angle;
    v.circle = this.circle;
    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,
      occupant: undefined,
      startPoint: true
    });
    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.angle,
      occupant: undefined,
      startPoint: false
    });
    return snaps;
  }

  public snap = (p: Placeable, snapDistance: number): void => {
    snapDistance = snapDistance ? snapDistance : 12;

    for (let i = 0; i < p.getSnapPoints().length; i++) {
      let point = p.getSnapPoints()[i];
      if (point.startPoint === false) {
        let d = this.distance(this.x, point.x, this.y, point.y);
        if (d < 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 {
      this.x = x - this.mouseOffsetX;
      this.y = y - this.mouseOffsetY;
    }
  };

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

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

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

  setDirection(direction: number): void {}

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