import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import audioContext from '../audio/utils/audioContext';

export const RECORDING_BUFFER_SIZE = Math.pow(2, 14);

export default (sampleRate: number, receiveBuffer: (buffer: AudioBuffer, firstWrittenSample: number) => void) => {
  const [deviceId, setDeviceId] = useState<string | null>(null);
  const [stream, setStream] = useState<MediaStream | null>(null);
  const afterGetPermissionsRef = useRef(() => {});

  const updateDevices = useCallback(() => {
    console.log('about to enumerate devices')
    navigator.mediaDevices.enumerateDevices().then((devices) => {
      console.log('got devices', devices);
      const audioInputDevices = devices.filter(({ kind }) => kind === 'audioinput');
      if (audioInputDevices[0]) {
        setDeviceId(audioInputDevices[0].deviceId)
      } else {
        console.error('No audio devices available');
      }
    });
  }, [setDeviceId]);

  useEffect(updateDevices, [updateDevices]);

  const stopAfterNextBuffer = useRef(false);

  const startRecording = useCallback((onGetMediaPermission: () => void) => {
    stopAfterNextBuffer.current = false;
    console.log('about to get device id');
    if (!deviceId) {
      console.log('no device id found. need media permission.');
      afterGetPermissionsRef.current = () => startRecording(onGetMediaPermission);
      navigator.mediaDevices.getUserMedia({
        audio: {
          sampleRate,
          echoCancellation: false,
          autoGainControl: false,
          noiseSuppression: false
        }
      }).then((stream) => {
        onGetMediaPermission();
        updateDevices();
        console.log('got media permission');
        setStream(stream);
      }).catch((error) => {
        console.log('did not get media permission', error);
        afterGetPermissionsRef.current = () => {}
      });
      return;
    }
    console.log('getting user media');
    navigator.mediaDevices.getUserMedia({
      audio: {
        deviceId: deviceId || undefined,
        echoCancellation: false,
        autoGainControl: false,
        noiseSuppression: false,
        sampleRate
      }
    }).then((stream) => {
      onGetMediaPermission();
      console.log('got user media, setting stream');
      setStream(stream);
    }).catch((error) => {
      console.log('error getting user media')
      console.error(error);
    });
  }, [deviceId, setStream, sampleRate, updateDevices]);

  const stopRecording = useCallback(() => {
    stopAfterNextBuffer.current = true;
  }, []);

  const bufferReceiver = useRef(receiveBuffer);

  useEffect(
    () => {
      bufferReceiver.current = receiveBuffer;
    },
    [receiveBuffer]
  );

  useEffect(
    () => {
      if (!stream) {
        return;
      }
      if (audioContext.state === 'suspended') {
        audioContext.resume();
      }
      const source = audioContext.createMediaStreamSource(stream);
      const processor = audioContext.createScriptProcessor(RECORDING_BUFFER_SIZE, 1, 1);
      const mute = audioContext.createGain();
      mute.gain.setValueAtTime(0, 0);
      let index = 0;
      processor.onaudioprocess = (e) => {
        const localIndex = index;
        index ++;
        console.log('processing audio', index);
        setTimeout(
          () => {
            bufferReceiver.current(e.inputBuffer, localIndex * RECORDING_BUFFER_SIZE);
            if (stopAfterNextBuffer.current && stream) {
              stopAfterNextBuffer.current = false;
              console.log('stopping')
              stream.getTracks().forEach((track: MediaStreamTrack) => {
                console.log('stopping track', track)
                track.stop();
              });
              setStream(null);
            }
          },
          0
        );
      }
      source.connect(processor);
      processor.connect(mute);
      mute.connect(audioContext.destination);
      return () => {
        source.disconnect();
        processor.disconnect();
        mute.disconnect();
      }
    },
    [stream, bufferReceiver]
  );

  const isRecording = useMemo(() => Boolean(stream), [stream]);

  return useMemo(() => ({ isRecording, startRecording, stopRecording }), [isRecording, startRecording, stopRecording]);
}
