// @flow

import * as React from 'react';
import {Alert, View} from 'react-native';
import i18n from "i18n-js";
import WebSectionList from "../../components/Screens/WebSectionList";
import * as Notifications from "../../utils/Notifications";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper';
import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem";
import ProxiwashConstants from "../../constants/ProxiwashConstants";
import CustomModal from "../../components/Overrides/CustomModal";
import AprilFoolsManager from "../../managers/AprilFoolsManager";
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
import ProxiwashSectionHeader from "../../components/Lists/Proxiwash/ProxiwashSectionHeader";
import {withCollapsible} from "../../utils/withCollapsible";
import type {CustomTheme} from "../../managers/ThemeManager";
import {Collapsible} from "react-navigation-collapsible";
import {StackNavigationProp} from "@react-navigation/stack";

const PushNotification = require("react-native-push-notification");

const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json";

let modalStateStrings = {};

const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
const LIST_ITEM_HEIGHT = 64;

type machine = {
    number: string,
    state: string,
    startTime: string,
    endTime: string,
    donePercent: string,
    remainingTime: string,
}

type Props = {
    navigation: StackNavigationProp,
    theme: CustomTheme,
    collapsibleStack: Collapsible,
}

type State = {
    refreshing: boolean,
    modalCurrentDisplayItem: React.Node,
    machinesWatched: Array<machine>,
    bannerVisible: boolean,
};


/**
 * Class defining the app's proxiwash screen. This screen shows information about washing machines and
 * dryers, taken from a scrapper reading proxiwash website
 */
class ProxiwashScreen extends React.Component<Props, State> {

    modalRef: Object;

    fetchedData: Object;
    allMachines: Array<machine>;

