import { AfterViewInit, AfterContentInit, Component, ElementRef, Input, OnInit, OnDestroy, NgZone, Renderer2, ViewChild, ViewContainerRef, TemplateRef, EmbeddedViewRef, ViewRef, ChangeDetectorRef, DoCheck, OnChanges, SimpleChange, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FormGroup, FormControl, FormArray, FormBuilder, Validators } from '@angular/forms';
import { Subscription } from 'rxjs/Subscription';
import { Subscriber } from 'rxjs/Subscriber';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { delay, share } from 'rxjs/operators';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { CurrencyService, ExchangeService, FilterService, UserService, LoaderService, TokenService, IKnokOngoingArgs } from '../../../core/shared';
import { IFilter, IKnokFeedArgs, IKnokRaiting, IKnokLosing, ICurrency, IExchange, IUser, IUserCredentials, IUserAccounts, User } from '../../../core/shared';
import { ILog, Log, applyMixins, ViewRefWatcherService } from '../../shared/helpers';
import * as MDC from 'material-components-web';
import { head as _head, findIndex as _findIndex, fill as _fill } from 'lodash/array';
import { has as _has } from 'lodash/object';
import { SimpleChanges } from '@angular/core/src/metadata/lifecycle_hooks';

@Component({
  moduleId: module.id,
  selector: 'knk-search-filter',
  templateUrl: './knk-search-filter.component.pug',
  styleUrls: ['./knk-search-filter.component.scss']
})
export class KnkSearchFilterComponent implements AfterViewInit, OnInit, OnDestroy, ILog {
  filterType: string;
  form: FormGroup = null;
  availableExchanges: IExchange[] = [];
  availableTradingPairs: ICurrency[] = [];
  availableTradingPairsObservable: Observable<ICurrency[]>;
  availableRaiting: IKnokRaiting[] = [];
  availableLoseStatus: IKnokLosing[] = [];
  // filter: IFilter = null;
  accounts: IUserAccounts = null;

  knoksersIFollow: boolean = false;
  containsTA: boolean = false;
  containsFA: boolean = false;
  price: number[] = [1, 40];

  knoks: number = 0;
  currentProfit: number = 0;
  totalPotentialProfit: number = 0;

  published: boolean = false;
  gaining: boolean = false;
  losing: boolean = false;
  knokser: string;

  sort: number = 1;

  userIsKnokser: boolean = false;

  set ongoingStatus(value: any) {
    this._ongoingStatus = value;

    if (this._ongoingStatus === 0 || this._ongoingStatus === false) {
      this.losing = true;
      this.gaining = false;
    } else if (this._ongoingStatus === 1 || this._ongoingStatus === true) {
      this.losing = false;
      this.gaining = true;
    } else if (this._ongoingStatus === -1 || this._ongoingStatus === null) {
      this.losing = false;
      this.gaining = false;
    }

    let losingControl: FormControl = this.form.get('losing') as FormControl;
    if (losingControl) {
      losingControl.setValue(this.losing);
    }

    let gainingControl: FormControl = this.form.get('gaining') as FormControl;
    if (gainingControl) {
      gainingControl.setValue(this.gaining);
    }
  }

  get ongoingStatus(): any {
    return this._ongoingStatus;
  }

  sliderPriceConfig: object = {
    orientation: 'horizontal',
    connect: true,
    tooltips: true,
    step: 1,
    range: {
      min: 0,
      max: 40
    }
  };

  /* Mixins */
  public log: (...args: any[]) => void;

  protected applyTimeout: any = null;
  protected lang: string;

  private _ongoingStatus: any = null;
  private loadUserSubscription: Subscription;
  private exchangeSubscription: Subscription;
  private currencySubscription: Subscription;
  private filterSubscription: Subscription;
  private filterRefreshSubscription: Subscription;
  private filterSortSubscription: Subscription;
  private userIsKnokserSubscription: Subscription;
  private filterTypeSubscription: Subscription;

  constructor(private changeDetector: ChangeDetectorRef,
              private element: ElementRef,
              private route: ActivatedRoute,
              private userService: UserService,
              private tokenService: TokenService,
              private exchangeService: ExchangeService,
              private currencyService: CurrencyService,
              private filterService: FilterService,
              private fb: FormBuilder) {
  }

