# THIS IS A SOURCE CODE FILE FROM I'M NOT EVEN HUMAN THE GAME.
# IT COULD BE USED IN A DIFFERENT PIECE OF SOFTWARE ( LIKE A
# DIFFERENT GAME ), BUT IT WAS ORIGINALLY WRITTEN FOR I'M NOT
# EVEN HUMAN THE GAME.

# THE DEVELOPERS OF THE GAME ARE : (C) J.Y.AMIHUD, AYYZEE AND 
# OTHER CONTRIBUTORS. THIS AND OTHER FILES IN THIS GAME,
# UNLESS SPECIFICALLY NOTED, COULD BE USED UNDER THE TERMS OF
# GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER VERSION.

import os
import json
import cairo
import math


def load_palete(game):

    """Loads colors from assets/palete.json"""

    with open("assets/palete.json") as f:
        game.palete = json.load(f)

def distance(list1, list2):

    """Calculates distnaces between two vectors"""
    d = []
    for n, i in enumerate(list1):
        try:
            d.append(max((i-list2[n]),(list2[n]-i)))
        except:
            d.append(0)
    return sum(d) / len(d)

def color(game, layer, code, alpha=1):

    """Sets a cairo brush color from a palete by looking for the
       closest match."""

    # If it's a code in hex like #FF0000 so we need to convert it
    # to numbers first

    if type(code) == str and code.startswith("#") and (len(code) -1) %3 == 0:
        new_c = []
        for c in range(3):
            ind = int((len(code)-1)/3)
            new_c.append( int(code[ind*c+1:ind*c+ind+1], 16)/255 )
        code = new_c
    # If it's a code in RGB, we look for the nearest
    # color on the palete.

    
    if type(code) == list or type(code) == tuple:

        name = "black"     # Place holder for a palete entry
        dist = 10000000000 # Imaginary large number

        # Chekcing for the lowest possible deviation of color
        for color in game.palete:
            new_dist = distance(game.palete[color], code)
            if dist > new_dist:
                name = color
                dist = new_dist
        r, g, b = game.palete[name] 

    elif type(code) == str:
        try:
            r, g, b = game.palete[code] 
        except:
            r, g, b = 0, 0, 0

    else:
        r, g, b = 0, 0, 0

    layer.set_source_rgba(r,g,b,alpha)

def cache_sprite_sheet(game, sheet, asset=False):

    """This function will cache sprite sheets into game.images"""

    if sheet not in game.images:

        # Loading the sheet image
        grid = cairo.ImageSurface.create_from_png(sheet)

        # Loading metadata
        try:
            with open(sheet.replace(".png", ".json")) as f:
                metadata = json.load(f)
        except:
            metadata = {}
            
        width = 1
        height = 1
        try:
            width = metadata["resolution"][0]
            height = metadata["resolution"][1]
        except:
            pass
            
            
        # Getting the resolution of the cell
        cellx = int(grid.get_width()/width) 
        celly = int(grid.get_height()/height)

        data = {}

        if asset:
          
            game.elements[asset[0]][asset[1]] = []
        
        for x in range(width):
            for y in range(height):

                # Creating individual cells
                cell = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                                          cellx,
                                          celly)
                drawcell = cairo.Context(cell)
                drawcell.set_antialias(cairo.ANTIALIAS_NONE)
                drawcell.set_source_surface(grid,
                                            1-(cellx*x+1),
                                            1-(celly*y+1))
                drawcell.paint()

                # Putting the cell into the data as in "0:0" or "43:69".
                cellname = str(x)+":"+str(y)
                cellnameindata = cellname
                celldata = {}
                try:

                    #print("SEE IT PLEASE!", game.elements[asset[0]][asset[1]])
                    
                    if asset and metadata[cellname] not in game.elements[asset[0]][asset[1]]:
                        
                        game.elements[asset[0]][asset[1]].append(metadata[cellname].copy())

                    cellnameindata = metadata[cellname]["title"]
                    celldata = metadata[cellname]
                except:
                    pass
                celldata["binary"] = cell
                if cellnameindata not in data:
                    data[cellnameindata] = [celldata]
                else:
                    data[cellnameindata].append(celldata)
        try:
            data["title"] = metadata["title"]
        except:
            data["title"] = sheet

        try:
            data["menu_color"] = metadata["menu_color"]
        except:
            data["menu_color"] = "#000000"

        try:
            data["authors"] = metadata["authors"]
        except:
            try:
                data["authors"] = metadata["autors"]
            except:
                data["authors"] = []

        try:
            data["licenses"] = metadata["licenses"]
        except:
            data["licenses"] = []
            
        #data["current_frame"] = 0 # Don't need it quite yet
        
        game.images[sheet] = data
    
