import tkinter as tk
import color
import main as quadraLudi
import importlib
from socket import socket, AF_INET, SOCK_STREAM
from datetime import datetime

# Server
SERVER_ADDRESS = 'eveldee.ddns.net'
SERVER_PORT = 1409

# Tags
TEXT_TIME = 'Time'
TEXT_PSEUDO = 'Pseudo'
TEXT_SEPARATION = 'Separation'
TEXT_MESSAGE = 'Message'
TEXT_RANK = 'Rank'
TEXT_SHIFT_SEPARATOR = 'Shift_Separation'
TEXT_SCORE = 'Score'

class Lobby:
    def __init__(self):
        # Win
        win = tk.Tk()
        win.title('QuadraLudi - Lobby')
        win.geometry('800x640')
        win['bg'] = color.LEVEL_1
        # win.resizable(False, False)
        self.win = win

        # Frame
        self.frame = None

        # Variables
        self.pseudo = tk.StringVar(win, "")
        self.message = tk.StringVar(win, "")
        self.scoreLen = 0
        self.chatLen = 0

        # Network
        self.networkManager = NetworkManager()
        self.isUpdating = True

        # First
        self.nameDisplay()

    def resetFrame(self):
        if self.frame != None:
            self.frame.destroy()

        frame = tk.Frame(self.win, bg = color.LEVEL_1)
        frame.pack(fill = tk.BOTH, expand = True, padx = 5, pady = 5)
        self.frame = frame

    def nameDisplay(self):
        self.resetFrame()

        # Place
        pseudoFrame = tk.LabelFrame(self.frame, text = 'Pseudo', bg = color.LEVEL_1, fg = color.PURPLE)
        pseudoFrame.place(relx = 0.5, rely = 0.5, width = 200, height = 50, anchor = 'center')

        entry = tk.Entry(pseudoFrame, textvariable = self.pseudo, bg = color.LEVEL_2, fg = color.GHOST)
        entry.bind('<Return>', lambda e: self.nameConfirm())
        entry.pack(side = tk.LEFT, expand = True)
        entry.focus()
        tk.Button(
            pseudoFrame, text = 'Valider', bg = color.PURPLE, fg = color.LEVEL_1,
            command = lambda: self.nameConfirm()
        ).pack(side = tk.LEFT, expand = True)

    def lobbyDisplay(self):
        self.resetFrame()

        # Configure layout
        mainFrame = self.frame
        mainFrame.columnconfigure(0, weight = 4)
        mainFrame.columnconfigure(1, weight = 1)
        mainFrame.rowconfigure(0, weight = 1)

        # Score
        scoreFrame = tk.LabelFrame(mainFrame, text = 'Score', bg = color.LEVEL_1, fg = color.PURPLE, font = (None, 12))
        scoreFrame.grid(column = 0, row = 0, sticky = 'NSEW', padx = 5)
        scoreFrameDisplay = tk.Text(scoreFrame, bg = color.LEVEL_1, bd = 0, wrap = tk.WORD, font = (None, 11), spacing1 = 7)
        scoreFrameDisplay.tag_config(TEXT_RANK, foreground = color.PURPLE)
        scoreFrameDisplay.tag_config(TEXT_SHIFT_SEPARATOR, foreground = color.BLUE)
        scoreFrameDisplay.tag_config(TEXT_PSEUDO, foreground = color.GREEN)
        scoreFrameDisplay.tag_config(TEXT_SEPARATION, foreground = color.YELLOW)
        scoreFrameDisplay.tag_config(TEXT_SCORE, foreground = color.PINK)
        scoreFrameScroll = tk.Scrollbar(scoreFrame, command = scoreFrameDisplay.yview)
        scoreFrameDisplay.config(yscrollcommand = scoreFrameScroll.set)
        scoreFrameScroll.pack(side = tk.RIGHT, fill = tk.Y)
        scoreFrameDisplay.pack(side = tk.LEFT, fill = tk.BOTH)
        self.scoreFrameDisplay = scoreFrameDisplay

        # Chat Frame
        chatFrame = tk.LabelFrame(mainFrame, text = 'Chat', bg = color.LEVEL_1, fg = color.PURPLE, font = (None, 12))
        chatFrame.grid(column = 1, row = 0, sticky = 'NSEW', padx = 5)

        # Chat input
        bottomFrame = tk.Frame(chatFrame, bg = color.LEVEL_5, name = 'bottomFrame')
        bottomFrame.pack(side = tk.BOTTOM, fill = tk.X, expand = True)
        entry = tk.Entry(bottomFrame, textvariable = self.message, bg = color.LEVEL_1, fg = color.GHOST, font = (None, 11))
        entry.bind('<Return>', lambda e: self.sendMessage())
        entry.pack(side = tk.LEFT, fill = tk.BOTH, expand = True, padx = 5, pady = 5)
        entry.focus()
        self.chatEntry = entry
        tk.Button(
            bottomFrame, text = 'Envoyer', bg = color.PURPLE, fg = color.LEVEL_1,
            command = lambda: self.sendMessage()
        ).pack(side = tk.LEFT, padx = 5, pady = 5)

        # Chat display
        chatTextFrame = tk.Frame(chatFrame, bg = color.LEVEL_1)
        chatTextFrame.pack(side = tk.TOP, fill = tk.BOTH, pady = 2)
        chatTextDisplay = tk.Text(chatTextFrame, bg = color.LEVEL_1, bd = 0, state = tk.DISABLED, wrap = tk.WORD, font = (None, 11), spacing1 = 7)
        chatTextDisplayScroll = tk.Scrollbar(chatTextFrame, command = chatTextDisplay.yview)
        chatTextDisplay.config(yscrollcommand = chatTextDisplayScroll.set)

        chatTextDisplayScroll.pack(side = tk.RIGHT, fill = tk.Y)
        chatTextDisplay.pack(side = tk.LEFT, fill = tk.BOTH, padx = 3)
        self.chatTextDisplay = chatTextDisplay

        chatTextDisplay.tag_config(TEXT_TIME, foreground = color.PURPLE)
        chatTextDisplay.tag_config(TEXT_PSEUDO, foreground = color.GREEN)
        chatTextDisplay.tag_config(TEXT_SEPARATION, foreground = color.YELLOW)
        chatTextDisplay.tag_config(TEXT_MESSAGE, foreground = color.PINK)

        # Play
        tk.Button(
            mainFrame, text = 'Jouer', bg = color.PURPLE, fg = color.LEVEL_1,
            command = lambda: self.play()
        ).grid(column = 0, row = 1, columnspan = 2, sticky = 'NSEW', padx = 5, pady = 5)

        self.update()

    def update(self):
        if self.isUpdating:
            messages = self.networkManager.requestMessages()
            scores = self.networkManager.requestScores()

            # Score
            if len(scores) > self.scoreLen:

                self.scoreLen = len(scores)

                rank = 1
                for score in scores:
                    pseudo, score = score.split(';')
                    self.addScoreEntry(rank, pseudo, score)
                    rank += 1

            # Chat
            if len(messages) > self.chatLen:
                for messsage in messages[self.chatLen:]:
                    split = messsage.split(';')
                    time, pseudo, content = split[0], split[1], ';'.join(split[2:])
                    self.addChatEntry(time, pseudo, content)

                self.chatLen = len(messages)

        self.win.after(1000, lambda: self.update())

    def nameConfirm(self):
        pseudo = self.pseudo.get().replace('@', ' ').replace(';', ' ')

        if pseudo == '『 』':
            return
        elif pseudo == '   ':
            self.pseudo.set('『 』')
        elif pseudo == '' or pseudo.isspace():
            return

        self.lobbyDisplay()

    def sendMessage(self):
        # Check empty
        message = self.message.get()
        pseudo = self.pseudo.get()
        if message == '':
            return

        # Send
        message = message.replace('@', ' ')
        self.networkManager.sendMessage(pseudo, message)

        # Clear
        now = datetime.now().time()
        time = f"{now.hour}:{str(now.minute).zfill(2)}"
        self.addChatEntry(time, pseudo, message)
        self.chatLen += 1
        self.message.set('')

    def sendScore(self, score):
        self.networkManager.sendScore(self.pseudo.get(), score)

    def play(self):
        # Reload
        importlib.reload(quadraLudi)

        # Minimize
        self.win.iconify()

        # Display
        self.isUpdating = False
        quadraLudi.main(self.win, lambda score: self.playEnd(score))

    def playEnd(self, score):
        # Display window, focus
        self.win.deiconify()
        self.win.focus_force()
        self.chatEntry.focus()
        self.isUpdating = True

        # Send score
        self.sendScore(score)

    def addScoreEntry(self, rank, pseudo, score):
        self.scoreFrameDisplay['state'] = tk.NORMAL

        self.scoreFrameDisplay.insert(tk.END, f'{rank}', TEXT_RANK)
        self.scoreFrameDisplay.insert(tk.END, f'> ', TEXT_SHIFT_SEPARATOR)
        self.scoreFrameDisplay.insert(tk.END, f'{pseudo}', TEXT_PSEUDO)
        self.scoreFrameDisplay.insert(tk.END, f': ', TEXT_SEPARATION)
        self.scoreFrameDisplay.insert(tk.END, f'{score}\n', TEXT_SCORE)

        self.scoreFrameDisplay['state'] = tk.DISABLED

    def addChatEntry(self, time, pseudo, message):
        self.chatTextDisplay['state'] = tk.NORMAL

        self.chatTextDisplay.insert(tk.END, f'[{time}] ', TEXT_TIME)
        self.chatTextDisplay.insert(tk.END, f'({pseudo})', TEXT_PSEUDO)
        self.chatTextDisplay.insert(tk.END, ': ', TEXT_SEPARATION)
        self.chatTextDisplay.insert(tk.END, f'{message}\n', TEXT_MESSAGE)

        self.chatTextDisplay.see(tk.END)
        self.chatTextDisplay['state'] = tk.DISABLED

    def show(self):
        self.win.mainloop()