  ngOnInit(): void {
    const langParamName: string = 'lang';
    this.lang = this.route.snapshot.data[langParamName];

    if (this.userService) {
      const isTestPropertyName: string = 'test';
      this.userService.testParam = this.route.snapshot.data[isTestPropertyName];
    }

    if (this.filterService) {
      this.filterTypeSubscription = this.filterService.filterTypeObservable.subscribe((filterType: string) => {
        this.filterType = filterType;
        if (this.isDiscoverKnoks) {
          this.filterSubscription = this.filterService.filterSettings.subscribe((filter) => {
            // this.filter = filter;
            this.availableRaiting = [];

            if (_has(filter, 'knokserJedi')) {
              this.availableRaiting.push({id: 4, name: 'Jedi', count: filter.knokserJedi});
            }

            if (_has(filter, 'knokserMaster')) {
              this.availableRaiting.push({id: 3, name: 'Master', count: filter.knokserMaster});
            }

            if (_has(filter, 'knokserAdept')) {
              this.availableRaiting.push({id: 2, name: 'Knight', count: filter.knokserAdept});
            }

            if (_has(filter, 'knokserRanger')) {
              this.availableRaiting.push({id: 1, name: 'Ranger', count: filter.knokserRanger});
            }

            if (_has(filter, 'knokserPadawan')) {
              this.availableRaiting.push({id: 0, name: 'Padawan', count: filter.knokserPadawan});
            }
          });
        }
        if (this.filterType === 'ended') {
          this.filterSubscription = this.filterService.filterEndedKnoksSettings.subscribe((filter) => {
            this.availableLoseStatus = [];

            if (_has(filter, 'exitPriceMissed')) {
              this.availableLoseStatus.push({id: 1, name: 'Exit price missed', count: filter.exitPriceMissed});
            }

            if (_has(filter, 'stopLossReached')) {
              this.availableLoseStatus.push({id: 2, name: 'Stop loss reached', count: filter.stopLossReached});
            }

            if (_has(filter, 'closedByKnokser')) {
              this.availableLoseStatus.push({id: 3, name: 'Closed by knokser', count: filter.closedByKnokser});
            }
          });
        }
        if (this.tokenService && this.userService && this.tokenService.isLogin) {
          this.loadUserSubscription = this.userService.loadUser().subscribe(accounts => {
            this.accounts = accounts;
            this.initAvailableExchanges();
            this.initAvailableTradingPairs();
          });
        }
      });

      this.filterRefreshSubscription = this.filterService.feedArgsRefreshObservable.subscribe((reset: boolean) => {
        if (reset) {
          this.reset();
        } else {
          this.apply();
        }
      });

      this.userIsKnokserSubscription = this.filterService.userIsKnokserObservable.subscribe((value: boolean) => {
        this.userIsKnokser = Boolean(value);
      });

      this.filterSortSubscription = this.filterService.sortObservable.subscribe((sort: number) => {
        this.sort = sort;
        this.apply();
      });
    }

    this.createForm();
  }

  ngAfterViewInit(): void {
    // todo: create auto init directive and clean it
    MDC.autoInit(this.element.nativeElement);
  }

  ngOnDestroy(): void {
    if (this.loadUserSubscription) {
      this.loadUserSubscription.unsubscribe();
    }

    if (this.exchangeSubscription) {
      this.exchangeSubscription.unsubscribe();
    }

    if (this.currencySubscription) {
      this.currencySubscription.unsubscribe();
    }

    if (this.filterSubscription) {
      this.filterSubscription.unsubscribe();
    }

    if (this.filterRefreshSubscription) {
      this.filterRefreshSubscription.unsubscribe();
    }

    if (this.filterSortSubscription) {
      this.filterSortSubscription.unsubscribe();
    }
    if (this.filterTypeSubscription) {
      this.filterTypeSubscription.unsubscribe();
    }
    if (this.userIsKnokserSubscription) {
      this.userIsKnokserSubscription.unsubscribe();
    }
  }

  apply(): void {
    if (this.applyTimeout) {
      clearTimeout(this.applyTimeout);
    }

    this.applyTimeout = setTimeout((): void => {
      this.applyFilterArgs();
      clearTimeout(this.applyTimeout);
    }, 1000);
  }

  reset(): void {
    if (this.applyTimeout) {
      clearTimeout(this.applyTimeout);
    }
    this.resetFilterArgs();
  }

  isChecked(group: string, value: string): boolean {
    let fa: FormArray;
    switch (group) {
      case 'raiting':
        fa = this.form.get('raiting') as FormArray;
        break;
      case 'trading':
        fa = this.form.get('trading') as FormArray;
        break;
      case 'exchanges':
        fa = this.form.get('exchanges') as FormArray;
        break;
      case 'loseStatus':
        fa = this.form.get('loseStatus') as FormArray;
        break;
      default:
        fa = null;
        break;
    }

    if (fa) {
      const index: number = _findIndex(fa.value, (v: string): boolean => {
        return String(v) === String(value);
      });
      return index !== -1;
    }
    return false;
  }

