'''
This code was written in 15-113 lecture on Tue 7-Feb and Thu 9-Feb.
It is only for demonstrational purposes, and may contain
dubious style and even an occasional bug.
'''

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
import time

def main():
    # ==========================================================================
    # ======================Initialization Phase================================
    # ==========================================================================
    PATH = "place the path to your webdriver here"
    driver = webdriver.Chrome(PATH)

    driver.get('https://lichess.org/')
    # driver.maximize_window()

    mainPathHTML = "/html/body/div/main"
    playCompPath = "/div/div[2]/button[3]"
    playComputer = driver.find_element(By.XPATH, mainPathHTML + playCompPath)
    #creates a driver object that we can now click!
    playComputer.click()

    # failing code!
    # chessLevelPath = "/div[1]/div[3]/div/div/div/div[3]/div[1]/group/div[5]"
    # chessLevel = driver.find_element(By.XPATH, mainPathHTML + chessLevelPath)
    # chessLevel.click()
    # fails because we didn't wait for the html to update
    # inconsistent

    chessLevelPath = "/div[1]/div[3]/div/div/div/div[3]/div[1]/group/div[8]"
    chessLevel = WebDriverWait(driver, 100).until(
        EC.presence_of_element_located(
            (By.XPATH, mainPathHTML + chessLevelPath)))
    # 100 is the number of seconds we wait until we give up
    chessLevel.click()

    playButtonPath = "/div[1]/div[3]/div/div/div/div[4]/button[3]"
    playButton = driver.find_element(By.XPATH, mainPathHTML + playButtonPath)
    playButton.click()
    # no longer need to wait since it simultaneously loaded

    time.sleep(3)
    # how much time we wait for to wait for board to load

    # ==========================================================================
    # ===========================Game Phase=====================================
    # ==========================================================================

    boardPath = "/html/body/div[2]/main/div[1]/div[1]/div/cg-container/cg-board"
    boardObject = driver.find_element(By.XPATH, boardPath)

    makeSampleMove(driver, boardPath, boardObject)
    # print(analyzeBoard(driver, boardObject))

    while True:
        try:
            yourTurnPath = "/html/body/div[2]/main/div[1]/div[8]/div"
            _ = driver.find_element(By.XPATH, yourTurnPath)
            time.sleep(0.1)
        except:
            continue
        board = analyzeBoard(driver, boardObject)
        # move = calculateBestMove(board)
        # makeMove(driver, move)
        makeMoveManual(driver, input("make a move!"), boardObject)
        if checkIfGameOver(driver):
            break
        time.sleep(0.1)

    time.sleep(50)
    print("Goodbye World")

def makeSampleMove(driver, boardPath, boardObject):
    boardPiecePath = "/piece[21]"
    boardPiece = driver.find_element(By.XPATH, boardPath + boardPiecePath)

    action = ActionChains(driver)
    action.move_to_element(boardObject)
    action.drag_and_drop_by_offset(boardPiece, 0, -150)
    action.perform()

def analyzeBoard(driver, boardObject):
    '''
    :param boardObject:
    :return: 2D list with every element representing a grid
             empty grids will be empty strings
             filled grids will have format "w/b" + "i"
             i = first letter of piece
    '''
    locations = []
    names = []
    pieces = boardObject.find_elements(By.TAG_NAME, "piece")
    for piece in pieces:
        pieceLocation = piece.get_attribute("style")
        pieceName = piece.get_attribute("class")
        coordinate = convertStringToTuple(pieceLocation)
        locations.append(coordinate)
        names.append(pieceName)

    gcd = getGCD(driver)
    assert(int(gcd) == gcd) # look I really don't want to deal with fractions
    fullList = list(zip(locations, names))
    # fullList is a zipped list of (location, name)
    # location is the coordinate given and name is the name of the piece
    result = [["" for i in range(8)] for j in range(8)]

    for (location, name) in fullList:
        i = int(location[1] // gcd)
        j = int(location[0] // gcd)
        result[i][j] = name.split(" ")[0][0] + name.split(" ")[1][0]
    return result

def convertStringToTuple(pieceLocation):
    piece = pieceLocation.split(" translate(")[1]
    piece = piece.split(');')[0]
    piece = piece.split(", ")
    return float(piece[0][:-2]), float(piece[1][:-2])

def getGCD(driver):
    '''
    :return: the minimum number x of those values
    '''
    sizePath = "/html/body/div[2]/main/div[1]/div[1]/div/cg-container"
    sizeObject = driver.find_element(By.XPATH, sizePath)
    size = sizeObject.get_attribute("style")
    # "width: 600px; height: 600px;"
    size = size.split("px; ")[0]
    return int(size[7:])/8

def calculateBestMove(board):
    pass

def makeMoveManual(driver, move, boardObject):
    letterToXValue = {
        "A": 0,
        "B": 1,
        "C": 2,
        "D": 3,
        "E": 4,
        "F": 5,
        "G": 6,
        "H": 7,
    }
    start = move.split(" ")[0]
    end = move.split(" ")[1]
    gcd = getGCD(driver)

    startCoord = (letterToXValue[start[0].upper()], 8 - int(start[1]))
    startXPx = str(int(float(startCoord[0]*gcd)))
    startYPx = str(int(float(startCoord[1]*gcd)))
    endCoord = (letterToXValue[end[0].upper()], 8 - int(end[1]))
    endXPx = endCoord[0]*gcd
    endYPx = endCoord[1]*gcd
    styleName = f"transform: translate({startXPx}px, {startYPx}px);"

    boardPieces = boardObject.find_elements(By.TAG_NAME, "piece")
    boardPiece = ""
    for piece in boardPieces:
        if piece.get_attribute("style") == styleName:
            boardPiece = piece
    # assert boardPiece != ""
    action = ActionChains(driver)
    action.move_to_element(boardObject)
    action.drag_and_drop_by_offset(boardPiece, endXPx - int(float(startXPx)),
                                               endYPx - int(float(startYPx)))
    action.perform()

def checkIfGameOver(driver):
    try:
        gameOverPath = "/html/body/div[2]/main/aside/div/section[2]"
        _ = driver.find_element(By.XPATH, gameOverPath)
    except:
        return False
    return True


main()