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

interface Props {
  mediaSet: MediaSet;
  position: VideoPosition;
  viewport: Frames;
  offsetPixels: number;
  onSelectionChange: (selection: Selection) => void;
}

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

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

      console.log('fetch audio segment, frames', viewport);

      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]);

  // 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

  const handleSelectionChange = useCallback(
    (selection: Selection) => {
      setSelectedPixels(selection);

      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),
      };

      setSelectedFrames(selectedFrames);
      onSelectionChange(selectedFrames);
    },
    [viewport, selectedFrames]
  );

  // helpers

  const frameToCanvasX = useCallback(
    (frame: number): number => {
      const pixelsPerFrame =
        CanvasLogicalWidth / (viewport.end - viewport.start);
      return Math.round((frame - viewport.start) * pixelsPerFrame);
    },
    [viewport]
  );

  // render component

  const containerStyles = {
    background: 'black',
    margin: '0 ' + offsetPixels + 'px',
    flexGrow: 1,
    position: 'relative',
  } as React.CSSProperties;

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

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