import {
  AfterViewInit,
  Component,
  ElementRef, EventEmitter,
  Input,
  Output,
  OnChanges,
  OnDestroy,
  OnInit,
  ViewChild
} from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { AudioDelegate } from './audio-delegate';
import { fromEvent, Subscription } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import { DragElement } from './drag-element';
import { NzMessageService } from 'ng-zorro-antd';

// interface Blob {
//   arrayBuffer: () => Promise<ArrayBuffer>;
// }
const MODE = {
  TEXT: 1,
  IMAGE: 2
};

function arrayBufferToString( buffer, encoding ) {
  return new Promise((resolve, reject) => {
    const blob = new Blob([buffer], { type: 'text/plain' });
    const reader = new FileReader();
    reader.onload = (evt) => {
      resolve(evt.target.result);
    };
    reader.readAsText(blob, encoding);
  });

}

@Component({
  selector: 'app-lrc-editor',
  templateUrl: './lrc-editor.component.html',
  styleUrls: ['./lrc-editor.component.scss']
})
export class LrcEditorComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {

  @ViewChild('waveEl', {static: true }) canvasElRef: ElementRef;
  @ViewChild('timeLineEl', {static: true }) timeLineEl: ElementRef;
  @ViewChild('uploadBtn', {static: true }) uploadBtn: ElementRef;

  lrcFileEncoding = 'GB18030';
  @Output()
  editFinished = new EventEmitter();
  // MODE = MODE;
  @Input()
  LRCData: any = {
      audio_url: null,
      fontSize: 24,
      lineHeight: 32,
      // mode: MODE.TEXT,
      lyrics: []
    };
  timeRangeModeConfObj = {};
  timeRangeObj = {
    min: 0,
    max: 100
  };
  isScaleTimeLine = false;

  timeRangeSelector = [0, 100];
  currentAudioTime = '00:00:00.000';
  currentAudioDuration = '00:00:00.000';
  selectHighlightTimePointIndex = -1;
  currentStep = 0;
  // item = {
  //   audio_url: null,
  //   mode: MODE.TEXT,
  //   lyrics: []
  // };
  audioObj = new AudioDelegate();
  isLoadingAudioBuffer = false;

  private waveWidth: number;
  private waveHeight: number;
  private ctx: CanvasRenderingContext2D;
  private adContext = new AudioContext();
  private timeLineTimer: number;
  private timeLine: any;
  timePointData = [];
  httpErrorTryCount = 0;
  httpErrorTryMax = 3;
  playbackRate = 1;
  // private audioBuffer: AudioBuffer;
  private audioArrayBuffer: ArrayBuffer;
  timePointDataFull: any;
  private currentTimeRangDuration: number;
  private timeRangeEditLines: number[];
  private audioPlayBarPositionSub: Subscription;
  private audioDataLoadedSub: Subscription;
  private audioPlayTimestampSub: Subscription;
  private audioPlayEndSub: Subscription;
  private dragMoveBarSub: Subscription;
  private arrowKeyUpSub: Subscription;
  private loadAudioSub: Subscription;