def image(game, layer, x, y, name, code="0:0", color=False, offset=False, alpha=1, frame=None, dynamic=False):

    x = int(x)
    y = int(y)
    
    """This function draws an image into a canvas. And load it if
       it's not loaded"""

    # Load the whole image as a sprite ( if it's not loaded yet )
    cache_sprite_sheet(game, name)

    # Drawing the image

    

    source = game.images[name].get(code,
                                   list(game.images[name].values())[-5])


    # We need to know what frame of an animation to use 'anim_pulse' and
    # how much frames ago the pulse was changed. We record the current frame
    # into the "anim_last" on every change.
    
    if "anim_pulse" not in game.current:
        game.current["anim_pulse"] = 0
    if "anim_last" not in game.current:
        game.current["anim_last"] = game.current["frame"]
    
    # And so if a specific amount of frames ( 1 second of frames / 10 ) is passed
    
    if ( game.current["frame"] - game.current["anim_last"] ) > int(game.FPS/10):
        game.current["anim_pulse"] += 1     # We advance one frame
        game.current["anim_last"] = game.current["frame"] # And we record the time of
                                                          # this change

    # The current frame is the modulo of the 'anim_pulse' by the amount of frames in
    # this particular animation.
    if frame == None:
        cf = game.current["anim_pulse"] % len(source)
    else:
        cf = frame
    
    if offset:
        off = source[cf].get("offset", [0,0])
        x += off[0]
        y += off[1]

        if dynamic:
            x -= int(64/2)
            y -= int(36/2)
    
        
    
    if color: #If we forcing color
        
        layer.mask_surface(source[cf]["binary"],x, y)
        layer.set_antialias(cairo.ANTIALIAS_NONE)
        p = layer.get_source()
        p.set_filter(cairo.FILTER_NEAREST)
        layer.fill()
    else:     #If we just using the image
        
        layer.set_source_surface(source[cf]["binary"], x, y)
        layer.set_antialias(cairo.ANTIALIAS_NONE)
        p = layer.get_source()
        p.set_filter(cairo.FILTER_NEAREST)
        if alpha == 1:
            layer.paint()
        else:
            layer.paint_with_alpha(alpha)
        
def text(game, layer, string, x, y, color=True, align="left"):

    """This function will draw text using an assets/font.png file."""

    for n, l in enumerate(string):
        ax =  x+(8*n)
        if align == "center":
            ax = ax - int(len(string)*4)
        elif align == "right":
            ax = ax - int(len(string)*8)
        image(game, layer, ax, y, "assets/font.png", l, color=color)
                
