# THIS FILE IS A PART OF VCStudio
# PYTHON 3

import os
import datetime

# GTK module ( Graphical interface
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
import cairo
import datetime
import threading

# Own modules
from settings import settings
from settings import talk
from project_manager import pm_project

# Studio
from studio import analytics
from studio import story
from studio import studio_storyDeletionLayer
from studio import studio_storyLayer
from studio import studio_settingsLayer
from studio import studio_assetLayer
from studio import studio_analyticsLayer
from studio import studio_scriptLayer
from studio import studio_multiuserLayer

from troubleshooter import error_notify

# UI modules
from UI import UI_testing
from UI import UI_color
from UI import UI_elements

# Network
from network import network_renders
from network import network_multiuser
from network import multiuser_terminal

def previous(win):
    win.previous = {}
    for i in win.current:
        if type(win.current[i]) == list or type(win.current[i]) is dict:
            win.previous[i] = win.current[i].copy()
        else:
            win.previous[i] = win.current[i]

# OK let's make a window
def run(project, win):
    # In the Blender-Organizer I was putting the version into the title. Not cool.
    # Because if you would snap it to the sidebar in Ubuntu. On mouse over it would
    # show the first ever version. So there will be a better way to see version.
    # I think let's do that like in Blender. Drawn with in the window somewhere.
    
    try:
        win.destroy()
    except:
        pass

    
    
    # Setting up the window
    win = Gtk.Window()
    win.set_default_size(1200,720)
    win.set_position(Gtk.WindowPosition.CENTER)
    #win.maximize()
    win.connect("destroy", Gtk.main_quit)
    win.set_title("VCStudio")
    win.set_default_icon_from_file("tinyicon.png")
    
    # Setting up the events ( mouse and keyboard handling )
    win.connect("button-press-event", mouse_button_press, win)
    win.connect("button-release-event", mouse_button_release, win)
    win.connect("key-press-event", key_press, win)
    win.connect("key-release-event", key_release, win)
    
    # Guess what. The entire time on Blender-Organizer 4 ( 2018 -2020 ) and 
    # few days of trying connecting the scroll event directly to window or to
    # the drawing area. And just now I finally made it work. BY DOING THIS 
    # Why scroll event is only on ScrolledWindow ? OMG !!!
    
    scroll = Gtk.ScrolledWindow()
    scroll.connect("scroll-event", scrolling, win)
    
    # Setting up the global variables. (kinda)
    win.animations = {}
    win.previous   = {}
    win.current    = {}
    win.images     = {}
    win.imageload  = 0
    win.text       = {}
    win.textactive = ""
    win.scroll     = {}
    win.FPS        = 0
    win.url        = "story_editor"
    win.cur        = ""  # This will be used to get precicelly what asset / scene / shot we are in
    win.update     = {"versions":{}}
    win.project    = project
    win.out_dots  = {}
    win.assets    = {}
    win.szone     = [[100,100],[100,100]] # Square drawn if selected more then one node.
    win.surround  = {  # And this is the list of the squares. Because it's not
        "frame":0,     # as easy to do. See UI / UI_math / rectangle_surround()
        "rects":[]     # for details of this implementation.
        }
    win.calllayer = False
    win.layercashe = {} # Here I gonna store layers that are inactive to speed up stuff
    win.checklists = {}
    win.blink = False  # Cursor blinking thing. 
    win.renders = {} # List of current active renders.
    win.undo_history = []
    win.undo_index = 0
    win.multiuser = {
        "server":False,     # Whether we are connected to the server. And the server ID
        "userid":"",        # The id of current user in the server
        "last_request": "", # The last request send to the server. ( for stopping bloat )
        "curs":{},          # The information about various items on other users machines.
        "request":[["story"]],       # Requests done in UI space
        "users":{},         # List of users information Names, IPs etc.
        "messages":[],      # List of messages
        "unread":0   ,      # Amount of unread messages
        "terminal":[],      # The outputs from the server
        "asset_list_check":False,    # Whether the initial update happened when connecting to the server
        "story_check":False, # Whether the first story check was done.
        "analytics_check":False
        }
    
    if pm_project.is_legacy(project):
        win.story        = story.get_legacy(project)
        win.analytics    = analytics.get_legacy(project)
    else:
        win.story        = story.load(project)
        win.analytics    = analytics.load(project)
    
    # Try to put a name of the project at the title
    win.set_title("VCStudio : "+win.analytics["name"])
    
    # Cashed tables
    win.color    = UI_color.get_table()
    win.settings = settings.load_all()
    
    # Default values
    win.current["frame"]      = 0
    win.current["testing"]    = False
    win.current["LMB"]        = False
    win.current["MMB"]        = False
    win.current["RMB"]        = False
    win.current["keys"]       = []
    win.current["key_letter"] = ""
    win.current["scroll"]     = [0,0]
    win.current["project"]    = ""
    win.current["tool"]       = "selection"
    win.current["draw_dot"]   = "end"
    win.current["calls"]      = {}   # Calls. See sutdio/studio_dialogs.py
    win.current["script_find"] = [0,0]
    win.current["camera_arrived"] = False
    
    
    if "pointers" not in win.story:
        win.story["pointers"] = {} # List of text pointers per scene
    
    new_date_format = "%Y/%m/%d"
    today = datetime.datetime.strftime(datetime.datetime.today(), new_date_format)
    win.current["date"] = today # Don't even ask. I'm litteraly tired already.
    
    previous(win)
    
    # Version of the software
    win.version = 0.0
    try:
        vfile = open("settings/update.data")
        vfile = vfile.read()
        vfile = vfile.split("\n")
        
        for line in vfile:
            if line.startswith("VERSION "):
                win.version = float(line.replace("VERSION ", ""))
                break
    except:
        win.version = 0.0
        
    # FPS
    win.sFPS = datetime.datetime.now()
    
    # Setting the drawable
    pmdraw = Gtk.DrawingArea()
    pmdraw.set_size_request(1040, 720)
    scroll.set_size_request(1040, 720) # This step is because GTK developers are
    win.add(scroll)                    # well. A good, nice, people who knows
    scroll.add_with_viewport(pmdraw)   # what they are doing. Really. 
    pmdraw.connect("draw", pmdrawing, win)
    
    # Before we run the UI. Let's run the mutliuser client.
    multiuser = threading.Thread(target=network_multiuser.client, args=(win,))
    multiuser.setDaemon(True)
    multiuser.start()
    
    # And let's start also the multiuser_terminal
    multiuser_term = threading.Thread(target=multiuser_terminal.listen, args=(win,))
    multiuser_term.setDaemon(True)
    multiuser_term.start()
    
    #run
    win.show_all()
    Gtk.main()