  constructor(private client: HttpClient, private nzMessageService: NzMessageService) {

  }
  ngOnInit() {
    window['lrc'] = this;
    // console.log('this.LRCData 1', this.LRCData);
    if (!this.LRCData) {
      this.LRCData = {};
    }
    // console.log('this.LRCData 1', this.LRCData);
    this.LRCData = Object.assign( {
      audio_url: null,
      fontSize: 24,
      lineHeight: 32,
      // mode: MODE.TEXT,
      lyrics: []
    }, this.LRCData);
    // window.player = this;
    if (this.audioPlayBarPositionSub) {
      this.audioPlayBarPositionSub.unsubscribe();
    }
    this.audioPlayBarPositionSub = this.audioObj.audioPlayBarPosition.subscribe((evt) => {
      this.calcPlayBarPositionByTime(evt.time);
    });

    if (this.audioDataLoadedSub) {
      this.audioDataLoadedSub.unsubscribe();
    }
    this.audioDataLoadedSub = this.audioObj.audioDataLoaded.subscribe((ab) => {
      console.log('this.audioObj.audioDataLoaded');
      this.timeRangeObj.max = this.audioObj.duration;
      // this.timeRangeSelector[1] = this.audioObj.duration;
      this.timeRangeSelector = [0, this.audioObj.duration];
      this.currentAudioDuration = this.audioObj.durationFormatted;
      if (this.LRCData && this.LRCData.lyrics) {
        this.timePointData = [];
        this.LRCData.lyrics.forEach(timeline => {
          this.setTimestampPoint(timeline);
        });
      }
      this.drawArrayBuffer(ab);

    });

    if (this.audioPlayTimestampSub) {
      this.audioPlayTimestampSub.unsubscribe();
    }
    this.audioPlayTimestampSub = this.audioObj.audioPlayTimestamp.subscribe((evt) => {
      this.currentAudioTime = evt.timeFormat;
      let time = evt.time;
      // if (this.isScaleTimeLine) {
      //   if ( evt.time >= this.timeRangeSelector[0]) {
      //     time = evt.time - this.timeRangeSelector[0];
      //   }
      //   if (evt.time >= this.timeRangeSelector[1]) {
      //     this.audioObj.pause();
      //     this.audioObj.currentTime = evt.time; //  - 5 * 60 / 1000;
      //     this.calcPlayBarPositionByTime(this.timeRangeSelector[0]);
      //   }
      // }
      this.calcPlayBarPositionByTime(time);
    });

    if (this.audioPlayEndSub) {
      this.audioPlayEndSub.unsubscribe();
    }
    this.audioPlayEndSub = this.audioObj.audioPlayEnd.subscribe(() => {
      this.calcPlayBarPositionByTime(0);
      this.selectHighlightTimePointIndex = -1;
    });

    this.uploadBtn.nativeElement.addEventListener('change', () => {
      const lrcFile = this.uploadBtn.nativeElement.files[0];
      if (!lrcFile) {
        this.nzMessageService.error('请正确选择文件');
        return;
      }
      lrcFile.arrayBuffer().then(ab => {
        arrayBufferToString(ab, this.lrcFileEncoding).then((s: string) => {
          this.timePointData.length = 0;
          s.split('[').forEach(l => {
            const p =  l.indexOf(']');
            const t = l.substr(0, p);
            const data = l.substr(p + 1); // .trim();
            if (!isNaN(Number(l[0]))) {
              const time = this.audioObj.convertTagToTime(t);

              this.timePointData.push({
                time,
                timeFormatted: t,
                data,
                position: `translateX(${ this.getXPositionByTime(time)}px)`,
              });
            }
            console.log( t, data);
          });
        });

      });

    });
    if (this.LRCData.audio_url) {
      this.goNextStep2();
    }
  }
  drawArrayBuffer(ab) {
    this.adContext.close();
    this.adContext = new AudioContext();
    this.adContext.decodeAudioData(ab).then(adb => {
      this.draw(adb);
      this.isLoadingAudioBuffer = false;
      (document.activeElement as HTMLButtonElement).blur();
      this.canvasElRef.nativeElement.focus();

    });
  }
  ngOnChanges(value) {
    if (value.LRCData && !value.LRCData.firstChange) {
      // console.log(1111111111)
      this.ngOnInit();
      // this.onAudioUploaded({url: this.LRCData.audio_url});
    }
    // console.log('ngOnChanges', JSON.stringify(value)); // this.ngOnInit();
  }
  ngOnDestroy(): void {
    this.audioPlayBarPositionSub && this.audioPlayBarPositionSub.unsubscribe();
    this. audioDataLoadedSub && this. audioDataLoadedSub.unsubscribe();
    this. audioPlayTimestampSub && this. audioPlayTimestampSub.unsubscribe();
    this. audioPlayEndSub && this. audioPlayEndSub.unsubscribe();
    this. dragMoveBarSub && this. dragMoveBarSub.unsubscribe();
    this. arrowKeyUpSub && this. arrowKeyUpSub.unsubscribe();
    this. loadAudioSub && this. loadAudioSub.unsubscribe();
  }

