import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { HttpErrorResponse } from '@angular/common/http';
import { Subscription, Subject, interval } from 'rxjs';
import { startWith, take, takeUntil, switchMap } from 'rxjs/operators';
import { HttpService } from './services/http.service';
import { PowerTunerToken } from './models/power-tuner-token';
import { TunerPayload, defaultTunerPayload } from './models/tuner-payload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  private readonly trackedSubscriptions: Subscription[] = [];

  startModel = defaultTunerPayload();
  formGroup: FormGroup;
  resultsBack: boolean;
  results = null;
  resultsProcessing: boolean;
  resultsError: boolean;
  executionToken: string;
  visualisationUrl: SafeResourceUrl;
  validationTriggered = false;

  operationTypes = [
    'New Tuner',
    'Existing execution ID'
  ];

  strategies = [
    {
      label: 'Balanced',
      value: 'balanced'
    },
    {
      label: 'Cost',
      value: 'cost'
    },
    {
      label: 'Speed',
      value: 'speed'
    }
  ];

  powerValues = this.getPowerValues();

  get operationType(): string {
    return this.formGroup.controls.operationType.value;
  }

  get hasVisualisation(): boolean {
    return this.resultsBack && this.results && this.results.stateMachine && this.results.stateMachine.visualization;
  }

  get formDisabled(): boolean {
    return ((this.operationType === 'Existing execution ID' && !this.formGroup.controls.executionId.value)
      || (this.operationType === 'New Tuner' && this.formGroup.invalid)) || this.resultsProcessing;
  }

  get getFormLabel(): string {
    return this.operationType === 'New Tuner' ? 'Start power tuner' : 'Fetch tuner result';
  }

  get getVisLabel(): string {
    const strat = this.formGroup.controls.strategy.value;
    let base = `Visualization with ${this.formGroup.controls.strategy.value} strategy`;
    if (strat === 'balanced') {
      base += ` (${this.formGroup.controls.balancedWeight.value})`;
    }

    return base;
  }

  constructor(
    private sanitizer: DomSanitizer,
    private formBuilder: FormBuilder,
    private httpService: HttpService
  ) { }

  ngOnInit() {
    this.setupFormGroup();
  }

  setupFormGroup() {
    const token = localStorage.getItem('token');
    this.startModel.executionId = token ? token : '',
      this.formGroup = this.formBuilder.group({
        operationType: [this.startModel.operationType],
        lambdaARN: [this.startModel.lambdaARN, [Validators.required,
        // tslint:disable-next-line:max-line-length
        Validators.pattern('arn:(aws[a-zA-Z-]*)?:lambda:[a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-\\d{1}:\\d{12}:function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?$')]],
        strategy: [this.startModel.strategy],
        powerValues: [this.startModel.powerValues, [Validators.required]],
        balancedWeight: [this.startModel.balancedWeight, [Validators.required]],
        num: [this.startModel.num, [Validators.required]],
        payload: [this.startModel.payload],
        includePayload: [false],
        useCustom: [this.startModel.useCustom],
        executionId: [this.startModel.executionId],
        parallelInvocation: [this.startModel.parallelInvocation]
      });

    this.formGroup.controls.strategy.valueChanges.subscribe(value => {
      if (value === 'speed') {
        this.formGroup.controls.balancedWeight.setValue(1);
      } else if (value === 'cost') {
        this.formGroup.controls.balancedWeight.setValue(0);
      } else {
        this.formGroup.controls.balancedWeight.setValue(0.5);
      }
    });

    this.formGroup.controls.includePayload.valueChanges.subscribe(value => {
      if (!value) {
        this.formGroup.controls.payload.setValue(`{}`);
      }
    });

    this.formGroup.controls.useCustom.valueChanges.subscribe(value => {
      if (value === 'custom') {
        this.formGroup.controls.powerValues.setValue([]);
      } else if (value === 'default') {
        this.formGroup.controls.powerValues.setValue(['128', '256', '512', '1024', '1536', '3008']);
      } else {
        this.formGroup.controls.powerValues.setValue('ALL');
      }
    });
  }

  getPowerValues() {
    const increment = 64;
    const powerValues = [];
    for (let value = 128; value <= 10240; value += increment) {
      powerValues.push(value);
    }
    return powerValues;
  }

  updateOperationType(type: string) {
    this.formGroup.controls.operationType.setValue(type);
  }

  resetTuning() {
    this.results = null;
    this.resetTuner();
    localStorage.setItem('token', '');
    this.resultsError = false;
    this.startModel.operationType = this.operationType;
    this.setupFormGroup();
  }

  formatValue(duration: number, noDecimals = 0): string {
    return duration.toFixed(noDecimals);
  }

  resetTuner() {
    this.resultsBack = false;
    this.resultsProcessing = false;
  }

  startTuning() {
    this.executionToken = '';
    this.results = null;
    this.resultsError = false;
    this.resultsBack = false;
    this.resultsProcessing = true;
    const form = this.formGroup.getRawValue() as TunerPayload;
    if (this.operationType === 'New Tuner') {
      if (this.formGroup.valid) {
        form.payload = JSON.parse(form.payload);
        this.trackedSubscriptions.push(this.httpService.performPowerTunerStepFunction(form).subscribe(token => {
          this.startPolling(token);
        }, (error) => {
          this.processErrorResults(error, true);
        }));
      }
    } else {
      const token = {
        executionToken: this.formGroup.controls.executionId.value
      } as PowerTunerToken;
      this.startPolling(token);
    }
  }

  startPolling(token: PowerTunerToken) {
    this.executionToken = token.executionToken;
    this.formGroup.controls.executionId.setValue(this.executionToken);
    localStorage.setItem('token', this.executionToken);

    const subject = new Subject<string>();
    let attempt = 0;
    this.trackedSubscriptions.push(interval(5000)
      .pipe(
        startWith(0),
        take(24),
        takeUntil(subject),
        switchMap(() => this.httpService.fetchPowerTunerStepFunction(token))
      )
      .subscribe(
        ratesResult => {
          attempt += 1;
          if (this.checkStatusToEndPolling(ratesResult.status)) {
            if (ratesResult.status === 'SUCCEEDED') {
              this.processResults(JSON.parse(ratesResult.output));
              this.resultsBack = true;
              this.resultsProcessing = false;
            } else {
              this.resultsError = true;
              this.resetTuner();
            }
            subject.next('Finished');
          }
          if (attempt >= 24) {
            subject.next('Finished');
          }
        },
        error => {
          this.processErrorResults(error);
          subject.next('Error');
        }
      ));
  }

  navigateToUrl(url: string) {
    window.open(url, '_blank');
  }

  processResults(results) {
    this.results = results;
    if (results && results.stateMachine && results.stateMachine.visualization) {
      this.visualisationUrl = this.sanitizer.bypassSecurityTrustResourceUrl(results.stateMachine.visualization);
    } else {
      this.resultsError = true;
    }
  }

  checkStatusToEndPolling(status: string) {
    return status === 'SUCCEEDED' || status === 'FAILED' || status === 'CANCELLED' || !status;
  }

  processErrorResults(error: HttpErrorResponse = null, firstCall = false) {
    this.resetTuner();
    if (firstCall) {
      this.executionToken = '';
    }
    this.resultsError = true;
  }

}