import * as React from "react";
import { Container } from "reactstrap";
import { isUndefined } from "util";
import { InternalAnswerQuestion } from "./InternalAnswerQuestion";
import { InternalResults } from "./InternalResults";
import { InternalReviewQuestion } from "./InternalReviewQuestion";
import { InternalStart } from "./InternalStart";
import { Progress } from "./Progress";
import { IQuestionProps } from "./Question";
import { QuizData } from "./QuizData";
import { toArray } from "./utils";

export interface IQuizProps {
  title: React.ReactChild;
  seed?: number;
  randomOrder?: boolean;
  autoStart?: boolean;
  children:
    | Array<React.ReactElement<IQuestionProps>>
    | React.ReactElement<IQuestionProps>;
}

export class Quiz extends React.PureComponent<IQuizProps> {
  static defaultProps = {
    autoStart: false
  };
  static is(value: any): value is Quiz {
    return React.isValidElement(value) && value.type === Quiz;
  }
  static parse(props: IQuizProps): QuizData {
    return QuizData.parse(props);
  }

  data: QuizData;

  constructor(props: IQuizProps) {
    super(props);

    this.data = Quiz.parse(this.props);
  }

  componentWillReceiveProps(props: IQuizProps) {
    this.data = Quiz.parse(props);
  }

  render() {
    return <InternalQuiz quiz={this.data} />;
  }
}

export interface IResult {
  score?: number;
  input?: any[];
  correct?: boolean;
  explained?: boolean;
  step?: number;
}

export const isDone = (result: IResult) =>
  isUndefined(result.explained) && isUndefined(result.correct);
export const isCorrect = (result: IResult) => result.correct === true;
export const isIncorrect = (result: IResult) => result.correct === false;
export const isExplained = (result: IResult) =>
  isUndefined(result.correct) && result.explained === true;

export enum QuizState {
  start = "start",
  questions = "questions",
  results = "results",
  reviewing = "reviewing"
}

export interface IInternalQuizProps {
  quiz: QuizData;
}
export interface IInternalQuizState {
  state: QuizState;
  input: any[];
  current: IResult;
  currentIndex: number;
  results: IResult[];
}

export class InternalQuiz extends React.PureComponent<
  IInternalQuizProps,
  IInternalQuizState
> {
  constructor(props: IInternalQuizProps) {
    super(props);

    const results = this.getInitialResults();
    this.state = {
      state: this.props.quiz.autoStart ? QuizState.questions : QuizState.start,
      results,
      currentIndex: 0,
      input: [],
      current: results[0]
    };
  }

  getResults(): IResult[] {
    return this.state.results;
  }
  getCorrect() {
    return this.getResults().reduce(
      (acc, result) => acc + (result.correct === true ? 1 : 0),
      0
    );
  }
  getCount() {
    return this.getResults().length;
  }
  render() {
    return (
      <Container className="Quiz mt-4 mb-4">{this.renderState()}</Container>
    );
  }
  private renderState() {
    switch (this.state.state) {
      case QuizState.start: {
        return (
          <>
            <div>{this.props.quiz.descriptions}</div>
            <InternalStart
              key="start"
              title={this.props.quiz.title}
              count={this.props.quiz.questions.length}
              onStart={this.onStart}
            />
          </>
        );
      }
      case QuizState.questions: {
        const question = this.props.quiz.questions[this.state.currentIndex];

        return (
          <>
            <Progress
              currentIndex={this.state.currentIndex}
              results={this.state.results}
              setCurrent={this.setCurrent}
            />
            <div>{this.props.quiz.descriptions}</div>
            <InternalAnswerQuestion
              question={question}
              key={this.state.currentIndex}
              input={this.state.input}
              correct={this.state.current.correct}
              explained={this.state.current.explained}
              explainationStep={this.state.current.step || 0}
              update={this.update}
              onExplain={this.onExplain}
              onSubmit={this.onSubmit}
              onNext={this.onNext}
              onNextStep={this.onNextStep}
            />
          </>
        );
      }
      case QuizState.results: {
        return (
          <>
            <Progress
              currentIndex={this.state.currentIndex}
              results={this.state.results}
              setCurrent={this.setCurrent}
            />
            <div>{this.props.quiz.descriptions}</div>
            <InternalResults
              key="results"
              count={this.props.quiz.questions.length}
              correct={this.getCorrect()}
              onReset={this.onReset}
              onReview={this.onReview}
            />
          </>
        );
      }
      case QuizState.reviewing: {
        const question = this.props.quiz.questions[this.state.currentIndex];

        return (
          <>
            <Progress
              currentIndex={this.state.currentIndex}
              results={this.state.results}
              setCurrent={this.setCurrent}
            />
            <div>{this.props.quiz.descriptions}</div>
            <InternalReviewQuestion
              question={question}
              key={this.state.currentIndex}
              input={this.state.current.input || []}
              correct={this.state.current.correct}
              explained={this.state.current.explained}
              score={this.state.current.score}
              onNext={this.onNext}
            />
          </>
        );
      }
    }
  }
  private setCurrent = (currentIndex: number) => {
    const current = this.state.results[currentIndex];

    if (current) {
      return this.setState({
        currentIndex,
        current,
        input: []
      });
    }
  };
  private getInitialResults(): IResult[] {
    return toArray(this.props.quiz.questions).map(() => ({}));
  }
  private update = (fn: (state: any[]) => any[]) => {
    this.setState({
      input: fn(this.state.input)
    });
  };
  private onStart = () => {
    this.setState({
      state: QuizState.questions
    });
  };
  private onExplain = () => {
    this.updateCurrent({
      input: this.state.input || [],
      explained: true
    });
  };
  private onSubmit = (score: number) => {
    this.updateCurrent({
      score,
      correct: score >= 0.5,
      input: this.state.input || []
    });
  };
  private onNextStep = () => {
    const current = this.state.current,
      currentQuestion = this.props.quiz.questions[this.state.currentIndex],
      step = (current.step || 0) + 1;

    if (
      step <
      (currentQuestion && currentQuestion.explaination
        ? currentQuestion.explaination.children.length
        : -1)
    ) {
      this.updateCurrent({
        step
      });
    }
  };
  private updateCurrent = (partialCurrent: IResult) => {
    const current = {
        ...this.state.current,
        ...partialCurrent
      },
      results = this.state.results.slice();

    results[this.state.currentIndex] = current;

    this.setState({
      current,
      results
    });
  };
  private findIndexNextUnfinished(index: number): number {
    for (let i = index, il = this.state.results.length; i < il; i++) {
      const result = this.state.results[i];

      if (isDone(result)) {
        return i;
      }
    }
    if (index !== 0) {
      return this.findIndexNextUnfinished(0);
    }
    return -1;
  }
  private onNext = () => {
    const currentIndex = this.findIndexNextUnfinished(this.state.currentIndex),
      current = this.state.results[currentIndex];

    if (current) {
      return this.setState({
        currentIndex,
        current,
        input: []
      });
    } else {
      return this.setState({
        currentIndex,
        input: [],
        state: QuizState.results
      });
    }
  };
  private onReview = () => {
    this.setState({
      currentIndex: 0,
      input: [],
      state: QuizState.reviewing,
      current: this.state.results[0]
    });
  };
  private onReset = () => {
    const results = this.getInitialResults();

    this.props.quiz.sortQuestions();

    this.setState({
      results,
      state: this.props.quiz.autoStart ? QuizState.questions : QuizState.start,
      currentIndex: 0,
      input: [],
      current: results[0]
    });
  };
}