  doSearch(value: string): void {
    let fc: FormControl = this.form.get('knokser') as FormControl;
    if (fc) {
      fc.setValue(value);
    }
  }

  onCheckChange(event: Event, group: string): void {
    const target: HTMLInputElement | null = event.target as HTMLInputElement | null;
    let fa: FormArray;
    switch (group) {
      case 'raiting':
        fa = this.form.get('raiting') as FormArray;
        break;
      case 'trading':
        fa = this.form.get('trading') as FormArray;
        break;
      case 'exchanges':
        fa = this.form.get('exchanges') as FormArray;
        break;
      case 'loseStatus':
        fa = this.form.get('loseStatus') as FormArray;
        break;
      default:
        fa = null;
        break;
    }

    if (target && fa) {
      const checked: boolean = target.checked;
      const value: string = target.value as string;
      const index: number = _findIndex(fa.value, (v: string): boolean => {
        return String(v) === String(value);
      });

      if (checked && index === -1) {
        fa.push(new FormControl(value));
      } else if (!checked && index !== -1) {
        fa.removeAt(index);
      }
    }
  }

  checkboxDelegate(event: Event): void {
    const target: HTMLElement | null = event.target as HTMLElement | null;
    const checkbox: HTMLInputElement | null = target.querySelector('input[type="checkbox"], input[type="radio"]') as HTMLInputElement | null;
    if (target && checkbox) {
      let change: Event = new Event('change', {bubbles: false});
      checkbox.checked = !checkbox.checked;
      checkbox.dispatchEvent(change);
    }
  }

  onToggleChange(event: CustomEvent, control: string): void {
    const target: HTMLElement | null = event.target as HTMLElement | null;
    const detail: any | null = <CustomEvent>event.detail as any | null;
    let fc: FormControl;
    switch (control) {
      case 'follow':
        fc = this.form.get('follow') as FormControl;
        break;
      case 'containsTA':
        fc = this.form.get('containsTA') as FormControl;
        break;
      case 'containsFA':
        fc = this.form.get('containsFA') as FormControl;
        break;
      case 'published':
        fc = this.form.get('published') as FormControl;
        break;
      case 'gaining':
        fc = this.form.get('gaining') as FormControl;
        break;
      case 'losing':
        fc = this.form.get('losing') as FormControl;
        break;
      default:
        fc = null;
        break;
    }

    if (target && fc) {
      const enabled: boolean = !!detail.isOn || false;
      fc.setValue(enabled);
    }
  }

  onSliderModelChange(range: number[], group: string): void {
    let fg: FormGroup;
    switch (group) {
      case 'price':
        fg = this.form.get('price') as FormGroup;
        break;
      default:
        fg = null;
        break;
    }

    if (fg) {
      if (range.length === 2) {
        if (fg.contains('min') && fg.contains('max')) {
          fg.controls.min.setValue(range[0]);
          fg.controls.max.setValue(range[1]);
        }
      }
    }
  }

  trackByFn(index: any, item: any): any {
    return index;
  }

  booleanConverter(some: any): boolean {
    try {
      return !!some;
    } catch (e) {
      return false;
    }
  }

  get exchanges(): IExchange[] {
    return this.availableExchanges;
  }

  get isEndedKnoks(): boolean {
    return this.filterType === 'ended';
  }

  get isOngoingKnoks(): boolean {
    return this.filterType === 'track';
  }

  get isDiscoverKnoks(): boolean {
    return this.filterType === 'discover';
  }

  private createForm(): void {
    this.form = this.fb.group({
      raiting: this.fb.array([]),
      trading: this.fb.array([]),
      exchanges: this.fb.array([]),
      loseStatus: this.fb.array([]),
      follow: this.fb.control(this.knoksersIFollow),
      containsTA: this.fb.control(this.containsTA),
      containsFA: this.fb.control(this.containsFA),
      published: this.fb.control(this.published),
      gaining: this.fb.control(this.gaining),
      losing: this.fb.control(this.losing),
      price: this.fb.group({
        min: this.fb.control(this.price[0]),
        max: this.fb.control(this.price[1])
      }),
      knokser: this.fb.control(this.knokser)
    });

    this.form.valueChanges.distinctUntilChanged().debounceTime(500).subscribe((value: any): void => {
      if (this.form.valid && this.form.enabled) {
        this.apply();
      }
    });

    /*const checked: HTMLInputElement[] | null = this.element.nativeElement.querySelectorAll('input[type="checkbox"], input[type="radio"]') as HTMLInputElement[] | null;
    if (checked && checked.length) {
      checked.forEach((node): void => {
        node.checked = this.isChecked(node.name, node.value);
      });
    }*/
  }

