from colorama import init, Fore

#This class will be used to load the flashcards from the file and includes all associated methods
class FlashCardLoader():
    def __init__(self, initial_filepath):
        self.__cards = [] #List that will store the flashcards after loading
        self.__filepath = initial_filepath #Filepath for the text file that stores the questions and answers
        self.__delimiter = "," #Delimiter that is used in the file to separate questions from answers

    def load_cards(self): #This method will load the cards from our text file
        try: #Here we're using exception handling - this enables us to catch potential runtime errors before they happen
            with open(self.__filepath, "r") as file: #This is a set construct used to read a file (without having to use file.close() afterwards)
                self.__cards = file.read().splitlines() #The file is read and each 'card' is put into a separate index in the cards list
                for i in range(0,len(self.__cards)): #We use a for loop to iterate through the cards list
                    current_card = self.__cards[i] #We store the current card in a variable for easier access
                    #We split the current card into question and answer using the delimiter and update the cards list accordingly
                    self.__cards[i] = current_card.strip().split(self.__delimiter) #We use the strip method also to remove any leading or trailing whitespace from each card
            print(self.__cards) #For testing purposes we print out the cards list to verify it's loaded correctly
        except FileNotFoundError: #Here we catch the error if the file isn't found and define what to do next
            print("Flashcards file not found.") #We just simply print out a relevant statement in this case

    def get_cards(self): #An accessor method is used here to retrieve the cards list from the class
        return self.__cards

    def get_filepath(self): #An accessor method is used here to retrieve the file path from the class
        return self.__filepath

    def get_delimiter(self): #An accessor method is used here to retrieve the delimiter from the class
        return self.__delimiter

    def set_delimiter(self, new_delimiter): #A mutator method is used here to change the delimiter used within the class
        self.__delimiter = new_delimiter
        
    def set_filepath(self, new_filepath): #A mutator method is used here to change the file path used within the class
        self.__filepath = new_filepath

