"""
#last edit 27/10/2023 (ver.9)

*Compleat??? Edit By NN

!------------------------------------------------------------

TODO:Mini Project Programming Fundamental KMUTNB BKK Thailand
=> By Noppasin Renruang CprE. 6601012620119

?ref. https://www.geeksforgeeks.org/how-to-find-the-index-of-value-in-numpy-array/
?ref. https://www.adamsmith.haus/python/answers/how-to-replace-elements-in-a-numpy-array-if-a-condition-is-met-in-python
?ref. https://github.com/jakevdp/PythonDataScienceHandbook
?ref. https://stackoverflow.com/questions/1345827/how-do-i-find-the-time-difference-between-two-datetime-objects-in-python

!------------------------------------------------------------
"""

from tkinter import *
from tkinter import PhotoImage
from tkinter import messagebox
from PIL import Image, ImageTk
import numpy as np
from random import choices
import datetime
import time as t

class BackEnd:
    def __init__(self, array) -> None:
        self.array = array
        self.first_time = datetime.datetime.now()
        self.lst_value = [-1, 1]
        self.round_ = 1
        self.createArray()

    def createArray(self) -> None:
        """
        The function creates a 2D array and initializes certain elements with specific values.
        """
        self.array[4, 4] = -1
        self.array[4, 5] = 1
        self.array[5, 4] = 1
        self.array[5, 5] = -1
        for i in [0, 9]:
            for j in range(10):
                self.array[i, j] = 9
                self.array[j, i] = 9

    def showNumberOfBlackWhite(self, white_value: int = -1, black_value: int = 1) -> str:
        """
        The function `showNumberOfBlackWhite` returns the count of white and black values in an array, along
        with their sum.

        :param white_value: The `white_value` parameter is an optional integer parameter that represents the
        value of white in the array. By default, it is set to -1
        :type white_value: int
        :param black_value: The `black_value` parameter is an optional integer parameter that represents the
        value used to identify black elements in the array. By default, it is set to 1, defaults to 1
        :type black_value: int (optional)
        :return: a string that displays the number of white and black values in the array, as well as the
        sum of both.
        """
        return f'White: {np.sum(self.array == white_value)}   Black: {np.sum(self.array == black_value)}'

    def getNewValueOfDirection(self, row: int, col: int, key_direction: str) -> int:
        """
        The function `getNewValueOfDirection` returns the value of a specific direction in a 2D array given
        the row, column, and direction key.

        :param row: The parameter `row` represents the row index of the element in the array
        :type row: int
        :param col: The parameter `col` represents the column index of the element in the array
        :type col: int
        :param key_direction: The `key_direction` parameter is a string that represents the direction for
        which you want to retrieve the value from the `self.array` array. The possible values for
        `key_direction` are 'NW', 'N', 'NE', 'W', 'E', 'SW', 'S', and
        :type key_direction: str
        :return: the value of the specified direction in the array.
        """
        direction_value = {
            'NW':  self.array[row - 1, col - 1], 'N': self.array[row - 1, col], 'NE': self.array[row - 1, col + 1],
            'W':   self.array[row, col - 1], 'E': self.array[row, col + 1],
            'SW':  self.array[row + 1, col - 1], 'S': self.array[row + 1, col], 'SE': self.array[row + 1, col + 1]
        }
        return direction_value[key_direction]

    def getNewRowColOfDirection(self, row: int, col: int, key_direction: str) -> tuple:
        """
        The function `getNewRowColOfDirection` takes in a current row and column position, as well as a key
        direction, and returns the new row and column position based on the given direction.

        :param row: The parameter `row` represents the current row position in a grid or matrix. It is an
        integer value
        :type row: int
        :param col: The `col` parameter represents the column index of a cell in a grid
        :type col: int
        :param key_direction: The `key_direction` parameter is a string that represents the direction in
        which you want to move. It can have one of the following values: 'NW', 'N', 'NE', 'W', 'E', 'SW',
        'S', 'SE'
        :type key_direction: str
        :return: a tuple containing the new row and column values based on the given key_direction.
        """
        direction = {
            'NW':  {'row': row - 1, 'col': col - 1},    'N': {'row': row - 1, 'col': col}, 'NE': {'row': row - 1, 'col': col + 1},
            'W':   {'row': row, 'col': col - 1},        'E': {'row': row, 'col': col + 1},
            'SW':  {'row': row + 1, 'col': col - 1},    'S': {'row': row + 1, 'col': col}, 'SE': {'row': row + 1, 'col': col + 1}
        }
        return (direction[key_direction]['row'], direction[key_direction]['col'])

    def letEat(self, row: int, col: int, key_direction: str) -> None:
        """
        The function `letEat` changes the color of adjacent cells in a grid until it reaches a cell with a
        different color.

        :param row: The row parameter represents the current row index in the array
        :type row: int
        :param col: The parameter "col" represents the column index of the current position in the array
        :type col: int
        :param key_direction: The `key_direction` parameter is a string that represents the direction in
        which the movement should occur. It can have one of the following values: "up", "down", "left", or
        "right"
        :type key_direction: str
        """
        # original value (center)
        value = self.array[row, col]
        while True:
            # change another color
            if (value != self.getNewValueOfDirection(row, col, key_direction)):
                # change color
                self.array[self.getNewRowColOfDirection(row, col, key_direction)[
                    0], self.getNewRowColOfDirection(row, col, key_direction)[1]] = value
                # move 1 direction to check
                row, col = self.getNewRowColOfDirection(
                    row, col, key_direction)
            else:
                break

    def getWayToEat(self, row: int, col: int) -> None:
        """
        The function `getWayToEat` checks all directions around a given position in a 2D array and performs
        certain actions based on the values in those directions.

        :param row: The row parameter represents the current row position in the array
        :type row: int
        :param col: The parameter `col` represents the column index of a 2D array
        :type col: int
        """
        direction = {
            'NW':  self.array[row - 1, col - 1], 'N': self.array[row - 1, col], 'NE': self.array[row - 1, col + 1],
            'W':   self.array[row, col - 1], 'E': self.array[row, col + 1],
            'SW':  self.array[row + 1, col - 1], 'S': self.array[row + 1, col], 'SE': self.array[row + 1, col + 1]
        }
        # save original row and col
        save_row, save_col = row, col
        # check all direction
        for key, value in direction.items():
            # -----------------------------------------------
            # black 1 white -1  =>True
            # empty 0           => False
            # outside map 9     => False
            # -----------------------------------------------
            if (value != 9 and value != 0):
                # set row and col
                row, col = save_row, save_col
                # save color (center)
                save_val = self.array[row, col]
                while True:
                    # print("\t", key, value, "row=>", row, "col=>", col, sep="\t")
                    # change row and col with direction to check value
                    row, col = self.getNewRowColOfDirection(row, col, key)
                    # get same color    => True
                    if save_val == self.getNewValueOfDirection(row, col, key):
                        # print()
                        # print('last position = ', 'row:', row,'col', col, '| direction:', key)
                        # print()
                        # call function to change color use reference row and col to change
                        self.letEat(save_row, save_col, key)
                        break
                    # get 0 (empty) or 9 (outside map)  => True
                    elif not (self.getNewValueOfDirection(row, col, key)) or self.getNewValueOfDirection(row, col, key) == 9:
                        break

    def getWayToEatPreview(self, row: int, col: int, value_to_check: int) -> list:
        """
        The function `getWayToEatPreview` takes in a row and column position, as well as a value to check,
        and returns a list of positions where the value can be eaten based on the surrounding values in the
        array.

        :param row: The row parameter represents the current row position in the array
        :type row: int
        :param col: The parameter "col" represents the column index of the element in the array that we want
        to check for a way to eat
        :type col: int
        :param value_to_check: The `value_to_check` parameter is the value that you want to check against in
        the `direction` dictionary. It is used to determine if a certain condition is met in the `if`
        statement inside the `for` loop
        :type value_to_check: int
        :return: a list of tuples, where each tuple represents the row and column coordinates of a position
        in the array that can be eaten.
        """
        direction = {
            'NW':  self.array[row - 1, col - 1], 'N': self.array[row - 1, col], 'NE': self.array[row - 1, col + 1],
            'W':   self.array[row, col - 1], 'E': self.array[row, col + 1],
            'SW':  self.array[row + 1, col - 1], 'S': self.array[row + 1, col], 'SE': self.array[row + 1, col + 1]
        }
        # save original row and col
        save_row, save_col = row, col
        data_to_eat = []
        for key, value in direction.items():
            # black 1 white -1 =>True    empty 0 => False
            if (value != 9 and value != 0 and value != value_to_check and value != 2):
                # set row and col
                row, col = save_row, save_col
                # save color (center)
                save_val = self.array[row, col]
                while True:
                    row, col = self.getNewRowColOfDirection(row, col, key)
                    # get same color   => True
                    if save_val == self.getNewValueOfDirection(row, col, key):
                        break

                    # get 0 (empty) => True
                    elif not (self.getNewValueOfDirection(row, col, key)):
                        # move 1 direction to set value
                        row, col = self.getNewRowColOfDirection(row, col, key)
                        # set preview to value 2 in self.array
                        self.array[row, col] = 2
                        data_append = (row, col)
                        data_to_eat.append(data_append)
                        break
                    # get 9 (outside map) or get 2 (preview)   => True
                    elif (self.getNewValueOfDirection(row, col, key) == 9) or (self.getNewValueOfDirection(row, col, key) == 2):
                        break
        return data_to_eat

    def preview(self, value_to_check: int) -> list:
        """
        The `preview` function takes an integer value as input and returns a list of valid moves based on
        the value's position in the array.

        :param value_to_check: The value that you want to check for in the array
        :type value_to_check: int
        :return: a list of valid moves.
        """
        valid_move = []
        # loop all index that have your value
        for i in range(len(np.where(self.array == value_to_check)[0])):
            data = self.getWayToEatPreview(row=np.where(self.array == value_to_check)[
                                           0][i], col=np.where(self.array == value_to_check)[1][i], value_to_check=value_to_check)
            valid_move.extend(data)
        return valid_move

    @staticmethod
    def protectInput(text: str) -> str:
        """
        The `protectInput` function prompts the user for input and returns the input only if it is not
        empty.

        :param text: The `text` parameter is a string that represents the prompt or message that will be
        displayed to the user when asking for input
        :type text: str
        :return: The function `protectInput` returns a string.
        """
        while True:
            result = input(text)
            if result:
                return result

    def checkWin(self, turn: int) -> bool:
        """
        The function `checkWin` checks if there is a winner in a game based on the current state of the
        board and the current turn.

        :param turn: The "turn" parameter represents the current turn in the game. It is an integer value
        that indicates which player's turn it is
        :type turn: int
        :return: The function `checkWin` returns a boolean value. It returns `True` if there is a win
        condition (either all spaces on the board are filled or there are no valid moves left), and `False`
        otherwise.
        """
        # count empty if not have mean full board
        if np.sum(self.array == -1) + np.sum(self.array == 1) == 64:
            colorWin = 'White' if np.sum(
                self.array == -1) > np.sum(self.array == 1) else 'Black'
            print(colorWin, "Win")
            return True
        # can't move => lose
        elif self.valid_move == []:
            # turn => lose
            # black 1 white -1  *toggle stage*
            colorWin = 'White' if turn == 1 else 'Black'
            print(colorWin, "Win")
            return True
        else:
            return False

    def getDeltaTime(self) -> str:
        """
        The `getDeltaTime` function calculates the time difference between the current time and a previously
        set time and returns it in minutes and seconds.
        :return: The function `getDeltaTime` returns a string that represents the time difference between
        the current time and the time stored in the `self.first_time` variable. The string format is
        "{minutes} minutes, {seconds} seconds".
        """
        # self.first_time => set at __init__
        later_time = datetime.datetime.now()
        difference = later_time - self.first_time

        datetime.timedelta(0, 8, 562000)
        seconds_in_day = 24 * 60 * 60

        delta_time = divmod(
            difference.days * seconds_in_day + difference.seconds, 60)
        return f"{delta_time[0]} minutes, {delta_time[1]} seconds"

