import { useRef, useState, useCallback, useMemo, createContext } from 'react';
import audioContext from '../audio/utils/audioContext';
import { ProjectState } from '../types';
import getBufferListLength from '../audio/audioFunctions/getBufferListLength';
import concatBuffers from '../audio/audioFunctions/concatBuffers';

const useProjectPlayer = (state: ProjectState) => {
  const fastGetCurrentTimeRef = useRef<() => number>(() => 0);
  const [paused, setPaused] = useState(true);
  const [lastSeek, setLastSeek] = useState(0);
  const [bufferSourceNodes, setBufferSourceNodes] = useState<AudioBufferSourceNode[]>([]);

  const bufferSourceNodesRef = useRef<AudioBufferSourceNode[]>([]);
  const pausedRef = useRef(true);

  const duration = useMemo(
    () => Math.max(0, ...state.tracks.map(({ audioBuffers }) => getBufferListLength(audioBuffers))),
    [state.tracks]
  );

  const pause = useCallback(
    () => {
      pausedRef.current = true;

      if (paused) {
        return;
      }

      bufferSourceNodesRef.current.forEach((bufferSourceNode) => {
        bufferSourceNode.stop();
      });

      setPaused(true);
      pausedRef.current = true;
      const currentTime = fastGetCurrentTimeRef.current();
      fastGetCurrentTimeRef.current = () => currentTime;
    },
    [setPaused, paused]
  );

  const seek = useCallback(
    (samples: number, userSpecified: boolean = false) => {
      if (!pausedRef.current) {
        setLastSeek(samples);
        return;
      }
      fastGetCurrentTimeRef.current = () => samples;
      if (userSpecified) {
        setLastSeek(samples);
      }
    },
    [setLastSeek]
  );

  const analysers = useMemo(() => {
    const output = [];
    for (let i = 0; i < state.tracks.length; i ++) {
      const analyser = audioContext.createAnalyser()
      analyser.fftSize = 4096;
      output.push(analyser);
    }
    return output;
  }, [state.tracks.length]);

  const play = useCallback(
    (startPoint?: number) => {
      if (!paused) {
        return;
      }

      bufferSourceNodesRef.current = [];

      const startPlaybackPoint = startPoint === undefined ? lastSeek : startPoint;

      state.tracks.forEach(({ audioBuffers }, index) => {
        const bufferSourceNode = audioContext.createBufferSource();
        bufferSourceNode.buffer = concatBuffers(audioBuffers);
        bufferSourceNode.connect(analysers[index]);
        analysers[index].connect(audioContext.destination);
        bufferSourceNodesRef.current.push(bufferSourceNode);
        bufferSourceNode.start(0, startPlaybackPoint / state.sampleRate);
      });

      setBufferSourceNodes(bufferSourceNodesRef.current);

      const startTime = Date.now();
      const startCurrentTime = startPlaybackPoint;
      fastGetCurrentTimeRef.current = () => startCurrentTime + (((Date.now() - startTime) / 1000) * state.sampleRate);

      setPaused(false);
      pausedRef.current = false;
    },
    [lastSeek, paused, setPaused, setBufferSourceNodes, state.sampleRate, state.tracks, analysers]
  );

  return { play, pause, seek, lastSeek, paused, fastGetCurrentTimeRef, analysers, duration, bufferSourceNodes };
}

export const PlayerContext = createContext<ReturnType<typeof useProjectPlayer>>(undefined as never);

export default useProjectPlayer;