#This class will be used for the main flashcard logic and will encompass the following functions:
"""
1. Looping through flashcards when revising
2. Quizzing yourself on existing flashcards
3. Adding new flashcards temporarily (to the list at runtime but not to the saved text file)
4. Evaluating answers from the flashcards quiz
5. Tracking your score when attempting a quiz
"""
class FlashCardEngine():
    def __init__(self):
        self.__cards = [] #Store the cards that are loaded in a list
        self.__user_choice = 0 #Update this when the user choice input is taken
        self.__score = 0 #Store the score for the quiz
        #Use a dictionary to store the menu options that the user can select
        self.__menu = {1: "1. Loop through flashcards", 2: "2. Add new flashcards temporarily", 3: "3. Quiz yourself on the current flashcards", 4: "4. Quit"}

    #Method to display the different menu items
    def display_menu(self):
        for item in self.__menu.items(): #Uses a for loop and dictionary items method to loop through the menu
            print(item[1]) #print the relevant menu item

    #Method to take user input and verify it
    def get_user_choice(self):
        print("Choose an option from the menu above")
        max_choice = max(list(self.__menu.keys())) #Uses the max, list and dictionary keys method to find the highest choice number available
        choice = input(f"Enter your choice as a number between 1 and {max_choice}: ") #Takes the choice as a number between 1 and max_choice
        choice_valid = self.check_user_choice(choice) #Calls the below method to check if the user choice is a valid number
        if choice_valid: #An if statement to activate when the choice is valid
            self.__user_choice = int(choice) #sets the user_choice attribute that's mentioned in class constructor
            return True #returns True to signify that the choice is valid
        else: #If the choice isn't valid - handles it likewise
            print("Please enter a valid choice") #Issues a user warning via print statement
            return False #returns False to signify that the choice isn't valid

    #Method to check if the user's choice is a valid number
    def check_user_choice(self, choice):
        try: #Here we use exception handling once again - we need to check if the choice is a number
            user_choice = int(choice) #To achieve this we use type casting (explored in episode 2 or 3) and convert it to a number
        except ValueError: #If the type casting failed we'll receive this error and can catch it
            return False #We return False if it can't be converted to signify that the choice is invalid
        # Now we need to check if the choice is part of our available options - we can use the menu dictionary for that
        if user_choice not in list(self.__menu.keys()): #Specifically the .keys method allows us to check the menu options
            return False #We return False as even though the number conversion check passed, the menu option is invalid e.g. 5
        else: #Otherwise, all checks have passed and we can assume the user entered a correct number for their menu choice
            return True #We return True to signify that the user input is valid and can then progress to setting the user_choice attribute (in the above method)

    #Method to call the different menu options depending on the user's choice
    def use_menu(self):
        if self.__user_choice == 1: #If the choice is 1, we activate this statement
            self.display_flashcards() #We can call the method to display the flashcards (defined below)
        elif self.__user_choice == 2: #If the choice is 2, we activate this statement
            self.add_new_flashcards() #We can call the method to add new flashcards (defined below)
        elif self.__user_choice == 3: #If the choice is 3, we activate this statement
            self.attempt_quiz() #We can call the method to attempt the flashcards quiz (defined below)
        else: #Otherwise we know that the user wants to quit - as there's no other options - for more options, more statements could be added as above
            print(Fore.YELLOW + "Thanks for using the Python Programmer Flashcards System!")
            quit() #This method stops the programme from running and ends the process automatically

    #Method to display all flashcards currently stored in memory
    def display_flashcards(self):
        for card in self.__cards: #We use a for loop to read all the flashcards from the cards list (stored as private attribute)
            print(f"Question: {card[0]}") #Prints out the question (stored as the first part of the flashcard)
            input("Press Enter to view answer: ") #We allow the user to press enter before viewing the answer instead of giving it straight away
            print(f"Answer: {card[1]}") #Prints out the answer of the flashcard (stored as the second part)

    #Method that allows users to add new flashcards temporarily
    def add_new_flashcards(self):
        cards_to_add = 0 #We set this variable to 0 to begin with
        try: #Once again we use exception handling - this time to make sure that cards_to_add is a number that can be used in the for loop
            cards_to_add = int(input("Enter the number of flashcards to add to the list: ")) #We take this as user input and cast to integer directly
        except ValueError: #If a ValueError occurs (i.e. it's not an integer) then we can handle it here
            print("Please enter a valid integer.")
            self.add_new_flashcards() #Here we call this method again to take the input again and then progress
        for num in range(cards_to_add): #We use a for loop to keep allowing the user to enter new cards for as many cards as they said before
            print("Question",num+1) #We print the question number that the user is adding (e.g. 1 if on the first one)
            question = input("Enter your question: ") #We use a user input to get the question
            answer = input("Now enter your answer: ") #Aother user input is used to get the answer to the question
            self.__cards.append([question, answer]) #The question and answer are appended to the cards list
        if cards_to_add != 0: #If there were cards added then we can proceed with this part
            print(f"All {cards_to_add} flashcards have been added to the list.")
            view_list = input("Do you wish to view the full cards list now (Y/N)? ") #We allow the user to view the cards list via user input
            if view_list.lower() == "y": #If they chose to view the cards we can progress with this part
                print("All cards in the list are displayed below:")
                for card in self.__cards: #We use a for loop to read the cards list
                    print(f"Question: {card[0]}") #We print out the question as the first part of the flashcard
                    print(f"Answer: {card[1]}") #We print out the answer as teh second part of the flashcard
        else: #Otherwise there either weren't any cards added or there was an error and we can handle that here
            print("No flashcards added")

    #Method to allow the user to quiz themselves on the flashcards
    def attempt_quiz(self):
        #This will mostly be where we will use the special colours from colorama to make it look nice
        self.__score = 0 #We set the score to 0 first for every new quiz
        for card in self.__cards: #We use a for loop to read the cards for the quiz
            print(f"Question: {card[0]}") #We print out the question first (as first part of the card)
            answer = input("Enter your answer: ") #Then we take the user's answer as user input
            if answer.lower() == card[1].lower(): #If the answer matches (we use .lower to ignore the case) we can progress here
                print(Fore.GREEN + "Correct! " + card[1] + " is the correct answer.")
                self.__score += 1 #We increase the score by 1
            else: #If the answer is incorrect we can progress here
                print(Fore.RED + "Incorrect. " + card[1] + " is the actual correct answer.")
            print("Your current score is:",self.__score) #After each question we print the score
        #After the quiz finishes we can print this 
        print(f"Quiz finished! You scored {self.__score} out of {len(self.__cards)} flashcards!")

    #Method to get the current score (accessor method)
    def get_score(self):
        return self.__score

    #Method to get the current cards list (accessor method)
    def get_cards(self):
        return self.__cards

    #Method to set the current cards list (mutator method)
    def set_cards(self, new_cards):
        self.__cards = new_cards

# Main programme logic to run the flashcards system
def main():
    init(autoreset=True) #Initialises colorama with autoreset to avoid color issues in terminal
    print(Fore.CYAN + "Welcome to the Python Programmer Flashcards System!") #Prints a welcome message in cyan color
    flashcard_loader = FlashCardLoader("flashcards.txt") #Creates an instance of the FlashCardLoader class with the specified file path
    flashcard_loader.load_cards() #Calls the load_cards method to load the flashcards from the file
    cards = flashcard_loader.get_cards() #Retrieves the loaded cards using the accessor method
    flashcard_engine = FlashCardEngine() #Creates an instance of the FlashCardEngine class
    flashcard_engine.set_cards(cards) #Sets the loaded cards into the flashcard engine using the mutator method
    while True: #Main loop to keep displaying the menu until the user decides to quit
        flashcard_engine.display_menu() #Displays the menu options
        choice_valid = flashcard_engine.get_user_choice() #Gets and validates the user's choice
        if choice_valid: #If the choice is valid, proceed to use it
            flashcard_engine.use_menu() #Calls the method to execute the chosen menu option

# Run the main programme logic
if __name__ == "__main__":
    main()

"""
Ok, so now we've got a fully working app.
However, there's still one little part left. 
Remember we pip installed colorama at the start of this episode.
Now we'll add different colors to our print statements to make the app look a bit more visually appealing.
"""

