import tkinter as tk
import tkinter.messagebox as tk_msg
import time as t


class MyApp:
    MSG_AGAIN = "Your score : {}\nYou found {} words.\n" \
                "Do you want to play again ?"

    def __init__(self, window, board, game, time):
        """
        Initialize the class
        :param window: the window object
        :param board: the board of the game
        :param game: the game object
        :param time: the time of a game
        """
        self.__window = window
        self.__create_window()
        self.__board = board
        self.__game = game
        self.__buttons = {}
        self.__word = ''
        self.__play_again = True
        self.__exit_window = False
        self.__pause_on_off = tk.BooleanVar(value=True)
        self.__time_game = tk.IntVar(value=time)
        self.__options_frame, self.__home_frame = tk.Frame(), tk.Frame()
        self.__birth, self.__bg_button, self.__button_play = None, None, None

    # GRAPHICS

    def __create_window(self):
        """
        Create the window
        """
        self.__window.minsize(width=600, height=450)
        self.__window.resizable(width=False, height=False)
        self.__bg_image = tk.PhotoImage(file='data/background.png')
        self.__main_can = tk.Canvas(self.__window, bg='black')
        self.__main_can.pack(fill=tk.BOTH, expand=True)
        self.__window.title("The Boggle Game")
        self.__window.protocol("WM_DELETE_WINDOW", self.__exit)
        self.__window.iconbitmap('@data/logo_final.xbm')

    # THE HOME SCREEN

    def __create_home_frame(self):
        """
        Create the home frame
        """
        self.__home_frame = tk.Frame(self.__main_can, bg='black')
        self.__frame_buttons = tk.Frame(self.__home_frame, bg='black')
        self.__home_frame.pack(expand=True, side=tk.TOP)
        self.__frame_buttons.pack(expand=True, side=tk.BOTTOM)

    def __create_home_lbl(self):
        """
        Create the home label
        """
        self.__home_title = tk.Label(self.__home_frame, text='The Boggle Game',
                                     font=('Arial', 52, 'italic'), bg='black',
                                     fg='#C8C8C8')
        self.__home_subtitle = tk.Label(self.__home_frame,
                                        text='by Avy and Chalom',
                                        font=('Arial', 16, 'italic'),
                                        bg='black', fg='#D6D6D6')
        self.__home_space = tk.Label(self.__home_frame, text='\n' * 5,
                                     bg='black')
        self.__home_title.pack()
        self.__home_subtitle.pack()
        self.__home_space.pack()

    def __create_home_buttons(self):
        """
        Create the home buttons
        """
        self.__bg_button_play = tk.PhotoImage(file='data/bg-button.png')
        self.__bg_button_options = tk.PhotoImage(file='data/options.png')
        self.__button_play = tk.Button(self.__frame_buttons, width=200,
                                       height=100, text='PLAY', fg='white',
                                       bg='black', image=self.__bg_button_play,
                                       anchor=tk.NW,
                                       command=lambda:
                                       self.play(self.__pause_on_off.get(),
                                                 self.__time_game.get()))
        self.__button_options = tk.Button(self.__frame_buttons, width=200,
                                          height=100, text='PLAY', fg='white',
                                          bg='black',
                                          image=self.__bg_button_options,
                                          anchor=tk.NW, command=self.__options)
        self.__button_play.pack(side=tk.LEFT)
        self.__button_options.pack()

    def __create_home_screen(self):
        """
        Main function of the home screen creation
        """
        self.__create_home_frame()
        self.__create_home_lbl()
        self.__create_home_buttons()

    # THE PARAMETERS SCREEN

    def __create_options_frame(self):
        """
        Create the parameters frames
        """
        self.__options_frame = tk.Frame(self.__main_can, bg='black')
        self.__options_frame.pack(expand=True)

    def __create_options_lbl(self):
        """
        Create the parameters labels
        """
        self.__options_title = tk.Label(self.__options_frame, text='Parameters',
                                        font=('Arial', 35, 'italic'),
                                        bg='black', fg='#C8C8C8')
        self.__options_space1 = tk.Label(self.__options_frame, text='\n' * 4,
                                         bg='black')
        self.__options_space2 = tk.Label(self.__options_frame, text='\n',
                                         bg='black', font=('Arial', 1))
        self.__options_space3 = tk.Label(self.__options_frame, text='\n' * 3,
                                         bg='black')
        self.__enter_time_lbl = tk.Label(self.__options_frame,
                                         text='Enter the time of a game (in '
                                              'seconds) :', bg='black',
                                         fg='white', font=('Arial', 10))
        self.__options_title.pack(side=tk.TOP)
        self.__options_space1.pack()

    def __create_options_others(self):
        """
        Create the parameters button, checkbox and entry zone
        """
        self.__check_pause = tk.Checkbutton(self.__options_frame,
                                            text='Activate the pause during the'
                                                 ' game',
                                            bg='black', fg='white',
                                            variable=self.__pause_on_off,
                                            font=('Arial', 10),
                                            activebackground='black',
                                            activeforeground='white',
                                            selectcolor="black")
        self.__enter_time = tk.Entry(self.__options_frame,
                                     textvariable=self.__time_game,
                                     bg='black', fg='white', font=('Arial', 10),
                                     justify='center')
        self.__save_button = tk.Button(self.__options_frame,
                                       text='Save and exit',
                                       bg='black', fg='white',
                                       font=('Arial', 15),
                                       command=lambda:
                                       self.home(self.__pause_on_off.get(),
                                                 self.__time_game.get()))
        self.__check_pause.pack(side=tk.TOP)
        self.__options_space2.pack()
        self.__enter_time_lbl.pack()
        self.__enter_time.pack()
        self.__options_space3.pack()
        self.__save_button.pack()

    def __create_options_screen(self):
        """
        Main function of the parameters screen creation
        """
        self.__create_options_frame()
        self.__create_options_lbl()
        self.__create_options_others()

    # THE GAME SCREEN

    def __create_game_frame(self):
        """
        Create the game frames
        """
        self.__main_can.delete(tk.ALL)
        self.__main_can.create_image(0, 0, image=self.__bg_image, anchor=tk.NW)
        self.__main_can.pack(fill=tk.BOTH, expand=True)
        self.__frame_left = tk.LabelFrame(self.__main_can, bd=10, bg='black')
        self.__frame_right = tk.LabelFrame(self.__main_can, bg='black', bd=20,
                                           labelanchor=tk.N)
        self.__frame_right.config(labelwidget=tk.Label(text='Founded Words',
                                                       font=('Arial', 10),
                                                       bg='black', fg='white'))
        self.__frame_center = tk.LabelFrame(self.__main_can, bg='black', bd=10,
                                            relief='sunken')
        self.__frame_c_top = tk.Frame(self.__frame_center, bg='black')
        self.__frame_c_center = tk.Frame(self.__frame_center, bg='black')
        self.__frame_c_bottom = tk.Frame(self.__frame_center, bg='black')
        self.__frame_left.pack(side=tk.LEFT, expand=True)
        self.__frame_center.pack(side=tk.LEFT, expand=True)
        self.__frame_right.pack(side=tk.LEFT, expand=True)
        self.__frame_c_top.pack()
        self.__frame_c_center.pack()
        self.__frame_c_bottom.pack()

    def __create_game_labels(self):
        """
        Create the game labels
        """
        self.__txt = tk.Label(self.__frame_c_center, bg='black',
                              font=('Arial', 10), fg='white')
        self.__answer = tk.Label(self.__frame_c_center, bg='black',
                                 font=('Arial', 10), fg='white')
        self.__time_lbl = tk.Label(self.__frame_left, text='Time :\n', width=15,
                                   bg='black', font=('Arial', 10), fg='white')
        self.__lbl = tk.Label(self.__frame_left, text='\n' * 3, bg='black')
        self.__score_lbl = tk.Label(self.__frame_left, text='Score :\n' + str(
                                    self.__game.get_score()), width=15,
                                    bg='black', font=('Arial', 10), fg='white')
        self.__list_words = tk.Label(self.__frame_right, text='\n', width=15,
                                     bg='black', font=('Arial', 10), fg='white')
        self.__txt.pack()
        self.__answer.pack()
        self.__time_lbl.pack()
        self.__lbl.pack()
        self.__score_lbl.pack()
        self.__list_words.pack()

    def __create_game_buttons_letters(self):
        """
        Create the buttons of the board
        """
        self.__img = []
        for line in range(len(self.__board)):
            for letter in range(len(self.__board[line])):
                txt = self.__board[line][letter]
                self.__img.append(tk.PhotoImage(file=f'data/image-of-{txt}.png'
                                                ))
                button = tk.Button(self.__frame_c_top, width=40, height=40,
                                   image=self.__img[-1], bg='black',
                                   text=txt.upper())
                button.grid(row=line, column=letter)
                self.__buttons[button] = (letter, line)

    def __create_game_buttons_others(self):
        """
        Create the other game buttons
        """
        self.__validate_img = tk.PhotoImage(file="data/validate-button.png")
        self.__validate_button = tk.Button(self.__frame_c_bottom,
                                           image=self.__validate_img,
                                           width=30, height=30,
                                           command=self.__validate, bg='black')
        self.__validate_button.pack(side=tk.BOTTOM)
        self.__img_left = tk.PhotoImage(file='data/left.png')
        self.__img_right = tk.PhotoImage(file='data/right.png')
        self.__turn_left_btn = tk.Button(self.__frame_c_bottom,
                                         image=self.__img_left,
                                         command=self.__turn_left, bg='black')
        self.__turn_right_btn = tk.Button(self.__frame_c_bottom,
                                          image=self.__img_right,
                                          command=self.__turn_right, bg='black')
        self.__turn_left_btn.pack(side=tk.LEFT)
        self.__turn_right_btn.pack(side=tk.RIGHT)

    def __create_menu(self):
        """
        Create the menu cascade on the game screen
        """
        menu = tk.Menu(self.__window)
        options = tk.Menu(menu, tearoff=0)
        options.add_command(label="New Game", command=self.__replay)
        if self.__pause_on_off.get():
            options.add_command(label="Pause", command=self.__pause)
        options.add_command(label="Return to menu", command=self.__return_menu)
        menu.add_cascade(label="Menu", menu=options)
        self.__window.config(menu=menu)

    def __play_lightning(self):
        """
        Play the gif of the lightning
        """
        for i in range(0, 45, 2):
            self.__lightning = tk.PhotoImage(file='data/lightning.gif',
                                             format='gif -index %i' % i)
            self.__main_can.create_image(0, 0, image=self.__lightning,
                                         anchor=tk.NW)
            self.__main_can.pack(fill=tk.BOTH, expand=True)
            self.__main_can.update()
        t.sleep(0.1)

    def __create_game_screen(self):
        """
        The main function of the creation of game screen
        """
        self.__play_lightning()
        self.__create_game_frame()
        self.__create_game_buttons_letters()
        self.__create_game_buttons_others()
        self.__create_game_labels()
        self.__create_menu()

    # GAME OPTIONS

    def __define_corners(self):
        """
        Defines the images of the corners buttons
        :return: the 4 images
        """
        img_ul, img_ur, img_dl, img_dr = None, None, None, None
        for button in self.__buttons:
            if self.__buttons[button] == (0, 0):
                img_ul = button['image']
            elif self.__buttons[button] == (3, 0):
                img_ur = button['image']
            elif self.__buttons[button] == (0, 3):
                img_dr = button['image']
            elif self.__buttons[button] == (3, 3):
                img_dl = button['image']
        return img_ul, img_ur, img_dl, img_dr

    def __set_buttons_black(self):
        """
        Set all the buttons black
        """
        self.__black_image = tk.PhotoImage(file='data/black image.png')
        for button in self.__buttons:
            button.config(image=self.__black_image)

    def __pause(self):
        """
        Do a pause in the game
        """
        time = t.time()
        birth = self.__birth
        self.__time_lbl.config(text="Time:\n PAUSE")
        self.__birth = None
        self.__word = ''
        self.__set_buttons_black()
        msg = tk_msg.showinfo("Pause", "Click 'OK' to return to the game")
        if msg == 'ok':
            passed_time = t.time() - time
            self.__birth = birth + passed_time
            self.__create_game_buttons_letters()
            self.__loop()

    def __animation(self, direction):
        """
        Do an animation, to turn the board
        :param direction: left or right
        """
        img_ul, img_ur, img_dl, img_dr = self.__define_corners()
        for i in range(4):
            l_iter, r_iter = i, i
            if direction == 'left':
                r_iter = 0
            else:
                l_iter = 0
            self.__set_buttons_black()
            for button in self.__buttons:
                if self.__buttons[button] == (0 + r_iter, 0 + l_iter):
                    button.config(image=img_ul)
                    button.update()
                if self.__buttons[button] == (3 - l_iter, 0 + r_iter):
                    button.config(image=img_ur)
                    button.update()
                if self.__buttons[button] == (0 + l_iter, 3 - r_iter):
                    button.config(image=img_dr)
                    button.update()
                if self.__buttons[button] == (3 - r_iter, 3 - l_iter):
                    button.config(image=img_dl)
                    button.update()
            t.sleep(0.2)

    def __reverse_board(self):
        """
        Reverse the board on the left
        :return: The new board
        """
        new_board = []
        for letter in range(4):
            lst_letters = []
            for line in self.__board:
                lst_letters.append(line[-1 - letter])
            new_board.append(lst_letters)
        self.__board = new_board

    def __turn_left(self):
        """
        Turn the game board on the left and play the animation
        """
        self.__word = ''
        self.__reverse_board()
        self.__animation('left')
        self.__create_game_buttons_letters()

    def __turn_right(self):
        """
        Turn the game board on the right and play the animation
        """
        self.__board = self.__board[::-1]
        self.__reverse_board()
        self.__board = self.__board[::-1]
        self.__animation('right')
        self.__create_game_buttons_letters()

    def __return_menu(self):
        """
        Return to the home screen
        """
        self.__play_again = False
        self.__window.destroy()

    def __end(self):
        """
        Finish the game and ask if play again
        """
        score = self.__game.get_score()
        founded = len(self.__game.get_founded_words())
        play = tk_msg.askquestion("GAME OVER",
                                  self.MSG_AGAIN.format(score, founded))
        self.__play_again = (play == 'yes')
        self.__window.destroy()

    def __exit(self):
        """
        Close the window
        """
        ask = tk_msg.askokcancel('Exit', 'Are you sure to quit the game ?')
        if ask:
            self.__exit_window = True
            self.__window.destroy()

    def __replay(self):
        """
        Start a new game
        """
        self.__play_again = True
        self.__window.destroy()

    # THE GAME

    def __check_time_int(self):
        """
        Check if the time is an int
        """
        try:
            if int(self.__time_game.get()) > 0:
                return True
            else:
                tk_msg.showerror('ERROR',
                                 'Sorry, the time has to be positive')
        except ValueError:
            tk_msg.showerror('ERROR', 'Sorry, the time has to be an integer')
        return False

    def __options(self):
        """
        Open the parameters screen
        :return:
        """
        self.__home_frame.destroy()
        self.__create_options_screen()

    def __validate(self):
        """
        Check if a word proper, if yes add points to score and reset the board
        """
        if self.__game.check_and_update(self.__word) == -1:
            self.__answer.configure(text='You already found this word !',
                                    fg='orange')
        elif self.__game.check_and_update(self.__word):
            self.__list_words['text'] += str(self.__word) + '\n'
            self.__answer.configure(text='YOU FOUND A WORD !', fg='green')
        else:
            self.__answer.configure(text='Try again !', fg='red')
        self.__word = ''
        self.__score_lbl.configure(
                         text='Score :\n' + str(self.__game.get_score()))
        self.__create_game_buttons_letters()

    def __check_letter(self, button, old_button):
        """
        Check if the selected letter may be selected or not
        :param button: The selected button
        :param old_button: The last button selected
        :return: True or False
        """
        new_coordinate = self.__buttons[button]
        old_coordinate = self.__buttons[old_button]
        if old_coordinate == new_coordinate:
            return False
        if old_coordinate[0] - 1 <= new_coordinate[0] <= old_coordinate[0] + 1 \
                and old_coordinate[1] - 1 <= new_coordinate[1] <= \
                old_coordinate[1] + 1:
            return True
        return False

    def __select_letter(self, word, letter, button, old_button):
        """
        Select or not a button, according to his position
        :param word: The current word
        :param letter: The selected letter
        :param button: The selected button
        :param old_button: The last button selected
        """
        if self.__check_letter(button, old_button):
            old_button.configure(text='')
            self.__chose_next_letter(word + letter, button)
        else:
            self.__chose_next_letter(word, old_button)

    def __chose_next_letter(self, word, old_button):
        """
        Give the option to select a button
        :param word: The current word
        :param old_button: The last selected button
        """
        old_button.configure(state='disabled')
        self.__word = word
        self.__txt.configure(text=self.__word)
        for button in self.__buttons:
            button.bind('<Button-1>',
                        lambda x, letter=button['text'], button_to_send=button:
                        self.__select_letter(word, letter, button_to_send,
                                             old_button))

    def __chose_first_letter(self, word):
        """
        Give the option to select the first button
        :param word: The current word
        """
        self.__word = word
        self.__txt.configure(text=self.__word)
        for button in self.__buttons:
            button.bind('<Button-1>',
                        lambda x, letter=button['text'], button_to_send=button:
                        self.__chose_next_letter(word + letter, button_to_send))

    def __time(self):
        """
        Update the time left
        :return: True if it lefts time, False if not
        """
        time_of_game = self.get_time()
        time = time_of_game - int(t.time() - self.__birth)
        if time < 0:
            self.__word = ''
            return False
        else:
            second = str(time % 60)
            minutes = str(time // 60)
            if len(second) < 2:
                second = '0' + str(second)
            self.__time_lbl.configure(
                text='Time :\n' + str(minutes) + ':' + str(second))
            return True

    def __loop(self):
        """
        The loop function, run the game
        """
        if self.__birth:
            if self.__time():
                if len(self.__word) < 1:
                    self.__chose_first_letter(self.__word)
                self.__window.after(100, self.__loop)
            else:
                self.__end()

    # CALLABLE FUNCTIONS

    def play(self, pause, time_of_game):
        """
        Run one game
        :param pause: Boolean, if the pause activate or not
        :param time_of_game: Int, the time of a game
        """
        self.__pause_on_off = tk.BooleanVar(value=pause)
        self.__time_game = tk.IntVar(value=time_of_game)
        self.__birth = t.time()
        self.__home_frame.destroy()
        self.__create_game_screen()
        self.__loop()

    def home(self, pause, time_of_game):
        """
        Display the home screen
        :param pause: Boolean, if the pause activate or not
        :param time_of_game: Int, the time of a game
        """
        if self.__check_time_int():
            self.__pause_on_off = tk.BooleanVar(value=pause)
            self.__time_game = tk.StringVar(value=time_of_game)
            self.__options_frame.destroy()
            self.__create_home_screen()

    def get_play_again(self):
        """
        :return: True or False, the value of play_again
        """
        return self.__play_again

    def get_time(self):
        """
        :return: Int, the value of time_game
        """
        return self.__time_game.get()

    def get_pause(self):
        """
        :return: True or False, the value of pause_on_off
        """
        return self.__pause_on_off.get()

    def get_end(self):
        """
        :return: True or False, the value of exit_window
        """
        return self.__exit_window
