import * as React from "react";

import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import Popover from "@material-ui/core/Popover";
import LinearProgress from "@material-ui/core/LinearProgress";
import CircularProgress from "@material-ui/core/CircularProgress";
import Paper from "@material-ui/core/Paper";
import Tooltip from "@material-ui/core/Tooltip";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";

import CloseIcon from "@material-ui/icons/Close";

import { withRouter } from "react-router-dom";

import VocabularyData from "../components/VocabularyData";
import {
    IVocab, IReviewCard, vocabToReviewCard, reviewQTypeToStr, VocabType
} from "../models/vocab";
import { ReviewType, IReviewMetadata } from "../models/review";

//@ts-ignore
import lev from "js-levenshtein";
import {
    LEVENSHTEIN_MAX_DISTANCE, MAX_ERROR_THRESHOLD,
    REVIEW_HELP_MOD
} from "../config";

import { BACKEND_URL } from "../config";

import { Queue } from "../utils/queue";

interface IProps {
    levelId?: number;
    vocabByLevel?: (level: number) => Promise<IVocab[]>;
    vocabByQueue?: () => Promise<IVocab[]>;
    updateDoneLevels?: (id: string) => void;
    setLastReview: (meta: IReviewMetadata, sm2: any, delta: number) => void;
    reviewType: ReviewType;

    history: any;

    dialogOpen: boolean;
    loading: boolean;
    vocab: IVocab[];
    current: IReviewCard;
    metadata: IReviewMetadata;
    popoverOpen: boolean;
    popoverText: string;
    popoverColor: string;
    popoverTextColor: string;
    showHelp: boolean;

    setReviewDialog: (state: boolean) => void;
    setSummary: (state: boolean) => void;
    setPopover: (state: boolean, text: string, color: string, textColor: string) => void;
    drawerButtonState: (state: boolean) => void;
    setReview: (curent: IReviewCard, meta: IReviewMetadata) => void;
    setLoading: (state: boolean) => void;
    setShowHelp: (state: boolean) => void;
}

