// @refresh reset
import AsyncStorage from '@react-native-community/async-storage';
import {format, parse} from 'date-fns';
import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
// @ts-ignore
import * as firebase from 'firebase';
import 'firebase/firestore';
import React, {ReactElement, useCallback, useEffect, useState} from 'react';
import {AppRegistry, SafeAreaView, Text, View, YellowBox} from 'react-native';
import {
  Appbar,
  Button,
  DefaultTheme,
  HelperText,
  Provider as PaperProvider, RadioButton,
  Snackbar,
  Subheading,
  TextInput,
  Title,
} from 'react-native-paper';
import {expo as appExpo} from './app.json';
import {CancelButton} from './components/Common';
import {BarCodeDisplay, PrintButton, PrintingMessage} from './components/Print';
import {IdNumberInput, InputIdButton, ScanButton, Scanner} from './components/Scan';
import {colors, styles} from './components/Styles';
import {BarcodeScannerAppState} from './models/BarcodeScannerAppState';
import {CameraType, ElementProps, StateProps} from './models/ElementProps';
import {Sample} from './models/Sample';

const firebaseConfig = {
  apiKey: 'api_key_goes_here',
  authDomain: "uva-covid19-testing-kiosk.firebaseapp.com",
  databaseURL: "https://uva-covid19-testing-kiosk.firebaseio.com",
  projectId: 'project_id_goes_here',
  storageBucket: "uva-covid19-testing-kiosk.appspot.com",
  messagingSenderId: 'sender_id_goes_here',
  appId: 'app_id_goes_here'
};

// Initialize Firebase if not already initialized.
if (firebase.apps.length === 0) {
  firebase.initializeApp(firebaseConfig);
}

YellowBox.ignoreWarnings([
  'Setting a timer for a long period of time',  // Ignore Firebase timer warnings
  'Remote debugger is in a background tab',  // Ignore Firebase timer warnings
]);

const db = firebase.firestore();
const samplesCollection = db.collection('samples');

const theme = {
  ...DefaultTheme,
  colors: colors,
}

