"""Universal quiz system supporting multiple question types."""

from __future__ import annotations

import json
import random
from dataclasses import dataclass, field
from enum import Enum
from functools import cached_property
from pathlib import Path
from typing import Any


class QuestionType(Enum):
    """Supported question types."""

    MULTIPLE_CHOICE = "multiple_choice"
    FILL_BLANK = "fill_blank"
    TRUE_FALSE = "true_false"
    ESSAY = "essay"


@dataclass
class Question:
    """Base question data structure."""

    question_id: str
    question_type: QuestionType
    question_text: str
    points: float = 1.0

    def check_answer(self, answer: Any) -> tuple[bool, str]:
        """Check if answer is correct. Returns (is_correct, explanation)."""
        raise NotImplementedError

    def to_dict(self) -> dict[str, Any]:
        """Convert question to dictionary format."""
        return {
            "question_id": self.question_id,
            "question_type": self.question_type.value,
            "question_text": self.question_text,
            "points": self.points,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Question:
        """Create question instance from dictionary."""
        qtype = QuestionType(data["question_type"])

        if qtype == QuestionType.MULTIPLE_CHOICE:
            return MultipleChoiceQuestion.from_dict(data)
        elif qtype == QuestionType.FILL_BLANK:
            return FillBlankQuestion.from_dict(data)
        elif qtype == QuestionType.TRUE_FALSE:
            return TrueFalseQuestion.from_dict(data)
        elif qtype == QuestionType.ESSAY:
            return EssayQuestion.from_dict(data)
        else:
            raise ValueError(f"Unknown question type: {qtype}")


@dataclass
class MultipleChoiceQuestion(Question):
    """Multiple choice question with single or multiple answers."""

    options: list[str] = field(default_factory=list)
    correct_answer: list[int] | int = field(
        default_factory=list
    )  # Index/indices of correct options
    allow_multiple: bool = False

    def check_answer(self, answer: int | list[int]) -> tuple[bool, str]:
        """Check if selected option(s) match correct answer."""
        if isinstance(answer, int):
            answer = [answer]

        if not isinstance(self.correct_answer, list):
            correct = [self.correct_answer]
        else:
            correct = self.correct_answer

        is_correct = sorted(answer) == sorted(correct)
        explanation = self._generate_explanation(answer, correct)
        return is_correct, explanation

    def _generate_explanation(self, selected: list[int], correct: list[int]) -> str:
        """Generate explanation for the answer."""
        if not self.allow_multiple:
            correct_text = self.options[correct[0]]
            if sorted(selected) == sorted(correct):
                return f"Correct! {correct_text} is the right answer."
            return f"Incorrect. The correct answer is: {correct_text}"

        correct_texts = [self.options[i] for i in correct]
        if sorted(selected) == sorted(correct):
            return f"Correct! {', '.join(correct_texts)} are the right answers."
        return f"Incorrect. The correct answers are: {', '.join(correct_texts)}"

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary format."""
        base = super().to_dict()
        base.update({
            "options": self.options,
            "correct_answer": self.correct_answer,
            "allow_multiple": self.allow_multiple,
        })
        return base

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MultipleChoiceQuestion:
        """Create instance from dictionary."""
        return cls(
            question_id=data["question_id"],
            question_type=QuestionType.MULTIPLE_CHOICE,
            question_text=data["question_text"],
            points=data.get("points", 1.0),
            options=data["options"],
            correct_answer=data["correct_answer"],
            allow_multiple=data.get("allow_multiple", False),
        )


@dataclass
class FillBlankQuestion(Question):
    """Fill in the blank question."""

    correct_answers: list[str] = field(
        default_factory=list
    )  # Multiple acceptable answers
    case_sensitive: bool = False

    def check_answer(self, answer: str) -> tuple[bool, str]:
        """Check if answer matches any correct option."""
        if not self.case_sensitive:
            answer = answer.strip().lower()
            valid_answers = [a.strip().lower() for a in self.correct_answers]
        else:
            answer = answer.strip()
            valid_answers = [a.strip() for a in self.correct_answers]

        is_correct = answer in valid_answers
        if is_correct:
            return True, f"Correct! '{answer}' is the right answer."
        return False, f"Incorrect. Valid answers are: {', '.join(self.correct_answers)}"

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary format."""
        base = super().to_dict()
        base.update({
            "correct_answers": self.correct_answers,
            "case_sensitive": self.case_sensitive,
        })
        return base

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> FillBlankQuestion:
        """Create instance from dictionary."""
        return cls(
            question_id=data["question_id"],
            question_type=QuestionType.FILL_BLANK,
            question_text=data["question_text"],
            points=data.get("points", 1.0),
            correct_answers=data["correct_answers"],
            case_sensitive=data.get("case_sensitive", False),
        )


@dataclass
class TrueFalseQuestion(Question):
    """True/False question."""

    correct_answer: bool = True

    def check_answer(self, answer: bool | str) -> tuple[bool, str]:
        """Check if answer matches correct value."""
        if isinstance(answer, str):
            answer_lower = answer.strip().lower()
            if answer_lower in ("true", "t", "yes", "y"):
                answer = True
            elif answer_lower in ("false", "f", "no", "n"):
                answer = False
            else:
                return False, "Invalid input. Please enter True or False."

        is_correct = answer == self.correct_answer
        correct_text = "True" if self.correct_answer else "False"
        if is_correct:
            return True, f"Correct! The statement is {correct_text}."
        return False, f"Incorrect. The statement is {correct_text}."

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary format."""
        base = super().to_dict()
        base.update({"correct_answer": self.correct_answer})
        return base

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> TrueFalseQuestion:
        """Create instance from dictionary."""
        return cls(
            question_id=data["question_id"],
            question_type=QuestionType.TRUE_FALSE,
            question_text=data["question_text"],
            points=data.get("points", 1.0),
            correct_answer=data["correct_answer"],
        )


@dataclass
class EssayQuestion(Question):
    """Essay question requiring detailed answer."""

    model_answer: str = ""
    keywords: list[str] = field(
        default_factory=list
    )  # Optional keywords for validation

    def check_answer(self, answer: str) -> tuple[bool, str]:
        """Check if answer contains key points (soft validation)."""
        answer_lower = answer.lower()

        if self.keywords:
            matched_keywords = [
                kw for kw in self.keywords if kw.lower() in answer_lower
            ]
            keyword_count = len(self.keywords)
            matched_count = len(matched_keywords)

            if matched_count >= keyword_count * 0.5:  # At least 50% of keywords
                return (
                    True,
                    f"Good answer! You covered {matched_count}/{keyword_count} key points: {', '.join(matched_keywords)}",
                )
            return (
                False,
                f"Your answer is incomplete. Consider including: {', '.join(self.keywords)}",
            )

        return True, "Answer recorded. Review model answer for comparison."

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary format."""
        base = super().to_dict()
        base.update({
            "model_answer": self.model_answer,
            "keywords": self.keywords,
        })
        return base

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> EssayQuestion:
        """Create instance from dictionary."""
        return cls(
            question_id=data["question_id"],
            question_type=QuestionType.ESSAY,
            question_text=data["question_text"],
            points=data.get("points", 1.0),
            model_answer=data.get("model_answer", ""),
            keywords=data.get("keywords", []),
        )


@dataclass
class QuizResult:
    """Result of answering a question."""

    question: Question
    user_answer: Any
    is_correct: bool
    explanation: str

    def to_dict(self) -> dict[str, Any]:
        """Convert result to dictionary."""
        return {
            "question": self.question.to_dict(),
            "user_answer": self.user_answer,
            "is_correct": self.is_correct,
            "explanation": self.explanation,
        }


@dataclass
class QuizSession:
    """Manage a quiz session with multiple questions."""

    questions: list[Question] = field(default_factory=list)
    random_order: bool = False
    wrong_answer_file: str = "wrong_answers.json"
    results: list[QuizResult] = field(default_factory=list)
    _current_index: int = 0
    _shuffled_indices: list[int] = field(default_factory=list)
    # Flag to control adaptive behavior (default False for regular QuizSession)
    adaptive_mode: bool = False

    @cached_property
    def total_questions(self) -> int:
        """Get total number of questions."""
        return len(self.questions)

    @cached_property
    def correct_count(self) -> int:
        """Get number of correct answers."""
        return sum(1 for r in self.results if r.is_correct)

    @cached_property
    def wrong_count(self) -> int:
        """Get number of wrong answers."""
        return sum(1 for r in self.results if not r.is_correct)

    @cached_property
    def total_points(self) -> float:
        """Get total possible points."""
        return sum(q.points for q in self.questions)

    @cached_property
    def earned_points(self) -> float:
        """Get points earned."""
        return sum(r.question.points for r in self.results if r.is_correct)

    @cached_property
    def accuracy(self) -> float:
        """Calculate accuracy percentage."""
        if len(self.results) == 0:
            return 0.0
        return (self.correct_count / len(self.results)) * 100

    def load_from_json(self, json_file: str | Path) -> None:
        """Load questions from JSON file."""
        with open(json_file, encoding="utf-8") as f:
            data = json.load(f)

        self.questions = [Question.from_dict(q) for q in data.get("questions", [])]
        self._initialize_order()

    def _initialize_order(self) -> None:
        """Initialize question order based on settings."""
        if self.random_order:
            self._shuffled_indices = list(range(self.total_questions))
            random.shuffle(self._shuffled_indices)
        else:
            self._shuffled_indices = list(range(self.total_questions))

    def get_current_question(self) -> Question | None:
        """Get current question."""
        if self._current_index >= len(self._shuffled_indices):
            return None
        idx = self._shuffled_indices[self._current_index]
        return self.questions[idx]

    def submit_answer(self, answer: Any) -> QuizResult | None:
        """Submit answer for current question."""
        question = self.get_current_question()
        if question is None:
            return None

        is_correct, explanation = question.check_answer(answer)
        result = QuizResult(
            question=question,
            user_answer=answer,
            is_correct=is_correct,
            explanation=explanation,
        )
        self.results.append(result)
        self._current_index += 1
        return result

    def is_finished(self) -> bool:
        """Check if quiz is finished."""
        return self._current_index >= len(self._shuffled_indices)

    def get_wrong_answers(self) -> list[QuizResult]:
        """Get all wrong answers."""
        return [r for r in self.results if not r.is_correct]

    def save_wrong_answers(self, file_path: str | Path | None = None) -> None:
        """Save wrong answers to JSON file."""
        wrong_answers = self.get_wrong_answers()
        if not wrong_answers:
            return

        output_path = Path(file_path) if file_path else Path(self.wrong_answer_file)
        data = {"wrong_answers": [r.to_dict() for r in wrong_answers]}
        with open(output_path, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

    def get_summary(self) -> dict[str, Any]:
        """Get quiz session summary."""
        return {
            "total_questions": self.total_questions,
            "answered": len(self.results),
            "correct": self.correct_count,
            "wrong": self.wrong_count,
            "total_points": self.total_points,
            "earned_points": self.earned_points,
            "accuracy": self.accuracy,
            "is_finished": self.is_finished(),
        }

    def reset(self, random_order: bool | None = None) -> None:
        """Reset quiz session."""
        self._current_index = 0
        self.results = []
        if random_order is not None:
            self.random_order = random_order
        self._initialize_order()


def create_sample_quiz_data(output_file: str | Path) -> None:
    """Create sample quiz data file with all question types."""
    questions = [
        {
            "question_id": "mc1",
            "question_type": "multiple_choice",
            "question_text": "What is the capital of France?",
            "options": ["London", "Paris", "Berlin", "Rome"],
            "correct_answer": 1,
            "allow_multiple": False,
            "points": 1.0,
        },
        {
            "question_id": "mc2",
            "question_type": "multiple_choice",
            "question_text": "Which of the following are programming languages? (Select all that apply)",
            "options": ["Python", "HTML", "Java", "CSS", "JavaScript"],
            "correct_answer": [0, 2, 4],
            "allow_multiple": True,
            "points": 2.0,
        },
        {
            "question_id": "fb1",
            "question_type": "fill_blank",
            "question_text": "The planet closest to the Sun is _____.",
            "correct_answers": ["Mercury"],
            "case_sensitive": False,
            "points": 1.0,
        },
        {
            "question_id": "fb2",
            "question_type": "fill_blank",
            "question_text": "The largest ocean on Earth is the _____ Ocean.",
            "correct_answers": ["Pacific", "pacific", "PACIFIC"],
            "case_sensitive": False,
            "points": 1.0,
        },
        {
            "question_id": "tf1",
            "question_type": "true_false",
            "question_text": "Python is a compiled language.",
            "correct_answer": False,
            "points": 1.0,
        },
        {
            "question_id": "tf2",
            "question_type": "true_false",
            "question_text": "The Earth revolves around the Sun.",
            "correct_answer": True,
            "points": 1.0,
        },
        {
            "question_id": "es1",
            "question_type": "essay",
            "question_text": "Explain the concept of object-oriented programming.",
            "model_answer": "Object-oriented programming (OOP) is a programming paradigm based on the concept of objects, which can contain data and code. The four main principles are encapsulation, abstraction, inheritance, and polymorphism.",
            "keywords": [
                "objects",
                "classes",
                "inheritance",
                "encapsulation",
                "polymorphism",
                "abstraction",
            ],
            "points": 5.0,
        },
    ]

    data = {"title": "General Knowledge Quiz", "questions": questions}
    output_path = Path(output_file)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)


def main():
    """Main entry point for quizbase CLI."""
    import argparse

    parser = argparse.ArgumentParser(description="Universal quiz system")
    parser.add_argument(
        "--create-sample", action="store_true", help="Create sample quiz data file"
    )
    parser.add_argument("--file", type=str, help="Quiz JSON file to load")
    parser.add_argument("--random", action="store_true", help="Shuffle questions")
    parser.add_argument(
        "--adaptive",
        action="store_true",
        help="Use adaptive question selection based on performance",
    )
    parser.add_argument(
        "--history-file",
        type=str,
        help="File to load/save user performance history",
        default="user_performance.json",
    )
    parser.add_argument("--wrong", type=str, help="Save wrong answers to file")
    args = parser.parse_args()

    if args.create_sample:
        sample_file = Path("sample_quiz.json")
        create_sample_quiz_data(sample_file)
        print(f"Sample quiz data created: {sample_file}")
        return

    if args.file:
        # Use adaptive session if adaptive flag is set
        if args.adaptive:
            session = AdaptiveQuizSession(random_order=args.random)
            session.history_file = args.history_file
            session.load_performance_history()
        else:
            session = QuizSession(random_order=args.random)

        if args.wrong:
            session.wrong_answer_file = args.wrong
        session.load_from_json(args.file)

        print(
            f"\n=== Quiz Started ({'Adaptive' if args.adaptive else 'Random' if args.random else 'Sequential'} Order) ==="
        )
        print(f"Total Questions: {session.total_questions}\n")

        while not session.is_finished():
            question = session.get_current_question()
            if question is None:
                break

            print(f"\nQuestion {len(session.results) + 1}/{session.total_questions}")
            print(f"[{question.question_type.value}] {question.question_text}")
            print(f"Points: {question.points}")

            if isinstance(question, MultipleChoiceQuestion):
                for i, option in enumerate(question.options):
                    print(f"  {i}. {option}")

            answer = input("\nYour answer: ")

            try:
                if isinstance(question, MultipleChoiceQuestion):
                    if question.allow_multiple:
                        answer = [int(x.strip()) for x in answer.split(",")]
                    else:
                        answer = int(answer)
                elif isinstance(question, TrueFalseQuestion):
                    answer = answer.strip().lower() in ("true", "t", "yes", "y")
                elif isinstance(question, EssayQuestion):
                    answer = answer
                else:
                    answer = answer

                result = session.submit_answer(answer)
                if result:
                    print(f"\n{'✓' if result.is_correct else '✗'} {result.explanation}")

            except (ValueError, IndexError) as e:
                print(f"\nInvalid input: {e}")
                continue

        # Save performance history if using adaptive session
        if args.adaptive and hasattr(session, "save_performance_history"):
            session.save_performance_history()

        session.save_wrong_answers()
        summary = session.get_summary()
        print("\n=== Quiz Summary ===")
        print(f"Answered: {summary['answered']}/{summary['total_questions']}")
        print(f"Correct: {summary['correct']} | Wrong: {summary['wrong']}")
        print(f"Points: {summary['earned_points']:.1f}/{summary['total_points']:.1f}")
        print(f"Accuracy: {summary['accuracy']:.1f}%")

        # Print performance summary if using adaptive session
        if args.adaptive and hasattr(session, "get_progress_summary"):
            print("\n=== Performance Summary ===")
            perf_summary = session.get_progress_summary()
            for qid, stats in list(perf_summary["performance_by_question"].items())[
                :5
            ]:  # Show first 5
                print(
                    f"{qid}: {stats['correct_count']} correct, {stats['incorrect_count']} incorrect, {stats['success_rate']:.2f} success rate"
                )
            if len(perf_summary["performance_by_question"]) > 5:
                print(
                    f"... and {len(perf_summary['performance_by_question']) - 5} more questions"
                )
    else:
        print(
            "Usage: quizbase --create-sample | --file <quiz.json> [--random] [--adaptive] [--history-file <file>] [--wrong <file>]"
        )


@dataclass
class QuestionPerformance:
    """Track performance statistics for a single question."""

    question_id: str
    correct_count: int = 0
    incorrect_count: int = 0
    total_attempts: int = 0

    def get_success_rate(self) -> float:
        """Get success rate (0.0 to 1.0)."""
        if self.total_attempts == 0:
            return 0.5  # Default to 50% if no attempts
        return self.correct_count / self.total_attempts

    def get_weight(self) -> float:
        """
        Calculate weight for adaptive selection.
        Lower weight for well-performed questions, higher weight for poorly performed questions.
        """
        success_rate = self.get_success_rate()
        # Invert the success rate so that poorly answered questions have higher weights
        # Add a small constant to ensure even perfectly answered questions have some chance of appearing
        return (1.0 - success_rate) + 0.1


@dataclass
class AdaptiveQuizSession(QuizSession):
    """Quiz session with adaptive question selection based on user performance."""

    # Track user performance for each question
    performance_history: dict[str, QuestionPerformance] = field(default_factory=dict)
    # File to save/load performance history
    history_file: str = "user_performance.json"
    # Flag to control adaptive behavior
    adaptive_mode: bool = True

    def __post_init__(self):
        """Initialize after dataclass fields are set."""
        if not self.performance_history:
            self.performance_history = {}

    def load_performance_history(self, file_path: str | Path | None = None) -> None:
        """Load user performance history from file."""
        path = Path(file_path) if file_path else Path(self.history_file)

        if path.exists():
            try:
                with open(path, encoding="utf-8") as f:
                    data = json.load(f)

                for qid, perf_data in data.items():
                    self.performance_history[qid] = QuestionPerformance(
                        question_id=qid,
                        correct_count=perf_data.get("correct_count", 0),
                        incorrect_count=perf_data.get("incorrect_count", 0),
                        total_attempts=perf_data.get("total_attempts", 0),
                    )
            except (json.JSONDecodeError, KeyError) as e:
                print(f"Could not load performance history: {e}")
                self.performance_history = {}
        else:
            self.performance_history = {}

    def save_performance_history(self, file_path: str | Path | None = None) -> None:
        """Save user performance history to file."""
        path = Path(file_path) if file_path else Path(self.history_file)

        data = {}
        for qid, perf in self.performance_history.items():
            data[qid] = {
                "correct_count": perf.correct_count,
                "incorrect_count": perf.incorrect_count,
                "total_attempts": perf.total_attempts,
            }

        with open(path, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

    def update_performance(self, question: Question, is_correct: bool) -> None:
        """Update performance statistics for a question."""
        qid = question.question_id

        if qid not in self.performance_history:
            self.performance_history[qid] = QuestionPerformance(question_id=qid)

        perf = self.performance_history[qid]
        perf.total_attempts += 1

        if is_correct:
            perf.correct_count += 1
        else:
            perf.incorrect_count += 1

    def get_adaptive_question_indices(self) -> list[int]:
        """Get question indices with adaptive weighting based on performance."""
        if not self.questions:
            return []

        # If we don't have performance data for all questions, use uniform distribution initially
        all_have_data = all(
            q.question_id in self.performance_history for q in self.questions
        )

        if not all_have_data:
            # Return shuffled indices for initial uniform exposure
            indices = list(range(len(self.questions)))
            random.shuffle(indices)
            return indices

        # Calculate weights for each question based on performance
        weights = []
        for _, question in enumerate(self.questions):
            perf = self.performance_history.get(question.question_id)
            weight = perf.get_weight() if perf else 0.5
            weights.append(weight)

        # Normalize weights to probabilities
        total_weight = sum(weights)
        if total_weight == 0:
            # If all weights are zero, use uniform distribution
            probabilities = [1.0 / len(weights)] * len(weights)
        else:
            probabilities = [w / total_weight for w in weights]

        # Create a weighted shuffle of indices
        indices = list(range(len(self.questions)))
        adaptive_indices = []
        remaining_indices = indices[:]
        remaining_probs = probabilities[:]

        # Use weighted selection without replacement to create order
        while remaining_indices:
            # Normalize remaining probabilities
            total_remaining_prob = sum(remaining_probs)
            if total_remaining_prob == 0:
                # Fallback to random selection if all probs are 0
                selected_idx = random.randint(0, len(remaining_indices) - 1)
            else:
                norm_probs = [p / total_remaining_prob for p in remaining_probs]
                selected_idx = random.choices(
                    range(len(remaining_indices)), weights=norm_probs
                )[0]

            # Add selected index to result and remove from remaining
            adaptive_indices.append(remaining_indices.pop(selected_idx))
            remaining_probs.pop(selected_idx)

        return adaptive_indices

    def submit_answer(self, answer: Any) -> QuizResult | None:
        """Submit answer and update performance tracking."""
        question = self.get_current_question()
        if question is None:
            return None

        is_correct, explanation = question.check_answer(answer)
        result = QuizResult(
            question=question,
            user_answer=answer,
            is_correct=is_correct,
            explanation=explanation,
        )

        # Update performance history before appending to results
        self.update_performance(question, is_correct)

        self.results.append(result)
        self._current_index += 1
        return result

    def reset(self, random_order: bool | None = None) -> None:
        """Reset quiz session while preserving performance history."""
        self._current_index = 0
        self.results = []

        if random_order is not None:
            self.random_order = random_order

        # Re-initialize order based on adaptive algorithm if enabled
        self._initialize_order()

    def _initialize_order(self) -> None:
        """Initialize question order based on settings and performance."""
        if self.adaptive_mode and self.random_order:
            # Use adaptive ordering if we have performance data, otherwise random
            self._shuffled_indices = self.get_adaptive_question_indices()
        else:
            # Use regular random or sequential ordering
            if self.random_order:
                self._shuffled_indices = list(range(self.total_questions))
                random.shuffle(self._shuffled_indices)
            else:
                self._shuffled_indices = list(range(self.total_questions))

    def get_progress_summary(self) -> dict[str, Any]:
        """Get detailed progress summary for all questions."""
        summary = {
            "total_questions": self.total_questions,
            "answered_questions": len(self.results),
            "performance_by_question": {},
            "overall_accuracy": self.accuracy,
        }

        for question in self.questions:
            perf = self.performance_history.get(question.question_id)
            if perf:
                summary["performance_by_question"][question.question_id] = {
                    "question_text": question.question_text,
                    "correct_count": perf.correct_count,
                    "incorrect_count": perf.incorrect_count,
                    "total_attempts": perf.total_attempts,
                    "success_rate": perf.get_success_rate(),
                    "weight_for_selection": perf.get_weight(),
                }
            else:
                summary["performance_by_question"][question.question_id] = {
                    "question_text": question.question_text,
                    "correct_count": 0,
                    "incorrect_count": 0,
                    "total_attempts": 0,
                    "success_rate": 0.0,
                    "weight_for_selection": 0.5,
                }

        return summary


if __name__ == "__main__":
    main()