def pmdrawing(pmdrawing, main_layer, win):
    
    # This function draws the actuall image. I'm doing full frames redraws. It's
    # a bit simpler then making some kind of dynamic draw call system that might
    # be used in such an application. But to hell with it. I did the same on the
    # Blender-Organizer altho with way less cairo. And it works well enought.

    try:
        

        # FPS counter
        win.fFPS = datetime.datetime.now()
        win.tFPS = win.fFPS - win.sFPS

        if win.current["frame"] % 5 == 0:
            win.blink = not win.blink # Iterating the blink

        if win.current["frame"] % 10 == 0:
            win.FPS = int ( 1.0 / ( win.tFPS.microseconds /1000000))

            if "Auto_De-Blur" not in win.settings:
                win.settings["Auto_De-Blur"] = True

            # Fail switch for Graphics.
            if win.FPS < 10 and win.settings["Auto_De-Blur"]:
                win.settings["Blur"] = False    

        win.sFPS = datetime.datetime.now()

        # Current frame (for animations and things like this)
        win.current["frame"] += 1    

        # Getting data about the frame
        win.current['mx'] = win.get_pointer()[0]
        win.current['my'] = win.get_pointer()[1]
        win.current['w']  = win.get_size()[0]
        win.current['h']  = win.get_size()[1]


        win.cursors = {
            "arrow":Gdk.Cursor.new(Gdk.CursorType.ARROW),
            "watch":Gdk.Cursor.new(Gdk.CursorType.WATCH),
            "text" :Gdk.Cursor.new(Gdk.CursorType.XTERM),
            "hand" :Gdk.Cursor.new(Gdk.CursorType.HAND1),
            "cross":Gdk.Cursor.new(Gdk.CursorType.CROSS)
            }
        win.current["cursor"] = win.cursors["arrow"]

        # Attemt to make things straight when pressing Ctrl.    
        if 65507 in win.current["keys"] and win.current["LMB"]:

            # Let's see what's got more distance. X or Y motion of the mouse.

            dx = win.current["LMB"][0] - win.current["mx"]
            dx = max(dx, dx*-1)
            dy = win.current["LMB"][1] - win.current["my"]
            dy = max(dy, dy*-1)

            # If X has more ditance. Then Y should be the same as begining.

            if dx > dy:
                win.current["my"] = win.current["LMB"][1]
            else:
                win.current["mx"] = win.current["LMB"][0]

        # Attempt at making the mouse cursor a bit more usefull for stuff.
        if win.previous["LMB"] and len(win.previous["LMB"]) > 2 and win.current["LMB"]:                
            win.current["LMB"] = win.previous["LMB"]

        #Background color
        UI_color.set(main_layer, win, "background")
        main_layer.rectangle(
            0,
            0,
            win.current['w'],
            win.current['h'])
        main_layer.fill()

        # Tooltips and other junk has to be defined here. And then drawn later to 
        # the screen. So here we get a special layer. That will be drawn to during
        # the time of drawing. And later composeted over everything. 

        win.tooltip_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
                                                                      win.current['h'])
        win.tooltip = cairo.Context(win.tooltip_surface)
        win.tooltip.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)


        # Layers. Order of them matter
        Layers = []

        if win.url == "story_editor":
            Layers.append([studio_storyLayer.layer(win),"story_editor"])
            if "story_editor" in win.layercashe:
                del win.layercashe["story_editor"]
        else:
            if "story_editor"  not in win.layercashe:
                win.layercashe["story_editor" ] = studio_storyLayer.layer(win)

            Layers.append([win.layercashe["story_editor" ],"story_editor"])

        if win.url == "story_deletion_dialog":
            Layers.append([studio_storyDeletionLayer.layer(win),"story_deletion_dialog"])    

        elif win.url == "settings_layer":
            Layers.append([studio_settingsLayer.layer(win),"settings_layer"])  

        elif win.url == "analytics":
            Layers.append([studio_analyticsLayer.layer(win),"analytics"])

        elif win.url == "assets":
            Layers.append([studio_assetLayer.layer(win),"assets"])  

        elif win.url == "script":
            Layers.append([studio_scriptLayer.layer(win), "script"])

        elif win.url == "multiuser":
            Layers.append([studio_multiuserLayer.layer(win), "multiuser"])

        # Call layers. See studio/studio_dialogs.py for explanation. It's wild.

        win.calllayer = False
        remlater = []

        for call in list(win.current["calls"].keys()):

            if win.current["calls"][call]["var"] == None:
                Layers.append([win.current["calls"][call]["draw"](win, call)])
                win.url = win.current["calls"][call]["url"]
                win.calllayer = True    

            else:
                win.url = win.current["calls"][call]["back"]
                win.current["calls"][call]["call"](win, win.current["calls"][call]["var"])
                win.textactive = ""
                remlater.append(call)

        for call in remlater:

            del win.current["calls"][call]



        Layers.append([UI_testing.layer(win)])
        Layers.append([win.tooltip_surface])

        # Combining layers
        for layer in Layers:
            if len(layer) > 1:
                layer, url = layer
                blur = UI_elements.animate(url+"_blur", win, 50)
                if (win.url != url or win.calllayer):
                    if win.current["tool"] not in ["schedule", "grab"]:
                        blur = UI_elements.animate(url+"_blur", win, blur, 50, 2, True)
                    else:
                        blur = UI_elements.animate(url+"_blur", win, 50, 50, 0, True)
                else:
                    if win.current["tool"] not in ["schedule", "grab"]:
                        blur = UI_elements.animate(url+"_blur", win, blur, 0, 2, True)
                    else:
                        blur = UI_elements.animate(url+"_blur", win, 0, 0, 0, True)
                layer = UI_elements.blur(layer, win, blur)
            else:
                layer = layer[0]
            main_layer.set_source_surface(layer, 0 , 0)
            main_layer.paint()


        win.get_root_window().set_cursor(win.current["cursor"])

        # If you press ESC you get back from any window to the main menu.
        if 65307 in win.current["keys"] and win.url != "install_updates":
            win.url = "story_editor"
            win.story["selected"] = []
            win.current["tool"] = "selection"
            win.current["keys"] = []
            win.textactive = ""


        # For the rendering I will need to read render info at each frame and parse
        # it in various windows. 

        network_renders.read_renders(win)


        # There is a but in the Gnome I guess. That autopresses the Shift and Ctrl
        # keys when you scroll to different windows. So here is a little fix.
        if not win.is_active():
            win.current["keys"] = []

        elif "is_active" in win.previous and not win.previous["is_active"]:
            win.multiuser["last_request"] = ""
            UI_elements.reload_images(win)

        win.current["is_active"] = win.is_active()

        # Saving data about this frame for the next one. A bit hard to get WTF am I
        # doing here. Basically trying to avoid current and previous data to be links
        # of the same data.

        previous(win) # Moved it into a seprate function for obvoius reasons

        # Refreshing those that need to be refrashed
        win.current["scroll"] = [0,0]

        # Refreshing the frame automatically
        pmdrawing.queue_draw()
    except:
        Gtk.main_quit()
        error_notify.show()
        