  private initAvailableExchanges(): void {
    if (this.exchangeService) {
      const userId: number = (_has(this.accounts, ['user', 'userId'])) ? this.accounts.user.userId : 0;
      this.exchangeSubscription = this.exchangeService.getExchanges(this.isDiscoverKnoks, this.isOngoingKnoks, this.isEndedKnoks, userId).subscribe((exchanges) => {
        this.availableExchanges = (_has(exchanges, 'items')) ? exchanges.items : [];
        this.changeDetector.detectChanges();
      });
    }
  }

  private initAvailableTradingPairs(): void {
    if (this.currencyService) {
      const userId: number = (_has(this.accounts, ['user', 'userId'])) ? this.accounts.user.userId : 0;
      this.currencySubscription = this.currencyService.getFilterCurrencies(this.isDiscoverKnoks, this.isOngoingKnoks, this.isEndedKnoks, userId).subscribe((currencies) => {
        this.availableTradingPairs = (_has(currencies, 'items')) ? currencies.items : [];
        this.availableTradingPairsObservable = of(this.availableTradingPairs).pipe(delay(10));
        this.changeDetector.detectChanges();
      });
    }
  }

  private applyFilterArgs(): void {
    const fields: any = (this.form && this.form.value) || null;
    if (this.isDiscoverKnoks) {
      const args: IKnokFeedArgs = {
        sortOrderAsc: false,
        sortField: this.sort, // (_has(fields, 'sort')) ? fields.sort : _head(this.availableDiscoverSort).id,
        raitings: (_has(fields, 'raiting') && fields.raiting.length) ? fields.raiting : null,
        knoksersIFollow: (_has(fields, 'follow')) ? !!fields.follow : false,
        containsTA: (_has(fields, 'containsTA')) ? !!fields.containsTA : false,
        containsFA: (_has(fields, 'containsFA')) ? !!fields.containsFA : false,
        knokPriceMin: null, // (_has(fields, ['price', 'min'])) ? fields.price.min : null,
        knokPriceMax: null, // (_has(fields, ['price', 'max'])) ? fields.price.max : null,
        exchangesIds: (_has(fields, 'exchanges') && fields.exchanges.length) ? fields.exchanges : null,
        tradingPairs: (_has(fields, 'trading') && fields.trading.length) ? fields.trading : null
      };
      this.filterService.feedArgs = args;
    } else if (this.filterType === 'track' || this.filterType === 'ended') {
      const args: IKnokOngoingArgs = {
        sortOrderAsc: false,
        sortField: this.sort, // (_has(fields, 'sort')) ? fields.sort : _head(this.availableTrackSort).id,
        exchangesIds: (_has(fields, 'exchanges') && fields.exchanges.length) ? fields.exchanges : null,
        tradingPairs: (_has(fields, 'trading') && fields.trading.length) ? fields.trading : null,
        published: (_has(fields, 'published')) ? !!fields.published : false,
        gaining: (_has(fields, 'gaining')) ? !!fields.gaining : false,
        losing: (_has(fields, 'losing')) ? !!fields.losing : false,
        loseStatus: (_has(fields, 'loseStatus') && fields.loseStatus.length) ? fields.loseStatus : null,
        knokser: (_has(fields, 'knokser')) ? fields.knokser : null
       };
      this.filterService.feedArgs = args;
    }
  }

  private resetFilterArgs(): void {
    if (this.isDiscoverKnoks) {
      this.knoksersIFollow = false;
      this.containsTA = false;
      this.containsFA = false;
      this.price = [1, 40];
      this.createForm();

      const args: IKnokFeedArgs = {
        sortOrderAsc: false,
        sortField: 1, // _head(this.availableDiscoverSort).id,
        raitings: null, // [_head(this.availableRaiting).id]
        knoksersIFollow: false,
        containsTA: false,
        containsFA: false,
        knokPriceMax: null,
        knokPriceMin: null,
        exchangesIds: null,
        tradingPairs: null
      };
      this.filterService.feedArgs = args;
    } else if (this.filterType === 'track' || this.filterType === 'ended') {
      this.published = false;
      this.gaining = false;
      this.losing = false;
      this.createForm();

      const args: IKnokOngoingArgs = {
        sortOrderAsc: false,
        sortField: 1, // _head(this.availableTrackSort).id,
        published: false,
        gaining: false,
        losing: false,
        exchangesIds: null,
        tradingPairs: null,
        loseStatus: null,
        knokser: null
      };
      this.filterService.feedArgs = args;
    }
    this.filterService.sort = 1;
  }
}

applyMixins(KnkSearchFilterComponent, [Log]);