  ngAfterViewInit() {

    this.timeLine = this.timeLineEl.nativeElement;
    // this.timeLineBar = this.timeLineBarEl.nativeElement;
    this.waveWidth = this.canvasElRef.nativeElement.offsetWidth;
    this.waveHeight = this.canvasElRef.nativeElement.offsetHeight;
    this.ctx = this.canvasElRef.nativeElement.getContext('2d');
    const dpr = window.devicePixelRatio || 1;
    this.canvasElRef.nativeElement.width = this.waveWidth * dpr;
    this.canvasElRef.nativeElement.height = this.waveHeight * dpr;
    this.ctx.scale(dpr, dpr);
    const cb = (e) => {
      if (e.code === 'ArrowUp'
        || e.code === 'ArrowDown'
        || e.code === 'ArrowLeft'
        || e.code === 'ArrowRight') {
        e.preventDefault();
        e.stopPropagation();
        // set time point
      }
    };
    document.removeEventListener('keydown', cb );
    document.addEventListener('keydown', cb );
    if (this.arrowKeyUpSub) {
      this.arrowKeyUpSub.unsubscribe();
    }
    this.arrowKeyUpSub = fromEvent<KeyboardEvent>(document, 'keyup')
      .pipe(throttleTime(100) )
      .subscribe((e) => {
        e.preventDefault();
        e.stopPropagation();
        if (e.code === 'ArrowUp') {
          this.togglePlayAudio();
          this.canvasElRef.nativeElement.focus();
        }
        if (e.code === 'ArrowDown') {

          this.setTimestampPoint();
          this.canvasElRef.nativeElement.focus();
          // set time point
        }
        if (e.code === 'ArrowLeft') {
          if (this.selectHighlightTimePointIndex >= 0) {
            this.updateTimePointData(this.selectHighlightTimePointIndex, -0.05);
          } else {
            this.audioObj.currentTime -= 1;
          }
          this.canvasElRef.nativeElement.focus();

        }
        if (e.code === 'ArrowRight') {
          if (this.selectHighlightTimePointIndex >= 0) {
            this.updateTimePointData(this.selectHighlightTimePointIndex, 0.05);
          } else {
            this.audioObj.currentTime += 1;
          }
          this.canvasElRef.nativeElement.focus();
        }
      });

    this.ctx.fillStyle = '#0F0';
    if (this.dragMoveBarSub) {
      this.dragMoveBarSub.unsubscribe();
    }
    this.dragMoveBarSub = new DragElement(this.timeLine, this.waveWidth).onMove.subscribe(evt => {
      this.selectHighlightTimePointIndex = -1;
      const percent = evt.position / this.waveWidth;
      let baseDur = this.audioObj.duration;
      let startPoint = 0;
      let currentTime = percent * this.audioObj.duration;
      if (this.isScaleTimeLine) {
        baseDur = this.currentTimeRangDuration;
        startPoint = this.timeRangeObj.min;
        currentTime = this.timeRangeObj.min + percent * baseDur;
      }
      this.audioObj.currentTime = currentTime;
      console.log('dd', baseDur, this.audioObj.currentTime  );
      this.currentAudioTime = this.audioObj.currentTimeFormatted();
      this.calcPlayBarPositionByTime(currentTime);
    });

  }
  get isPlaying() {
    return this.audioObj.isPlaying;
  }
  updateTimePointData(index, time) {
    const p = this.timePointData[index];
    if (p) {
      const newTime = p.time + time;
      let posTime = newTime;
      if (this.isScaleTimeLine) {
        posTime = posTime - this.timeRangeObj.min;
      }
      this.audioObj.currentTime = newTime;
      p.time = newTime;
      p.position = this.calcPlayBarPositionByTime(posTime, true);
      p.timeFormatted = this.audioObj.currentTimeFormatted(newTime);
      if (p.time > this.audioObj.duration) {
        p.warn = true;
      }
    }
  }
  getXPositionByTime(time) {
    let base = this.audioObj.duration;
    if (this.currentTimeRangDuration) {
      time = time - this.timeRangeObj.min;
      if (time < 0) {
        time = 0;
      }
      base = this.currentTimeRangDuration;
    }
    return ((time / base) * this.waveWidth) ;
  }
  calcPlayBarPositionByTime(time, returnVal = false) {
    const x = this.getXPositionByTime(time);
    // console.log(x);
    const pos = `translateX(${x}px)`;
    if (returnVal) {
      return pos;
    }
    this.timeLine.style.transform = pos;
  }
  togglePlayAudio(evt?) {
    if (evt) {
      evt.target.blur();
    }
    console.log('togglePlayAudio', evt);
    if (this.isPlaying) {
      this.audioObj.pause();
      clearInterval(this.timeLineTimer);
      return;
    }
    if (this.isScaleTimeLine
      && (this.audioObj.currentTime < this.timeRangeSelector[0]
        || this.audioObj.currentTime >= this.timeRangeSelector[1] )) {
      this.audioObj.currentTime = this.timeRangeSelector[0];
    }
    if (this.selectHighlightTimePointIndex >= 0) {
      const p = this.timePointData[this.selectHighlightTimePointIndex];
      this.audioObj.currentTime = p.time;
    }
    this.audioObj.play().then(() => {
      // this.timeLineTimer = window.setInterval(() => {
        //   if (canSetPoint) {
        //     console.log(audio.currentTime,audioImages[audio.currentTime] )
        //     audioImages.push([audio.currentTime, pointAudio]);
        //     canSetPoint = false;
        //   }
          // this.timeLine.style.width = `${Math.ceil((this.audioObj.currentTime / this.audioObj.duration) * 100) }%`;
      // }, 32);
    });
  }

