import { useEffect, useState } from 'react';
import { Frames, VideoPosition, newRPC } from './App';
import { MediaSetServiceClientImpl, MediaSet } from './generated/media_set';
import { WaveformCanvas } from './WaveformCanvas';
import { HudCanvas, SelectionChangeEvent } from './HudCanvas';
import { EmptySelectionAction, SelectionMode } from './HudCanvasState';
import { from, Observable } from 'rxjs';
import { bufferCount } from 'rxjs/operators';

interface Props {
  mediaSet: MediaSet;
  position: VideoPosition;
  viewport: Frames;
  onSelectionChange: (selectionState: SelectionChangeEvent) => void;
}

export const CanvasLogicalWidth = 2000;
export const CanvasLogicalHeight = 500;

export const Waveform: React.FC<Props> = ({
  mediaSet,
  position,
  viewport,
  onSelectionChange,
}: Props) => {
  const [peaks, setPeaks] = useState<Observable<number[]>>(from([]));
  const [selectedFrames, setSelectedFrames] = useState({ start: 0, end: 0 });

  // selectedPixels are the currently selected waveform canvas pixels. They may
  // not match the actual selection due to the viewport setting, and probably
  // shouldn't be leaking outside of the HudCanvasState.
  const [selectedPixels, setSelectedPixels] = useState({
    start: 0,
    end: 0,
  });

  const [positionPixels, setPositionPixels] = useState<number | null>(0);

  // effects

  // load peaks on MediaSet change
  useEffect(() => {
    (async function () {
      if (mediaSet == null) {
        return;
      }

      if (viewport.start >= viewport.end) {
        return;
      }

      const service = new MediaSetServiceClientImpl(newRPC());
      const segment = await service.GetPeaksForSegment({
        id: mediaSet.id,
        numBins: CanvasLogicalWidth,
        startFrame: viewport.start,
        endFrame: viewport.end,
      });

      console.log('got segment', segment);

      const peaks = from(segment.peaks).pipe(
        bufferCount(mediaSet.audioChannels)
      );
      setPeaks(peaks);
    })();
  }, [viewport, mediaSet]);

  // convert position to canvas pixels
  useEffect(() => {
    const frame = Math.round(position.currentTime * mediaSet.audioSampleRate);
    if (frame < viewport.start || frame > viewport.end) {
      setPositionPixels(null);
      return;
    }
    const pixelsPerFrame = CanvasLogicalWidth / (viewport.end - viewport.start);
    const positionPixels = (frame - viewport.start) * pixelsPerFrame;
    setPositionPixels(positionPixels);
  }, [mediaSet, position, viewport]);

  // update selectedPixels on viewport change
  useEffect(() => {
    const start = Math.max(frameToCanvasX(selectedFrames.start), 0);
    const end = Math.min(
      frameToCanvasX(selectedFrames.end),
      CanvasLogicalWidth
    );
    setSelectedPixels({ start, end });
  }, [viewport, selectedFrames]);

  // handlers

  // convert selection change from canvas pixels to frames, and trigger callback.
  const handleSelectionChange = (selectionState: SelectionChangeEvent) => {
    const { mode, prevMode, selection } = selectionState;

    const framesPerPixel = (viewport.end - viewport.start) / CanvasLogicalWidth;
    const selectedFrames = {
      start: Math.round(viewport.start + selection.start * framesPerPixel),
      end: Math.round(viewport.start + selection.end * framesPerPixel),
    };

    if (mode == SelectionMode.Normal && prevMode != SelectionMode.Normal) {
      setSelectedPixels(selection);
      setSelectedFrames(selectedFrames);
    }

    onSelectionChange({
      ...selectionState,
      selection: selectedFrames,
    });
  };

  // helpers

  const frameToCanvasX = (frame: number): number => {
    const numFrames = viewport.end - viewport.start;
    if (numFrames == 0) {
      return 0;
    }

    const pixelsPerFrame = CanvasLogicalWidth / numFrames;
    return Math.round((frame - viewport.start) * pixelsPerFrame);
  };

  // render component

  const hudStyles = {
    borderLineWidth: 0,
    borderStrokeStyle: 'transparent',
    positionLineWidth: 6,
    positionStrokeStyle: 'red',
    hoverPositionStrokeStyle: '#666666',
  };

  return (
    <>
      <div className={`relative grow`}>
        <WaveformCanvas
          peaks={peaks}
          channels={mediaSet.audioChannels}
          width={CanvasLogicalWidth}
          height={CanvasLogicalHeight}
          strokeStyle="green"
          fillStyle="black"
          alpha={1}
        ></WaveformCanvas>
        <HudCanvas
          width={CanvasLogicalWidth}
          height={CanvasLogicalHeight}
          emptySelectionAction={EmptySelectionAction.SelectNothing}
          styles={hudStyles}
          position={positionPixels}
          selection={selectedPixels}
          onSelectionChange={handleSelectionChange}
        />
      </div>
    </>
  );
};