class NetworkManager:
    def __init__(self):
        self.address = (SERVER_ADDRESS, SERVER_PORT)

    def _createSocket(self):
        # Create TCP socket
        sock = socket(AF_INET, SOCK_STREAM)
        sock.connect(self.address)

        return sock

    def _sendPacket(self, command, content):
        packet = f"{command}\n{content}".encode('utf-8')
        length = len(packet).to_bytes(4, 'little')

        sock = self._createSocket()
        sock.sendall(length)
        sock.sendall(packet)
        sock.close()

    def _receivePacket(self, command):
        sock = self._createSocket()

        # Send command
        packet = f"{command}\n".encode('utf-8')
        length = len(packet).to_bytes(4, 'little')

        sock.sendall(length)
        sock.sendall(packet)

        # Receive
        # Length
        length = sock.recv(4)
        length = int.from_bytes(length, 'little')
        # Data
        response = sock.recv(length)
        response = response.decode('utf-8')

        sock.close()

        split = response.split('\n')
        return (split[0], '\n'.join(split[1:]))

    def sendMessage(self, pseudo, message):
        now = datetime.now().time()
        time = f"{now.hour}:{str(now.minute).zfill(2)}"

        self._sendPacket('ChatAdd',  f"{time};{pseudo};{message}")

    def sendScore(self, pseudo, score):
        self._sendPacket('ScoreAdd', f"{pseudo};{score}")

    def requestMessages(self):
        _, response = self._receivePacket('ChatRequest')
        return response.split('@')

    def requestScores(self):
        _, response = self._receivePacket('ScoreRequest')
        return response.split('@')

def main():
    lobby = Lobby()
    lobby.show()

if __name__ == "__main__":
    main()
