import React, { useEffect, useState, useRef } from 'react'
import { API, graphqlOperation, Auth } from 'aws-amplify'
import { Heading, Grid, useTheme, Button, SliderField } from '@aws-amplify/ui-react';
import { useNavigate } from "react-router-dom";
import * as customStatements from '../graphql/custom-statements'
import { CORE_OVERVIEW_ID, RACE_STATES, RACE_OPERATIONS, RACE_CONFIRMATION_MESSAGES, RACE_STATE_ICONS, CARS } from '../utils/constants'
import { MQTT_TOPICS } from '../utils/constants';
import { PahoMqttClient, IotCoreMqttClient } from '../utils/mqttClient'
import awsExports from "../aws-exports";
import * as queries from '../graphql/queries'
const {
REACT_APP_MQTT_ENDPOINT_HOST_REMOTE,
REACT_APP_MQTT_ENDPOINT_HOST_LOCAL,
REACT_APP_MQTT_ENDPOINT_PORT
} = process.env
export default function Admin() {
const [race, setRace] = useState()
const [nrOfLaps, setNrOfLaps] = useState(30)
const [secretPin, setSecretPin] = useState("")
const [loading, setLoading] = useState(false)
const [overview, setOverview] = useState()
const [raceSubscription, setRaceSubscription] = useState()
const raceSubscriptionRef = useRef(raceSubscription)
const clientRef = useRef()
const navigate = useNavigate();
const loadingText = "Loading ..."
useEffect(() => {
checkPin()
init()
return () => {
tearDownConnections()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const tearDownConnections = () => {
if (raceSubscriptionRef.current) {
raceSubscriptionRef.current.unsubscribe()
}
if (clientRef.current) {
clientRef.current.disconnect()
}
}
const checkPin = () => {
const adminPin = prompt("Provide admin PIN")
if (adminPin !== process.env.REACT_APP_ADMIN_PIN) { alert("Incorrect PIN"); navigate(`/`) }
setSecretPin(adminPin)
}
const init = async () => {
await initWsClient()
const raceId = await setup()
if (raceId) {
const updateRaceSubscription = await API.graphql({
query: customStatements.customOnUpdateRaceById,
variables: { id: raceId }
}).subscribe({
next: ({ _, value }) => { console.log("Updated Race: ", value); fetchCurrentRace(value.data.onUpdateRaceById.id) },
error: error => console.warn(error)
});
raceSubscriptionRef.current = updateRaceSubscription
setRaceSubscription(updateRaceSubscription)
return updateRaceSubscription
}
return null
}
const initWsClient = async () => {
const isLocal = awsExports.aws_appsync_graphqlEndpoint.startsWith('http://')
if (clientRef.current) { return }
if (isLocal === false) {
console.log("Remote Client")
const { accessKeyId, secretAccessKey, sessionToken } = await Auth.currentCredentials()
const client = new IotCoreMqttClient(
REACT_APP_MQTT_ENDPOINT_HOST_REMOTE,
accessKeyId,
secretAccessKey,
sessionToken,
[MQTT_TOPICS.GAME_STATE_UPDATE]
)
clientRef.current = client
} else {
console.log("Local Client")
const client = new PahoMqttClient(
REACT_APP_MQTT_ENDPOINT_HOST_LOCAL,
REACT_APP_MQTT_ENDPOINT_PORT,
[MQTT_TOPICS.GAME_STATE_UPDATE]
)
clientRef.current = client
}
}
const setup = async () => {
const overview = await getOverview()
setOverview(overview)
if (overview && overview.currentRace !== null) {
await fetchCurrentRace(overview.currentRace.id)
return overview?.currentRace?.id
}
return null
}
const getOverview = async () => {
var overview = null;
try {
const overviewData = await API.graphql(graphqlOperation(customStatements.customGetOverview, { id: CORE_OVERVIEW_ID }))
overview = overviewData.data.getOverview || null
} catch (error) {
console.error("No overview found, likely that you need to initialise")
console.log(error)
}
return overview
}
const fetchCurrentRace = async (raceId) => {
setLoading(true)
try {
const raceData = await API.graphql(graphqlOperation(customStatements.customGetRace, { id: raceId }))
const currentRace = raceData.data.getRace
const carClaims = currentRace.currentRaceState === RACE_STATES.FORMATION_LAPS ? getFormationLapClaimsArray(currentRace) : getCarClaimsArray(currentRace)
const updatedState = {
raceId: currentRace.id,
gameState: currentRace.currentRaceState,
carClaims
}
clientRef.current.sendPayload(JSON.stringify(updatedState), MQTT_TOPICS.GAME_STATE_UPDATE)
setRace(currentRace)
} catch (err) {
console.error(err)
}
setLoading(false)
}
const getCarClaimsArray = (raceData) => {
var array = []
for (const player of raceData.players.items) {
var object = {
carId: parseInt(player.playerCarId),
playerId: ""
}
if (player.claims.items.length > 0) {
object.playerId = player.claims.items[0].id
}
array.push(object)
}
return array
}
const getFormationLapClaimsArray = (raceData) => {
var array = []
for (const player of raceData.players.items) {
const carId = parseInt(player.playerCarId)
array.push({
carId,
playerId: CARS[carId-1]?.claimable ? player.id : ""
})
}
return array
}
const initialise = async () => {
setLoading(true)
try {
await API.graphql(graphqlOperation(queries.raceOperations, {
input: {
operation: RACE_OPERATIONS.INITIALISE,
secretPin,
additionalParams: {
nrOfLaps
}
}
}))
} catch (error) {
console.error(error)
}
window.location.reload();
setLoading(false)
}
const createNewRace = async () => {
const operation = RACE_OPERATIONS.CREATE_NEW_RACE
setLoading(true)
// eslint-disable-next-line no-restricted-globals
const confirmed = confirm(RACE_CONFIRMATION_MESSAGES[operation])
if (confirmed === true) {
try {
await API.graphql(graphqlOperation(queries.raceOperations, {
input: {
operation: operation,
secretPin,
additionalParams: {
nrOfLaps
}
}
}))
raceSubscriptionRef.current.unsubscribe()
await init()
} catch (error) {
console.error(error)
}
}
setLoading(false)
}
const { tokens } = useTheme();
const raceIsOngoing = () => {
const state = race?.currentRaceState
return race && state !== RACE_STATES.CHECKERED_FLAG && state !== RACE_STATES.LOBBY && state !== RACE_STATES.ABORTED && state !== RACE_STATES.PRACTICE
}
const canChangeToThisActiveRaceState = (desiredState) => {
const state = race?.currentRaceState
return raceIsOngoing() && state !== RACE_STATES.LOBBY && state !== desiredState
}
const raceOperation = async (operation) => {
setLoading(true)
if (RACE_CONFIRMATION_MESSAGES[operation] !== undefined) {
// eslint-disable-next-line no-restricted-globals
const confirmed = confirm(RACE_CONFIRMATION_MESSAGES[operation])
if (!confirmed) { setLoading(false); return }
}
try {
await API.graphql(graphqlOperation(queries.raceOperations, {
input: {
operation: operation,
secretPin,
additionalParams: {
raceId: race.id
}
}
}))
} catch (error) {
console.error(error)
setLoading(false)
}
}
const actionButtons = () => {
if (!overview) {
return (
<>
One-time initialisation ....
>
)
}
else {
return (
<>
>
)
}
}
return (
<>
Admin
{race &&
<>
Current race id: {race?.id}
Current race state: {race?.currentRaceState}
>
}
{actionButtons()}
>
)
}