def button(game, layer, x, y, w, h,
           menu="", icon="", string="", func=False,
           scroll="", borders=True, btnTxtColor="#00FF00"):

    """Draws a button"""

    

    # Let's add this button into a menu.

    menu_selected = False
    select_number = 0
    
    if menu:
        if menu not in game.menus :
            game.menus[menu] = {"selected":0,
                                "buttons":[]}


        bmenudata = [x,y]

        if bmenudata not in game.menus[menu]["buttons"]:
            game.menus[menu]["buttons"].append(bmenudata)
        
        for n, i in enumerate(game.menus[menu]["buttons"]):
            
            if i == bmenudata:
                
                select_number = n
                if n == game.menus[menu]["selected"]:
                    menu_selected = True

    if scroll and scroll in game.scroll:
       

        # Making sure that while I press either UP or DOWN it will
        # scroll to the correct spot.

        if (65362 in game.current["keys"]\
        or 65364 in game.current["keys"])\
        and menu_selected:
            game.scroll[scroll] = 0 - y + int(game.current["h"] / 2)

        y = y + int(game.scroll[scroll])
    
        
    x = int(round(x))
    y = int(round(y))
    w = int(round(w))
    h = int(round(h))

    
        
    color(game, layer, btnTxtColor)
    layer.set_line_width(1)
    if borders:
        layer.rectangle(x,y,w,h)
        layer.stroke()

        

    mo = False
    if int(game.current["mx"]) in range(x, x+w) \
    and int(game.current["my"]) in range(y, y+h):
        mo = True
        
    if mo or menu_selected:
        if menu:
            game.menus[menu]["selected"] = select_number
        
        if mo:
            layer.rectangle( x+1,y+1, w-3, h-3 )
            layer.fill()
        else:
            #layer.rectangle( x+1,y+1, w-2, h-2 )
            #layer.stroke()

            #Fancy lines
            layer.set_line_width(3)
            
            layer.move_to(x,y+h/3)
            layer.line_to(x,y)
            layer.line_to(x+w/3, y)
            layer.stroke()

            layer.move_to(x+w,y+h-h/3)
            layer.line_to(x+w,y+h)
            layer.line_to(x+w-w/3, y+h)
            layer.stroke()
            
            layer.set_line_width(1)

    # If you clicked the button
    if ( game.previous["LMB"] \
         and int(game.previous["LMB"][0]) in range(x, x+w) \
         and int(game.previous["LMB"][1]) in range(y, y+h) ) \
         or ( menu_selected and 65293 in game.previous["keys"]):

        color(game, layer, "yellow")
        layer.rectangle( x+1,y+1, w-3, h-3 )
        layer.fill()

        # If the is doing something
        if (( not game.current["LMB"] and game.previous["LMB"] and mo) \
             or (65293 not in game.current["keys"] \
                 and 65293 in game.previous["keys"])) \
                 and func:
            func()
            game.previous["LMB"] = False
            game.current["LMB"]  = False
            game.current["keys"] = []
            game.previous["keys"] = []

    if icon:
        if mo:
            color(game, layer, "black")
        image(game, layer, x+2,y+1,"assets/menu_icons.png",
              icon, True)
        
    if string:
        if mo:
            color(game, layer, "black")
            
        widthleft = w - 4
        xd = x
        if icon:
            widthleft -= 12
            xd += 12
        sl = int(widthleft / 8)
        if len(string)*8 > widthleft:
            
            fp = int(game.current["frame"]/10) % (len(string)+7)
            text(game, layer, (string+"       "+string)[fp:fp+sl], xd+2, y-int(h/5)+3 )
        #elif len(string)*8 > widthleft:
        #    text(game, layer, string[:sl-3]+"...", xd+2, y-int(h/5)+3 )
            
        else:
            text(game, layer, string, x+int(w/2), y-int(h/5)+3, align="center")
        
def button_navigate(game, menu):

    """This function will run in each layer to provide keyboard navigation."""

    # We don't want to run it if non of the arrow keys are pressed.
    # Thus we check for either of the arrow keys is pressed.
    if 65361 in game.current["keys"]\
    or 65362 in game.current["keys"]\
    or 65363 in game.current["keys"]\
    or 65364 in game.current["keys"]:

        # We want to know things about the currently selected button
        select = game.menus[menu]["selected"] # It's number in the list
        cur    = game.menus[menu]["buttons"][select] # It's coordinates x, y
        prevdistance = 100000000000 # Arbitrary huge number

        # For all buttons in the menu list, we are going to see if it's
        # in a correct direction from the currently selected button, based on
        # the arrow key:
        #      65361 - RIGHT
        #      65362 - UP
        #      65363 - LEFT
        #      65364 - DOWN
        # But since there could be more then one button towards the correct
        # side, we are also checking for the closest one ( by comparing the
        # lowest possible distance to the currently selected button ).
        
        for n, i in enumerate( game.menus[menu]["buttons"] ):
            curdistance = distance(i, cur)
            if ((65361 in game.current["keys"] and i[0] < cur[0] )\
            or  (65362 in game.current["keys"] and i[1] < cur[1] )\
            or  (65363 in game.current["keys"] and i[0] > cur[0] )\
            or  (65364 in game.current["keys"] and i[1] > cur[1] ))\
            and (curdistance < prevdistance)\
            and i != cur:
                select = n
                prevdistance = curdistance

        # And we restart keys, so it will not do this on each frame
        game.current["keys"] = []
        # And we write the number of the selected key
        game.menus[menu]["selected"] = select
            
            
