// @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, 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 type {CustomTheme} from "../../managers/ThemeManager";
import {StackNavigationProp} from "@react-navigation/stack";
import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash";
import {Modalize} from "react-native-modalize";
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
import MascotPopup from "../../components/Mascot/MascotPopup";

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

let modalStateStrings = {};

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

export type Machine = {
    number: string,
    state: string,
    startTime: string,
    endTime: string,
    donePercent: string,
    remainingTime: string,
    program: string,
}

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

type State = {
    refreshing: boolean,
    modalCurrentDisplayItem: React.Node,
    machinesWatched: Array<Machine>,
    mascotDialogVisible: 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: null | Modalize;

    fetchedData: {
        dryers: Array<Machine>,
        washers: Array<Machine>,
    };

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

    /**
     * Creates machine state parameters using current theme and translations
     */
    constructor(props) {
        super(props);
        modalStateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t('screens.proxiwash.modal.ready');
        modalStateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t('screens.proxiwash.modal.running');
        modalStateStrings[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] = i18n.t('screens.proxiwash.modal.runningNotStarted');
        modalStateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t('screens.proxiwash.modal.finished');
        modalStateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t('screens.proxiwash.modal.broken');
        modalStateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t('screens.proxiwash.modal.error');
        modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t('screens.proxiwash.modal.unknown');
    }

    /**
     * Callback used when closing the banner.
     * This hides the banner and saves to preferences to prevent it from reopening
     */
    onHideMascotDialog = () => {
        this.setState({mascotDialogVisible: 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: () =>
                <MaterialHeaderButtons>
                    <Item title="information" iconName="information" onPress={this.onAboutPress}/>
                </MaterialHeaderButtons>,
        });
    }

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

    /**
     * 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;

    /**
     * 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
     */
    setupNotifications(machine: Machine) {
        if (!isMachineWatched(machine, this.state.machinesWatched)) {
            Notifications.setupMachineNotification(machine.number, true, getMachineEndDate(machine))
                .then(() => {
                    this.saveNotificationToState(machine);
                })
                .catch(() => {
                    this.showNotificationsDisabledWarning();
                });
        } else {
            Notifications.setupMachineNotification(machine.number, false, null)
                .then(() => {
                    this.removeNotificationFromState(machine);
                });
        }
    }

    /**
     * Shows a warning telling the user notifications are disabled for the app
     */
    showNotificationsDisabledWarning() {
        Alert.alert(
            i18n.t("screens.proxiwash.modal.notificationErrorTitle"),
            i18n.t("screens.proxiwash.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),
        );
    }

    /**
     * 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.state.machinesWatched =
            getCleanedMachineWatched(this.state.machinesWatched, [...data.dryers, ...data.washers]);
        return [
            {
                title: i18n.t('screens.proxiwash.dryers'),
                icon: 'tumble-dryer',
                data: data.dryers === undefined ? [] : data.dryers,
                keyExtractor: this.getKeyExtractor
            },
            {
                title: i18n.t('screens.proxiwash.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: Machine, isDryer: boolean) {
        let button = {
            text: i18n.t("screens.proxiwash.modal.ok"),
            icon: '',
            onPress: undefined
        };
        let message = modalStateStrings[item.state];
        const onPress = this.onSetupNotificationsPress.bind(this, item);
        if (item.state === ProxiwashConstants.machineStates.RUNNING) {
            let remainingTime = parseInt(item.remainingTime)
            if (remainingTime < 0)
                remainingTime = 0;

            button =
                {
                    text: isMachineWatched(item, this.state.machinesWatched) ?
                        i18n.t("screens.proxiwash.modal.disableNotifications") :
                        i18n.t("screens.proxiwash.modal.enableNotifications"),
                    icon: '',
                    onPress: onPress
                }
            ;
            message = i18n.t('screens.proxiwash.modal.running',
                {
                    start: item.startTime,
                    end: item.endTime,
                    remaining: remainingTime,
                    program: item.program
                });
        } else if (item.state === ProxiwashConstants.machineStates.AVAILABLE) {
            if (isDryer)
                message += '\n' + i18n.t('screens.proxiwash.dryersTariff');
            else
                message += '\n' + i18n.t('screens.proxiwash.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 (data[i].state === ProxiwashConstants.machineStates.AVAILABLE)
                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('screens.proxiwash.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('screens.proxiwash.dryers');
        return (
            <ProxiwashListItem
                item={item}
                onPress={this.showModal}
                isWatched={isMachineWatched(item, this.state.machinesWatched)}
                isDryer={isDryer}
                height={LIST_ITEM_HEIGHT}
            />
        );
    };

    render() {
        const nav = this.props.navigation;
        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>
                <MascotPopup
                    visible={this.state.mascotDialogVisible}
                    title={i18n.t("screens.proxiwash.mascotDialog.title")}
                    message={i18n.t("screens.proxiwash.mascotDialog.message")}
                    icon={"information"}
                    buttons={{
                        action: null,
                        cancel: {
                            message: i18n.t("screens.proxiwash.mascotDialog.ok"),
                            icon: "check",
                            onPress: this.onHideMascotDialog,
                        }
                    }}
                    emotion={MASCOT_STYLE.NORMAL}
                />
                <CustomModal onRef={this.onModalRef}>
                    {this.state.modalCurrentDisplayItem}
                </CustomModal>
            </View>
        );
    }
}

export default withTheme(ProxiwashScreen);