class PVP(BackEnd):
    def __init__(self, array) -> None:
        super().__init__(array)

    def runGame(self, i, j):
        while main.run:
            # player turn (white)
            if self.lst_value[self.round_]:
                # self.valid_move = self.preview(self.lst_value[self.round_])
                print('=>>>>>',self.valid_move)
                callback = self.tyrPlayerMove(i, j)
                if callback == 'break':
                    # Get Win
                    break
                elif callback == 'not_valid_move':
                    # self.lst_value[self.round_] = -1
                    break

    def tyrPlayerMove(self, i, j) -> str:
        # self.valid_move = self.preview(self.lst_value[self.round_])

        print("preview >>\n\n", self.array)
        self.row, self.col = i, j

        if (self.row, self.col) not in self.valid_move:
            print(self.valid_move,i,j)
            print("***\tnot valid move\t***")
            return 'not_valid_move'
        # reset_preview
        self.array = np.where(self.array == 2, 0, self.array)
        print("reset preview >>\n\n", self.array)

        # set color from input
        self.array[self.row, self.col] = self.lst_value[self.round_]
        self.getWayToEat(row=self.row, col=self.col)
        print(self.showNumberOfBlackWhite())
        print(self.getDeltaTime())
        self.round_ = not (self.round_)
        self.valid_move = self.preview(self.lst_value[self.round_])
        main.update()
        add_string = '>> {} Move '.format("White" if self.lst_value[self.round_] == -1 else ("Black " if self.lst_value[self.round_] == 1 else "empty"))
        row_chr = [chr(k) for k in range(ord('A'),ord('I'))]
        add_string += f'{row_chr[self.row - 1]}{self.col}\n'
        main.mytext.insert('1.0',add_string)
        if (self.checkWin(self.lst_value[self.round_])):
            print('in here we win', self.lst_value[self.round_])
            main.show_winner()
            return 'break'
            # win

class PVA_Easy(BackEnd):
    def __init__(self, array) -> None:
        super().__init__(array)

    def runGame(self, i, j):

        # for _ in range(10):
        while main.run:
            # player turn (white)
            if self.lst_value[self.round_] == -1:
                # self.valid_move = self.preview(self.lst_value[self.round_])
                print('=>>>>>',self.valid_move)
                callback = self.tyrPlayerMove(i, j)
                if callback == 'break':
                    # Get Win
                    break
                elif callback == 'not_valid_move':
                    self.lst_value[self.round_] = -1
                    break
            # Bot turn
            else:
                print("preview Bot (no2)>>\n\n", self.array)

                bot_move = choices(self.valid_move)
                self.row, self.col = bot_move[0][0], bot_move[0][1]

                # bot_move = choices(self.valid_move)
                # print("bot move =>",bot_move)
                # print("valid_move =>",self.valid_move)
                self.array = np.where(self.array == 2, 0, self.array)
                print("reset preview >>\n\n", self.array)
                # set color from input
                self.array[self.row, self.col] = self.lst_value[self.round_]
                self.getWayToEat(row=self.row, col=self.col)
                print(self.showNumberOfBlackWhite())
                print(self.getDeltaTime())
                self.round_ = not (self.round_)
                self.valid_move = self.preview(self.lst_value[self.round_])
                main.update()
                add_string = '>> {} Move '.format("White" if self.lst_value[self.round_] == -1 else ("Black " if self.lst_value[self.round_] == 1 else "empty"))
                row_chr = [chr(k) for k in range(ord('A'),ord('I'))]
                add_string += f'{row_chr[self.row - 1]}{self.col}\n'
                main.mytext.insert('1.0',add_string)
                if (self.checkWin(self.lst_value[self.round_])):
                    print('in here we win', self.lst_value[self.round_])
                    main.show_winner()
                    # win
                break

    def tyrPlayerMove(self, i, j) -> str:
        # self.valid_move = self.preview(self.lst_value[self.round_])

        print("preview >>\n\n", self.array)
        self.row, self.col = i, j

        if (self.row, self.col) not in self.valid_move:
            print(self.valid_move,i,j)
            print("***\tnot valid move\t***")
            return 'not_valid_move'
        # reset_preview
        self.array = np.where(self.array == 2, 0, self.array)
        print("reset preview >>\n\n", self.array)

        # set color from input
        self.array[self.row, self.col] = self.lst_value[self.round_]
        self.getWayToEat(row=self.row, col=self.col)
        print(self.showNumberOfBlackWhite())
        print(self.getDeltaTime())
        self.round_ = not (self.round_)
        self.valid_move = self.preview(self.lst_value[self.round_])
        main.update()
        add_string = '>> {} Move '.format("White" if self.lst_value[self.round_] == -1 else ("Black " if self.lst_value[self.round_] == 1 else "empty"))
        row_chr = [chr(k) for k in range(ord('A'),ord('I'))]
        add_string += f'{row_chr[self.row - 1]}{self.col}\n'
        main.mytext.insert('1.0',add_string)
        if (self.checkWin(self.lst_value[self.round_])):
            print('in here we win', self.lst_value[self.round_])
            main.show_winner()
            return 'break'
            # win

class PVA_Normal(BackEnd):
    def __init__(self, array) -> None:
        super().__init__(array)

    def runGame(self, i, j):

        # for _ in range(10):
        while main.run:
            # player turn (white)
            if self.lst_value[self.round_] == -1:
                # self.valid_move = self.preview(self.lst_value[self.round_])
                print('=>>>>>',self.valid_move)
                callback = self.tyrPlayerMove(i, j)
                if callback == 'break':
                    # Get Win
                    break
                elif callback == 'not_valid_move':
                    self.lst_value[self.round_] = -1
                    break
            # Bot turn
            else:
                print("preview Bot (no2)>>\n\n", self.array)

                bast_value = abs(self.valid_move[0][0] - self.valid_move[0][1])
                bast_move = (self.valid_move[0][0], self.valid_move[0][1])
                for move in self.valid_move:
                    if bast_value > abs(move[0] - move[1]):
                        bast_value = abs(move[0] - move[1])
                        bast_move = (move[0], move[1])
                bot_move = bast_move

                # bot_move = choices(self.valid_move)
                # print("bot move =>",bot_move)
                # print("valid_move =>",self.valid_move)
                self.array = np.where(self.array == 2, 0, self.array)
                print("reset preview >>\n\n", self.array)
                self.row, self.col = bot_move[0], bot_move[1]
                # set color from input
                self.array[self.row, self.col] = self.lst_value[self.round_]
                self.getWayToEat(row=self.row, col=self.col)
                print(self.showNumberOfBlackWhite())
                print(self.getDeltaTime())
                self.round_ = not (self.round_)
                self.valid_move = self.preview(self.lst_value[self.round_])
                main.update()
                if (self.checkWin(self.lst_value[self.round_])):
                    print('in here we win', self.lst_value[self.round_])
                    main.show_winner()
                    # win
                break

    def tyrPlayerMove(self, i, j) -> str:
        # self.valid_move = self.preview(self.lst_value[self.round_])

        print("preview >>\n\n", self.array)
        self.row, self.col = i, j

        if (self.row, self.col) not in self.valid_move:
            print(self.valid_move,i,j)
            print("***\tnot valid move\t***")
            return 'not_valid_move'
        # reset_preview
        self.array = np.where(self.array == 2, 0, self.array)
        print("reset preview >>\n\n", self.array)

        # set color from input
        self.array[self.row, self.col] = self.lst_value[self.round_]
        self.getWayToEat(row=self.row, col=self.col)
        print(self.showNumberOfBlackWhite())
        print(self.getDeltaTime())
        self.round_ = not (self.round_)
        self.valid_move = self.preview(self.lst_value[self.round_])
        main.update()
        add_string = '>> {} Move '.format("White" if self.lst_value[self.round_] == -1 else ("Black " if self.lst_value[self.round_] == 1 else "empty"))
        row_chr = [chr(k) for k in range(ord('A'),ord('I'))]
        add_string += f'{row_chr[self.row - 1]}{self.col}\n'
        main.mytext.insert('1.0',add_string)
        if (self.checkWin(self.lst_value[self.round_])):
            print('in here we win', self.lst_value[self.round_])
            main.show_winner()
            return 'break'
            # win