def scroll_area(game, layer, menu, x, y, width, height, max_value,
                strenght=6):

    """This function makes an invisible area in the UI, where there
    could be scroolling."""

    
    if max_value == 0:
        max_value = 1
    
    x = int(x)
    y = int(y)
    width = int(width)
    height = int(height)
    max_value += 5

    amount = 0.0
    if  int(game.current['mx']) in range(x, x+width) \
    and int(game.current['my']) in range(y, y+height):
    
        amount = game.current["scroll"][1] * strenght
        game.current["scroll"] = [0, 0]

        # Middle mouse drag ( for those who have no working wheel
        # like myself, lol )
        
        if game.current["MMB"]\
        and int(game.current["mx"]) in range(x, x+width)\
        and int(game.current["my"]) in range(y, y+height):
            amount = 0- ( game.current["my"] - game.previous["my"] )

    def logic():
        
        # Scroll logic
        game.scroll[menu] -= amount
        
        # If too low  
        if game.scroll[menu] < (1-max_value+height):
            game.scroll[menu] = (1-max_value+height)
        # If too high
        if game.scroll[menu] > 0:
            game.scroll[menu] = 0    

        
            
    logic() 

    if game.current["testing"]:
        color(game, layer, "red")
        layer.rectangle(x,y+1,width-1,height-1)
        layer.stroke()

def blur(surface, game, amount):
    
    # This function will blur a given layer by scaling it down and scaling it 
    # back up. It will be doing it only when a given blur setting it active.
    
    # To avoid all kinds of Zero devision problems. And speed up the draw if
    # using animated blur values.
    if amount < 2: # When Blue value was less then 3 it felt sharp but not enough
        return surface # Which caused sense of uneasiness. 
    
    # If to active blur. Will be changed in the graphics settings. 
    if game.settings.get("Blur", True):
        # scaling down
        surface1 = cairo.ImageSurface(cairo.FORMAT_ARGB32, game.current['w'],
                                                           game.current['h'])

        

        slacedownlayer = cairo.Context(surface1)
        slacedownlayer.scale(1/amount, 
                             1/amount)
        
        slacedownlayer.set_source_surface(surface, 0 , 0)

        if game.settings["NN-Downscaler-Blur"]:
            p = slacedownlayer.get_source()
            p.set_filter(cairo.FILTER_NEAREST)
        
        slacedownlayer.paint()
        
        #scaling back up
        surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, game.current['w'],
                                                           game.current['h'])
        slaceuplayer = cairo.Context(surface2)
        
        slaceuplayer.scale(amount, 
                           amount)

        
        
        slaceuplayer.set_source_surface(surface1, 0 , 0)

        if game.settings["NN-Upscaler-Blur"]:
            p = slaceuplayer.get_source()
            p.set_filter(cairo.FILTER_NEAREST)

        slaceuplayer.paint()
        
        return surface2
    else:
        return surface

def round_rect(layer, x, y, width, height, r):

    if width < r*2:
        width = r*2
    if height < r*2:
        height = r*2
    
    layer.move_to(x,y+r)
    layer.arc(x+r, y+r, r, math.pi, 3*math.pi/2)
    layer.arc(x+width-r, y+r, r, 3*math.pi/2, 0)
    layer.arc(x+width-r, y+height-r, r, 0, math.pi/2)
    layer.arc(x+r, y+height-r, r, math.pi/2, math.pi)
    layer.close_path()