    state = {
        refreshing: false,
        modalCurrentDisplayItem: null,
        machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current),
        bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1',
    };

    /**
     * Creates machine state parameters using current theme and translations
     */
    constructor(props) {
        super(props);
        modalStateStrings[ProxiwashConstants.machineStates.TERMINE] = i18n.t('proxiwashScreen.modal.finished');
        modalStateStrings[ProxiwashConstants.machineStates.DISPONIBLE] = i18n.t('proxiwashScreen.modal.ready');
        modalStateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.modal.running');
        modalStateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.modal.broken');
        modalStateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.modal.error');
        this.allMachines = [];
    }

    /**
     * Callback used when closing the banner.
     * This hides the banner and saves to preferences to prevent it from reopening
     */
    onHideBanner = () => {
        this.setState({bannerVisible: false});
        AsyncStorageManager.getInstance().savePref(
            AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key,
            '0'
        );
    };

    /**
     * Setup notification channel for android and add listeners to detect notifications fired
     */
    componentDidMount() {
        this.props.navigation.setOptions({
            headerRight: this.getAboutButton,
        });
    }

    /**
     * Callback used when pressing the about button.
     * This will open the ProxiwashAboutScreen.
     */
    onAboutPress = () => this.props.navigation.navigate('proxiwash-about');

    /**
     * Gets the about header button
     *
     * @return {*}
     */
    getAboutButton = () =>
        <MaterialHeaderButtons>
            <Item title="information" iconName="information" onPress={this.onAboutPress}/>
        </MaterialHeaderButtons>;

    /**
     * Extracts the key for the given item
     *
     * @param item The item to extract the key from
     * @return {*} The extracted key
     */
    getKeyExtractor = (item: machine) => item.number;


    getMachineEndDate(machine: machine) {
        const array = machine.endTime.split(":");
        let date = new Date();
        date.setHours(parseInt(array[0]), parseInt(array[1]));
        if (date < new Date())
            date.setDate(date.getDate() + 1);
        return date;
    }

    /**
     * Setups notifications for the machine with the given ID.
     * One notification will be sent at the end of the program.
     * Another will be send a few minutes before the end, based on the value of reminderNotifTime
     *
     * @param machine The machine to watch
     * @returns {Promise<void>}
     */
    setupNotifications(machine: machine) {
        if (!this.isMachineWatched(machine)) {
            Notifications.setupMachineNotification(machine.number, true, this.getMachineEndDate(machine))
                .then(() => {
                    this.saveNotificationToState(machine);
                })
                .catch(() => {
                    this.showNotificationsDisabledWarning();
                });
        } else {
            Notifications.setupMachineNotification(machine.number, false)
                .then(() => {
                    this.removeNotificationFromState(machine);
                });
        }
    }

    /**
     * Shows a warning telling the user notifications are disabled for the app
     */
    showNotificationsDisabledWarning() {
        Alert.alert(
            i18n.t("proxiwashScreen.modal.notificationErrorTitle"),
            i18n.t("proxiwashScreen.modal.notificationErrorDescription"),
        );
    }

    /**
     * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences
     *
     * @param machine
     */
    saveNotificationToState(machine: machine) {
        let data = this.state.machinesWatched;
        data.push(machine);
        this.saveNewWatchedList(data);
    }

    /**
     * Removes the given index from the watchlist array and saves it to preferences
     *
     * @param machine
     */
    removeNotificationFromState(machine: machine) {
        let data = this.state.machinesWatched;
        for (let i = 0; i < data.length; i++) {
            if (data[i].number === machine.number && data[i].endTime === machine.endTime) {
                data.splice(i, 1);
                break;
            }
        }
        this.saveNewWatchedList(data);
    }

    saveNewWatchedList(list: Array<machine>) {
        this.setState({machinesWatched: list});
        AsyncStorageManager.getInstance().savePref(
            AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key,
            JSON.stringify(list),
        );
    }

    /**
     * Checks whether the machine of the given ID has scheduled notifications
     *
     * @param machine
     * @returns {boolean}
     */
    isMachineWatched(machine: machine) {
        let watched = false;
        const list = this.state.machinesWatched;
        for (let i = 0; i < list.length; i++) {
            if (list[i].number === machine.number && list[i].endTime === machine.endTime) {
                watched = true;
                break;
            }
        }
        return watched;
    }

    getMachineOfId(id: string) {
        for (let i = 0; i < this.allMachines.length; i++) {
            if (this.allMachines[i].number === id)
                return this.allMachines[i];
        }
        return null;
    }

    getCleanedMachineWatched() {
        const list = this.state.machinesWatched;
        let newList = [];
        for (let i = 0; i < list.length; i++) {
            let machine = this.getMachineOfId(list[i].number);
            if (machine !== null
                && list[i].number === machine.number && list[i].endTime === machine.endTime
                && ProxiwashConstants.machineStates[list[i].state] === ProxiwashConstants.machineStates["EN COURS"]) {
                newList.push(machine);
            }
        }
        return newList;
    }

    /**
     * Creates the dataset to be used by the flatlist
     *
     * @param fetchedData
     * @return {*}
     */
    createDataset = (fetchedData: Object) => {
        let data = fetchedData;
        if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
            data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy
            AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers);
            AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers);
        }
        this.fetchedData = data;
        this.allMachines = [...data.dryers, ...data.washers];
        this.state.machinesWatched = this.getCleanedMachineWatched();
        return [
            {
                title: i18n.t('proxiwashScreen.dryers'),
                icon: 'tumble-dryer',
                data: data.dryers === undefined ? [] : data.dryers,
                keyExtractor: this.getKeyExtractor
            },
            {
                title: i18n.t('proxiwashScreen.washers'),
                icon: 'washing-machine',
                data: data.washers === undefined ? [] : data.washers,
                keyExtractor: this.getKeyExtractor
            },
        ];
    };

    /**
     * Shows a modal for the given item
     *
     * @param title The title to use
     * @param item The item to display information for in the modal
     * @param isDryer True if the given item is a dryer
     */
    showModal = (title: string, item: Object, isDryer: boolean) => {
        this.setState({
            modalCurrentDisplayItem: this.getModalContent(title, item, isDryer)
        });
        if (this.modalRef) {
            this.modalRef.open();
        }
    };

    /**
     * Callback used when the user clicks on enable notifications for a machine
     *
     * @param machine The machine to set notifications for
     */
    onSetupNotificationsPress(machine: machine) {
        if (this.modalRef) {
            this.modalRef.close();
        }
        this.setupNotifications(machine);
    }

    /**
     * Generates the modal content.
     * This shows information for the given machine.
     *
     * @param title The title to use
     * @param item The item to display information for in the modal
     * @param isDryer True if the given item is a dryer
     * @return {*}
     */
    getModalContent(title: string, item: Object, isDryer: boolean) {
        let button = {
            text: i18n.t("proxiwashScreen.modal.ok"),
            icon: '',
            onPress: undefined
        };
        let message = modalStateStrings[ProxiwashConstants.machineStates[item.state]];
        const onPress = this.onSetupNotificationsPress.bind(this, item);
        if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]) {
            button =
                {
                    text: this.isMachineWatched(item.number) ?
                        i18n.t("proxiwashScreen.modal.disableNotifications") :
                        i18n.t("proxiwashScreen.modal.enableNotifications"),
                    icon: '',
                    onPress: onPress
                }
            ;
            message = i18n.t('proxiwashScreen.modal.running',
                {
                    start: item.startTime,
                    end: item.endTime,
                    remaining: item.remainingTime
                });
        } else if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates.DISPONIBLE) {
            if (isDryer)
                message += '\n' + i18n.t('proxiwashScreen.dryersTariff');
            else
                message += '\n' + i18n.t('proxiwashScreen.washersTariff');
        }
        return (
            <View style={{
                flex: 1,
                padding: 20
            }}>
                <Card.Title
                    title={title}
                    left={() => <Avatar.Icon
                        icon={isDryer ? 'tumble-dryer' : 'washing-machine'}
                        color={this.props.theme.colors.text}
                        style={{backgroundColor: 'transparent'}}/>}

                />
                <Card.Content>
                    <Text>{message}</Text>
                </Card.Content>

                {button.onPress !== undefined ?
                    <Card.Actions>
                        <Button
                            icon={button.icon}
                            mode="contained"
                            onPress={button.onPress}
                            style={{marginLeft: 'auto', marginRight: 'auto'}}
                        >
                            {button.text}
                        </Button>
                    </Card.Actions> : null}
            </View>
        );
    }

    /**
     * Callback used when receiving modal ref
     *
     * @param ref
     */
    onModalRef = (ref: Object) => {
        this.modalRef = ref;
    };

    /**
     * Gets the number of machines available
     *
     * @param isDryer True if we are only checking for dryer, false for washers
     * @return {number} The number of machines available
     */
    getMachineAvailableNumber(isDryer: boolean) {
        let data;
        if (isDryer)
            data = this.fetchedData.dryers;
        else
            data = this.fetchedData.washers;
        let count = 0;
        for (let i = 0; i < data.length; i++) {
            if (ProxiwashConstants.machineStates[data[i].state] === ProxiwashConstants.machineStates["DISPONIBLE"])
                count += 1;
        }
        return count;
    }

    /**
     * Gets the section render item
     *
     * @param section The section to render
     * @return {*}
     */
    getRenderSectionHeader = ({section}: Object) => {
        const isDryer = section.title === i18n.t('proxiwashScreen.dryers');
        const nbAvailable = this.getMachineAvailableNumber(isDryer);
        return (
            <ProxiwashSectionHeader
                title={section.title}
                nbAvailable={nbAvailable}
                isDryer={isDryer}/>
        );
    };

    /**
     * Gets the list item to be rendered
     *
     * @param item The object containing the item's FetchedData
     * @param section The object describing the current SectionList section
     * @returns {React.Node}
     */
    getRenderItem = ({item, section}: Object) => {
        const isDryer = section.title === i18n.t('proxiwashScreen.dryers');
        return (
            <ProxiwashListItem
                item={item}
                onPress={() => this.onSetupNotificationsPress(item)}
                isWatched={this.isMachineWatched(item)}
                isDryer={isDryer}
                height={LIST_ITEM_HEIGHT}
            />
        );
    };

    render() {
        const nav = this.props.navigation;
        const {containerPaddingTop} = this.props.collapsibleStack;
        return (
            <View
                style={{flex: 1}}
            >
                <View style={{
                    position: "absolute",
                    width: "100%",
                    height: "100%",
                }}>
                    <WebSectionList
                        createDataset={this.createDataset}
                        navigation={nav}
                        fetchUrl={DATA_URL}
                        renderItem={this.getRenderItem}
                        renderSectionHeader={this.getRenderSectionHeader}
                        autoRefreshTime={REFRESH_TIME}
                        refreshOnFocus={true}
                        updateData={this.state.machinesWatched.length}/>
                </View>
                <Banner
                    style={{marginTop: containerPaddingTop,}}
                    visible={this.state.bannerVisible}
                    actions={[
                        {
                            label: 'OK',
                            onPress: this.onHideBanner,
                        },
                    ]}
                    icon={() => <Avatar.Icon
                        icon={'information'}
                        size={40}
                    />}
                >
                    {i18n.t('proxiwashScreen.enableNotificationsTip')}
                </Banner>
                <CustomModal onRef={this.onModalRef}>
                    {this.state.modalCurrentDisplayItem}
                </CustomModal>
            </View>
        );
    }
}

export default withCollapsible(withTheme(ProxiwashScreen));