class AVA_Easy(BackEnd):
    def __init__(self, array) -> None:
        super().__init__(array)

    def runGame(self):
        if main.run:
            # for _ in range(20):
            print('-'*100)
            # player turn (white)
            if self.lst_value[self.round_] == -1:
                print("Bot1"*10)
                print("preview Bot (no2)>>\n\n", self.array)
                self.valid_move = self.preview(self.lst_value[self.round_])

                print(self.showNumberOfBlackWhite())
                print(self.getDeltaTime())

                print('#'*100)
                print("preview move>>\n\n", self.array)
                print("#"*100)

                # reset_preview
                self.array = np.where(self.array == 2, 0, self.array)

                if (self.checkWin(self.lst_value[self.round_])):
                    print("-"*50, "1")
                    main.show_winner()
                else:
                    bot_move = choices(self.valid_move)
                    print("bot move =>", bot_move)
                    print("valid_move =>", self.valid_move)
                    self.row, self.col = bot_move[0][0], bot_move[0][1]
                    # set color from input
                    self.array[self.row,self.col] = self.lst_value[self.round_]
                    self.getWayToEat(row=self.row, col=self.col)
                    self.round_ = not (self.round_)
                    main.update()
                    add_string = '>> {} Move '.format("White" if self.lst_value[self.round_] == -1 else ("Black " if self.lst_value[self.round_] == 1 else "empty"))
                    row_chr = [chr(k) for k in range(ord('A'),ord('I'))]
                    add_string += f'{row_chr[self.row - 1]}{self.col}\n'
                    main.mytext.insert('1.0',add_string)
                    main.after(750, main.mode.runGame)

            # Bot turn
            else:
                print("Bot2"*10)
                print("preview Bot (no2)>>\n\n", self.array)
                self.valid_move = self.preview(self.lst_value[self.round_])

                print(self.showNumberOfBlackWhite())
                print(self.getDeltaTime())

                print('#'*100)
                print("preview move>>\n\n", self.array)
                print("#"*100)

                # reset_preview
                self.array = np.where(self.array == 2, 0, self.array)

                if (self.checkWin(self.lst_value[self.round_])):
                    print("-"*50, "2")
                    main.show_winner()
                else:
                    bot_move = choices(self.valid_move)
                    print("bot move =>", bot_move)
                    print("valid_move =>", self.valid_move)
                    self.row, self.col = bot_move[0][0], bot_move[0][1]
                    # set color from input
                    self.array[self.row,self.col] = self.lst_value[self.round_]
                    self.getWayToEat(row=self.row, col=self.col)
                    self.round_ = not (self.round_)
                    main.update()
                    add_string = '>> {} Move '.format("White" if self.lst_value[self.round_] == -1 else ("Black " if self.lst_value[self.round_] == 1 else "empty"))
                    row_chr = [chr(k) for k in range(ord('A'),ord('I'))]
                    add_string += f'{row_chr[self.row - 1]}{self.col}\n'
                    main.mytext.insert('1.0',add_string)
                    main.after(750, main.mode.runGame)

class UI(Tk):
    def __init__(self):
        super().__init__()

        self.set_value()
        self.home_page()

    def set_value(self):
        # ''' ### Font-End value ###'''#
        self.geometry("1000x700+500+50")
        self.title("Othello")

        self.x_axis = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
        self.y_axis = ['1', '2', '3', '4', '5', '6', '7', '8']
        self.btn = [['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', ''],
                    ['', '', '', '', '', '', '', '']]  # Board
        self.button = ['', '', '', '', '', '']  # homepage + 2btn
        self.textbtn = ['PVP', 'PVA Easy', 'PVA Normal',
                        'AVA', 'Home page', 'Restart']  # homepage + 2btn
        self.picture = [ImageTk.PhotoImage(Image.open("Empty.png")),
                        ImageTk.PhotoImage(Image.open("black.png")),
                        ImageTk.PhotoImage(Image.open("pre.png")),
                        ImageTk.PhotoImage(Image.open("nothing.png")),
                        ImageTk.PhotoImage(Image.open("home_page.png")),
                        ImageTk.PhotoImage(Image.open("restart.png")),
                        ImageTk.PhotoImage(Image.open("button.png")),
                        ImageTk.PhotoImage(Image.open("tone.png")),
                        ImageTk.PhotoImage(Image.open("white.png"))]  # 0 1 2 3(nothing) -1
        self.bg_image = ImageTk.PhotoImage(Image.open("background.png"))
        self.custom_font = ("Times New Roman", 24)

        # ''' ### Back-End value ###'''#
        self.modes = [PVP, PVA_Easy, PVA_Normal, AVA_Easy]
        self.Game = True
        self.run = 0

    def home_page(self):
        self.clean_UI()
        background_label = Label(self, image=self.bg_image)
        background_label.place(x=0, y=0, relwidth=1, relheight=1)
        # using for make a button stick in middle
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(2, weight=1)
        self.grid_rowconfigure(0,weight=8)
        self.grid_rowconfigure(1,weight=3)
        self.grid_rowconfigure(2,weight=3)
        self.grid_rowconfigure(3,weight=3)
        self.grid_rowconfigure(5,weight=5)
        #self.label = Label(self, text="Othello", font=("Times New Roman",72))
        #self.label.grid(row=0, column=1, sticky="NEW")

        for i in range(4):
            self.button = Button(
                self, text=self.textbtn[i],font=self.custom_font,image=self.picture[6],
                compound="center",foreground="lime", command=lambda num=i: self.getmode(num))
            self.button.grid(row=i+1, column=1, sticky="NEW")

    def create_board(self):
        self.clean_UI()
        self.Game = True
        self.run = 1
        self.label = Label(self, image=self.picture[3])
        self.label.grid(row=0, column=0, padx=5, pady=5)
        background_label = Label(self, image=self.picture[7])
        background_label.place(x=0, y=0, relwidth=1, relheight=1)

        # self.realtime() # Label real time
        self.realTime = Label(self, text=f'{self.mode.getDeltaTime()}', font=self.custom_font,background="#BBF5EF",compound="center")
        self.realTime.grid(row=0, column=9, padx=20)
        self.after(1000, self.realtime)
        self.Turn = Label(self, text='{} Turn'.format("White" if self.mode.lst_value[self.mode.round_] == -1 else ("Black" if self.mode.lst_value[self.mode.round_] == 1 else "empty")), font=self.custom_font,background="#BBF5EF",compound="center")
        self.Turn.grid(row=1, column=9, padx=20)
        self.numbw = Label(self, text=f'{self.mode.showNumberOfBlackWhite()}', font=self.custom_font,background="#BBF5EF",compound="center")
        self.numbw.grid(row=2, column=9)
        self.mytext = Text(self, width = 22, height = 10, font = ('Calibri',12), wrap = WORD, padx = 10, pady = 10, bd = 4, selectbackground = 'Green', selectforeground = 'Red')
        self.mytext.grid(row=3, column=9,rowspan=4)

        for i in range(8):  # ABCD
            self.label = Label(
                self, text=self.x_axis[i], width=1, height=1, font=self.custom_font,background="#BBF5EF",compound="center")
            self.label.grid(row=0, column=i+1)

        for i in range(8):  # 1234
            self.label = Label(
                self, text=self.y_axis[i], width=1, height=1, font=self.custom_font,background="#BBF5EF",compound="center")
            self.label.grid(row=i+1, column=0)

        for i in range(4, 6):  # 2 btn
            self.button = Button(
                self, text=self.textbtn[i],image= self.picture[i], command=lambda i=i: self.ingame(i))
            self.button.grid(row=i+3, column=9)

        for i in range(8):  # board
            for j in range(8):
                self.btn[i][j] = Button(
                    self, text='', width=72, height=72, image=self.picture[3])
                self.btn[i][j].grid(row=i+1, column=j+1)

    def clean_UI(self):
        for widget in self.winfo_children():
            widget.grid_forget()

    def getmode(self, num):
        global restart
        self.clean_UI()
        self.mode = self.modes[num](np.zeros((10, 10), dtype=np.int8))
        restart = self.modes[num]
        self.start_game()

    def start_game(self):
        self.mode.round_ = not (self.mode.round_)
        if 'AVA_Easy' in str(self.mode):
            self.create_board()
            self.mode.runGame()
        else:
            self.mode.valid_move = self.mode.preview(
                self.mode.lst_value[self.mode.round_])
            print(self.mode.valid_move)

        self.create_board()
        self.update()

    def restart_game(self):
        self.mode = restart(np.zeros((10, 10), dtype=np.int8))
        self.clean_UI()
        self.start_game()

    def update(self):
        for i in range(8):  # ยัดกระดานเข้าUI
            for j in range(8):
                if self.mode.array[i+1][j+1] in [0, 1, -1]:
                    self.btn[i][j].config(
                        image=self.picture[self.mode.array[i+1][j+1]])
                else:
                    self.btn[i][j].config(
                        image=self.picture[2], command=lambda i=i+1, j=j+1: self.mode.runGame(i, j))

        self.Turn.config(text='{} Turn'.format("White" if self.mode.lst_value[self.mode.round_] == -1 else (
            "Black" if self.mode.lst_value[self.mode.round_] == 1 else "empty")))
        self.numbw.config(text=f'{self.mode.showNumberOfBlackWhite()}')

    def ingame(self, i):
        self.run = 0
        self.Game = False
        if i == 4:  # back to home page
            self.home_page()
        else:  # restart the game
            self.mode.round_ = not (self.mode.round_)
            self.restart_game()

    def realtime(self):
        if self.Game:
            self.realTime.config(text=f'{self.mode.getDeltaTime()}')
            self.after(1000, self.realtime)
        else:
            pass

    def show_winner(self):
        self.Game = not (self.Game)
        messagebox.showinfo("GAME END!!!", "{} WIN".format("White" if np.sum(
            self.mode.array == -1) > np.sum(self.mode.array == 1) else "Black"))

if __name__ == '__main__':
    # *** PVP MODE *** #
    # pvp_mode = PVP(np.zeros((10, 10), dtype=np.int8))
    # pvp_mode.runGame()

    # *** PVA MODE EASY *** #
    # pva_mode_easy = PVA_Easy(np.zeros((10, 10), dtype=np.int8))
    # pva_mode_easy.runGame()

    # *** PVA MODE NORMAL *** #
    # mode = PVA_Normal(np.zeros((10, 10), dtype=np.int8))

    # *** AVA MODE *** #
    # ava_mode_easy = AVA_Easy(np.zeros((10, 10), dtype=np.int8))
    # ava_mode_easy.runGame()
    main = UI()
    main.mainloop()


from typing import Tuple

