import {
  Component,
  Input,
  AfterViewInit,
  ElementRef,
  ChangeDetectorRef,
  OnDestroy,
  ViewChild,
  EventEmitter
} from "@angular/core";
import { Popover } from "./popover.directive";

@Component({
  selector: "popover-content",
  template: require('./popover-conent.component.html'),
  styles:   [require('./popover-content.component.scss')]
})
export class PopoverContent implements AfterViewInit, OnDestroy {

  // -------------------------------------------------------------------------
  // Inputs / Outputs
  // -------------------------------------------------------------------------

  // @Input()
  // hostElement: HTMLElement;

  @Input()
  content: string;

  @Input()
  placement: "top"|"bottom"|"left"|"right" = "bottom";

  @Input()
  title: string;

  @Input()
  animation: boolean = true;

  @Input()
  closeOnClickOutside: boolean = true;

  @Input()
  closeOnMouseOutside: boolean = false;

  @Input()
  flavor: 'default' | 'success' | 'danger' | 'warning' | 'info' = 'default';

  // -------------------------------------------------------------------------
  // Properties
  // -------------------------------------------------------------------------

  @ViewChild("popoverDiv")
  popoverDiv: ElementRef;

  popover: Popover;
  onCloseFromOutside  = new EventEmitter();
  top: number         = -1000;
  left: number        = -1000;
  isIn: boolean       = false;
  displayType: string = "none";

  // -------------------------------------------------------------------------
  // Anonymous
  // -------------------------------------------------------------------------

  /**
   * Closes dropdown if user clicks outside of this directive.
   */
  onDocumentMouseDown = (event: any) => {
    const element = this.element.nativeElement;
    if (!element || !this.popover) return;
    if (element.contains(event.target) || this.popover.getElement().contains(event.target)) return;
    this.hide();
    this.onCloseFromOutside.emit(undefined);
  };

  // -------------------------------------------------------------------------
  // Constructor
  // -------------------------------------------------------------------------

  constructor(private element: ElementRef,
              private cdr: ChangeDetectorRef) {
  }

  // -------------------------------------------------------------------------
  // Lifecycle callbacks
  // -------------------------------------------------------------------------

  ngAfterViewInit(): void {
    if (this.closeOnClickOutside)
      document.addEventListener("mousedown", this.onDocumentMouseDown);
    if (this.closeOnMouseOutside)
      document.addEventListener("mouseover", this.onDocumentMouseDown);

    this.show();
    this.cdr.detectChanges();
  }

  ngOnDestroy() {
    if (this.closeOnClickOutside)
      document.removeEventListener("mousedown", this.onDocumentMouseDown);
    if (this.closeOnMouseOutside)
      document.removeEventListener("mouseover", this.onDocumentMouseDown);
  }

  // -------------------------------------------------------------------------
  // Public Methods
  // -------------------------------------------------------------------------

  show(): void {
    if (!this.popover || !this.popover.getElement())
      return;

    const p          = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement);
    this.displayType = "block";
    this.top         = p.top;
    this.left        = p.left;
    this.isIn        = true;
  }

  hide(): void {
    this.top  = -1000;
    this.left = -1000;
    this.isIn = true;
    this.popover.hide();
  }

  hideFromPopover() {
    this.top  = -1000;
    this.left = -1000;
    this.isIn = true;
  }

  // -------------------------------------------------------------------------
  // Private Methods
  // -------------------------------------------------------------------------

  private positionElements(hostEl: HTMLElement,
                           targetEl: HTMLElement,
                           positionStr: string,
                           appendToBody: boolean = false): { top: number, left: number } {
    let positionStrParts = positionStr.split("-");
    let pos0             = positionStrParts[0];
    let pos1             = positionStrParts[1] || "center";
    let hostElPos        = appendToBody ? this.offset(hostEl) : this.position(hostEl);
    let targetElWidth    = targetEl.offsetWidth;
    let targetElHeight   = targetEl.offsetHeight;
    let shiftWidth: any  = {
      center: function (): number {
        return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
      },
      left:   function (): number {
        return hostElPos.left;
      },
      right:  function (): number {
        return hostElPos.left + hostElPos.width;
      }
    };

    let shiftHeight: any = {
      center: function (): number {
        return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
      },
      top:    function (): number {
        return hostElPos.top;
      },
      bottom: function (): number {
        return hostElPos.top + hostElPos.height;
      }
    };

    let targetElPos: { top: number, left: number };
    switch (pos0) {
      case "right":
        targetElPos = {
          top:  shiftHeight[pos1](),
          left: shiftWidth[pos0]()
        };
        break;

      case "left":
        targetElPos = {
          top:  shiftHeight[pos1](),
          left: hostElPos.left - targetElWidth
        };
        break;

      case "bottom":
        targetElPos = {
          top:  shiftHeight[pos0](),
          left: shiftWidth[pos1]()
        };
        break;

      default:
        targetElPos = {
          top:  hostElPos.top - targetElHeight,
          left: shiftWidth[pos1]()
        };
        break;
    }

    return targetElPos;
  }

  private position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } {
    let offsetParentBCR  = {top: 0, left: 0};
    const elBCR          = this.offset(nativeEl);
    const offsetParentEl = this.parentOffsetEl(nativeEl);
    if (offsetParentEl !== window.document) {
      offsetParentBCR = this.offset(offsetParentEl);
      offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
      offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
    }

    const boundingClientRect = nativeEl.getBoundingClientRect();
    return {
      width:  boundingClientRect.width || nativeEl.offsetWidth,
      height: boundingClientRect.height || nativeEl.offsetHeight,
      top:    elBCR.top - offsetParentBCR.top,
      left:   elBCR.left - offsetParentBCR.left
    };
  }

  private offset(nativeEl: any): { width: number, height: number, top: number, left: number } {
    const boundingClientRect = nativeEl.getBoundingClientRect();
    return {
      width:  boundingClientRect.width || nativeEl.offsetWidth,
      height: boundingClientRect.height || nativeEl.offsetHeight,
      top:    boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop),
      left:   boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft)
    };
  }

  private getStyle(nativeEl: HTMLElement, cssProp: string): string {
    if ((nativeEl as any).currentStyle) // IE
      return (nativeEl as any).currentStyle[cssProp];

    if (window.getComputedStyle)
      return (window.getComputedStyle as any)(nativeEl)[cssProp];

    // finally try and get inline style
    return (nativeEl.style as any)[cssProp];
  }

  private isStaticPositioned(nativeEl: HTMLElement): boolean {
    return (this.getStyle(nativeEl, "position") || "static" ) === "static";
  }

  private parentOffsetEl(nativeEl: HTMLElement): any {
    let offsetParent: any = nativeEl.offsetParent || window.document;
    while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) {
      offsetParent = offsetParent.offsetParent;
    }
    return offsetParent || window.document;
  }

}