const ReviewPageWithRouter = withRouter(
    class ReviewPage extends React.Component<IProps> {
        private vocab: IVocab[] = [];
        private reviewQueue: Queue<IReviewCard> = new Queue();
        // Used for positioning the popover
        private buttonRef: HTMLButtonElement;
        private inputRef: HTMLInputElement;
        // How often a group has been wrongly answered
        // (Mapping: Vocab Id -> amount of times it was wrongly answered)
        private error_data = {};
        // Mapping: Vocab Id -> Correctly answered
        private sm2_metadata: any = {};
        private score_delta = 0;

        componentDidMount() {
            // Hide the drawer button
            this.props.drawerButtonState(false);

            // Show a loading spinner
            this.props.setLoading(true);

            // Get the correct vocabulary
            const { reviewType, vocabByLevel, levelId, vocabByQueue } = this.props;

            // Just to make TSC shut up
            const noopPromise = () => {
                return new Promise<IVocab[]>((res, rej) => {
                    rej([]);
                });
            };
            const vocabByLevelW = vocabByLevel || noopPromise;
            const vocabByQueueW = vocabByQueue || noopPromise;
            const getVocab = {
                [ReviewType.LEVEL]: () => vocabByLevelW(levelId),
                [ReviewType.QUEUE]: () => vocabByQueueW(),
            }[reviewType];

            // Track the start of a session
            fetch(`${BACKEND_URL}/api/tracker`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                }),
                method: "POST",
                body: JSON.stringify({
                    session: window.sessionStorage.getItem("tracker_session"),
                    event: "START_LEARNING",
                }),
            });

            getVocab().then((res: IVocab[]) => {
                // Check if we received any vocabulary
                if (res.length === 0) {
                    // TODO: Replace with a snackbar
                    alert("Du hast noch keine Vokabeln in der Review Queue");

                    // Reset the button state
                    this.props.drawerButtonState(true);
                    this.props.history.push("/dashboard");
                    return;
                }

                // Stop the loading
                this.props.setLoading(false);
                this.vocab = res;

                // Convert the vocab items into review queue cards
                res.forEach(vocab => {
                    // Set the error data for the group
                    this.error_data[vocab.id] = 0;

                    vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
                });

                // Set the initial vocabulary card
                this.props.setReview(this.reviewQueue.dequeue(), {
                    correct: 0,
                    wrong: 0,
                });
            });
        }

        openDialog = () => {
            this.props.setReviewDialog(true);
        }
        closeDialog = () => {
            this.props.setReviewDialog(false);
        }

        cancelReview = () => {
            this.closeDialog();

            // Track the cancellation of a learning session
            fetch(`${BACKEND_URL}/api/tracker`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                }),
                method: "POST",
                body: JSON.stringify({
                    session: window.sessionStorage.getItem("tracker_session"),
                    event: "CANCEL_LEARNING",
                }),
            });

            // Show the drawer button again
            this.props.drawerButtonState(true);

            this.props.history.push("/dashboard");
        }

        closeHelp = () => {
            this.props.setShowHelp(false);
        }

        increaseMeta = (correct: number, wrong: number): IReviewMetadata => {
            const { metadata } = this.props;

            return {
                wrong: metadata.wrong + wrong,
                correct: metadata.correct + correct,
            };
        }

        // When a vocabulary item has been answered, we need to update
        // the group's SuperMemo2-data.
        // We update based on the following State-Machine: (C = Correct; W = Wrong)
        // Answer | Group | New group
        //        |       |   state
        // -------+-------+----------
        //    C   |   C   |  C
        //    C   |   W   |  W
        //    W   |   C   |  W
        //    W   |   W   |  W
        // (1)C   |   -   |  C
        // (2)W   |   -   |  W
        // Args:
        //   @correct: Was the answer given correct?
        updateGroupSM2 = (correct: boolean) => {
            const { id } = this.props.current;
            switch (correct) {
                case true:
                    // Case (1)
                    if (!(id in this.sm2_metadata)) {
                        this.sm2_metadata[id] = true;
                        break;
                    }

                    switch (this.sm2_metadata[id]) {
                        case true:
                            this.sm2_metadata[id] = true;
                            break;
                        case false:
                            this.sm2_metadata[id] = false;
                            break;
                    }
                    break;
                case false:
                    // We don't need to explicitly catch case (2), as we set
                    // anything, that was incorrectly answered to false.
                    this.sm2_metadata[id] = false;
                    break;
            }
        }

        checkInput = () => {
            // Check if the given answer is somewhere in the german words
            const input = this.inputRef.value || "";

            const { current } = this.props;

            // Map all possible answers to lowercase ( => ignore casing)
            const answers = current.answers.map((el) => el.toLowerCase());
            // Calculate the distances to all possible answers
            const dists = answers.map((el) => lev(input.toLowerCase(), el));
            // Find the lowest distance
            const minDist = Math.min(...dists);

            // Check if the user's answer was correct
            if (minDist === 0) {
                this.updateGroupSM2(true);
                this.score_delta += 1;

                // Empty the input field
                this.inputRef.value = "";

                // TODO: Show it's correct?
                // Show the next vocab word
                if (this.reviewQueue.size() === 0) {
                    // Update the metadata
                    const newMeta = this.increaseMeta(1, 0);
                    this.props.setReview(current, newMeta);
                    this.props.setLastReview(newMeta, this.sm2_metadata, this.score_delta);
                    this.props.setLoading(true);

                    // If we're reviewing a level, then that level should be
                    // marked as complete
                    if (this.props.reviewType === ReviewType.LEVEL) {
                        // NOTE: We can ensure that both updateDoneLevels and
                        //       levelId are attributes of this.props, since
                        //       reviewType === ReviewType.LEVEL requires those.
                        //@ts-ignore
                        this.props.updateDoneLevels(this.props.levelId);
                    }

                    // Show the drawer button again
                    this.props.drawerButtonState(true);

                    // Go to the summary screen
                    this.props.history.push("/review/summary");
                } else {
                    // Update the metadata
                    this.props.setReview(this.reviewQueue.dequeue(), this.increaseMeta(1, 0));
                }
            } else if (minDist <= LEVENSHTEIN_MAX_DISTANCE) {
                this.props.setPopover(true, "Das war fast richtig", "yellow", "black");
            } else {
                // Update the metadata
                this.updateGroupSM2(false);

                // Update the error data
                this.error_data[current.id] += 1;
                if (this.error_data[current.id] <= MAX_ERROR_THRESHOLD) {
                    // Read the vocabulary group to the queue
                    // Find the vocabulary item
                    // TODO: if(!vocab)
                    const vocab = this.vocab.find(el => el.id === current.id);

                    // Re-add the vocabulary group
                    // NOTE: We don't need to force a re-render, as the state
                    //       will be updated since we need to show the popover.
                    vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
                } else if (this.error_data[current.id] % REVIEW_HELP_MOD === 0) {
                    // Help the user
                    this.props.setShowHelp(true);
                }

                this.props.setReview(this.props.current, this.increaseMeta(0, 1));
                this.props.setPopover(true, "Das war nicht richtig", "red", "white");
            }
        }

        helpDialog = () => {
            // Find the vocabulary
            // TODO: if (!vocab)
            const vocab = this.vocab.find(el => el.id === this.props.current.id) as IVocab;

            return <Dialog
                open={this.props.showHelp}
                onClose={this.closeHelp}>
                <DialogTitle>Wiederholung von {vocab.latin.grundform}</DialogTitle>
                <DialogContent>
                    <VocabularyData vocab={vocab} />
                </DialogContent>
                <DialogActions>
                    <Button
                        onClick={this.closeHelp}>
                        Zurück zur Wiederholung
                                    </Button>
                </DialogActions>
            </Dialog>;
        }

        render() {
            if (this.props.loading) {
                return <div>
                    <Grid
                        container
                        spacing={0}
                        direction="column"
                        alignItems="center"
                        justify="center"
                        style={{ minHeight: '100vh' }}>
                        <Grid item xs={12}>
                            <Paper className="paper">
                                <Grid container direction="column" spacing={8}>
                                    <CircularProgress />
                                </Grid>
                            </Paper>
                        </Grid>
                    </Grid>

                </div>;
            }

            const { question, qtype } = this.props.current;
            const questionTitle = `${question} (${reviewQTypeToStr(qtype)})`;

            // NOTE: This assumes that adverbs get mapped to only 1 card,
            //       while every other type of vocabulary gets mapped to
            //       exactly 3 cards each.
            // NOTE: The 'numCards === 0 ?' is neccessary as (for some reasdn) numCards
            //       starts of by being 0, which results in progress === -Inifinity.
            //       That looks weird as the user sees the progressbar jump.
            // NOTE: The arrow function needs the ': number' annotation as TS
            //       will then deduce the type of acc and curr to be '1|3',
            //       leads to compiler warnings, as a, b element {1, 3} is not
            //       element {1, 3}.
            // NOTE: We need to check if this.vocab is empty because otherwise
            //       the '.reduce' call will throw and crash the entire
            //       application.
            // TOFIX: This is pure garbage. Fix this
            const numCards = this.vocab.length > 0 ? this.vocab.map((vocab): number => {
                if (vocab.type === VocabType.ADVERB) {
                    return 1;
                } else {
                    return 3;
                }
            }).reduce((acc, curr) => acc + curr) : 0;

            const progress = numCards === 0 ? (
                0
            ) : (
                    100 * (numCards - (this.reviewQueue.size() + 1)) / numCards
                );

            return <div>
                <Grid container justify="center">
                    <Grid item style={{ width: "100%" }}>
                        <Card>
                            <CardContent>
                                <Grid container direction="column">
                                    <center>
                                        <Typography variant="display2">
                                            {questionTitle}
                                        </Typography>
                                    </center>
                                    <TextField
                                        margin="normal"
                                        fullWidth={true}
                                        inputRef={node => this.inputRef = node}
                                        onKeyPress={(ev) => {
                                            // Allow checking of the answer by pressing Enter
                                            if (ev.key === "Enter")
                                                this.checkInput();
                                        }} />
                                    <LinearProgress
                                        variant="determinate"
                                        value={progress} />
                                    <Popover
                                        open={this.props.popoverOpen}
                                        anchorOrigin={{
                                            vertical: "center",
                                            horizontal: "center"
                                        }}
                                        transformOrigin={{
                                            vertical: "bottom",
                                            horizontal: "center"
                                        }}
                                        anchorEl={this.buttonRef}
                                        onClose={() => this.props.setPopover(false, "", "", "")}
                                        PaperProps={{
                                            style: {
                                                backgroundColor: this.props.popoverColor,
                                                padding: 10,
                                                color: this.props.popoverTextColor,
                                            }
                                        }}>
                                        <Typography
                                            variant="button"
                                            color="inherit">
                                            {this.props.popoverText}
                                        </Typography>
                                    </Popover>
                                    <Button
                                        onClick={this.checkInput}
                                        buttonRef={node => this.buttonRef = node}>
                                        Prüfen
                                    </Button>
                                </Grid>
                            </CardContent>
                        </Card>
                    </Grid>
                </Grid>
                <Tooltip
                    title="Abbrechen"
                    placement="top">
                    <Button
                        variant="fab"
                        color="primary"
                        className="review-fab"
                        onClick={this.openDialog}>
                        <CloseIcon />
                    </Button>
                </Tooltip>
                {
                    this.props.showHelp ? (
                        this.helpDialog()
                    ) : undefined
                }
                <Dialog
                    open={this.props.dialogOpen}
                    onClose={this.closeDialog}>
                    <DialogTitle>Willst du die Wiederholung abbrechen?</DialogTitle>
                    <DialogContent>
                        <DialogContentText>
                            Wenn du jetzt abbricht, dann geht dein in dieser Wiederholung gesammelte Fortschritt
                            verloren.
                        </DialogContentText>
                    </DialogContent>
                    <DialogActions>
                        <Button
                            onClick={this.closeDialog}>
                            Zurück zur Wiederholung
                        </Button>
                        <Button
                            onClick={this.cancelReview}>
                            Wiederholung abbrechen
                        </Button>
                    </DialogActions>
                </Dialog>
            </div>;
        }
    }
);
export default ReviewPageWithRouter;