import numpy as np
import torch
import PIL

from torchvision.transforms import transforms

from PIL import Image

from config.exception import InvalidImageModeException

class JitCycleGANModel:
    # i extract transform condition from original cycle gan repo
    transform = transforms.Compose([
        transforms.Resize((256, 256)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])

    # if you want to apply mlops, you need to get model from aws s3 or google drive
    jit_path = "./weight/jit_real_to_cartoon.pth"

    def __init__(self):
        self.model = torch.jit.load(self.jit_path)
        self.model.eval()

    @staticmethod
    def convert_pil_to_tensor(pil_image: PIL.Image.Image) -> Tuple[torch.Tensor, PIL.Image.Image]:

        if pil_image.mode == "RGB":
            pass
        elif pil_image.mode in ("L", "RGBA"):
            pil_image = pil_image.convert("RGB")
        else:
            raise InvalidImageModeException

        image: torch.Tensor = JitCycleGANModel.transform(pil_image)  # [3, 224, 224]
        return (image.unsqueeze(dim=0),  # [1, 3, 224, 224]
                pil_image)

    @staticmethod
    def convert_tensor_to_pil(tensor_image: torch.Tensor, origin_pil_image: PIL.Image.Image) -> PIL.Image.Image:
        # tensor_image : [1, 3, 256, 256]
        image_numpy = tensor_image[0].cpu().float().numpy()
        image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0  # post-processing: transpose and scaling
        generated_pil_image = Image.fromarray(image_numpy.astype(np.uint8))
        return generated_pil_image.resize(size=origin_pil_image.size)

    def inference(self, pil_image: PIL.Image.Image) -> PIL.Image.Image:
        (tensor_image, origin_pil_image) = self.convert_pil_to_tensor(pil_image)

        with torch.no_grad():
            output_tensor: torch.Tensor = self.model(tensor_image)  # [1, 3, 256, 256]

        return self.convert_tensor_to_pil(output_tensor, origin_pil_image)

if __name__ == '__main__':
    load_path = "./sample/a.png"
    output_path = "./sample/jit_cg_a.jpeg"
    input_pil_image = Image.open(load_path)

    jit_cg_model = JitCycleGANModel()
    output_pil_image = jit_cg_model.inference(input_pil_image)
    output_pil_image.save(output_path)


import sys
from time import sleep

import pygame

from bullet import Bullet
from alien import Alien

from alien_creator import *

def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    """ 响应 """
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        elif event.type ==pygame.KEYUP:
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_click_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)

def check_keydown_events(event, ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):

    if event.key == pygame.K_UP:
        ship.moving_up = True
    elif event.key == pygame.K_RIGHT:
        ship.moving_right = True
    elif event.key == pygame.K_DOWN:
        ship.moving_down = True
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True
    elif event.key == pygame.K_SPACE:
        if not stats.game_active:
            start_new_game(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        else:
            fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_RETURN or event.key == pygame.K_KP_ENTER:
        if not stats.game_active:
            start_new_game(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)
        #else:
        #    fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_q: # event.key == pygame.K_ESCAPE:
        sys.exit()

def check_keyup_events(event, ship):

    if event.key == pygame.K_UP:
        ship.moving_up = False
    elif event.key == pygame.K_RIGHT:
        ship.moving_right = False
    elif event.key == pygame.K_DOWN:
        ship.moving_down = False
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False

def check_click_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    play_button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    if play_button_clicked and not stats.game_active:
        start_new_game(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)

def start_new_game(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    ai_settings.initialize_dynamic_settings()

    stats.reset_stats()
    stats.game_active = True

    sb.prep_all()

    aliens.empty()
    bullets.empty()

    create_fleet(ai_settings, stats, screen, ship, aliens)
    ship.center_ship()

def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    # repaint
    screen.fill(ai_settings.bg_color)
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    ship.blitme()
    for alien in aliens.sprites():
        alien.blitme()
    # aliens.draw(screen)

    sb.show_score()

    if not stats.game_active:
        play_button.draw_button()

    # visual
    pygame.display.flip()

def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """ """
    bullets.update()
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

    check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)

def fire_bullet(ai_settings, screen, ship, bullets):
    """ create a bullet, add it into group"""
    if len(bullets) < ai_settings.bullet_allowed:
            new_bullet = Bullet(ai_settings, screen, ship)
            bullets.add(new_bullet)

def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
        sb.prep_score()
    if len(aliens) == 0:
        refresh_next_fleet(ai_settings, screen, stats, sb, ship, aliens, bullets)

def check_fleet_edges(ai_settings, aliens):
    for alien in aliens.sprites():
        if alien.check_edges():
            change_fleet_direction(ai_settings, aliens)
            break

def change_fleet_direction(ai_settings, aliens):
    for alien in aliens.sprites():
        alien.rect.y += ai_settings.fleet_drop_speed
    ai_settings.fleet_direction *= -1

def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):
    screen_rect = screen.get_rect()
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
            break

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):

    if stats.ships_left > 0:
        stats.ships_left -= 1

        aliens.empty()
        bullets.empty()

        sleep(1.0)

        create_fleet(ai_settings, stats, screen, ship, aliens)
        ship.center_ship()

    else:
        stats.game_active = False

def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
    check_fleet_edges(ai_settings, aliens)
    aliens.update()
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)

def refresh_next_fleet(ai_settings, screen, stats, sb, ship, aliens, bullets):
    bullets.empty()
    stats.level += 1
    if stats.level>3 and stats.level%5!=0:
        ai_settings.increase_speed()
    sb.prep_level()
    create_fleet(ai_settings, stats, screen, ship, aliens)


# -*- coding: utf-8 -*-

import streamlit as st
from PIL import Image
import cv2
import numpy as np

def main():

    selected_box = st.sidebar.selectbox(
    'Choose any one',
    ('Welcome','Image Processing', 'Video', 'Face Detection',  'Object Detection')
    )

    if selected_box == 'Welcome':
        welcome()
    if selected_box == 'Image Processing':
        photo()
    if selected_box == 'Video':
        video()
    if selected_box == 'Face Detection':
        face_detection()
    if selected_box == 'Object Detection':
        object_detection()

def welcome():

    st.title('Image Processing using Streamlit')

    st.subheader('A simple app that shows few image processing algorithms.'
             )

    st.image('ImageProcessing.jpg',use_column_width=True)

def load_image(filename):
    image = cv2.imread(filename)
    return image

def photo():

    st.header("Thresholding, Edge Detection and Contours")

    if st.button('Original Image of Minion'):

        original = Image.open('minion.jpg')
        st.image(original, use_column_width=True)

    image = cv2.imread('minion.jpg')
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    x = st.slider('Change Threshold value',min_value = 50,max_value = 255)

    ret,thresh1 = cv2.threshold(image,x,255,cv2.THRESH_BINARY)
    thresh1 = thresh1.astype(np.float64)
    st.image(thresh1, use_column_width=True,clamp = True)

    st.text("Bar Chart of the image")
    histr = cv2.calcHist([image],[0],None,[256],[0,256])
    st.bar_chart(histr)

    if st.button('Original Image of Cartoon'):

        original = Image.open('cartoon1.jpg')
        st.image(original, use_column_width=True)

    st.text("Press the button below to view Canny Edge Detection Technique")
    if st.button('Canny Edge Detector'):
        image = load_image("cartoon.jpg")
        edges = cv2.Canny(image,50,300)
        st.image(edges,use_column_width=True,clamp=True)

    y = st.slider('Change Value to increase or decrease contours',min_value = 50,max_value = 255)

    if st.button('Contours'):
        im = load_image("cartoon1.jpg")

        imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
        ret,thresh = cv2.threshold(imgray,y,255,0)
        contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

        img = cv2.drawContours(im, contours, -1, (0,255,0), 2)

        st.image(thresh, use_column_width=True, clamp = True)
        st.image(img, use_column_width=True, clamp = True)

def video():
    uploaded_file = st.file_uploader("Choose a video file to play")
    if uploaded_file is not None:
         bytes_data = uploaded_file.read()

         st.video(bytes_data)

    video_file = open('video.mp4', 'rb')

    video_bytes = video_file.read()
    st.video(video_bytes)

def face_detection():
    st.header("Face Detection using haarcascade")

    if st.button('See Original Image'):

        original = Image.open('friends.jpg')
        st.image(original, use_column_width=True)

    image2 = cv2.imread("friends.jpg")

    face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
    faces = face_cascade.detectMultiScale(image2)
    print(f"{len(faces)} faces detected in the image.")
    for x, y, width, height in faces:
        cv2.rectangle(image2, (x, y), (x + width, y + height), color=(255, 0, 0), thickness=2)

    cv2.imwrite("faces.jpg", image2)

    st.image(image2, use_column_width=True,clamp = True)

def object_detection():

    st.header('Object Detection')
    st.subheader("Object Detection is done using different haarcascade files.")
    img = load_image("clock.jpg")
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    clock = cv2.CascadeClassifier('haarcascade_clock.xml')
    found = clock.detectMultiScale(img_gray,minSize =(20, 20))

    amount_found = len(found)
    st.text("Detecting a clock from an image")
    if amount_found != 0:
        for (x, y, width, height) in found:

            cv2.rectangle(img_rgb, (x, y),
                          (x + height, y + width),
                          (0, 255, 0), 5)
    st.image(img_rgb, use_column_width=True,clamp = True)

    st.text("Detecting eyes from an image")

    image = load_image("eyes.jpg")
    img_gray_ = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    img_rgb_ = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    eye = cv2.CascadeClassifier('haarcascade_eye.xml')
    found = eye.detectMultiScale(img_gray_,
                                       minSize =(20, 20))
    amount_found_ = len(found)

    if amount_found_ != 0:
        for (x, y, width, height) in found:

            cv2.rectangle(img_rgb_, (x, y),
                              (x + height, y + width),
                              (0, 255, 0), 5)
        st.image(img_rgb_, use_column_width=True,clamp = True)

if __name__ == "__main__":
    main()


#Import libraries
import streamlit as st
import matplotlib.pyplot as plt
import pandas as pd
from pickle import load

#
st.title('2016 Election Modeling')
st.markdown('See how your county voted in the 2016 election relative to its regression predicted vote proportion')

#
df = pd.read_csv('Data/election_full_dataset.csv')
counties = df['countystate'].values

