import React, {createContext, useEffect, useState} from "react";
import {
  connectWorkout,
  createNewWorkout,
  fetchActiveWorkouts, fetchBikes, fetchPrograms,
  openWebsocket, pauseWorkout,
  startWorkout,
  stopWorkout
} from "../hooks/net";
import useMilestones from "../hooks/milestones";

export const StatusContext = createContext({
  bike: null,
  program: null,
  bikes: null,
  programs: null,
  workout: null,
  workoutStatus: null,
  setBike: null,
  setProgram: null,
  state: null,
  milestones: [],
  create: () => null,
  resume: () => null,
  start: () => null,
  pause: () => null,
  stop: () => null,
});

export const StatusContextProvider = ({children}) => {
  const [bike, setBike] = useState(null);
  const [program, setProgram] = useState(null);
  const [bikes, setBikes] = useState(null);
  const [programs, setPrograms] = useState(null);
  const [workout, setWorkout] = useState(null);
  const [workoutStatus, setWorkoutStatus] = useState(null);
  const [state, setState] = useState("offline");
  const {milestones, prevDiff, prevLongDiff, msDispatch} = useMilestones();
  const [socket, setSocket] = useState(null);

  useEffect(() => {
    if (programs === null) {
      fetchPrograms().then(newPrograms => setPrograms(newPrograms));
    }
  }, [programs]);

  useEffect(() => {
    if (bikes === null) {
      fetchBikes().then(newBikes => {
        setBikes(newBikes);

        if (newBikes.length === 1) {
          setBike(newBikes[0]);
        }
      });
    }
  }, [bikes]);

  useEffect(() => {
    if (bike === null && program === null && workout === null) {
      resume();
    }
  }, [bike, program, workout]);

  useEffect(() => {
    if (bikes === null || programs === null) {
      setState("loading");
    } else if (workout !== null) {
      setState(workout.state || "unknown");
    } else if (bike === null) {
      setState("bike");
    } else if (program === null) {
      setState("program");
    }
  }, [bike, program, workout, bikes, programs]);

  useEffect(() => {
    if (socket === null) {
      return;
    }

    if (workout === null) {
      socket.close();
      setSocket(null);
      return;
    }

    socket.onmessage = ({data}) => {
      onSocketMessage(workout, program, JSON.parse(data));
    };

    return () => {
      socket.onmessage = null;
    }
  }, [socket, workout, program]);

  function onSocketMessage(myWorkout, program, body) {
    if (typeof body.workout !== "undefined") {
      setWorkout({...body.workout, ...myWorkout});
    }

    if (typeof body.workoutStatusBackfill !== "undefined") {
      body.workoutStatusBackfill.forEach(wsbf => {
        setWorkoutStatus(wsbf);
        msDispatch({type: "measure", payload: {...wsbf, program}});
      });
    }

    if (typeof body.workoutStatus !== "undefined") {
      setWorkoutStatus(body.workoutStatus);
      msDispatch({type: "measure", payload: {...body.workoutStatus, program}});
    }
  }

  async function create(myProgram = null) {
    setWorkoutStatus(null);

    let newWorkout = await createNewWorkout(bike, myProgram || program);

    setWorkout(newWorkout);
    setWorkout(await connectWorkout(newWorkout));

    setSocket(openWebsocket(newWorkout));
  }

  async function resume() {
    const activeWorkouts = await fetchActiveWorkouts();
    const count = activeWorkouts.length;
    const last = count > 0 ? activeWorkouts[count - 1] : null;

    if (last !== null) {
      setProgram(last.program);
      setWorkout(last);
      setBike(last.bike);

      setSocket(openWebsocket(last));
    }
  }

  async function start() {
    setWorkout(await startWorkout(workout));
  }

  async function pause() {
    setWorkout(await pauseWorkout(workout));
  }

  async function stop() {
    await stopWorkout(workout);

    setBike(null);
    setProgram(null);
    setWorkout(null);
    msDispatch({type: "clear"});
  }

  return (
    <StatusContext.Provider value={{
      bike, program, workout, workoutStatus, state, milestones,
      setBike, setProgram,
      create, resume, start, pause, stop,
      programs, bikes,
    }}>
      {children}
    </StatusContext.Provider>
  )
};
