import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, Renderer2, NgZone, ViewChild, ChangeDetectorRef, Inject } from '@angular/core';
import { ActivatedRoute, Router, RouterOutlet, UrlTree } from '@angular/router';
import { Params } from '@angular/router/src/shared';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/src/Subscriber';
import { Subject } from 'rxjs/Subject';
import { of } from 'rxjs/observable/of';
import { delay, share } from 'rxjs/operators';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { applyMixins, ILog, Log, convertUTCDateToLocalDate } from '../../shared/helpers';
import { IFeedKnok, IFeedStatus, IKnok, IKnokFeedData, IKnoker, Knok, Knoker, IKnokFullData } from '../../shared/knok/knok.model';
import { KnokService } from '../../shared/knok/knok.service';
import { IReference, IUserAccounts, KnokRollService, TokenService, UserService } from '../../shared';
import { ExpansionPanels } from '../../shared//libs/expansion-panels';
import { getKnokserRank, formatTimeLeft } from '../../shared/helpers/converters';
import { Chart } from 'chart.js';
import { KnokLinearScale, KnokLinearPlugin } from '../../shared/libs/chart_js/';
import { findIndex as _findIndex } from 'lodash/array';
import { has as _has } from 'lodash/object';
import * as MDC from 'material-components-web';
import { DomSanitizer, SafeResourceUrl, SafeUrl } from '@angular/platform-browser';
import 'chartjs-plugin-zoom';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material';
import { OverlayContainer } from '@angular/cdk/overlay';

Chart.scaleService.registerScaleType('knok-linear', KnokLinearScale, Chart.scaleService.getScaleDefaults('linear'));
Chart.plugins.register(KnokLinearPlugin);

@Component({
  selector: 'knk-track-knok',
  templateUrl: './knk-track-knok.component.pug',
  styleUrls: ['./knk-track-knok.component.scss']
})
export class KnkTrackKnokComponent implements AfterViewInit, OnInit, OnDestroy, ILog {
  @ViewChild('canvas') canvas;
  chart: any;
  panels: any;
  availableFundamentalReferences: IReference[] = [];
  timeLeft: any;
  timeLeftOnFeed: any;
  
  /* Mixins */
  public log: (...args: any[]) => void;

  private id: number;
  private siblings: number[] = [];
  private knok: IKnok;
  private knokser: IKnoker;
  private status: IFeedStatus;
  private chartData: Array<{x: Date, y: number}>;
  private accounts: IUserAccounts = null;
  private subjectDataLoaded: Subject<boolean>;
  private router: Router;
  private route: ActivatedRoute;
  private ngUnsubscribe: Subject<void> = new Subject<void>();

  constructor(public dialogRef: MatDialogRef<KnkTrackKnokComponent, any>,
              private changeDetector: ChangeDetectorRef,
              private zone: NgZone,
              private domSanitizer: DomSanitizer,
              private element: ElementRef,
              private renderer: Renderer2,
              router: Router,
              route: ActivatedRoute,
              private userService: UserService,
              private tokenService: TokenService,
              private knokService: KnokService,
              private knokRollService: KnokRollService,
              @Inject(MAT_DIALOG_DATA) private data: any) {
    this.subjectDataLoaded = new Subject<boolean>();
    this.router = data.router || router;
    this.route = data.route || route;
  }

  ngOnInit(): void {
    this.id = parseInt(this.data.id, 10);
    this.userService.loadUser()
      .takeLast(1)
      .subscribe((accounts: IUserAccounts): void => {
        if (this.tokenService.isLogin && accounts) {
          this.accounts = accounts;
        }

        if (this.id > 0) {
          this.knokService.getKnokData(this.id, this.userId)
            .takeLast(1)
            .subscribe((data: IKnokFullData) => {
              this.knok = data.knok;
              this.knokser = data.knoker;
              this.status = data.status;
              this.chartData = data.chartData;
              console.log('IKnokFullData', data);
              Observable.timer(10, 30000).takeUntil(this.ngUnsubscribe).subscribe((): void => {
                this.timeLeft = this.getTimeLeft();
                this.timeLeftOnFeed = this.getTimeLeftOnFeed();
              });

            }, (error: any): void => {
              console.error(error);
            }, (): void => {
              this.subjectDataLoaded.next(true);
              this.subjectDataLoaded.complete();
            });
        }
      });
  }