#
label = 'Select a county name from the dropdown menu below (785 largest US counties only)'
county = st.selectbox(label, counties)

#
county_datapoint = df[df['countystate']==county]
features = ['incomeroot','medianageroot','unemployment','bachelorsdegree','white','hispanic',
           'popdensityroot','veteran%root','disability%root','manufacturing/capitaroot',
           'agriculture/capita','mining/capita']
x = county_datapoint[features]
y = county_datapoint['voteproportion'].values[0]*100

#
ss = load(open('Pickles/standard_scaler.pickle', 'rb'))
x = ss.transform(x)

#
lr = load(open('Pickles/linear_regression.pickle', 'rb'))
y_pred = lr.predict(x)[0]*100

#
columns = ['County', 'State', 'Predicted Trump Vote %', 'Actual Trump Vote %']
output = pd.DataFrame(columns=columns)
output.loc[0] = [county.split(',')[0], county.split(',')[-1], y_pred, y]
output.index = [' ']

#
st.table(output)


# coding=utf-8

import xlrd

def read_xlrd(excelFile):
    data = xlrd.open_workbook(excelFile)
    table = data.sheet_by_index(0)
    dataFile = []

    for rowNum in range(table.nrows):
        # if 去掉表头
        if rowNum > 0:
            dataFile.append(table.row_values(rowNum))

    return dataFile

if __name__ == '__main__':
    excelFile = 'C:\\Users\Administrator\Desktop\Python代码练习\excel与python交互/demo.xlsx'
    print(read_xlrd(excelFile=excelFile))


# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations

# -------------------------------------------------------------------------
# This is the main controller
# - index is the default action of any application
# - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming)
# -------------------------------------------------------------------------
from web2py import gluon
from applications.UPTANE.modules.test_external import create_meta
from collections import OrderedDict
import datetime
import demo
import os
import re
import xmlrpc.client

@auth.requires_login()
def index():
    """
    example action using the internationalization operator T and flash
    rendered by views/default/index.html or views/generic.html

    if you need a simple wiki simply replace the two lines below with:
    return auth.wiki()
    """
    user = auth.user.username
    # if OEM1
    if user == 'oem1':
        return database_contents()
    # if OEM2
    if user == 'oem2':
        return database_contents()
    # if Supplier 1
    if user == 'supplier1':
        return dict(form=update_form(), db_contents=database_contents())
    # if Supplier 2
    if user == 'supplier2':
        return dict(form=update_form(), db_contents=database_contents())
    else:
        return dict(message=T('Hello unknown user..'))

@auth.requires_login()
def hacked():
    return database_contents()

@auth.requires_login()
def hacked_repo():
    return dict(form=update_form(), db_contents=database_contents())

def user():
    """
    exposes:
    http://..../[app]/default/user/login
    http://..../[app]/default/user/logout
    http://..../[app]/default/user/register
    http://..../[app]/default/user/profile
    http://..../[app]/default/user/retrieve_password
    http://..../[app]/default/user/change_password
    http://..../[app]/default/user/bulk_register
    use @auth.requires_login()
        @auth.requires_membership('group name')
        @auth.requires_permission('read','table name',record_id)
    to decorate functions that need access control
    also notice there is http://..../[app]/appadmin/manage/auth to allow administrator to manage users
    """
    return dict(form=auth())

@cache.action()
def download():
    """
    allows downloading of uploaded files
    http://..../[app]/default/download/[filename]
    """
    return response.download(request, db)

def determine_available_updates():
    available_update_list = []
    vehicles =  db(db.vehicle_db.oem == auth.user.username).select()
    #print('vehicles:\t {0}'.format(vehicles))
    # Retrieve a list of vehicles associated w/ OEM
    if vehicles:
        for v in vehicles:
            #print('\nv:\t{0}'.format(v))
            # Iterate through ECUs for each vehicle to determine if a newer one exists
            for e in v.ecu_list:
                try:
                    #print('\ne:\t{0}'.format(e))
                    # Retrieve the ecu based off the id
                    ecu = db(db.ecu_db.id == e).select().first()
                    #print('\necu:\t{0}'.format(ecu))
                    # Retrieve the type from the ecu object
                    if ecu:
                        ecu_type = ecu.ecu_type
                        #print('\necu_type:\t{0}'.format(ecu_type))
                        # Query the database for updates for ecu_type and select the last one (i.e., most recent update)
                        ecu_type_updates = db(db.ecu_db.ecu_type == ecu_type).select().last()
                        # If the last update for this ecu_type id is > ecu.id then there's a newer update available
                        # so append the vehicle id to the available update list and break
                        if ecu_type_updates.id > e:
                            available_update_list.append(v.id)
                            break
                        else:
                            continue
                except Exception as e:
                    print('Unable to determine the available updates due to this error: {0}'.format(e))
    #print(available_update_list)
    return available_update_list

def call():
    """
    exposes services. for example:
    http://..../[app]/default/call/jsonrpc
    decorate with @services.jsonrpc the functions to expose
    supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
    """
    return service()

@auth.requires_login()
def all_records():
    print('ALL RECORDS BABY!!!')
    grid = SQLFORM.grid(db.ecu_db.supplier_name==auth.user.username,user_signature=False, csv=False)
    return locals()

@auth.requires_login()
def update_form():
    ''' This is the form the Supplier will see at the top of the screen that allows them to upload a new firmware image
    :return: the form that will be utilized by the user
    '''

    imagerepo = xmlrpc.client.ServerProxy('http://' + str(demo.IMAGE_REPO_SERVICE_HOST) +
                                            ':' + str(demo.IMAGE_REPO_SERVICE_PORT))

    #print(request.args)
    record = db.ecu_db(request.args(1))
    #print(record)
    form=SQLFORM(db.ecu_db)#, record)

    if form.validate():
        # Adding the update to the Image (Supplier) Repo
        cwd = os.getcwd()
        update_image = form.vars.update_image

        # After getting the file image name, convert the name of the file from hex to ascii
        # and use this value to populate the imagerepo db with
        filename = return_filename(update_image)

        # Add uploaded images to imagerepo repo + write to repo
        imagerepo.add_target_to_image_repo(cwd+'/applications/UPTANE/static/uploads/'+update_image, filename)
        imagerepo.write_image_repo()

        # Metadata was initially intended to be showed, so this place holder function call was created
        meta = create_meta(form.vars.ecu_type + '_' + form.vars.update_version)

        # Add or Update this instantiation of the ECU in the ECU db
        id_added = db.ecu_db.update_or_insert((db.ecu_db.supplier_name == auth.user.username) &
                                        (db.ecu_db.ecu_type == form.vars.ecu_type) &
                                        (db.ecu_db.update_version == form.vars.update_version),
                                        ecu_type=form.vars.ecu_type,
                                        update_version=form.vars.update_version,
                                        supplier_name=auth.user.username,
                                        metadata=meta,
                                        update_image=form.vars.update_image)
        print('id_added: {0}\necu:{1}'.format(id_added, db.ecu_db.id==id_added))

        response.flash = 'form accepted'
    elif form.process().errors:
        response.flash = 'form has errors'
    else:
        response.flash = 'please fill out the form'
    return form

@auth.requires_login()
def add_ecu_validation(form):
    meta = create_meta('3')
    id_added = db.ecu_db.update_or_insert((db.ecu_db.supplier_name == auth.user.username) &
                                        (db.ecu_db.ecu_type == form.vars.ecu_type) &
                                        (db.ecu_db.update_version == form.vars.update_version),
                                        ecu_type=form.vars.ecu_type,
                                        update_version=form.vars.update_version,
                                        supplier_name=auth.user.username,
                                        metadata=meta,
                                        update_image=form.vars.update_image)

@auth.requires_login()
def create_vehicle(form):
    try:
        #print('\n\nCREATE_VEHICLE()')
        director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                            ':' + str(demo.DIRECTOR_SERVER_PORT))
        #print('\n\nAFTER CREATING DIRECTOR@ ADDR: {0}:{1}'.format(str(demo.DIRECTOR_SERVER_HOST), str(demo.DIRECTOR_SERVER_PORT)))

        # Add a new vehicle to the director repo (which includes writing to the repo)
        director.add_new_vehicle(form.vars.vin)
        director.write_director_repo(form.vars.vin)

        pri_ecu_key = demo.import_public_key('primary')
        sec_ecu_key = demo.import_public_key('secondary')
        ecu_pub_key = ''
        #print('\n\n\ncreating vehicle now\n{0}\ttype: {1}\n'.format(form.vars, type(form.vars)))
        #print('pri_ecu_key: {0}\nsec_ecu_key: {1}\necu_pub_key: {2}'.format(pri_ecu_key, sec_ecu_key, ecu_pub_key))
        for e_id in form.vars.ecu_list:
            ecu = db(db.ecu_db.id==e_id).select().first()

            # Retrieve the filename to add the target to the director
            cwd = os.getcwd()
            filename = return_filename(ecu.update_image)
            filepath = cwd + str('/applications/UPTANE/test_uploads/'+filename)

            # Determine if ECU is primary or secondary
            is_primary = True if ecu.ecu_type == 'INFO' else False

            # If it's a secondary, then add the target to the director and write to the director repo
            if not is_primary:
                director.add_target_to_director(filepath, filename, form.vars.vin, ecu.ecu_type+str(form.vars.vin))
                director.write_director_repo(form.vars.vin)

            # Register the ecu w/ the vehicle
            ecu_pub_key = pri_ecu_key if is_primary else sec_ecu_key
            #print('\necu.ecu_type: {0} + form.vars.vin: {1}\tis_primary: {2}'.format(ecu.ecu_type, form.vars.vin, is_primary))
            # only register ecus ONCE - correct?
            director.register_ecu_serial(ecu.ecu_type+str(form.vars.vin), ecu_pub_key, form.vars.vin, is_primary)
            # Necessary?
            #director.write_director_repo(form.vars.vin)
    except Exception as e:
        print('Unable to create a new vehicle due to the following issue: {0}'.format(e))