# This program will have things like mouse and keyboard input. And this setup
# Will be done in both PM and the actuall Project window. ( Also in the render
# Window. Basically all separate windows will have to have this setup separatelly.

# Mouse
def mouse_button_press(widget, event, win):

    # This function marks activation of the button. Not it's deactivation.

    # I'm going to attempt something quite disturbing. Basically I want to save
    # the state of the mouse as the press begun untill it's released. And I'm 
    # going to do in a slightly weird way. Because I'm bored I guess. The prob-
    # lem is that it will require to check whether the data even exists in the
    # first place. If x. Before parsing it. Because it might be False.
    
    for i, button in enumerate(["LMB", "MMB", "RMB"]):
        if i+1 == int(event.get_button()[1]):
            win.current[button] = [event.x, event.y]
            
    # If you folowed the code. By checking for example if win.current["LMB"]
    # You can know if it's even pressed to begin with. Because if it's not
    # It's False.
    
def mouse_button_release(widget, event, win):
    
    # This function reverses the effects of the mouse_button_press() function.
    
    for i, button in enumerate(["LMB", "MMB", "RMB"]):
        if i+1 == int(event.get_button()[1]):
            win.current[button] = False

# I guess it's time to make something similar for the keyboard keys as well.
# I'm going to reuse the old system from the Blender-Organizer. Just a list of
# pressed keys. Maybe as well a strting thingy. Because I want to type in this
# app. 

def key_press(widget, event, win):
    if event.keyval not in win.current["keys"]:
        win.current["keys"].append(event.keyval)
        win.current["key_letter"] = event.string
        
def key_release(widget, event, win):
    try:
        win.current["keys"].remove(event.keyval)
    except:
        win.current["keys"] = []
    
    # I also want to clean the key letter. Because other wise in the
    # script writer I had weird key presses all the time. 
    if not win.current["keys"]:    
        win.current["key_letter"] = ""
    
def scrolling(widget, event, win):
    e, x, y = event.get_scroll_deltas()
    win.current["scroll"] = [x,y]
    