  ngOnDestroy(): void {
    this.destroyChart();
    this.destroyExpansionPanels();
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

  ngAfterViewInit(): void {
    this.subjectDataLoaded.asObservable().takeLast(1).debounceTime(250).subscribe((): void => {
      this.availableFundamentalReferences = [];
      this.zone.run((): void => {
        const timeout: any = setTimeout((): void => {
          clearTimeout(timeout);

          this.initChart();
          this.initExpansionPanels();

          if (this.knokHasFundamental && this.knokFundamental.references) {
            let references: Array<{ reference: string }> = [];
            if (Array.isArray(this.knokFundamental.references)) {
              references = Object.assign([], this.knokFundamental.references);
            } else if (typeof this.knokFundamental.references === 'string') {
              references = Object.assign([], JSON.parse(this.knokFundamental.references));
            }
            references.forEach((item: any) => {
              if (_findIndex(this.availableFundamentalReferences, (reference: IReference) => reference.url === item.reference) === -1) {
                this.knokService.getKnokFAReference({url: item.reference}).subscribe((reference: IReference) => {
                  this.changeDetector.detectChanges();
                  this.availableFundamentalReferences.push(reference);
                });
              }
            });
          }
        }, 10);
      });
    });
  }

  get userId(): number | null {
    return (_has(this.accounts, ['user', 'userId'])) ? this.accounts.user.userId : null;
  }

  get isMine(): boolean {
    if (this.knokExists && this.userId > 0) {
      return this.knokser.userId === this.userId;
    }
    return false;
  }

  get userTokens(): number {
    if (_has(this.knok, ['pricePool'])) {
      return this.knok.pricePool;
    } else {
      return 0;
    }
  }

  get knokExists(): boolean {
    return this.knok !== null && this.knok !== undefined;
  }

  get knokHasTechnical(): boolean {
    return this.knok.hasTechnical;
  }

  get knokHasFundamental(): boolean {
    return this.knok.hasFundamental;
  }

  get knokPurchases(): number {
    if (_has(this.status, ['purchases'])) {
      return this.status.purchases;
    } else {
      return 0;
    }
  }

  get knokUserReview(): number {
    if (_has(this.knok, ['userReview'])) {
      return this.knok.userReview;
    } else {
      return 0;
    }
  }

  get knokUserReviewClarity(): number {
    if (_has(this.knok, ['userReviewClarity'])) {
      return this.knok.userReviewClarity;
    } else {
      return 0;
    }
  }
  get knokUserReviewUsefulness(): number {
    if (_has(this.knok, ['userReviewUsefulness'])) {
      return this.knok.userReviewUsefulness;
    } else {
      return 0;
    }
  }

  get knokUserReviewProfitability(): number {
    if (_has(this.knok, ['userReviewProfitability'])) {
      return this.knok.userReviewProfitability;
    } else {
      return 0;
    }
  }

  get knokBuyerReviews(): number {
    if (_has(this.knok, ['buyerReviews'])) {
      return this.knok.buyerReviews;
    } else {
      return 0;
    }
  }

  get knokPurchaseAt(): string | null {
    const date: Date | undefined = (_has(this.knok, ['buyDate'])) ? new Date(this.knok.buyDate) : undefined;
    let result: string = null;
    if (date) {
      let hours: string = (date.getHours() < 10) ? '0' + date.getHours() : '' + date.getHours();
      let minutes: string = (date.getMinutes() < 10) ? '0' + date.getMinutes() : '' + date.getMinutes();
      result = hours + ':' +  minutes;
    }
    return (result) ? result : null;
  }

  get knokPublishedAt(): string | null {
    const date: Date | undefined = (_has(this.knok, ['publishedDate'])) ? new Date(this.knok.publishedDate) : undefined;
    let result: string = null;
    if (date) {
      let current_date: Date = convertUTCDateToLocalDate(date);
      let hours: string = (current_date.getHours() < 10) ? '0' + current_date.getHours() : '' + current_date.getHours();
      let minutes: string = (current_date.getMinutes() < 10) ? '0' + current_date.getMinutes() : '' + current_date.getMinutes();
      result = hours + ':' + minutes;
    }
    return (result) ? result : null;
  }

  get knokDuration(): string {
    return `${this.knok.duration} Days`;
  }

  getTimeLeft(): any {
    const dayTime: number = (24 * 60 * 60 * 1000);
    if (this.knok && _has(this.knok, ['createDate'])) {
      return formatTimeLeft(new Date(this.knok.createDate.getTime() + (this.knok.duration * dayTime)), false);
    } else {
      return 0;
    }
  }

  getTimeLeftOnFeed(): any {
    const minutesTime: number = (60 * 1000);
    const currDate: Date = new Date();
    if (this.knok && _has(this.knok, ['minutesLeftOnFeed'])) {
      return formatTimeLeft(new Date(currDate.getTime() + (this.knok.minutesLeftOnFeed * minutesTime)), false);
    } else {
      return formatTimeLeft(new Date(currDate.getTime()), false);
    }
  }

  get knokMarket(): string {
    return this.knok.currency1;
  }

  get knokCurrency(): string {
    return this.knok.currency2;
  }

  get knokCurrentPrice(): number {
    return this.knok.currentPrice;
  }

  get knokCurrentProfitPercent(): number {
    return this.knok.currentProfitPercent;
  }

  get knokExchange(): string {
    return this.knok.exchangeName;
  }

  get knokEntryPriceFrom(): number {
    return this.knok.entryPriceFrom as number;
  }

  get knokEntryPriceTo(): number {
    return this.knok.entryPriceTo as number;
  }

  get knokExitPriceFrom(): number {
    return this.knok.exitPriceFrom as number;
  }

  get knokExitPriceTo(): number {
    return this.knok.exitPriceTo as number;
  }

  get knokStopLoss(): number {
    return this.knok.stopLoss as number;
  }

  get knokPotentialProfit(): string {
    /*
     * Low: Up to 30%
     * Medium: 30%-60%
     * High: 60-100%
     * Very High: Above 100%
     */
    const potentialProfit: number = this.knok.potentialProfit as number;
    return potentialProfit === 1 ? 'Low' :
      potentialProfit === 2 ? 'Medium' :
      potentialProfit === 3 ? 'High' :
      potentialProfit === 4 ? 'Very High' : null;
  }

  get knokPotentialProfitPercent(): number {
    if (typeof this.knok.potentialProfitValue === 'number' && !Number.isNaN(this.knok.potentialProfitValue)) {
      return Math.round((this.knok.potentialProfitValue as number) * 100);
    } else {
      return 0;
    }
  }

  get knokPriceFormat(): string {
    if (typeof this.knok.precision === 'number' && !Number.isNaN(this.knok.precision)) {
      return `1.${this.knok.precision}-${this.knok.precision}`; // (this.knok.precision === 8 ? '1' : '') + '.' + this.knok.precision.toString();
    } else {
      return '1.8-8';
    }
  }

  get knokTechnical(): { [key: string]: any } {
    return (this.knokHasTechnical) ? this.knok.technical : {};
  }

  get knokTechnicalImage(): SafeUrl | SafeResourceUrl | string {
    let safeUrl: SafeUrl | SafeResourceUrl | string = null;
    if (this.knokHasTechnical) {
      const image: any = this.knokTechnical.image;
      const url: string = image.url;
      const content: string = image.content;
      const contentType: string = image.contentType;

      if (url) {
        safeUrl = this.domSanitizer.bypassSecurityTrustUrl(url);
      } else if (image.href) {
        return `${SETTINGS.API_URL}${image.href}`;
      } else if (content && contentType) {
        safeUrl = this.domSanitizer.bypassSecurityTrustResourceUrl(`data:${contentType};base64,${content}`);
      }
    }
    return safeUrl;
  }

  get knokFundamental(): { [key: string]: any } {
    return (this.knokHasFundamental) ? this.knok.fundamental : {};
  }

  get knokserExists(): boolean {
    return this.knokser !== null && this.knokser !== undefined;
  }

  get knokserAvatarUrl(): string {
    return `${SETTINGS.API_URL}` + this.knokser.avatarUrl;
  }

  get knokserName(): string {
    return !this.knokser.firstName && !this.knokser.lastName ? this.knokser.userName : this.knokser.firstName;
  }

  get knokserSurname(): string {
    return this.knokser.lastName;
  }

  get knokserFollowers(): number {
    return this.knokser.countFollowers as number || 0;
  }

  get knokserRankLabel(): string {
    return getKnokserRank(this.knokser.rank as number);
  }

  initChart(): void {
    const oneDayTiks: number = (24 * 60 * 60 * 1000);
    let pointRadiusData: Array<number> = new Array(this.chartData.length);
    pointRadiusData.fill(0);
    const endDate: Date = new Date(this.knok.createDate.getTime() + (this.knok.duration * oneDayTiks));

    for (let i: number = this.chartData.length - 1; i >= 0 ; i--) {
      if (this.chartData[i].y) {
        pointRadiusData[i] = 8.5;
        break;
      }
    }

    if (this.chartData.length > 1 && !this.chartData[0].y) {
      this.chartData[0].y = this.chartData[1].y;
    }

    // Add expected end chart value
    if (this.chartData[this.chartData.length - 1].y) {
      this.chartData.push({x: endDate, y: null});
    }

    let chartColor: string = 'rgba(0, 186, 255, 1)';
    let chartBgColor: string = 'rgba(0, 186, 255, 0.15)';
    if (this.knok.endedStatus === 1) {
      chartColor = 'rgba(98, 98, 98, 1)';
      chartBgColor = 'rgba(98, 98, 98, 0.15)';
    }
    if (this.knok.endedStatus === 2) {
      chartColor = 'rgba(255, 84, 77, 1)';
      chartBgColor = 'rgba(255, 84, 77, 0.15)';
    }
    if (this.knok.endedStatus === 3) {
      chartColor = 'rgba(98, 98, 98, 1)';
      chartBgColor = 'rgba(98, 98, 98, 0.15)';
    }
    if (this.knok.endedStatus === 4) {
      chartColor = 'rgba(33, 222, 171, 1)';
      chartBgColor = 'rgba(33, 222, 171, 0.15)';
    }

    const canvas: HTMLCanvasElement | undefined = (this.canvas && this.canvas.nativeElement) || undefined;
    let day: string = '';
    let shift: number = 0;
    let prevIndex: number = 0;
    if (canvas) {
      const ctx: CanvasRenderingContext2D = canvas.getContext('2d');
      // Get min chart price
      let minItem: {x: Date, y: number} = this.chartData.reduce((prev, curr) => curr.y === null || prev.y < curr.y ? prev : curr);
      let h: number = Math.abs(minItem.y - ((this.knok.entryPriceFrom + this.knok.entryPriceTo) / 2));
      let chartScale: number = ctx.canvas.height / (((this.knok.exitPriceFrom + this.knok.exitPriceTo) / 2) - this.knok.stopLoss);
      let chartBackgroundHeight: number = ctx.canvas.height / (chartScale * h) * 20;
      console.log(`h:${ctx.canvas.height} ch: ${chartBackgroundHeight}`);
      // Calc min chart value
      let minTick: number = (minItem && minItem.y < this.knok.stopLoss ? minItem.y : this.knok.stopLoss);
      if (minTick < this.knok.stopLoss) {
        minTick = minTick - (( minTick / 100) * 10);
      }
      this.chart = new Chart(ctx, {
        pricesTicks: [],
        type: 'line',
        data: {
          datasets: [
            {
              showLine: true,
              fill: true,
              backgroundColor: this.getCanvasLinearGradient(ctx, [[0, chartBgColor], [1, 'rgba(255, 255, 255, 0.15)']], {y1: chartBackgroundHeight}),
              borderColor: chartColor,
              borderWidth: 2,
              pointBackgroundColor: chartColor,
              pointBorderColor: 'rgba(255, 255, 255, 1)',
              pointBorderWidth: 2,
              pointRadius: pointRadiusData,
              data: this.chartData,
              yAxisID: 'A'
            }
          ]
        },
        options: {
          layout: {
            padding: {
                left: 0,
                right: 0,
                top: 10,
                bottom: 10
            }
          },
          responsive: true,
          maintainAspectRatio: false,
          endedStatus: this.knok.endedStatus,
          easing: 'easeInOutBack',
          elements: {
            line: {
              tension: 0.5
            }
          },
          plugins: ['knok-linear'],
          scales: {
            xAxes: [
              {
                id: 'D',
                type: 'time',
                display: true,
                time: {
                  unit: 'hour',
                  stepSize: this.knok.ratesGranularityHours,
                  displayFormats: {
                    hour: 'DD MMM|HH'
                  },
                  tooltipFormat: 'MM/DD/YY'
                },
                ticks: {
                  display: true,
                  beginAtZero: false,
                  min: this.knok.createDate,
                  max: endDate,
                  maxRotation: 0,
                  callback: (value: string, index: number, values: Array<number>): string => {
                    const ticksToShow: number = 9;
                    const arr: string[] = value.split('|');
                    if (arr.length === 2) {
                      const skipCount: number = Math.floor(values.length / ticksToShow);
                      if (skipCount === 0 || Math.floor(index / skipCount) * skipCount === index) {
                        if (day === '' || day !== arr[0]) {
                          day = arr[0];
                          return arr[0];
                        } else {
                          return `${arr[1]}`;
                        }
                      }
                    }
                    return '';
                  }
                },
                gridLines: {
                  display: false,
                  drawBorder: true
                }
              }
            ],
            yAxes: [
              {
                id: 'A',
                type: 'knok-linear',
                position: 'right',
                display: true,
                ticks: {
                  display: true,
                  beginAtZero: false,
                  fontFamily: 'Roboto',
                  fontSize: 10,
                  exitPriceFrom: this.knok.exitPriceFrom,
                  exitPriceTo: this.knok.exitPriceTo,
                  entryPriceFrom: this.knok.entryPriceFrom,
                  entryPriceTo: this.knok.entryPriceTo,
                  stopLoss: this.knok.stopLoss,
                  min:  minTick,
                  precision: this.knok.precision,
                  grayedOut: this.knok.endedStatus === 1 || this.knok.endedStatus === 2 || this.knok.endedStatus === 3
                },
                gridLines: {
                  display: true,
                  drawBorder: false,
                  zeroLineColor: 'transparent'
                }
              }
            ]
          },
          legend: {
            display: false
          },
          title: {
            display: false
          },
          tooltips: {
            enabled: false,
            custom: (tooltipModel: any) => {
                // Tooltip Element
                let tooltipEl: any = document.getElementById('chartjs-tooltip');

                // Create element on first render
                if (!tooltipEl) {
                    tooltipEl = document.createElement('div');
                    tooltipEl.id = 'chartjs-tooltip';
                    tooltipEl.innerHTML = '<div></div>';
                    document.body.appendChild(tooltipEl);
                }

                // Hide if no tooltip
                if (tooltipModel.opacity === 0) {
                  tooltipEl.style.opacity = 0;
                  return;
                }

                // Set caret Position
                tooltipEl.classList.remove('above', 'below', 'no-transform');
                if (tooltipModel.yAlign) {
                    tooltipEl.classList.add(tooltipModel.yAlign);
                } else {
                    tooltipEl.classList.add('no-transform');
                }

                // Set Text
                if (tooltipModel.body) {
                    let titleLines: any = tooltipModel.title || [];
                    let bodyLines: any = tooltipModel.body.map((bodyItem: any): any => {
                      return bodyItem.lines;
                  });
                    let divRoot: any = tooltipEl.querySelector('div');
                    divRoot.innerHTML = `<bs-tooltip-container role="tooltip" class="tooltip in tooltip-top bs-tooltip-top top knk-tooltip show">
                    <div class="tooltip-arrow arrow"></div>
                    <div class="tooltip-inner">${bodyLines[0]}</div></bs-tooltip-container>`;
                }

                // `this` will be the overall tooltip
                let position: any = canvas.getBoundingClientRect();
                // Display, position, and set styles for font
                tooltipEl.style.opacity = 1;
                tooltipEl.style.position = 'absolute';
                tooltipEl.style.left = position.left + tooltipModel.caretX - 55 + 'px';
                tooltipEl.style.top = position.top + tooltipModel.caretY -  57 - document.documentElement.offsetTop + 'px';
                tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
                tooltipEl.style.fontSize = tooltipModel.bodyFontSize + 'px';
                tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
                tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
                tooltipEl.style.zIndex = 1001;
                // tooltipEl.style.border = '2px solid #000';
            }
          },

          pan: {
            // Boolean to enable zooming
            enabled: true,

            // Zooming directions. Remove the appropriate direction to disable
            // Eg. 'y' would only allow zooming in the y direction
            mode: 'x'
            // limits: {
            //   max: 10,
            //   min: 0.5
            // }
            // rangeMin: {
            //   // Format of min zoom range depends on scale type
            //   x: 1,
            //   y: null
            // },
            // rangeMax: {
            //   // Format of max zoom range depends on scale type
            //   x: 10,
            //   y: null
            // }
          },

          // Container for zoom options
          zoom: {
            // Boolean to enable zooming
            enabled: true,

            // Zooming directions. Remove the appropriate direction to disable
            // Eg. 'y' would only allow zooming in the y direction
            mode: 'x',
            // limits: {
            //   max: 10,
            //   min: 0.5
            // }
            rangeMin: {
              // Format of min zoom range depends on scale type
              x: 1000 * 60 * 60 * 24, // 1day
              y: null
            },
            rangeMax: {
              // Format of max zoom range depends on scale type
              x: 1000 * 60 * 60 * 24 * this.knok.duration,
              y: null
            }
          }
        }
      });
    }
  }

  destroyChart(): void {
    if (this.chart) {
      this.chart.destroy();
    }
  }

  initExpansionPanels(): void {
    const host: HTMLElement = this.element.nativeElement;
    this.panels = ExpansionPanels.init(host, this.renderer);
  }

  destroyExpansionPanels(): void {
    if (this.panels) {
      ExpansionPanels.destroy(this.panels);
    }
  }

  openUrlInNewTab(url: string): void {
    const win: Window | null = window.open(url, '_blank');
    if (win) {
      win.focus();
    }
  }

  before(): void {
    const knoId: number = this.knokRollService.before(this.id, true);
    if (knoId !== -1) {
      let url: UrlTree = this.router.createUrlTree(['.', {outlets: {'knok': [knoId]}}], {relativeTo: this.route.parent});
      this.router.navigateByUrl(url);
    }
  }

  after(): void {
    const knoId: number = this.knokRollService.after(this.id, true);
    if (knoId !== -1) {
      let url: UrlTree = this.router.createUrlTree(['.', {outlets: {'knok': [knoId]}}], {relativeTo: this.route.parent});
      this.router.navigateByUrl(url);
    }
  }

  close(): void {
    if (this.dialogRef) {
      this.dialogRef.close(true);
    }
  }

  private getCanvasLinearGradient(ctx: CanvasRenderingContext2D, colors: [number, string][], options?: { [key: string]: any }): CanvasGradient | null {
    if (ctx && ctx.canvas) {
      const y0: number = options.y0 || 0;
      const y1: number = options.y1 || 100;
      const gradient: CanvasGradient = ctx.createLinearGradient(0, y0, 0, y1);
      colors.forEach((value: [number, string]): void => {
        const index: number = value[0];
        const color: string = value[1];
        gradient.addColorStop(index, color);
      });
      return gradient;
    } else {
      return null;
    }
  }
}

applyMixins(KnkTrackKnokComponent, [Log]);