  setTimestampPoint(timelineData = null, select = false) {
    if (!this.audioObj.currentSrc) {
      return;
    }
    // console.log('setTimestampPoint');
    let pointTime = this.audioObj.currentTime;
    let data = '';
    let newLine = false;
    if (timelineData) {
      pointTime = timelineData.time;
      data = timelineData.data;
      newLine = timelineData.newLine;
    }
    let warn = false;
    if (pointTime > this.audioObj.duration || pointTime < 0) {
      warn = true;
    }

    const tmpAllTime = this.timePointData.map(d => {
      return {...d};
    });

    tmpAllTime.forEach(p => {
      p.timeFormatted = this.audioObj.convertTimeToTag(p.time, true);
      const delta =  Math.abs(p.time - pointTime ) * 1000;
      if (delta < 150) {
        p.warn = true;
      }

    });
    // if (this.isScaleTimeLine) {
    //   pointTime = pointTime - this.timeRangeObj.min;
    // }
    tmpAllTime.push({
      time:  pointTime, // this.audioObj.currentTime,
      timeFormatted: this.audioObj.currentTimeFormatted(pointTime),
      data,
      position: `translateX(${ this.getXPositionByTime(pointTime)}px)`,
      warn,
      newLine
    });
    tmpAllTime.sort((a, b) => {
      return a.time - b.time;
    });
    this.timePointData.length = 0;
    tmpAllTime.forEach(item => {
      this.timePointData.push(item);
    });


    if (select) {
      this.selectHighlightTimePointIndex = this.timePointData.length - 1;
    }
  }
  insertTimePoint(index) {
    const st =  this.timePointData[index].time;
    let et = st + 5;
    const next = this.timePointData[index + 1];
    if (next) {
      et = next.time;
    }
    const time = st + (et - st) / 2;
    // if (this.isScaleTimeLine) {
    //   time = time + this.timeRangeObj.min;
    // }
    this.audioObj.currentTime = time;
    this.setTimestampPoint({
      time,
      data: ''
    });

    // this.timePointData.splice(index + 1, 0, {
    //   time,
    //   timeFormatted: this.audioObj.currentTimeFormatted(time),
    //   data: '',
    //   position: `translateX(${ this.getXPositionByTime(time)}px)`,
    // } );
    this.selectHighlightTimePointIndex = index + 1;
  }
  removeTimePoint(index) {
    this.timePointData.splice(index, 1);
    this.selectHighlightTimePointIndex = -1;
  }
  selectTimePoint(index) {
    this.selectHighlightTimePointIndex = index;
    this.audioObj.currentTime = this.timePointData[index].time;
  }

  onAudioUploaded(evt) {
    // console.log(evt);
    this.LRCData.audio_url = evt.url;
    this.LRCData.lyrics.length = 0;
    this.timePointData.length = 0;
    this.goNextStep2();
  }
  loadingAudioBuffer() {
    this.isLoadingAudioBuffer = true;
    if (this.loadAudioSub) {
      this.loadAudioSub.unsubscribe();
    }
    this.loadAudioSub = this.client.get(this.LRCData.audio_url, {responseType: 'arraybuffer'})
      .subscribe( (ab) => {
        // const blob = new Blob([ab], { type: 'audio/wav' });
        // this.audioObj.src = URL.createObjectURL(blob);
        this.audioArrayBuffer = ab.slice(0);
        this.audioObj.setSource(ab);
        this.audioObj.load();
      }, ( error ) => {
        console.log(error);
        this.isLoadingAudioBuffer = false;
        if (this.httpErrorTryCount >= this.httpErrorTryMax) {
          this.nzMessageService.error('请检查网络，刷新重试');
          return;
        }
        this.httpErrorTryCount += 1;
        this.goNextStep2();
      });
  }
  goNextStep2() {
    this.currentStep = 1;
    this.loadingAudioBuffer();
  }



  filterAudioSamples( adb: AudioBuffer, samples) {
    const data = [];
    const length = adb.length;
    const blockSize = Math.floor(length / samples);
    const rawData = adb.getChannelData(0);
    for (let i = 0; i < samples; i++) {
      const blockStart = i * blockSize;
      let sum = 0;
      for (let j = 0; j < blockSize; j++) {
        sum = sum + Math.abs(rawData[blockStart + j]);
      }

      data.push(sum / blockSize);
    }

    return data;
  }
  normalizeData(data) {
    const base = Math.pow(Math.max(...data), -1);
    return data.map(n => n * base);
  }

