import { action, computed, makeObservable, observable } from "mobx";
import { audioLog } from "./AudioLog";

const BRIGHT_RED = "#ff0000";
const LIME_GREEN = "#45fc1e";
const DEFAULT_WAVE_COLOR = BRIGHT_RED;
const FFTSIZE = 2048;

export default class Visualizer {
  barWidth: number;
  canvas?: HTMLCanvasElement;
  height: number;
  on: boolean;
  width: number;

  constructor(canvas?: HTMLCanvasElement) {
    this.canvas = canvas;
    this.width = canvas !== null ? (canvas ? canvas.width : 0) : 0;
    this.height = canvas !== null ? (canvas ? canvas.height : 0) : 0;
    this.barWidth = 0;
    this.on = false;

    makeObservable(this, {
      barWidth: observable,
      currentBarWidth: computed,
      height: observable,
      on: observable,
      turnOff: action,
      visualizeFreqBar: action,
      visualizeOsciloscope: action,
      visualizeAudioMeter: action,
      width: observable,
      turnOn: action,
    });
  }

  get currentBarWidth() {
    return this.barWidth;
  }

  get visualizer() {
    return this.canvas;
  }

  setCanvas = (canvas: HTMLCanvasElement) => {
    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
  };

  visualizeFreqBar = (
    analyzerNode: AnalyserNode,
    fftsize?: number,
    color?: string,
  ) => {
    this.turnOn();
    const barColor = color || LIME_GREEN;
    analyzerNode.fftSize = fftsize || FFTSIZE;
    const draw = () => {
      if (this.on === true && this.canvas) {
        requestAnimationFrame(draw);
        const bufferLength = analyzerNode.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);
        analyzerNode.getByteFrequencyData(dataArray);
        this.barWidth = this.width / bufferLength;

        // console.log("canvas", this.barWidth);
        const canvasContext = this.canvas.getContext("2d");
        if (canvasContext) {
          canvasContext.clearRect(0, 0, this.width, this.height);
          dataArray.forEach((item, index) => {
            const y = ((item / 255) * this.height) / 2;
            const x = this.barWidth * index;
            canvasContext.fillStyle = barColor;
            canvasContext.fillRect(
              x,
              this.height - y,
              this.barWidth,
              y,
            );
          });
        } else {
          console.warn(
            "No canvas context available to draw visualizer",
          );
        }
      } else {
        console.warn(
          "No canvas element available to draw visualizer",
        );
      }
    };

    draw();
  };

  visualizeAudioMeter = (
    analyzerNode: AnalyserNode,
    color?: string,
  ) => {
    this.turnOn();
    analyzerNode.fftSize = FFTSIZE;
    let meterColor = color || LIME_GREEN;
    const maxStep = this.width;
    if (this.canvas) {
      const ctx = this.canvas.getContext("2d");
      if (ctx !== null) {
        const draw = () => {
          if (this.on === true) {
            requestAnimationFrame(draw);
            ctx.clearRect(0, 0, this.width, this.height);
            const bufferLength = analyzerNode.frequencyBinCount;
            const data = new Uint8Array(bufferLength);
            analyzerNode.getByteFrequencyData(data);
            const step = Math.ceil(data.length / this.width);

            const amp = this.height / 2;

            for (let i = 0; i < this.width; i++) {
              let neg = 0;
              let pos = 0;
              let max = Math.min(step, maxStep);
              for (let j = 0; j < max; j++) {
                const val = data[i * step + j];
                if (val < 0) {
                  neg += val;
                } else {
                  pos += val;
                }
              }
              neg = neg / max;
              pos = pos / max;
              const displacementX = i;
              const height = amp * (pos - neg);
              const amplitude = amp - pos * amp;
              const barWidth = 1;
              if (displacementX < 150) {
                ctx.fillStyle = "green";
              } else {
                ctx.fillStyle = meterColor;
              }

              ctx.fillRect(
                displacementX,
                amplitude,
                barWidth,
                height,
              );
            }
          } else {
            console.warn(
              "No canvas context available to draw visualizer",
            );
          }
        };

        draw();
      }
    }
  };

  visualizeWinamp = (analyser: AnalyserNode) => {
    this.turnOn();
    analyser.minDecibels = -90;
    analyser.maxDecibels = -10;

    analyser.fftSize = 256;
    var bufferLength = analyser.frequencyBinCount;

    var dataArray = new Uint8Array(bufferLength);
    if (this.canvas) {
      const ctx = this.canvas.getContext("2d");
      if (ctx) {
        ctx.clearRect(0, 0, this.width, this.height);

        const draw = () => {
          requestAnimationFrame(draw);
          if (this.on) {
            analyser.getByteFrequencyData(dataArray);

            ctx.fillStyle = "rgb(0, 0, 0)";
            ctx.fillRect(0, 0, this.width, this.height);

            var barWidth = (this.width / bufferLength) * 2.5 - 1;
            var barHeight;
            var x = 0;

            for (var i = 0; i < bufferLength; i++) {
              barHeight = dataArray[i];

              ctx.fillStyle = "rgb(" + (barHeight + 100) + ",50,50)";
              ctx.fillRect(
                x,
                this.height - barHeight / 2,
                barWidth,
                barHeight / 2,
              );

              x += barWidth;
            }
          }
        };
        draw();
      }
    }
  };
  visualizeOsciloscope = (
    analyzerNode: AnalyserNode,
    color?: string,
  ) => {
    this.turnOn();
    let waveColor: string = color || DEFAULT_WAVE_COLOR;
    analyzerNode.fftSize = FFTSIZE;
    var bufferLength = analyzerNode.frequencyBinCount;
    var dataArray = new Uint8Array(bufferLength);
    analyzerNode.getByteTimeDomainData(dataArray);
    // We now have the audio data for that moment in time captured in our array,
    // and can proceed to visualize it however we like,
    if (this.canvas) {
      const ctx = this.canvas.getContext("2d");

      if (ctx !== null) {
        ctx.clearRect(0, 0, this.width, this.height);

        const draw = () => {
          if (this.on === true) {
            requestAnimationFrame(draw);
            analyzerNode.getByteTimeDomainData(dataArray);
            ctx.fillStyle = "rgb(1, 1, 1)";
            ctx.fillRect(0, 0, this.width, this.height);
            ctx.lineWidth = 2;
            ctx.strokeStyle = `rgb(${hexToRGB(waveColor)})`;
            ctx.beginPath();

            let sliceWidth = (this.width * 1.0) / bufferLength;
            let x = 0;

            for (let i = 0; i < bufferLength; i++) {
              let v = dataArray[i] / 128.0;
              let y = (v * this.height) / 2;

              if (i === 0) {
                ctx.moveTo(x, y);
              } else {
                ctx.lineTo(x, y);
              }

              x += sliceWidth;
            }
            ctx.lineTo(this.width, this.height / 2);
            ctx.stroke();
          }
        };
        draw();
      }
    }
  };

  turnOn = () => {
    this.on = true;
    audioLog.logVizualizer("On");
  };

  turnOff = () => {
    this.on = false;
    audioLog.logVizualizer("Off");
  };
}

export const hexToRGB = (hex: string): string => {
  if (typeof hex !== "string") {
    throw new TypeError("Expected a string");
  }
  let hexColor = hex.replace(/^#/, "");
  if (hexColor.length === 3) {
    hexColor = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
  }
  const num = parseInt(hexColor, 16);
  return `${num >> 16}, ${(num >> 8) & 255}, ${num & 255}`;
};
