from PIL import Image, ImageDraw, ImageFont
import math as maths
import numpy as np
import logging
import pygame
import gameUtils
import NoPELib
import random
import string
import json
import time
import os

# TODO: The current alphabets in the fonts.json file is kinda bullshit
#       someone should probably play some test rounds and fix the diffuculty issue.
# TODO: Re-draw the captcha when a character is complete.

alphabet = string.ascii_letters + string.digits + string.punctuation

def normalDist(difficulty):
    """
    This function generates a random number between 0-1
    that should be used to get a font with a difficulty
    equal to or less than the number returned.
    """
    # Create a random number between 0-1
    v = random.randint(-1000, 1000) / 1000
    # Redistribute the number
    v = v**3
    # Shift the number based on game difficulty
    difficulty = difficulty * 1.5 - 0.25
    v += difficulty
    # Cap it to be 0-1 again and return
    return min(max(v, 0), 1)

def PIL2PG(pilImage: Image) -> pygame.Surface:
    """
    Converts a PIL image to a Pygame surface.
    """
    return pygame.image.fromstring(pilImage.tobytes(), pilImage.size, pilImage.mode)

class Font:
    def __init__(self, data, fontSize):
        self.path = data['file']
        self.font = ImageFont.truetype(os.path.join('./fonts/', data['file']), fontSize)
        self.alphabet = data['alphabet']
        self.difficulty = data['difficulty']

class Captcha:
    """
    Holds and manages the current captcha.
    """

    def __init__(self):
        self.chars = []
        self.fonts = []
        self.charIdx = 0 # Holds which character the player is on,
                        # anthing below this index should appear green.

    def addChar(self, char, font):
        self.chars.append(char)
        self.fonts.append(font)

    def match(self, char):
        if self.charIdx == len(self.chars):
            return True, True

        matches = char.upper() == self.chars[self.charIdx].upper()
        print(f'{char.upper()} == {self.chars[self.charIdx]}')
        if matches:
            self.charIdx += 1
            return True, self.charIdx == len(self.chars)
        else:
            return False, self.charIdx == len(self.chars)

class Game(gameUtils.Game):
    def __init__(self, *args, **kwargs):
        """
        Ran when the game starts.

        You have access to the following variables:
            self.surf (pygame.Surface): This is the surface you draw onto.
            self.pm (NoPELib.PlayerManager): This is where your players are stored.
            self.cfg (dict): Everything from the `game.toml` file. You can access it like this: self.details['title']
        """

        # Don't remove this. It does important things. :3
        super().__init__(*args, **kwargs)

        # A value between 0-1 that defines which fonts the game will use
        self.gameDifficulty = 0
        # The actual text in the captcha, used to check what the user typed
        self.currentCaptcha = None
        # The image of the captcha
        self.currentCaptchaImg = pygame.Surface((0, 0))
        # Holds when the timer started
        self.timerStart = 0
        # Holds how much time the user has in total (not updated)
        self.timerLength = 0
        # Used for animating the red flash on the current character if the users enters it wrong.
        self.lastWrong = 0

        self.fonts = []
        self.Drawsurface  = Image.new("RGB",(800,220),(255,255,255))

        # I haven't finished this yet, you might want to create
        # an object or something, I don't know. Keep in mind you
        # have to load all of the font files for use later.
        with open(f'./fonts/fonts.json', 'r') as f:
            for fontData in json.loads(f.read()):
                self.fonts.append(Font(fontData, 100))
            #self.fontList = json.loads(f.read())#loads the fonts from the json (Why the fuck was the variable called 'j' before????)

        #for fontData in j:
        #    self.fonts.update()

    def fontsByDifficulty(self, difficulty, width=0.3):
        """
        Gets a list of fonts that fall within the specified difficulty.
        """
        return [
            font for font in self.fonts
            if font.difficulty >= difficulty-width and font.difficulty <= difficulty+width
        ]

    def createCaptcha(self):
        """
        Use PIL (https://pillow.readthedocs.io/en/stable/handbook/tutorial.html)
        to draw the fonts, add noise, random shapes, transforms etc. based on
        self.gameDifficulty
        """
        # Create a surface for that captcha
        drawSurface  = Image.new("RGB", (800,220), (255,255,255))
        # Create a drawing object
        draw = ImageDraw.Draw(drawSurface)

        # Temporarily stores the current captcha text
        captcha = Captcha()
        # Get a random cpatcha length
        capLength = random.randint(3, 6)
        for i in range(capLength):
            # Calculate the base X position of this character
            # TODO: Make this better, I'm not too happy with my implementation here.
            x = self.size[0] / (capLength + 3)
            x *= i
            # Get the selected difficulty for this character
            charDiff = normalDist(self.gameDifficulty)
            # Pick a random font based on that difficulty
            font = random.choice(self.fontsByDifficulty(charDiff))
            # Pick a random character
            char = random.choice(font.alphabet)
            # Draw the selected character, with the selected font
            draw.text((x, 10), char, font=font.font, fill=(0,0,0))
            # Append the character to the captcha
            captcha.addChar(char, font)

        # Store the correct captcha text
        self.currentCaptcha = captcha

        # Store the created image
        self.currentCaptchaImg = PIL2PG(drawSurface)

        for char, font in zip(captcha.chars, captcha.fonts):
            print(f'{char}: {font.path}')

        # Start the timer
        self.timerStart = time.perf_counter()
        self.timerLength = random.randint(7, 15)

    def onEvent(self, event):
        """
        Ran when an event is fired.

        Args:
            event (pygame.Event): The event that was fired.
        """
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_F1:
                self.gameDifficulty -= 0.1
                print(self.gameDifficulty)
            elif event.key == pygame.K_F2:
                self.gameDifficulty += 0.1
                print(self.gameDifficulty)
            elif event.key == pygame.K_F3:
                self.createCaptcha()

            elif event.unicode != '' and event.unicode in alphabet:
                correct, finished = self.currentCaptcha.match(event.unicode)
                if not correct:
                    self.lastWrong = time.perf_counter()
                print(correct)
                if finished:
                    self.createCaptcha()
    
    def update(self):
        """
        Ran once per frame, put your drawing code and any
        game logic that should be ran once per frame in here.
        """

        r = 1 - min(time.perf_counter() - self.lastWrong, 1)

        self.surf.fill((r * 255, 0, 0))                                      #sets the background colour
        self.surf.blit(gameUtils.centre(self.currentCaptchaImg, self.size))  #draws the text to center of the screen
                                                                        #TODO: actually scale the text, however, more work needs to be done on the actual function of the game first
        
        if self.currentCaptcha is not None:
            x1 = self.size[0] / 2 - self.currentCaptchaImg.size[0] / 2
            y = self.size[1] / 2 + self.currentCaptchaImg.size[1] / 2
            x2 = x1 + (self.currentCaptcha.charIdx / len(self.currentCaptcha.chars)) * self.currentCaptchaImg.size[0]
            pygame.draw.line(self.surf, (0, 255, 0), (x1, y), (x2, y), 5)

        if self.timerLength != 0:
            timeLeft = self.timerLength - (time.perf_counter() - self.timerStart)
            normalisedTimeLeft = max(timeLeft / self.timerLength, 0)
            x = round((1-normalisedTimeLeft) * (self.size[0] // 2))
            w = normalisedTimeLeft * self.size[0]
            c = np.round((255, 255*normalisedTimeLeft, 255*normalisedTimeLeft))
            pygame.draw.rect(self.surf, c, (x, 0, w, 10))

    def close(self):
        """
        Ran when the game closes.
        """
        pass
