import {
  MediaSet,
  GrpcWebImpl,
  MediaSetServiceClientImpl,
  GetVideoProgress,
} from './generated/media_set';

import { useState, useEffect } from 'react';
import { VideoPreview } from './VideoPreview';
import { Overview } from './Overview';
import { Waveform } from './Waveform';
import { ControlBar } from './ControlBar';
import { SeekBar } from './SeekBar';
import './App.css';
import { Duration } from './generated/google/protobuf/duration';

// ported from backend, where should they live?
const thumbnailWidth = 177;
const thumbnailHeight = 100;

const initialViewportSeconds = 10;

// Frames represents a selection of audio frames.
export interface Frames {
  start: number;
  end: number;
}

export interface VideoPosition {
  currentTime: number;
  percent: number;
}

function App(): JSX.Element {
  const [mediaSet, setMediaSet] = useState<MediaSet | null>(null);
  const [video, _setVideo] = useState(document.createElement('video'));
  const [position, setPosition] = useState({ currentTime: 0, percent: 0 });
  const [viewport, setViewport] = useState({ start: 0, end: 0 });

  // effects

  // TODO: error handling
  const videoID = new URLSearchParams(window.location.search).get('video_id');
  if (videoID == null) {
    return <></>;
  }

  // fetch mediaset on page load:
  useEffect(() => {
    (async function () {
      const rpc = newRPC();
      const service = new MediaSetServiceClientImpl(rpc);
      const mediaSet = await service.Get({ youtubeId: videoID });

      console.log('got media set:', mediaSet);
      setMediaSet(mediaSet);
    })();
  }, []);

  // setup player on first page load only:
  useEffect(() => {
    if (mediaSet == null) {
      return;
    }

    // assume mediaSet never changes once loaded
    setInterval(() => {
      if (video.currentTime == position.currentTime) {
        return;
      }
      const duration = mediaSet.audioFrames / mediaSet.audioSampleRate;
      const percent = (video.currentTime / duration) * 100;

      setPosition({ currentTime: video.currentTime, percent: percent });
    }, 100);
  }, [mediaSet]);

  // load video when MediaSet is loaded:
  useEffect(() => {
    (async function () {
      if (mediaSet == null) {
        return;
      }

      console.log('getting video...');
      const rpc = newRPC();
      const service = new MediaSetServiceClientImpl(rpc);
      const videoProgressStream = service.GetVideo({ id: mediaSet.id });

      let url = '';
      // TODO: probably a nicer way to do this.
      await videoProgressStream.forEach((progress: GetVideoProgress) => {
        if (progress.url != '') {
          url = progress.url;
        }
      });

      video.src = url;
      video.muted = false;
      video.volume = 1;
      console.log('set video src', video.src);
    })();
  }, [mediaSet]);

  // set viewport when MediaSet is loaded:
  useEffect(() => {
    if (mediaSet == null) {
      return;
    }

    const numFrames = Math.min(
      mediaSet.audioSampleRate * initialViewportSeconds,
      mediaSet.audioFrames
    );

    setViewport({ start: 0, end: numFrames });
  }, [mediaSet]);

  useEffect(() => {
    console.debug('viewport updated', viewport);
  }, [viewport]);

  // handlers

  const handleOverviewSelectionChange = (selection: Frames) => {
    console.log('in handleOverviewSelectionChange', selection);
    if (mediaSet == null) {
      return;
    }

    setViewport({
      start: mediaSet.audioFrames * (selection.start / 100),
      end: mediaSet.audioFrames * (selection.end / 100),
    });
  };

  // render component

  const containerStyles = {
    border: '1px solid black',
    width: '90%',
    margin: '1em auto',
    minHeight: '500px',
    height: '700px',
    display: 'flex',
    flexDirection: 'column',
  } as React.CSSProperties;

  const offsetPixels = Math.floor(thumbnailWidth / 2);

  if (mediaSet == null) {
    // TODO: improve
    return <></>;
  }

  return (
    <>
      <div className="App">
        <div style={containerStyles}>
          <ControlBar
            onPlay={() => {
              video.play();
            }}
            onPause={() => {
              video.pause();
            }}
          />

          <Overview
            mediaSet={mediaSet}
            offsetPixels={offsetPixels}
            height={80}
            position={position}
            onSelectionChange={handleOverviewSelectionChange}
          />

          <Waveform
            mediaSet={mediaSet}
            position={position}
            viewport={viewport}
            offsetPixels={offsetPixels}
          />

          <SeekBar
            position={video.currentTime}
            duration={mediaSet.audioFrames / mediaSet.audioSampleRate}
            offsetPixels={offsetPixels}
            onPositionChanged={(position: number) => {
              video.currentTime = position;
            }}
          />

          <VideoPreview
            mediaSet={mediaSet}
            video={video}
            position={position}
            duration={millisFromDuration(mediaSet.videoDuration)}
            height={thumbnailHeight}
          />
        </div>
      </div>
    </>
  );
}

export default App;

function millisFromDuration(dur?: Duration): number {
  if (dur == undefined) {
    return 0;
  }
  return Math.floor(dur.seconds * 1000.0 + dur.nanos / 1000.0 / 1000.0);
}

const grpcHost = 'http://localhost:8888';

export function newRPC(): GrpcWebImpl {
  return new GrpcWebImpl(grpcHost, {});
}