@auth.requires_login()
def get_supplier_versions(list_of_vehicles):
    try:
        info = db(db.ecu_db.ecu_type=='INFO').select().last()
        bcu = db(db.ecu_db.ecu_type=='BCU').select().last()
        tcu = db(db.ecu_db.ecu_type=='TCU').select().last()
        #print('\n\ninfo: {0}\nbcu: {1}\ntcu: {2}'.format(info, bcu, tcu))
        supplier_version = str(bcu.ecu_type) + " : " + str(bcu.update_version) + '\n' + \
                           str(info.ecu_type) + " : " + str(info.update_version) + '\n' + \
                           str(tcu.ecu_type) + " : " + str(tcu.update_version)

        for vehicle in list_of_vehicles:
            # Assume all vehicles have TCU, BCU, and INFO
            vehicle.update_record(supplier_version=supplier_version)
    except Exception as e:
        print('Unable to get the supplier versions due to the following error: {0}'.format(e))

@auth.requires_login()
def get_director_versions(list_of_vehicles):
    try:
        for vehicle in list_of_vehicles:#db(db.vehicle_db.oem==auth.user.username).select():
            #print('\nvehicle: {0}'.format(vehicle))
            director_version = ''
            version_dict = {}
            #print('ecu_list unsorted: {0}\necu_list sorted: {1}'.format(vehicle.ecu_list, sorted(vehicle.ecu_list)))
            for ecu in vehicle.ecu_list:
                ecu_type       = db(db.ecu_db.id==ecu).select().first().ecu_type
                update_version = db(db.ecu_db.id==ecu).select().first().update_version
                version_dict[ecu_type] = update_version
                director_version += ' ' + str(ecu_type) + ' : ' + str(update_version)
                #print('director_version: {0}'.format(director_version))
                #print('version_dict: {0}'.format(version_dict))
                #print('ordered: version_dict: {0}'.format(OrderedDict(sorted(version_dict.items()))))
            # Director Str Version
            vehicle.update_record(director_version = director_version)
            #db.vehicle_db(db.vehicle_db.id == vehicle).update_record(director_version = director_version)
            # Dictionary Version
            #db.vehicle_db(db.vehicle_db.id == vehicle).update_record(director_version = version_dict)
    except Exception as e:
        print('Unable to get the director versions due to the following error: {0}'.format(e))

@auth.requires_login()
def get_vehicle_versions(list_of_vehicles):
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                            ':' + str(demo.DIRECTOR_SERVER_PORT))

    # Iterate through the list of vehicles in order to return the vehicle reported versions
    for vehicle in list_of_vehicles:
        #print('vehicle: {0} : vin#: {1}'.format(vehicle, vehicle.vin))
        try:
            # Currently we're iterating through the 3 ECU types we limit the Supplier in uploading in order to retrieve
            #  their ECU manifests.  These are used to append to the ecu_serial_string.
            ecu_list = ['BCU', 'INFO', 'TCU']
            ecu_serial_string = ''
            # If the default file is returned from the ECU manifests (secondary_firmware.txt), then is_default stays true
            #  and the checkin time for the vehicle will not be updated.  However, if the ECU manifests have a different
            #  file (i.e., the vehicle has 'updated'), then is_default gets set to False and the checkin time shall be
            #  updated.
            is_default = True
            update_checkin_time = False
            # Iterate through the list of ECU's (which is currently hardcoded) and gather their ecu_manifest
            #   From each ecu_manifest, parse the file name of their current image and add it to the ecu_serial_string
            for ecu in ecu_list:
                ecu_manifest = director.get_last_ecu_manifest(str(ecu)+str(vehicle.vin))
                try:
                    file_path = ecu_manifest['signed']['installed_image']['filepath']
                    if file_path == '/secondary_firmware.txt':
                        ecu_serial_string += '{0} : {1}  '.format(str(ecu), 'N/A')
                    else:
                        is_default = False
                        update_checkin_time = True
                        ecu_serial_string += '{0} : {1}  '.format(str(ecu), file_path[-7:-4])
                except Exception:
                    # This is to catch the error received from an ECU manifest that returns an error
                    ecu_serial_string += '{0} : {1}  '.format(str(ecu), 'N/A') if is_default else '{0} : {1}  '.format(str(ecu), '1.0')

            vehicle.update_record(vehicle_version=ecu_serial_string)
            cur_time = datetime.datetime.now()

            vehicle.update_record(checkin_date=cur_time) if update_checkin_time else print('Checkin date will not be updated for vehicle: {0}'.format(vehicle.vin))
        except Exception as e:
            vehicle.update_record(vehicle_version='None')
            print('did not work with this error: {0}'.format(e))

@auth.requires_login()
def get_status(list_of_vehicles):
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                            ':' + str(demo.DIRECTOR_SERVER_PORT))

    # Iterate through the list of vehicles in order to return the ECU reported attacks detected
    for vehicle in list_of_vehicles:
        try:
            # Get the vehicle's manifest
            vv = director.get_last_vehicle_manifest(vehicle.vin)
            attack_status = ''
            if 'signed' in vv:
                try:
                    # Retrieve all of the reported ecu_version manifests keys
                    ecu_serials = list(vv['signed']['ecu_version_manifests'].keys())
                    if ecu_serials:
                        for ecu in ecu_serials:
                            # Retrieve the ECU's last manifest to parse the attacks_detected
                            ecu_mani = director.get_last_ecu_manifest(ecu)
                            if ecu_mani:
                                attack_status = ecu_mani['signed']['attacks_detected']
                except Exception:
                    print('Unable to get the ecu_version_manifests from the vehicle manifest.')

            # If the attack_status is not the default of '' then we'll return the reported attack_status
            #   otherwise, we'll return a value of 'Good'
            attack_status = attack_status if attack_status else 'Good'
            vehicle.update_record(status=attack_status)

        except Exception as e:
            vehicle.update_record(status='Unknown')
            print('get_status failed with this error: {0}'.format(e))

@auth.requires_login()
def get_time_elapsed(list_of_vehicles):
    for vehicle in list_of_vehicles:
        cur_time = datetime.datetime.now()
        checkin_date = vehicle.checkin_date
        elapsed_time = cur_time - checkin_date

        # Convert the elapsed_time to days (d), hours (h), minutes (m), and seconds (s)
        d = divmod(elapsed_time.total_seconds(), 86400)
        h = divmod(d[1], 3600)
        m = divmod(h[1], 60)
        s = m[1]

        elapsed_time_string = '{0} days'.format(int(d[0]))
        #elapsed_time_string = '{0} days, {1} hours, {2} minutes'.format(int(d[0]),int(h[0]),int(m[0]))
        vehicle.update_record(time_elapsed=elapsed_time_string)

@auth.requires_login()
def database_contents():
    # return the database contents based off the current_user
    current_user = auth.user.username

    # If it's a supplier, pull up data based off their username
    if current_user == 'supplier1' or current_user == 'supplier2':
        if db.ecu_db.id != '':
            # The following sets the ID#'s to non-readable and pulls all ECU's created by the current user
            db.ecu_db.id.readable=False
            db_contents = SQLFORM.grid(db.ecu_db.supplier_name==auth.user.username, searchable=False, csv=False,
                                       create=False, editable=False, details=False)
                                       #onvalidation=add_ecu_validation)
        else:
            db_contents = T('Hello, you have no ecu/vehicles....')
        return db_contents

    # Else it's an OEM; so display database applicable to them
    else:
        #print('\nrequest: {0}'.format(request.vars['ecu_list']))
        list_of_vehicles = db(db.vehicle_db.oem==auth.user.username).select()
        get_supplier_versions(list_of_vehicles)
        get_director_versions(list_of_vehicles)
        get_vehicle_versions(list_of_vehicles)
        get_status(list_of_vehicles)
        get_time_elapsed(list_of_vehicles)
        db.vehicle_db.id.readable=False
        db_contents = SQLFORM.grid(db.vehicle_db.oem==auth.user.username, create=True,
                                   selectable=lambda vehicle_id: selected_vehicle(vehicle_id), csv=False,
                                   searchable=False, details=False, editable=False, oncreate=create_vehicle,
                                   selectable_submit_button='View Vehicle Data',
                                   maxtextlengths={'vehicle_db.supplier_version' : 50,
                                                   'vehicle_db.director_version'  : 50,#,
                                                   'vehicle_db.vehicle_version'  : 50})#,
                                    # HREF link to another page - may be good for 'available updates': links = [lambda row: A('Director Updates', _href=URL("default","show",args=[row.id]))])

        #print('request.args: {0}\n'.format(request.args))

        # If we are adding a new vehicle to the database
        if 'new' in request.args:
            # Regex used to find all ecu 'options' within the db_contents div tag
            #   String we are parsing db_contents for, thus reasoning for specific regex: '<div>[ecu_id]</div>'
            reg_val = re.findall("\>\d{1,3}\<", str(db_contents))
            # If there is a match, pull value from ecu_db
            if reg_val:
                for reg in reg_val:
                    ecu_id= reg[1:-1]
                    ecu = db(db.ecu_db.id==ecu_id).select().first()
                    ecu_name = ecu.ecu_type
                    ecu_ver  = ecu.update_version
                    ecu_str  = '>' + ecu_name + ' - ' + ecu_ver + '<'

                    # Find old 'option' string and replace with newly retrieved ECU Type + Version
                    db_contents = gluon.html.XML(str(db_contents).replace(reg, ecu_str))

        #print('request.args w/ error: {0}'.format(request.args))
        #print('db_contents: {0}'.format(db_contents))

        changed_ecu_list = request.vars['changed_ecu_list']
        edited_vehicle=request.vars['vehicle_id']
        error_message=request.vars['error_message']
        #print('request.vars: {0}\n'.format(request.vars))
        #print('error message: {0}\ntype: {1}'.format(error_message, type(error_message)))
        #print('changed ecu_list: {0}'.format(changed_ecu_list))
        #print('edited_vehicle: {0}'.format(edited_vehicle))
        #print('\nDetermining vehicles w/ available updates')
        available_updates = []
        available_updates = determine_available_updates()

        #db_contents = SQLFORM.smartgrid(db.vehicle_db)
        if changed_ecu_list == None:
            return dict(db_contents=db_contents, available_updates=available_updates, error_message=error_message)
        else:
            return dict(db_contents=db_contents,changed_ecu_list=changed_ecu_list,edited_vehicle=edited_vehicle,
                        available_updates=available_updates, error_message=error_message)

