import { Component, OnInit, ChangeDetectionStrategy, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { CdkDragMove, CdkDragStart, CdkDragEnd } from '@angular/cdk/drag-drop';
import { style, animate, AnimationBuilder } from '@angular/animations';

@Component({
  selector: 'rbj-resizable-box',
  templateUrl: './resizable-box.component.html',
  styleUrls: ['./resizable-box.component.scss']
})
export class ResizableBoxComponent implements OnInit {


  @ViewChild('resizeBox', { static: true }) resizeBox: ElementRef;
  @Input()
  id: string;
  @Input()
  gridSizeY: number = 20;
  @Input()
  gridSizeX: number = 300;
  @Input()
  gridPadding: number = 10;

  @Input()
  max_X: number = 1920;
  @Input()
  max_Y: number = 9999;
  @Input()
  min_X: number = 0;
  @Input()
  min_Y: number = 0;


  @Input()
  draggable: boolean = false;
  @Input()
  minWidth: number = 100;
  @Input()
  minHeight: number = 100;

  dragging: boolean = false;

  public get position(): position {
    return { x: this.x, y: this.y } as position;
  }
  public set position(v: position) {
    // this.x = v.x;
    // this.y = v.y;
    this.animationMove(v.x, v.y)
  }


  public get size(): size {
    return { width: this.width, height: this.height } as size;
  }
  public set size(v: size) {
    // this.width = v.width;
    // this.height = v.height;
    this.animationResize(v.width, v.height)
  }
  public get spec() {
    return new boxSpec(this.id, this.x, this.y, this.width, this.height);
  }



  @Output() draggingStart = new EventEmitter<any>();
  @Output() draggingEnd = new EventEmitter<any>();
  @Output() change = new EventEmitter<boxSpec>();
  @Output() positionChange = new EventEmitter<position>();
  @Output() sizeChange = new EventEmitter<size>();
  @Output() animationMoveEnd = new EventEmitter<any>();

  private _x: number = 0;
  @Input()
  public get x(): number {
    return this._x;
  }
  public set x(v: number) {
    this._x = v;
  }

  private _y: number = 0;
  @Input()
  public get y(): number {
    return this._y;
  }
  public set y(v: number) {
    this._y = v;
  }


  private _width: number = 0;
  @Input()
  public get width(): number {
    return this._width;
  }
  public set width(v: number) {
    this._width = v;
  }


  private _height: number = 0;
  @Input()
  public get height(): number {
    return this._height;
  }
  public set height(v: number) {
    this._height = v;
  }



  displayX: number = 0;
  displayY: number = 0;
  displayWidth: number = 1;
  displayHeight: number = 1;



  get resizeBoxElement(): HTMLElement {
    return this.resizeBox.nativeElement;
  }


  constructor(private animationBuilder: AnimationBuilder,) { }

  ngOnInit() {
  }

  ngAfterViewInit() {
  }

  lastMoveX: number = 0;
  lastMoveY: number = 0;

  dragStart($event: CdkDragStart<any>) {
    this.lastMoveX = 0;
    this.lastMoveY = 0;
    this.dragging = true;
    this.draggingStart.emit(this.spec);
  }
  moveEnd($event: CdkDragEnd<any>) {
    this.dragging = false;
    // let endX = Math.round(this.x / this.gridSizeX) * this.gridSizeX;
    // let endY = Math.round(this.y / this.gridSizeY) * this.gridSizeY;

    // endX = endX < this.min_X ? this.min_X : endX;
    // endX = endX > this.max_X ? this.max_X : endX;

    // endY = endY < this.min_Y ? this.min_Y : endY;
    // endY = endY > this.max_Y ? this.max_Y : endY;
    // this.animationMove(endX, endY);



    this.moveAndResize(this.spec);
    this.draggingEnd.emit(this.spec);
  }
  resizeEnd($event: CdkDragEnd<any>) {
    this.dragging = false;
    // let endWidth = Math.round(this.width / this.gridSizeX) * this.gridSizeX;
    // let endHeight = Math.round(this.height / this.gridSizeY) * this.gridSizeY;
    // this.animationResize(endWidth, endHeight);


    this.moveAndResize(this.spec);
    this.draggingEnd.emit(this.spec);
  }
  dragMove($event: CdkDragMove<any>) {
    // this.x += $event.distance.x - this.lastMoveX;
    // this.y += $event.distance.y - this.lastMoveY;
    this.displayX += $event.distance.x - this.lastMoveX;
    this.displayY += $event.distance.y - this.lastMoveY;
    this.lastMoveX = $event.distance.x;
    this.lastMoveY = $event.distance.y;

    this.x = Math.round(this.displayX / this.gridSizeX);
    this.y = Math.round(this.displayY / this.gridSizeY);

    if (this.x < 0) {
      this.x = 0;
    }
    // if (this.y < 0) {
    //   this.y = 0;
    // }

    if (this.spec.right > 12) {
      this.x = 12 - this.width;
    }
    this.positionChange.emit(this.position);
    this.change.emit(this.spec);
  }

  dragResize($event: CdkDragMove<any>) {

    this.displayWidth += $event.distance.x - this.lastMoveX;
    this.displayWidth = this.displayWidth < this.minWidth ? this.minWidth : this.displayWidth;
    this.displayHeight += $event.distance.y - this.lastMoveY;
    this.displayHeight = this.displayHeight < this.minHeight ? this.minHeight : this.displayHeight;

    if (this.spec.right > this.max_X) {
      this.displayWidth = (this.max_X - this.x);
    }

    this.lastMoveX = $event.distance.x;
    this.lastMoveY = $event.distance.y;


    this.width = Math.round(this.displayWidth / this.gridSizeX);
    this.height = Math.round(this.displayHeight / this.gridSizeY);

    if (this.spec.right > 12) {
      this.width = 12 - this.x;
    }

    if (this.width < 1) this.width = 1;
    if (this.height < 1) this.height = 1;


    this.sizeChange.emit(this.size);
    this.change.emit(this.spec);
  }

  animationInit(spec: boxSpec) {

    let x = spec.position.x;
    let y = spec.position.y;
    let w = spec.size.width;
    let h = spec.size.height;


    let newDisplayX = x * this.gridSizeX;
    let newDisplayY = y * this.gridSizeY;
    let newDisplayHeight = h * this.gridSizeY;
    let newDisplayWidth = w * this.gridSizeX;

    const animation = this.animationBuilder.build([
      style({
        transform: `translate(${this.displayX}px,${this.displayY}px)`,
        height: `${this.displayHeight}px`,
        width: `${this.displayWidth}px`
      }),
      animate(300, style({
        transform: `translate(${newDisplayX}px,${newDisplayY}px)`,
        height: `${newDisplayHeight}px`,
        width: `${newDisplayWidth}px`
      }))
    ]);

    const player = animation.create(this.resizeBoxElement);
    player.onDone(() => {
      this.height = h;
      this.width = w;
      this.resizeBoxElement.style.height = `${newDisplayHeight}px`;
      this.resizeBoxElement.style.width = `${newDisplayWidth}px`;

      this.x = x;
      this.y = y;

      this.displayHeight = newDisplayHeight;
      this.displayWidth = newDisplayWidth;
      this.displayX = newDisplayX;
      this.displayY = newDisplayY;

      this.resizeBoxElement.style.transform = `translate(${newDisplayX}px,${newDisplayY}px)`;
      player.destroy();
    })
    player.play();
  }
  animationMove(moveToX: number, moveToY: number, milesec: number = 300) {
    //var newWidth = this.spec.right <= this.max_X ? this.width : (this.max_X - moveToX);
    var newWidth = this.width;
    if (this.spec.right > this.max_X) {
      newWidth = (this.max_X - moveToX);
    }

    if (moveToX < this.min_X) {
      newWidth = this.width - (this.min_X - moveToX);
      moveToX = 0
    }

    const animation = this.animationBuilder.build([
      style({
        transform: `translate(${this.x}px,${this.y}px)`,
        width: this.width + 'px'
      }),
      animate(milesec, style({
        transform: `translate(${moveToX}px,${moveToY}px)`,
        width: newWidth + 'px'
      }))
    ]);

    const player = animation.create(this.resizeBoxElement);
    player.onDone(() => {
      this.x = moveToX;
      this.y = moveToY;
      this.width = newWidth;
      this.resizeBoxElement.style.transform = `translate(${moveToX}px,${moveToY}px)`;
      player.destroy();
      this.animationMoveEnd.emit();
    })
    player.play();
  }

  animationResize(width: number, height: number, milesec: number = 300) {
    const animation = this.animationBuilder.build([
      style({
        height: `${this.height}px`,
        width: `${this.width}px`
      }),
      animate(milesec, style({
        height: `${height}px`,
        width: `${width}px`
      }))
    ]);

    const player = animation.create(this.resizeBoxElement);
    player.onDone(() => {
      this.height = height;
      this.width = width;
      this.resizeBoxElement.style.height = `${height}px`;
      this.resizeBoxElement.style.width = `${width}px`;
      player.destroy();
    })
    player.play();
  }

  animationMoveAndResize(newSpec: boxSpec, milesec: number = 300) {
    const animation = this.animationBuilder.build([
      style({
        transform: `translate(${this.x}px,${this.y}px)`,
        height: `${this.height}px`,
        width: `${this.width}px`
      }),
      animate(milesec, style({
        transform: `translate(${newSpec.position.x}px,${newSpec.position.y}px)`,
        height: `${newSpec.size.height}px`,
        width: `${newSpec.size.width}px`
      }))
    ]);

    const player = animation.create(this.resizeBoxElement);
    player.onDone(() => {
      this.height = newSpec.size.height;
      this.width = newSpec.size.width;
      this.resizeBoxElement.style.height = `${newSpec.size.height}px`;
      this.resizeBoxElement.style.width = `${newSpec.size.width}px`;

      this.x = newSpec.position.x;
      this.y = newSpec.position.y;
      this.resizeBoxElement.style.transform = `translate(${newSpec.position.x}px,${newSpec.position.y}px)`;
      player.destroy();
    })
    player.play();
  }

  moveAndResize(newSpec: boxSpec, milesec: number = 300) {
    let x = newSpec.position.x;
    let y = newSpec.position.y;
    let w = newSpec.size.width;
    let h = newSpec.size.height;


    let newDisplayX = x * this.gridSizeX + (x * this.gridPadding);
    let newDisplayY = y * this.gridSizeY;
    let newDisplayHeight = h * this.gridSizeY;
    let newDisplayWidth = w * this.gridSizeX + ((w - 1) * this.gridPadding);

    const animation = this.animationBuilder.build([
      style({
        transform: `translate(${this.displayX}px,${this.displayY}px)`,
        height: `${this.displayHeight}px`,
        width: `${this.displayWidth}px`
      }),
      animate(milesec, style({
        transform: `translate(${newDisplayX}px,${newDisplayY}px)`,
        height: `${newDisplayHeight}px`,
        width: `${newDisplayWidth}px`
      }))
    ]);

    const player = animation.create(this.resizeBoxElement);
    player.onStart(() => {

      this.x = x;
      this.y = y;
      this.height = h;
      this.width = w;

      this.displayX = newDisplayX;
      this.displayY = newDisplayY;
      this.displayHeight = newDisplayHeight;
      this.displayWidth = newDisplayWidth;
    });
    player.onDone(() => {
      this.resizeBoxElement.style.height = `${newDisplayHeight}px`;
      this.resizeBoxElement.style.width = `${newDisplayWidth}px`;
      this.resizeBoxElement.style.transform = `translate(${newDisplayX}px,${newDisplayY}px)`;
      player.destroy();
    })
    player.play();
  }


}



export class boxSpec {
  /**
   *
   */
  constructor(id, x, y, w, h) {
    this.id = id;
    this.position = { x: x, y: y } as position;
    this.size = { height: h, width: w } as size;
  }
  id: string;
  position: position;
  size: size;
  stack: number = 0;
  public get left() {
    return this.position.x;
  }
  public get right() {
    return this.position.x + this.size.width;
  }

  public get top() {
    return this.position.y;
  }
  public get bottom() {
    return this.position.y + this.size.height;
  }

  diff(comparison: boxSpec) {
    return this.position.x != comparison.position.x || this.position.y != comparison.position.y || this.size.height != comparison.size.height || this.size.width != comparison.size.width;
  }
  toString() {
    return `left:${this.left},top:${this.top},right:${this.right},bottom:${this.bottom}`;
  }
  copy(): boxSpec {
    return new boxSpec(this.id, this.position.x, this.position.y, this.size.width, this.size.height);
  }
}
export class position {
  x: number;
  y: number;
}
export class size {
  width: number;
  height: number;
  largerThan(comparison: size) {
    return this.height > comparison.height && this.width > comparison.width;
  }
}