import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output, } from '@angular/core';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { concat, Observable, of, Subscription } from 'rxjs';
import { filter, map, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
import { LocalStorage } from 'ngx-webstorage';
import { BigNumber, bigNumberify } from 'ethers/utils';
import { GasPriceApiService } from '../services/gas-price.api/gas-price.api.service';
import { bnToNumberSafe, zeroValueBN } from '../utils';
import { GasPriceBN } from '../services/gas-price.api/gas-price.api.dto';

export type TxSpeed = 'normal' | 'fast' | 'instant' | 'custom';
export type GasPriceChangeDto = {
  gasPriceBN: BigNumber;
  gasPrice: string;
  txSpeed: TxSpeed
};

type GasPrice = {
  normal: number,
  fast: number,
  instant: number,
  normalBN: BigNumber,
  fastBN: BigNumber,
  instantBN: BigNumber
}

@Component({
  // tslint:disable-next-line:component-selector
  selector: 'oi-gas-settings',
  templateUrl: './gas-settings.component.html',
  styleUrls: ['./gas-settings.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class GasSettingsComponent implements OnInit, OnDestroy {

  private subscription = new Subscription();

  @LocalStorage('txSpeed', 'fast')
  txSpeed;

  @LocalStorage('customGasPrice', '')
  customGasPrice;

  public form = new FormGroup({
    txSpeedSelect: new FormControl(this.txSpeed),
    gasPriceInput: new FormControl(this.customGasPrice, [
      Validators.pattern('^[0-9.]*$'),
      Validators.min(1),
      Validators.required
    ])
  });

  gasPrice$: Observable<string>;

  get gasPriceInput(): AbstractControl {
    return this.form.controls.gasPriceInput;
  }

  get txSpeedSelect(): AbstractControl {
    return this.form.controls.txSpeedSelect;
  }

  private gasPriceValues: GasPrice = {
    normal: 0,
    fast: 0,
    instant: 0,
    normalBN: zeroValueBN,
    fastBN: zeroValueBN,
    instantBN: zeroValueBN
  };

  getGasPrice(txSpeed: TxSpeed): [BigNumber, string] {
    return [
      this.gasPriceValues[txSpeed + 'BN'],
      this.gasPriceValues[txSpeed],
    ];
  }

  // Be aware, it's not actual double way binding at the moment.
  // Because component read value only once at initTime
  // That could be improved later on once we have demand fo this

  @Output()
  gasPriceChange = new EventEmitter<GasPriceChangeDto>();

  selectTxSpeed(txSpeed: TxSpeed) {

    this.txSpeed = txSpeed;
    this.form.setValue({
      txSpeedSelect: txSpeed,
      gasPriceInput: this.customGasPrice
    });
  }

  constructor(private gasPriceApiService: GasPriceApiService) {
    this.setValues(this.gasPriceApiService.gasPrice.value);
    const gasChangesListener$ = this.gasPriceApiService.gasPrice.pipe(
      tap((gasPrice: GasPriceBN) => this.setValues(gasPrice))
    );
    this.subscription.add(gasChangesListener$.subscribe());
  }

  ngOnInit(): void {

    this.gasPrice$ = this.txSpeedSelect.valueChanges.pipe(
      switchMap((txSpeed: TxSpeed) => {
        console.log(`txSpeed=`, txSpeed);
        if (txSpeed !== 'custom') {
          return of(this.getGasPrice(txSpeed));
        }

        return this.gasPriceInput.valueChanges.pipe(
            startWith(this.gasPriceInput.value),
            filter(() => !this.gasPriceInput.errors),
            map((value) => {
              this.customGasPrice = value;
              return [formatGasPrice(value), value];
            })
          );

      }),
      map(([gasPriceBN, gasPrice]) => {
        this.gasPriceChange.next({
          gasPriceBN,
          gasPrice,
          txSpeed: this.txSpeedSelect.value
        });
        return gasPrice;
      }),
      shareReplay({ bufferSize: 1, refCount: true })
    );

    this.subscription.add(
      this.gasPrice$.subscribe()
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

  private setValues(gasPrice: GasPriceBN): void {

    this.gasPriceValues.normalBN = gasPrice.standard;
    this.gasPriceValues.fastBN = gasPrice.fast;
    this.gasPriceValues.instantBN = gasPrice.instant;

    this.gasPriceValues.normal = parseGasPrice(gasPrice.standard);
    this.gasPriceValues.fast = parseGasPrice(gasPrice.fast);
    this.gasPriceValues.instant = parseGasPrice(gasPrice.instant);

    this.txSpeedSelect.setValue(this.txSpeed);
  }
}

function parseGasPrice(gasPrice: BigNumber): number {
  return Math.round(bnToNumberSafe(gasPrice) / 1e9);
}

function formatGasPrice(gasPrice: string): BigNumber {
  return bigNumberify(+gasPrice * 100 * 1e9 / 100);
}