@auth.requires_login()
def selected_vehicle(vehicle_id):
    print('Here I am inside of the selectable...')
    # Ensure that a single vehicle is selected before continuing
    error_message = None
    if len(vehicle_id) == 1:
    #    print('vehicle_id')
    #    print(vehicle_id)
        vehicle = db(db.vehicle_db.id==vehicle_id[0]).select().first()
        #print selected_vehicle
        #print selected_vehicle.ecu_list
        vehicle_ecu_list = []
        ecu_id_list = []
        ecu_type_list = []
        for ecu in vehicle.ecu_list:
            #print ecu
            #print type(ecu)
            selected_ecu = db(db.ecu_db.id==ecu).select().first()
            #print selected_ecu.ecu_type
            #print type(selected_ecu)
            # Add all ecu_id's to the ecu_id_list
            vehicle_ecu_list.append(selected_ecu)
            ecu_id_list.append(selected_ecu.id)
            # Only add ecu_types if they are not currently in the ecu_type_list
            if selected_ecu.ecu_type not in ecu_type_list:ecu_type_list.append(selected_ecu.ecu_type)
            #name = selected_ecu.ecu_type
            #print name
        #print('ecu_id_list')
        #print(vehicle_ecu_list)
        #ecu_view = (dict(ecu_list=SQLFORM.grid((db.ecu_db.id==ecu_id_list[0]))))
        #return locals()

        redirect(URL('ecu_list', vars=dict(vehicle_ecu_list=vehicle_ecu_list, ecu_type_list=ecu_type_list, ecu_id_list=ecu_id_list, vehicle_id=vehicle_id)))
        #ecu_list(ecu_id_list)
        #redirect(URL('next', 'list_records', vars=vehicle_id[0]))
    else:
        print('Please select a single vehicle first.')
        error_message = 'Please select a single vehicle before proceeding.'
        redirect(URL('index', vars=dict(error_message=error_message)))

@auth.requires_login()
def ecu_list():
    vehicle_ecu_list =  request.vars['vehicle_ecu_list']
    ecu_id_list =  request.vars['ecu_id_list']
    ecu_type_list = request.vars['ecu_type_list']
    vehicle_id = request.vars['vehicle_id']
    vehicle_vin = db(db.vehicle_db.id==vehicle_id).select().first().vin

    # Now have to build the query that will effect what is shown on the screen
    num_ecus = 0
    query = ''
    for ecu in iter(ecu_type_list):
        #print ecu
        num_ecus += 1
        #query+="({0}=='{1}')".format(db.ecu_db.ecu_type,ecu)
        query+="(db.ecu_db.ecu_type=='" + ecu + "')"
        if num_ecus < len(ecu_type_list):
            query+=" | "

    #print('query: {0}'.format(query))
    # This line changes our custom query (created above) from a str to a type Query
    # This enables us to send the query as the first argument for SQLFORM.grid()
    mod_query = db(eval(query))
    #print('mod query: {0}'.format(mod_query))
    db.ecu_db.id.readable=False
    return dict(ecu_type_list=ecu_type_list, ecu_id_list=ecu_id_list,
                ecu_list=SQLFORM.grid(mod_query, selectable=lambda ecus: selected_ecus(ecus), csv=False,
                                      orderby=[db.ecu_db.ecu_type, ~db.ecu_db.update_version],
                                      searchable=False, editable=False, deletable=False, create=False,details=False,
                                      selectable_submit_button='Create Bundle',
                                      onupdate=create_bundle_update()),
                vehicle_vin=vehicle_vin)#, selected=ecu_id_list))

@auth.requires_login()
def create_bundle_update():
    print('\n\nUP inside the update')

@auth.requires_login()
def selected_ecus(selected_ecus):
    #print('inside selected ecus')
    ecu_id_list = request.vars['ecu_id_list']
    changed_ecu_list = []
    vehicle_id = request.vars['vehicle_id']

    is_primary = False
    for ecu in selected_ecus:
        if str(ecu) not in ecu_id_list:
            changed_ecu_list.append(ecu)
        else:
            print(str(ecu) + ' is in the list!')

    #print('changed_ecu_list: {0}'.format(changed_ecu_list))
    if changed_ecu_list:
        db.vehicle_db(db.vehicle_db.id == vehicle_id).update_record(ecu_list=selected_ecus)
        #if len(changed_ecu_list) == 1: # <~> Why use this condition?
        print('changed_ecu_list contains ' + repr(len(changed_ecu_list)))

        director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                                ':' + str(demo.DIRECTOR_SERVER_PORT))
        # <~> I'd expect vin to be the same within this call, since this
        # dialog is for an individual vehicle. No?
        vin = db(db.vehicle_db.id==vehicle_id).select().first().vin
        director.clear_vehicle_targets(vin)
        for i in range(len(changed_ecu_list)):

            cur_ecu = db(db.ecu_db.id==changed_ecu_list[i]).select().first()
            #print('\ncur_ecu: {0}'.format(cur_ecu))

            # Do a check to see if it's the Primary (potentially add it after appending to
            #   changed_ecu_list w/ boolean is_primary)
            is_primary = True if cur_ecu.ecu_type == 'INFO' else False

            #print('\ncur_ecu: {0}'.format(cur_ecu))
            #print('\nis_primary: {0}'.format(is_primary))

            # Add the bundle to the vehicle
            cwd = os.getcwd()
            #print('\ncwd now2: {0}'.format(cwd))

            # Retrieve the filename
            filename = return_filename(cur_ecu.update_image)
            # <~> Why are we using test_uploads paths? We can't assume the
            # image files exist in the test_uploads folder.
            filepath = cwd + str('/applications/UPTANE/test_uploads/'+filename)

            vehicle_id = request.vars['vehicle_id']
            #print('vehicle_id: {0}'.format(vehicle_id))
            ecu_serial = cur_ecu.ecu_type+str(vin)

            #print('filepath: {0}\nfilename: {1}\nvin: {2}\necu_serial: {3}'.format(filepath, filename, vin, ecu_serial))

            director.add_target_to_director(filepath, filename, vin, ecu_serial)
            director.write_director_repo(vin)

            pri_ecu_key = demo.import_public_key('primary')
            sec_ecu_key = demo.import_public_key('secondary')
            ecu_pub_key = ''

            # <~> Why would we register the ECU every time we want to update it?
            # (Am I missing something?)
            # # Register the ecu w/ the vehicle
            # is_primary = True if cur_ecu.ecu_type == 'INFO' else False
            # ecu_pub_key = pri_ecu_key if is_primary else sec_ecu_key
            # print('\necu.type+vin: {0}{1}\tform.vars.vin: {2}\tis_primary: {3}'.format(cur_ecu.ecu_type, str(vin), vin, is_primary))
            # director.register_ecu_serial(cur_ecu.ecu_type+str(vin), ecu_pub_key, vin, is_primary)

    redirect(URL('index', vars=dict(ecu_id_list=ecu_id_list, selected_ecu_list=selected_ecus, changed_ecu_list=changed_ecu_list, vehicle_id=vehicle_id)))

@auth.requires_login()
def return_filename(update_image):
    print('update_image: {0}'.format(update_image))

    # After getting the file image name, convert the name of the file from hex to ascii
    # and use this value to populate the supplier db with
    fname_after_split=str(update_image).split('.')[-2]
    filename = bytes.fromhex(fname_after_split).decode('utf-8')
    print('filename: {0}'.format(filename))
    return str(filename)

# This function is intended to be used with a Director/OEM resetting their system
@auth.requires_login()
def reset_system():
    ''' This method is invoked when the Reset System button is clicked on the OEM screen.
    The intention of this function is to remove only the latest added ECU update and vehicle.
    :return: Updated ecu_db and vehicle_db
    '''
    try:
        vehicles = db(db.vehicle_db.oem == auth.user.username).select()
        ecus     = db(db.ecu_db.supplier_name == 'supplier1').select()

        # If there are more than one ecu's then continue (don't want to delete the last remaining ecu)
        if ecus and len(ecus) >1:
            # Get the last ecu from the pydal.Objects.Rows -- could iterate through the rows if necessary
            ecu = ecus.last()

            # Uncomment this line below if you want to delete the ecu
            #db(db.ecu_db.id == ecu.id).delete()

        # If there are more than one vehicles then continue (don't want to delete the last remaining vehicle)
        if vehicles and len(vehicles) >1:
            # Setup the director to call the XMLRPC call
            # If the vehicle vin is needed can get it from: vehicle.vin (assuming it's a single vehicle and not the list of vehicles)
            #director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
            #                            ':' + str(demo.DIRECTOR_SERVER_PORT))
            #director.clear_vehicle_targets()

            # Get the last vehicle from the pydal.Objects.Rows -- could iterate through the rows if necessary
            vehicle = vehicles.last()

            # Uncomment this line below if you want to delete the ecu
            #db(db.vehicle_db.id == vehicle.id).delete()

    except Exception as e:
        print('Unable to reset the system due to this error: {0}'.format(e))

# The calls below are intended to be used with the hacked.html page
# for a hacked Director Repository.
# In each of the attack scripts and recovery scripts below, we'll hard-code the
# vehicle identifier and image filename for the demo.

