import connection, {
  calculateRoomTime,
  useRoomRunningAndReadyChanged,
  useTimelineSongFileChanged,
} from "../lib/Connection";
import { SyntheticEvent, useEffect, useRef, useState } from "react";

import { Button, Slider } from "antd";
import { SoundFilled, SoundOutlined } from "@ant-design/icons";

import "../css/player.css";
import { Room } from "../types/types";

const Player = () => {
  const room = useRoomRunningAndReadyChanged();
  const timeline = useTimelineSongFileChanged();

  const player = useRef<HTMLAudioElement>(null);
  const defaultVolume = parseInt(localStorage.getItem("volume") ?? "100");

  const [volume, setVolume] = useState(defaultVolume);
  const [muted, setMuted] = useState(false);
  const [finishedLoading, setFinishedLoading] = useState(false);

  const [timesSeeked, setTimesSeeked] = useState(0);
  const [hadError, setHadError] = useState(false);

  // If our time synchronisation algorithm thing thinks the time is off by more
  // than this value, we seek the running player to correct it.
  const diffSecondsRequiredToSeekRunningPlayer = 0.2;

  // Hard cap we are allowed to seek this player. Some browsers are slow or inaccurate
  // and will always be off. To avoid endless skipping of the song this cap stops seeking the
  // player.
  const maxTimesSeekAllow = 25;

  useEffect(() => {
    // Need to use an effect since 'player' will only contain a reference after first render.

    if (!timeline) {
      throw new Error("Player without active timeline.");
    }

    if (!player.current) {
      throw new Error("No player after mount.");
    }

    player.current.src = timeline.songFile;
  }, [timeline]);

  function handlePlayerOnPlay(e: SyntheticEvent) {
    e.preventDefault();
    // For when the user manually started the player for when autoplay is off.
    setHadError(false);
  }

  async function handlePlayerPause(e: SyntheticEvent) {
    if (!shouldPlay()) {
      // We should not be playing, pausing is fine.
      console.log("should not play, paused");
      return;
    }

    e.preventDefault();

    if (room) {
      setPlayerTime(room, true);
    }
    await player.current?.play();
  }

  function handlePlayerCanPlayThrough() {
    if (!finishedLoading) {
      setFinishedLoading(true);
      connection.requestStart();
    }
  }

  function shouldPlay() {
    return (
      player.current &&
      timeline &&
      room &&
      room.running &&
      room.readyToParticipate &&
      !player.current.ended
    );
  }

  async function startPlaying(manual: boolean) {
    if (!player.current) {
      return;
    }

    if (player.current.paused && !hadError) {
      setPlayerVolume(volume);

      try {
        await player.current.play();
        setHadError(false);
      } catch (e) {
        console.error("Error playing", e);
        setHadError(true);
      }
    }

    if (!hadError && room) {
      setPlayerTime(room, manual);
    }
  }

  function setPlayerTime(room: Room, manualAdjustment: boolean) {
    if (!player.current) {
      return;
    }

    // Player's currentTime is in seconds, not ms.
    const targetTime = calculateRoomTime() / 1000;
    const diff = Math.abs(player.current.currentTime - targetTime);

    // console.log('PLAYER DIFF', diff,
    //     'min req to seek: ', diffSecondsRequiredToSeekRunningPlayer,
    //     `(${timesSeeked} / ${maxTimesSeekAllow})`);

    if (diff <= diffSecondsRequiredToSeekRunningPlayer) {
      return;
    }

    if (timesSeeked >= maxTimesSeekAllow && !manualAdjustment) {
      // If we are adjusting manually we always allow a seek.
      console.warn(
        "The running player is off, but we've changed the time " +
          "too often, skipping synchronizing the player."
      );
      return;
    }

    player.current.currentTime = targetTime;
    player.current.playbackRate = Math.min(room.speedFactor, 5);

    if (!manualAdjustment) {
      setTimesSeeked(timesSeeked + 1);
    }

    console.log(
      `Player seeked: diff: ${diff}, target: ${targetTime}, (${timesSeeked} / ${maxTimesSeekAllow})`
    );
  }

  function toggleMute() {
    if (player.current != null) {
      player.current.muted = !player.current.muted;
      setMuted(!muted);
    }
  }

  function changeVolume(sliderValue: number) {
    setVolume(sliderValue);
    localStorage["volume"] = sliderValue;
    setPlayerVolume(sliderValue);
  }

  function setPlayerVolume(value: number) {
    if (player.current) {
      player.current.volume =
        value === 0.0 ? 0.0 : Math.pow(10, (value / 100 - 1) * 2);
    }
  }

  if (shouldPlay()) {
    startPlaying(false)
      .then(() => {
        //
      })
      .catch((e) => {
        console.error(e);
      });
  } else {
    if (player.current) {
      player.current.pause();
    }
  }

  return (
    <div id="audio">
      <audio
        ref={player}
        className="player"
        controls={true}
        loop={false}
        hidden={!hadError}
        onPause={handlePlayerPause}
        onPlay={handlePlayerOnPlay}
        onCanPlayThrough={handlePlayerCanPlayThrough}
      />
      <div id="volume-control">
        <Button onClick={toggleMute} shape="circle">
          {muted ? <SoundOutlined /> : <SoundFilled />}
        </Button>
        <Slider
          id="volume-slider"
          defaultValue={volume}
          onChange={changeVolume}
          trackStyle={{ backgroundColor: "var(--secondary-color)" }}
        />
      </div>
    </div>
  );
};

export default Player;