export default function Main() {
  const [appState, setAppState] = useState<BarcodeScannerAppState>(BarcodeScannerAppState.INITIAL);
  const [sampleId, setSampleId] = useState<string>('');
  const [barCodeId, setBarCodeId] = useState<string>('');
  const [sampleDate, setSampleDate] = useState<Date>(new Date());
  const [locationStr, setLocationStr] = useState<string>('4321');
  const [errorMessage, setErrorMessage] = useState<string>('');
  const [samples, setSamples] = useState<Sample[]>([]);
  const [cameraType, setCameraType] = useState<CameraType>('back');

  useEffect(() => {
    BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => {
      if (value.granted) {
        setAppState(BarcodeScannerAppState.DEFAULT);
      } else {
        setAppState(BarcodeScannerAppState.ERROR);
      }
    });

    const unsubscribe = samplesCollection.onSnapshot(querySnapshot => {
      // Transform and sort the data returned from Firebase
      const samplesFirestore = querySnapshot
        .docChanges()
        .filter(({type}) => type === 'added')
        .map(({doc}) => {
          const sample = doc.data();
          return {...sample, createdAt: sample.createdAt.toDate()} as Sample;
        })
        .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

      appendSamples(samplesFirestore);
    });

    return () => unsubscribe()
  }, []);

  const _doNothing = () => {
  };
  const _scan = () => {
    setErrorMessage('');
    setAppState(BarcodeScannerAppState.SCANNING);
  };
  const _inputIdNumber = () => {
    setErrorMessage('');
    setAppState(BarcodeScannerAppState.INPUT);
  };
  const _print = () => setAppState(BarcodeScannerAppState.PRINTING);
  const _printed = () => setAppState(BarcodeScannerAppState.PRINTED);
  const _home = () => setAppState(BarcodeScannerAppState.DEFAULT);
  const _settings = () => setAppState(BarcodeScannerAppState.SETTINGS);

  const handleBarCodeScanned = (e: BarCodeEvent) => {
    // Make sure the data is the right length.
    // Scanned barcodes will be exactly 14 digits long.
    // Manually-entered ID numbers will be exactly 9 digits long.
    const barCodeString = e.data;
    const pattern = /^[\d]{14}$|^[\d]{9}$/;
    console.log('barCodeString', barCodeString);
    if (pattern.test(barCodeString)) {
      const cardId = e.data.slice(0, 9);
      const newSampleDate = new Date();
      const newSampleId = [cardId, format(newSampleDate, 'yyyyMMddHHmm'), locationStr].join('-');

      setSampleId(newSampleId);
      setBarCodeId(cardId);
      setSampleDate(newSampleDate);
      setAppState(BarcodeScannerAppState.SCANNED);
    } else {
      setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`);
      setAppState(BarcodeScannerAppState.ERROR);
    }
  };

  const appendSamples = useCallback((newSamples) => {
    setSamples((previousSamples) => previousSamples.concat(newSamples));
  }, [samples]);

  const sendDataToFirebase = async (newSamples: Sample[]) => {
    const writes = newSamples.map(s => samplesCollection.doc(s.id).set(s));
    await Promise.all(writes);
  }

  function ErrorMessage(props: ElementProps): ReactElement {
    return <View style={styles.fullScreen}>
      <View style={styles.container}>
        <ScanButton onClicked={_scan}/>
        <InputIdButton onClicked={_inputIdNumber}/>
      </View>
      <Snackbar
        visible={appState === BarcodeScannerAppState.ERROR}
        onDismiss={_doNothing}
        style={styles.error}
      >{errorMessage === '' ? 'Something went wrong. Try again.' : errorMessage}</Snackbar>
    </View>
  }

  function LoadingMessage(props: ElementProps): ReactElement {
    return <Snackbar
      visible={appState === BarcodeScannerAppState.INITIAL}
      onDismiss={_doNothing}
    >Loading...</Snackbar>;
  }

  function SuccessMessage(props: ElementProps): ReactElement {
    return <Title>Your barcode label has printed successfully.</Title>;
  }

  function ActionButtons(props: ElementProps): ReactElement {
    return <View>
      <PrintButton onClicked={_print}/>
      <CancelButton onClicked={_home}/>
    </View>
  }

  function SettingsScreen(props: ElementProps): ReactElement {
    const [inputStr, setInputStr] = useState<string>(locationStr);

    const pattern = /^[\d]{4}$/;
    const hasErrors = () => {
      return !pattern.test(inputStr);
    };

    return <View style={styles.settings}>
      <View style={{marginBottom: 10}}>
        <Subheading style={{color: DefaultTheme.colors.text}}>Which camera to scan bar codes with?</Subheading>
        <RadioButton.Group
          onValueChange={value => setCameraType(value as CameraType)}
          value={cameraType as string}
        >
          <View style={styles.row}>
            <Text>Front</Text>
            <RadioButton
              value="front"
              color={colors.primary}
              uncheckedColor={colors.accent}
            />
          </View>
          <View style={styles.row}>
            <Text>Back</Text>
            <RadioButton
              value="back"
              color={colors.primary}
              uncheckedColor={colors.accent}
            />
          </View>
        </RadioButton.Group>
      </View>

      <View style={{marginBottom: 10}}>
        <Subheading style={{color: DefaultTheme.colors.text, marginBottom: 60}}>
          Please do NOT change this unless you know what you are doing. Entering an incorrect location number may
          prevent patients from getting accurate info about their test results.
        </Subheading>
        <TextInput
          label="Location #"
          value={inputStr}
          onChangeText={inputStr => setInputStr(inputStr)}
          mode="outlined"
          theme={DefaultTheme}
        />
        <HelperText type="error" visible={hasErrors()}>
          Location number must be exactly 4 digits. No other characters are allowed.
        </HelperText>
        <Button
          icon="content-save"
          mode="contained"
          color={colors.primary}
          style={{marginBottom: 10}}
          disabled={hasErrors()}
          onPress={() => {
            setLocationStr(inputStr);
            _home();
          }}
        >Save</Button>
        <Button
          icon="cancel"
          mode="outlined"
          color={colors.primary}
          onPress={_home}
        >Cancel</Button>
      </View>
    </View>
  }

  function App(props: StateProps): ReactElement {
    switch (props.appState) {
      case BarcodeScannerAppState.INITIAL:
        return <LoadingMessage/>;
      case BarcodeScannerAppState.DEFAULT:
        return <View style={styles.container}>
          <ScanButton onClicked={_scan}/>
          <InputIdButton onClicked={_inputIdNumber}/>
        </View>;
      case BarcodeScannerAppState.PRINTED:
        // Upload any changes to Firebase
        AsyncStorage.getAllKeys().then(keys => {
          const newSamples = keys
            .filter(s => /^[\d]{9}-[\d]{12}-[\d]{4}$/.test(s))
            .map(s => {
              const propsArray = s.split('-');
              return {
                id: s,
                barcodeId: propsArray[0],
                createdAt: parse(propsArray[1], 'yyyyMMddHHmm', new Date()),
                locationId: propsArray[2],
              } as Sample;
            });
          sendDataToFirebase(newSamples);
        });

        _home();

        return <SuccessMessage/>;
      case BarcodeScannerAppState.PRINTING:
        return <View style={styles.container}>
          <PrintingMessage
            onCancel={_printed}
            id={sampleId}
            barCodeId={barCodeId}
            date={sampleDate}
            location={locationStr}
          />
        </View>;
      case BarcodeScannerAppState.SCANNED:
        return <View style={styles.container}>
          <BarCodeDisplay
            id={sampleId}
            barCodeId={barCodeId}
            date={sampleDate}
            location={locationStr}
          />
          <ActionButtons/>
        </View>;
      case BarcodeScannerAppState.SCANNING:
        return <Scanner
          onScanned={handleBarCodeScanned}
          onCancel={_home}
          cameraType={cameraType}
        />;
      case BarcodeScannerAppState.INPUT:
        return <IdNumberInput
          onScanned={handleBarCodeScanned}
          onCancel={_home}
          cameraType={undefined}
        />;
      case BarcodeScannerAppState.SETTINGS:
        return <SettingsScreen/>;
      default:
        return <ErrorMessage/>;
    }
  }

  return (
    <PaperProvider theme={theme}>
      <Appbar.Header dark={true}>
        <Appbar.Content title={`${appExpo.description} #${locationStr}`}/>
        <Appbar.Action icon="home" onPress={_home}/>
        <Appbar.Action icon="settings" onPress={_settings}/>
      </Appbar.Header>
      <SafeAreaView style={styles.safeAreaView}>
        <App appState={appState}/>
      </SafeAreaView>
    </PaperProvider>
  );
};

AppRegistry.registerComponent(appExpo.name, () => Main);