def fancy_health_bar(game, x, y, w, h, oldlayer, value):

    # This is going to be a fancy health bar, dah.
    
    # Setting up a cairo layer
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                                 int(w),
                                 int(h))
    layer = cairo.Context(surface)
    layer.set_antialias(cairo.ANTIALIAS_NONE)

    # RED PART
    
    round_rect(layer, 0,0, w,h,h/2)
    layer.clip()
    
    color(game, layer, "red")
    layer.rectangle(0,0,w,h)
    layer.fill()

    color(game, layer, "brown")
    layer.rectangle(0,0,w,h/3)
    layer.fill()
    
    color(game, layer, "dark_red")
    layer.rectangle(0,0,w,h/5)
    layer.fill()

    color(game, layer, "bright_pink")
    layer.rectangle(w/2,h-5,w*0.45, 2)
    layer.fill()

    # shadow
    color(game, layer, "dark_red")
    round_rect(layer, 0,0, (w*value)+5 ,h ,h/2)
    layer.fill()

    # GREEN PART
    
    round_rect(layer, 0,0, w*value ,h ,h/2)
    layer.clip()
    
    color(game, layer, "dark_green2")
    layer.rectangle(0,0,w,h)
    layer.fill()

    color(game, layer, "dark_green")
    round_rect(layer, 0-h/5,0-h/5, (w*value) ,h ,h/2)
    layer.fill()

    color(game, layer, "green")
    round_rect(layer, 0-h/3,0-h/3, (w*value) ,h ,h/2)
    layer.fill()
    
    color(game, layer, "bright_green")
    layer.rectangle(w*0.05,3,(w*0.45)*value, 2)
    layer.fill()

    oldlayer.set_source_surface(surface, x, y)
    oldlayer.paint()

    layer.set_line_width(1)
    color(game, oldlayer, "black")
    round_rect(oldlayer, x,y, w,h,h/2)
    oldlayer.stroke()

def useless_interface_1(game, layer, x,y,w,h):


    # This will draw a background pattern
    frame = game.current["frame"]

    imx = max(w/3*2-100+30, w/3-30)
    imy = int(h/2)-75
        
    
    # 79th photo
    if frame > 60:
        factor = min(1, max((frame-60)/20, 0))

        

        color(game, layer, "green")
        layer.set_line_width(1)
        if frame > 80:
            layer.set_line_width(2)
        
        layer.move_to(imx-5, imy+5)
        layer.line_to(imx, imy-2)
        layer.line_to(imx+170*factor+5, imy-2)
        layer.line_to(imx+170*factor+5, imy+150*factor-2)
        layer.line_to(imx+170*factor, imy+150*factor+3)
        layer.line_to(imx-5,imy+150*factor+3)
        layer.line_to(imx-5,imy+5)
        layer.stroke_preserve()
    
    
    if frame > 80 and frame not in range(85, 90) and frame not in range(95,100):
        color(game, layer, "dark_blue")
        layer.fill()
        image(game, layer,
             imx-30,
             imy,
             "assets/menu-bg.png")

    
    # Buttons area
    if frame > 40:
        layer.set_line_width(2)
    else:
        layer.set_line_width(1)
    color(game, layer, "green")

    factor = min(1, max(frame/20, 0))
    
    layer.move_to(10,h-55*factor)
    layer.line_to(15, h-60*factor)
    layer.line_to(w/3+5, h-60*factor)
    layer.line_to(w/3+5, h-7)
    layer.line_to(w/3, h-3)
    layer.line_to(10,h-3)
    layer.line_to(10,h-55*factor)
    layer.stroke_preserve()
    if frame > 40 and frame not in range(45, 50) and frame not in range(55,60):
        color(game, layer, "dark_blue")
        layer.fill()


    
    factor = min(1, max((frame-40)/5, 0))
    color(game, layer, "green")
    layer.set_line_width(1)

    connectY = min(imy+150, h-70)
    
    layer.move_to(w/4, h-60)
    layer.line_to(w/4, (h-60)+((connectY-1)-(h-60))*factor)
    layer.stroke()
    factor = min(1, max((frame-45)/5, 0))
    layer.move_to(w/4, connectY)
    layer.line_to((w/4)+((imx-5)-(w/4))*factor, connectY)
    layer.stroke()
    
    
    if h > 200 and w > 400:
        if frame < 200:
            string = "I'm Not Even Human"
            part = max(0, min(int(frame/5)-20, len(string)))
            add_ = ""
            if part != len(string):
                add_ = "_"

            color(game, layer, "green")
            text(game, layer, string[:part]+add_,
                 15,
                 15)
        else:

            image(game, layer, 10, 15, "assets/game-title.png")

    else:
         text(game, layer, "INEH",
             15,
             15)