  draw(adb) {
    this.ctx.clearRect(0, 0 , this.waveWidth, this.waveHeight);
    const waveData = this.filterAudioSamples(adb, this.waveWidth);
    const normalizeDataForAllAudioBuffer = this.normalizeData(waveData);
    const width = this.waveWidth / normalizeDataForAllAudioBuffer.length;
    normalizeDataForAllAudioBuffer.forEach((d, i) => {
      const x = width * i;
      const height = d * this.waveHeight / 2;
      this.ctx.fillRect(x, this.waveHeight / 2 - height, 1, height * 2);
    });
  }
  saveUserData() {

    this.checkNeedBack();

    const result = [];
    this.timePointData.forEach(p => {
      const t = {...p};
      delete t.position;
      delete t.warn;
      delete t.timeFormatted;
      t.data = t.data;
      result.push(t);
    });
    this.LRCData.lyrics = result;
    this.editFinished.emit(this.LRCData);
   // console.log(this.timePointData);
  }
  checkNeedBack() {
    if (this.isScaleTimeLine) {
      this.restoreTimeLine();
    }
  }
  changePlaybackRate(val) {
    this.audioObj.playbackRate = val;
  }
  onImageUploadSuccess(evt) {

  }
  uploadLRC() {
    this.uploadBtn.nativeElement.click();
  }
  changeLrcFileEncoding(enc) {
    this.lrcFileEncoding = enc;
  }
  formatter = (value: number): string =>  {

    return this.audioObj.convertTimeToTag(value, false);
  }
  timeRangeAfterChange(value) {
    this.timeRangeSelector = value;
  }
  scaleTimeLine() {
    this.audioObj.pause();
    this.selectHighlightTimePointIndex = -1;
    this.isScaleTimeLine = true;
    if (!this.timePointDataFull) {
      this.timePointDataFull = this.timePointData.map(line => {
        return {...line};
      });
    }

    // this.timeRangeModeConfObj.startTime = this.timeRangeSelector[0];
    // this.timeRangeModeConfObj.endTime = this.timeRangeSelector[0];
    const start = this.timeRangeSelector[0];
    const end = this.timeRangeSelector[1];
    const sp = start / this.audioObj.duration;
    const ep = end / this.audioObj.duration;
    const sl = sp * this.audioArrayBuffer.byteLength;
    const el = ep * this.audioArrayBuffer.byteLength;
    this.currentTimeRangDuration = end - start;
    this.timeRangeObj.max = end;
    this.timeRangeObj.min = start;
      this.calcPlayBarPositionByTime(start);
    // this.timeRangeSelector[0] = start;
    // this.timeRangeSelector[1] = end;
    this.timeRangeSelector = [start, end];
    this.timeRangeEditLines = [];
    this.timePointData = this.timePointData.filter( (line, idx) => {
      if (line.time > start && line.time < end) {
        this.timeRangeEditLines.push(idx);
        return true;
      }
      return false;
    }).map( line => {
      line.position = this.calcPlayBarPositionByTime( line.time , true);
      return line;
    });
    const ab = this.audioArrayBuffer.slice(sl, el);
    // const ab = this.audioObj.getBufferClip(s, e);
    this.drawArrayBuffer(ab);
  }
  restoreTimeLine() {
    this.audioObj.pause();
    this.timeRangeObj.max = this.audioObj.duration;
    this.timeRangeObj.min = 0;
    this.timeRangeSelector = [0, this.audioObj.duration];
    // this.timeRangeSelector[1] = this.audioObj.duration;
    this.selectHighlightTimePointIndex = -1;
    this.isScaleTimeLine = false;
    this.currentTimeRangDuration = this.audioObj.duration;
    if (this.timeRangeEditLines.length) {
      const head = this.timePointDataFull.slice(0, Math.min(...this.timeRangeEditLines) );
      const tail = this.timePointDataFull.slice( Math.max(...this.timeRangeEditLines) + 1 );
      // this.timePointData = [...head, ...this.timePointData, ...tail];
      this.timePointData = [...head, ...this.timePointData, ...tail].map(line => {
        return {...line};
      }).sort((a, b) => {
        return a.time - b.time;
      });
    } else {
      this.timePointData = [...this.timePointDataFull, ...this.timePointData];
    }

    this.timePointData = this.timePointData.map(line => {
      line.position = this.calcPlayBarPositionByTime(line.time, true);
      return {...line};
    });
    this.timePointDataFull = null;
    const ab = this.audioArrayBuffer.slice(0);
    // const ab = this.audioObj.getBufferClip(s, e);
    this.drawArrayBuffer(ab);
  }
}