@auth.requires_login()
def hack1():
    print('\n\nDirector Hack1 - Arbitrary Package Nokeys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.mitm_arbitrary_package_attack('democar', 'TCU1.1.txt')
    print('\n\nDirector Hack1 - Arbitrary Package Nokeys - Done')

@auth.requires_login()
def hack2():
    print('\n\nDirector Hack2 - Prepare for Replay Attack Nokeys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.prepare_replay_attack_nokeys('democar')
    print('\n\nDirector Hack2 - Prepare for Replay Attack Nokeys - Done')

@auth.requires_login()
def hack3():
    print('\n\nDirector Hack3 - Replay Attack Nokeys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.replay_attack_nokeys('democar')
    print('\n\nDirector Hack3 - Replay Attack Nokeys - Done')

@auth.requires_login()
def hack4():
    print('\n\nDirector Repo Hack4 - Arbitrary Package Attack w/ Key CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.keyed_arbitrary_package_attack(
        'democar', 'TCUdemocar', 'TCU1.1.txt')
    print('\n\nDirector Repo Hack4 - Arbitrary Package Attack w/ Key - Done!')

@auth.requires_login()
def hack5():
    print('\n\nDirector Repo Hack5 - Sign with Compromised Keys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.sign_with_compromised_keys_attack()
    print('\n\nDirector Repo Hack5 - Sign with Compromised Keys - Done')

@auth.requires_login()
def hack6():
    print('\n\nHack6 Clicked - No Action')

@auth.requires_login()
def fix1():
    print('\n\nDirector Fix1 - Undo Arbitrary Package Nokeys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.undo_mitm_arbitrary_package_attack('democar', 'TCU1.1.txt')
    print('\n\nDirector Fix1 - Undo Arbitrary Package Nokeys - Done')

@auth.requires_login()
def fix2():
    print('\n\nFix2 Clicked - No Action')

@auth.requires_login()
def fix3():
    print('\n\Director Fix3 - Undo Replay Attack Nokeys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.undo_replay_attack_nokeys('democar')
    print('\n\Director Fix3 - Undo Replay Attack Nokeys - Done')

@auth.requires_login()
def fix4():
    print('\n\nDirector Fix4 - Undo Arbitrary Package Attack w/ Key - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.undo_keyed_arbitrary_package_attack(
        'democar', 'TCUdemocar', 'TCU1.1.txt')
    print('\n\nDirector Fix4 - Undo Arbitrary Package Attack w/ Key - Done')

@auth.requires_login()
def fix5():
    print('\n\nDirector Fix5 - Undo Sign with Compromised Keys - CLICKED!')
    director = xmlrpc.client.ServerProxy('http://' + str(demo.DIRECTOR_SERVER_HOST) +
                                        ':' + str(demo.DIRECTOR_SERVER_PORT))
    director.undo_sign_with_compromised_keys_attack()
    print('\n\nDirector Fix5 - Undo Sign with Compromised Keys - Done')

@auth.requires_login()
def fix6():
    print('\n\nFix6 Clicked - No Action')

# The calls below are intended to be used with the hacked_repo.html page
# for a hacked Image Repository.
@auth.requires_login()
def repo_hack1():
    """Arbitrary Package Attack on the Image Repo without Compromised Keys"""
    print('\n\nImage Repo Hack1 - MITM Arbitrary Package Nokeys - CLICKED!')
    imagerepo = xmlrpc.client.ServerProxy('http://' + str(demo.IMAGE_REPO_SERVICE_HOST) +
                                        ':' + str(demo.IMAGE_REPO_SERVICE_PORT))
    imagerepo.mitm_arbitrary_package_attack('TCU1.1.txt')

@auth.requires_login()
def repo_hack2():
    print('\n\nRepo Hack2 Clicked - No Action')

@auth.requires_login()
def repo_hack3():
    print('\n\nRepo Hack3 Clicked - No Action')

@auth.requires_login()
def repo_hack4():
    print('\n\nImage Repo Hack4 - Arbitrary Package Compromised Key CLICKED!')
    imagerepo = xmlrpc.client.ServerProxy('http://' + str(demo.IMAGE_REPO_SERVICE_HOST) +
                                        ':' + str(demo.IMAGE_REPO_SERVICE_PORT))
    imagerepo.keyed_arbitrary_package_attack('TCU1.1.txt')

@auth.requires_login()
def repo_hack5():
    print('\n\nRepo Hack5 Clicked - No Action')

@auth.requires_login()
def repo_hack6():
    print('\n\nRepo Hack6 Clicked - No Action')

@auth.requires_login()
def repo_fix1():
    """Undo Arbitrary Package attack w/out compromised keys"""
    print('\n\nImage Repo Fix1 - MITM Arbitrary Package Nokeys - CLICKED!')
    imagerepo = xmlrpc.client.ServerProxy('http://' + str(demo.IMAGE_REPO_SERVICE_HOST) +
                                        ':' + str(demo.IMAGE_REPO_SERVICE_PORT))
    imagerepo.undo_mitm_arbitrary_package_attack('TCU1.1.txt')

@auth.requires_login()
def repo_fix2():
    print('\n\nRepo Fix2 Clicked - No Action')

@auth.requires_login()
def repo_fix3():
    print('\n\nRepo Fix3 Clicked - No Action')

@auth.requires_login()
def repo_fix4():
    print('\n\nImage Repo Fix4 - Arbitrary Package Compromised Key Recovery CLICKED!')
    imagerepo = xmlrpc.client.ServerProxy('http://' + str(demo.IMAGE_REPO_SERVICE_HOST) +
                                        ':' + str(demo.IMAGE_REPO_SERVICE_PORT))
    imagerepo.undo_keyed_arbitrary_package_attack('TCU1.1.txt')

@auth.requires_login()
def repo_fix5():
    print('\n\nRepo Fix5 Clicked - No Action')

@auth.requires_login()
def repo_fix6():
    print('\n\nRepo Fix6 Clicked - No Action')


#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Description

"""

import sys
import os
import argparse
import random
import numpy as np
import torch
from ptranking.utils.args.argsUtil import ArgsUtil

from ptranking.ltr_adhoc.eval.ltr import LTREvaluator, LTR_ADHOC_MODEL
from ptranking.ltr_tree.eval.ltr_tree import TreeLTREvaluator, LTR_TREE_MODEL
from ptranking.ltr_adversarial.eval.ltr_adversarial import AdLTREvaluator, LTR_ADVERSARIAL_MODEL

def setup_seed(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True
"""
The command line usage:

(1) Without using GPU
python pt_ranking.py -model ListMLE -dir_json /home/dl-box/WorkBench/Dropbox/CodeBench/GitPool/wildltr_ptranking/testing/ltr_adhoc/json/

(2) Using GPU
python pt_ranking.py -cuda 0 -model ListMLE -dir_json /home/dl-box/WorkBench/Dropbox/CodeBench/GitPool/wildltr_ptranking/testing/ltr_adhoc/json/

"""

if __name__ == '__main__':

    """
    >>> Learning-to-Rank Models <<<
    (1) Optimization based on Empirical Risk Minimization
    -----------------------------------------------------------------------------------------
    | Pointwise | RankMSE                                                                   |
    -----------------------------------------------------------------------------------------
    | Pairwise  | RankNet                                                                   |
    -----------------------------------------------------------------------------------------
    | Listwise  | LambdaRank % ListNet % ListMLE % RankCosine %  ApproxNDCG %  WassRank     |
    |           | STListNet  % LambdaLoss                                                   |
    -----------------------------------------------------------------------------------------

    (2) Adversarial Optimization
    -----------------------------------------------------------------------------------------
    | Pointwise | IRGAN_Point                                                               |
    -----------------------------------------------------------------------------------------
    | Pairwise  | IRGAN_Pair                                                                |
    -----------------------------------------------------------------------------------------
    | Listwise  | IRGAN_List                                                                |
    -----------------------------------------------------------------------------------------

    (3) Tree-based Model (provided by LightGBM)
    -----------------------------------------------------------------------------------------
    | LightGBMLambdaMART                                                                    |
    -----------------------------------------------------------------------------------------

    >>> Supported Datasets <<<
    -----------------------------------------------------------------------------------------
    | LETTOR    | MQ2007_Super %  MQ2008_Super %  MQ2007_Semi %  MQ2008_Semi                |
    -----------------------------------------------------------------------------------------
    | MSLRWEB   | MSLRWEB10K %  MSLRWEB30K                                                  |
    -----------------------------------------------------------------------------------------
    | Yahoo_LTR | Set1 % Set2                                                               |
    -----------------------------------------------------------------------------------------
    | ISTELLA_LTR | Istella_S | Istella | Istella_X                                         |
    -----------------------------------------------------------------------------------------

    """

    parser = argparse.ArgumentParser('Run pt_ranking.')
    parser.add_argument('-cuda', type=int, help='specify the gpu id if needed, such as 0 or 1.', default=None)

    ''' path of json files specifying the evaluation details '''
    parser.add_argument('-dir_json', type=str, help='the path of json files specifying the evaluation details.')

    ''' learning rate for pretraining'''
    parser.add_argument('-pretrain_lr', type=float, help='the learning rate for pretraining.')

    ''' path of json files specifying the evaluation details '''
    parser.add_argument('-finetune_lr', type=float, help='the learning rate for finetuning.')

    ''' the output_dir '''
    parser.add_argument('-trial_num', type=int, help='the trial id.')

    ''' the augmentation '''
    parser.add_argument('-aug_type', type=str, help='the type of augmentation.')

    ''' the augmentation percentage '''
    parser.add_argument('-aug_percent', type=float, help='the percentage of augmentation.')

    ''' dim '''
    parser.add_argument('-dim', type=int, help='the dimension of pretrainer.')

    ''' layers '''
    parser.add_argument('-layers', type=int, help='the number of layers.')

    ''' pretrainer '''
    parser.add_argument('-pretrainer', type=str, help='the type of pretrainer.')

    ''' shrink '''
    parser.add_argument('-shrink', type=float, help='How much to shrink the train set')

    ''' freeze '''
    parser.add_argument('-freeze', type=int, help='Whether to freeze prev layers')

    ''' number of probing layers '''
    parser.add_argument('-probe_layers', type=int, help='Number of probing layers')

    ''' finetune_only '''
    parser.add_argument('-finetune_only', type=int, help='no pretrain')

    ''' finetune_trial '''
    parser.add_argument('-finetune_trials', type=float, help='finetuning trial id')

    argobj = parser.parse_args()

    if argobj.pretrainer == 'LightGBMLambdaMART':
        evaluator = TreeLTREvaluator()
        evaluator.run(model_id=argobj.pretrainer, dir_json=argobj.dir_json, config_with_json=True, argobj=argobj)

    else:
        if argobj.freeze:
            print('Finetune FROZEN')
        evaluator = LTREvaluator(cuda=argobj.cuda)
        # setup_seed(0)
        if argobj.aug_type != 'none' and not argobj.finetune_only:
            print('Starting pretraining!', sys.stderr)
            argobj.is_pretraining = True
            evaluator.run(model_id=argobj.pretrainer, dir_json=os.path.join(argobj.dir_json, '{0}/'.format(argobj.pretrainer)), config_with_json=True, argobj=argobj)

        print('Starting finetuning!', sys.stderr)
        argobj.is_pretraining = False
        if argobj.aug_type == 'none':
            evaluator.run(model_id="LambdaRank", dir_json=os.path.join(argobj.dir_json, 'lambdarank/'), config_with_json=True, argobj=argobj)
        else:
            evaluator.run(model_id="LambdaRankTune", dir_json=os.path.join(argobj.dir_json, 'lambdaranktune/'), config_with_json=True, argobj=argobj)