import { tools, Decoder, Reader } from './ebml';

const mimeType = 'video/webm\;';
/**
 * based on ts-ebml and support large file，optimize memory usage during repair
 * 
 * @param blob the blob you need to fix
 * @returns the blob that has been fixed
 * 
 */
export async function fixWebmDuration(blob: Blob): Promise<Blob> {
  if (!blob) {
    throw Error('call to fixWebmDuration requires a blob');
  }
  const decoder = new Decoder();
  const reader = new Reader();
  const readstream = blob.stream() as any;
  const readerBlob = readstream.getReader();

  while (true) {
    let { done, value } = await readerBlob.read();
    if (done) {
      reader.stop();
      break;
    }
    let elms = decoder.decode(value);
    // As browser upgrade webm meta attributes are gradually added,  
    // so filter unknown type to bypass this issue.
    elms = elms?.filter(elm => elm.type !== 'unknown')
    elms.forEach(elm => {
      reader.read(elm)
    });
    value = null;
  }
  const refinedMetadataBuf = tools.makeMetadataSeekable(reader.metadatas, reader.duration, reader.cues);
  const refinedMetadataBlob = new Blob([refinedMetadataBuf], { type: mimeType });
  const firstPartBlobWithoutMetadata = blob.slice(reader.metadataSize);
  const finalBlob = new Blob([refinedMetadataBlob, firstPartBlobWithoutMetadata], { type: mimeType });
  
  return finalBlob;
}

interface Callback {
  (size : number , blobWithHeader : Blob) : void;
}

export class FixWebmProcess {

  private _processBlob : Blob;
  private _nextBlob : Blob[];
  private _end : boolean;
  private _decoder : Decoder;
  private _reader : Reader;
  private _finish : boolean;
  private _callback : Callback;
  private _enableLog : boolean;

  constructor() {
    this._end = true;
    this._decoder = new Decoder();
    this._reader = new Reader();
    this._processBlob = new Blob();
    this._nextBlob = [];
    this._finish = false;
    this._callback = (size : number , blobWithHeader : Blob) => {};
    this._enableLog = true;
    // this._MyBlob = new Blob();
  }

  public processBlob(blob : Blob) : void {
    if (this._end) {
      this._processBlob = new Blob([blob], {type : mimeType});
      this._end = false;
      this.fixWebmDuration()
    } else {
      this._nextBlob[this._nextBlob.length] = blob;
    }
  }

  private async fixWebmDuration() : Promise<void> {
    if (!this._processBlob) {
      throw Error('call to fixWebmDuration requires a blob');
    }

    let readstream = this._processBlob.stream() as any;
    let readerBlob = readstream.getReader();
  
    while (true) {
      let { done, value } = await readerBlob.read();
      if (done) {
        if (this._nextBlob.length == 0) {
          this._end = true;
          if (this._finish) {
            this.LOG('<FixWebmProcess> finish from fixWebmDuration')
            this.finish(this._callback);
          }
          break;
        } else {
          this._processBlob = this._nextBlob.shift() as Blob;
          readstream = this._processBlob.stream() as any;
          readerBlob = readstream.getReader();
          continue;
        }
      }
      let elms = this._decoder.decode(value);
      // As browser upgrade webm meta attributes are gradually added,  
      // so filter unknown type to bypass this issue.
      elms = elms?.filter(elm => elm.type !== 'unknown')
      elms.forEach(elm => {
        this._reader.read(elm)
      });
      value = null;
    }
  }

  private LOG(log : String) {
    if (this._enableLog) {
      console.log(log);
    }
  }

  public finish(callback : Callback) {
    this._finish = true;
    this._callback = callback;
    this.LOG('<FixWebmProcess> call finish')
    if (this._end) {
      this.LOG('<FixWebmProcess> finish from finish function')
      this._reader.stop();
      this.fixBlob();
    }
  }

  public setEnableLog(enable : boolean) {
    this._enableLog = enable;
  }

  private fixBlob() {
    const refinedMetadataBuf = tools.makeMetadataSeekable(this._reader.metadatas, this._reader.duration, this._reader.cues);
    const refinedMetadataBlob = new Blob([refinedMetadataBuf], { type: mimeType });
    this._callback(this._reader.metadataSize, refinedMetadataBlob);
  }
  
}