import sys
sys.path.append("../svg")
from geometry import GeometryLoss
import numpy as np
import pygame as pg
import torch
import pydiffvg
import tkinter as tk
from tkinter import filedialog

def box_kernel(val):
    return np.heaviside(-val+1,0)

def cone_kernel(val):
    return np.maximum(0,1-val)

def nptosurf(arr):
    if arr.shape[2]==1:
        #greyscale
        shape=arr.shape
        shape=(shape[0],shape[1],3)
        arr=np.broadcast_to(arr,shape)
    return pg.surfarray.make_surface(arr*255)

def brush_tensor(screen_size,coords,radius,kernel):
    coordarr=np.stack(np.meshgrid(np.linspace(0,screen_size[0]-1,screen_size[0]),np.linspace(0,screen_size[1]-1,screen_size[1]),indexing='ij'),axis=2)
    ctrarr = np.reshape(np.array(coords), [1, 1, 2])
    distarr=np.sqrt(np.sum(np.power(coordarr-ctrarr,2),axis=2))
    valarr=kernel(distarr/radius)
    return torch.tensor(valarr,requires_grad=False,dtype=torch.float32)

def checkerboard(shape, square_size=2):
    xv,yv=np.meshgrid(np.floor(np.linspace(0,shape[1]-1,shape[1])/square_size),np.floor(np.linspace(0,shape[0]-1,shape[0])/square_size))
    bin=np.expand_dims(((xv+yv)%2),axis=2)
    res=bin*np.array([[[1., 1., 1.,]]])+(1-bin)*np.array([[[.75, .75, .75,]]])
    return torch.tensor(res,requires_grad=False,dtype=torch.float32)

def render(optim, viewport):
    scene_args = pydiffvg.RenderFunction.serialize_scene(*optim.build_scene())
    render = pydiffvg.RenderFunction.apply
    img = render(viewport[0],  # width
                 viewport[1],  # height
                 2,  # num_samples_x
                 2,  # num_samples_y
                 0,  # seed
                 None,
                 *scene_args)
    return img

def optimize(optim, viewport, brush_kernel, increase=True, strength=0.1):
    optim.zero_grad()

    geomLoss=torch.tensor(0.)

    for shape, gloss in zip(optim.scene[2],geometryLosses):
        geomLoss+=gloss.compute(shape)

    img=render(optim,viewport)

    imalpha=img[:,:,3]

    multiplied=imalpha*brush_kernel

    loss=((1-multiplied).mean() if increase else multiplied.mean())*strength

    loss+=geomLoss

    loss.backward()

    optim.step()

    return render(optim,viewport)

def get_infile():
    pydiffvg.set_use_gpu(False)
    root = tk.Tk()
    #root.withdraw()

    file_path = filedialog.askopenfilename(initialdir = ".",title = "Select graphic to optimize",filetypes = (("SVG files","*.svg"),("all files","*.*")))

    root.destroy()

    return file_path

def compositebg(img):
    bg=checkerboard(img.shape,2)
    color=img[:,:,0:3]
    alpha=img[:,:,3]
    composite=alpha.unsqueeze(2)*color+(1-alpha).unsqueeze(2)*bg

    return composite

def main():
    infile=get_infile()

    settings=pydiffvg.SvgOptimizationSettings()
    settings.global_override(["optimize_color"],False)
    settings.global_override(["transforms","optimize_transforms"], False)
    settings.global_override(["optimizer"], "SGD")
    settings.global_override(["paths","shape_lr"], 1e-1)

    optim=pydiffvg.OptimizableSvg(infile,settings)

    global geometryLosses
    geometryLosses = []

    for shape in optim.build_scene()[2]:
        geometryLosses.append(GeometryLoss(shape))

    scaling=1
    brush_radius=100
    graphic_size=optim.canvas
    screen_size=(graphic_size[1]*scaling, graphic_size[0]*scaling)

    pg.init()

    screen=pg.display.set_mode(screen_size)
    screen.fill((255,255,255))

    img=render(optim,graphic_size)
    print(img.max())

    npsurf = pg.transform.scale(nptosurf(compositebg(img).detach().permute(1,0,2).numpy()), screen_size)

    screen.blit(npsurf,(0,0))

    pg.display.update()
    clock=pg.time.Clock()

    z=0
    btn=0

    while True:
        clock.tick(60)
        for event in pg.event.get():
            if event.type==pg.QUIT:
                pg.quit()
                sys.exit()

            y, x = pg.mouse.get_pos()
            if event.type == pg.MOUSEBUTTONDOWN:
                if event.button in [1,3]:
                    z=1
                    btn=event.button
                elif event.button == 4:
                    brush_radius*=1.1
                elif event.button == 5:
                    brush_radius/=1.1
                    brush_radius=max(brush_radius,5)
            elif event.type == pg.MOUSEBUTTONUP:
                if event.button in [1,3]:
                    z=0

        if z==1:
            brush=brush_tensor((graphic_size[0],graphic_size[1]), (x/scaling, y/scaling), brush_radius, box_kernel)
            img=optimize(optim,graphic_size,brush,btn==1)
            npsurf = pg.transform.scale(nptosurf(compositebg(img).detach().permute(1,0,2).numpy()), screen_size)


        screen.blit(npsurf,(0,0))
        pg.draw.circle(screen, (255,255,255), (y,x), int(brush_radius*scaling), 1)
        pg.display.update()


if __name__ == '__main__':
    main()

