#####################################################################
#                                                                   #
#  THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE   #
# LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet )  #
# FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk )      #
# WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
# IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
# OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE.        #
#                                                                   #
#      ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS       #
# (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK.  #
# YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
# THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER    #
# VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG   #
# WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ).         #
#                                                                   #
# THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE   #
# NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
# THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT    #
# SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
# THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT.       #
#                                                                   #
# THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL  #
# FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE   #
# THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD   #
# TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT.  #
#                                                                   #
#####################################################################


# This file will contain elements used a lot with in the application.
# They are all built upon GTK, so it's implementable without using these
# but it may simplify your modification. Since the elements in will be
# specific to making something like this application easier.

import os
import time
import urllib.request
import threading
import json
from subprocess import *
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Pango
from gi.repository import GdkPixbuf
from PIL import Image, ImageSequence

from flbry import url

def icon( win, name, f="png"):

    # This function returns a fitting icon of the current theme, 
    # or if not available from another theme on the system.
    # If a custom icon theme is set, it returns the icon from the corresponding folder.

    if Gtk.IconTheme.get_default().has_icon(name) and win.settings["GTK_icon_theme"] == "System Theme":
    	  return Gtk.Image.new_from_icon_name(name, Gtk.IconSize.DND)
    else:
        # Real GTK Spinner for loading ? Why not?
        if name == "loading":
            s = Gtk.Spinner()
            s.set_size_request(32,32)
            s.start()
            return s
        
        return Gtk.Image.new_from_file("icons/"+win.settings["GTK_icon_theme"]+"/"+name+"."+f)

def resize_gif(filename, new_file, size):

    # This function will resize a gif

    gif = Image.open(filename)
    layers = ImageSequence.Iterator(gif)

    def rs(l):
        for i in l:
            rsv = i.copy()
            rsv.thumbnail(size, Image.ANTIALIAS)
            yield rsv
    layers = rs(layers)

    # Overwrite the original gif
    f = next(layers)
    f.info = gif.info
    f.save(new_file, save_all=True, append_images=list(layers))
    
def load(win, calculation_function, render_function, *args, wait=True):

    # This function will load widgets that take time to load.
    # Due to the peculiarities of the GTK main thread. I need
    # to separate the computation and the rendering part of
    # the job into two distingt functions.

    # One will do all the job to get the file or resolve the url
    # or whatever it needs to do, which does not require GTK to
    # be done. The second will be the GTK commands. The rendering,
    # done with in the GTK main thread.
    
    wbox = Gtk.HBox()
    widget = icon(win, "loading", "gif")
    wbox.pack_start(widget, True, False, False)
    widget.loaddo = True
    
    def resolve_widget_thread(widget, wbox, wf, rf, *args):
        
        calculations = wf(*args)
        
        def gtk_schedule(calculations, rf):
            # It seems to be important to edit GTK only
            # in the main thread. This will schedule it.

            new_widget = rf(calculations)

            widget.destroy()
            wbox.pack_start(new_widget, True, True, True)
            wbox.show_all()


        GLib.idle_add(gtk_schedule, calculations, rf)

    def load_event(w,e):
        if w.loaddo:
            load_thread = threading.Thread(target=resolve_widget_thread, args=(widget, wbox, calculation_function, render_function, *args))
            load_thread.setDaemon(True)
            load_thread.start()
            w.loaddo = False
    if wait:
        widget.connect("draw", load_event)
    else:
        load_event(widget, False)
    

    return wbox

image_cache = "/tmp/FastLBRY_GTK_image_cashe/"

def image_save_name(url):

    save_as = ""
    good = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOOPASDFGHJKLZXCVBNM_1234567890"
    for i in url:
        if i in good:
            save_as = save_as + i
        else:
            save_as = save_as + "_"
    try:
        os.mkdir(image_cache)
    except:
        pass
    save_as = image_cache+save_as
    return save_as

def clean_image_cache():

    for i in os.listdir(image_cache):
        os.remove(image_cache+i)

def net_image_calculation( url, size, save_as=False, allow_gif=False):

    ret = ["file", save_as]

    # This is when we want to load a file
    
    if save_as == "FORCELOAD":
        try:
            open(url)
            save_as = url
        except:
            save_as = ""
    
    if not save_as: 
        save_as = image_save_name(url)
        
    # This function will load the image in a separate thread.

    try:
        open(save_as) # In case it's already been saved
    except Exception as e:
        pass
    
        try:
            urllib.request.urlretrieve(url, save_as)
        except Exception as e:
            f = open(save_as, "w")
            f.close()

    if size:
        try:
            
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(save_as, size, size)
            ret = ["pixbuf", pixbuf] #Gtk.Image.new_from_pixbuf(pixbuf)
        except Exception as e:

            if "image file format" in str(e):
                try:
                    PILImage = Image.open(save_as).convert("RGBA")
                    PILImage.save(save_as+".png", "png")
                    pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(save_as+".png", size, size)
                    ret = ["pixbuf", pixbuf]
                except:
                    ret = ["file", save_as]
                    
            else:
                try:

                    os.rename(save_as, save_as+".gif")
                    resize_gif(save_as+".gif", save_as+"_2.gif", [size,size])
                    ret = ["file", save_as+"_2.gif"] # Gtk.Image.new_from_file(save_as+"_2.gif")
                    os.remove(save_as+"_2.gif")
                    os.rename(save_as+".gif", save_as)
                
                except Exception as e:
                    if allow_gif:
                        ret = ["file", save_as] #Gtk.Image.new_from_file(save_as)
                
    else:
        ret = ["file", save_as] #Gtk.Image.new_from_file(save_as)

    return ret

def net_image_render(calc):

    # This will make the image itself.
    ret = Gtk.Image()
    if calc[0] == "file":
        ret = Gtk.Image.new_from_file(calc[1])
    elif calc[0] == "pixbuf":
        ret = Gtk.Image.new_from_pixbuf(calc[1])
    
    return ret

# Converts seconds to time format (H:MM:SS or M:SS)
def seconds_to_time(seconds=0):
    m, s = divmod(seconds, 60)
    h, m = divmod(m, 60)
    if h:
        return '{:d}:{:02d}:{:02d}'.format(h, m, s)
    else:
        return '{:d}:{:02d}'.format(m, s)

def search_item(win, data, channel_load=True, upcoming_streams=True):

    # This will generate a little item for the claim_search


    
    box = Gtk.VBox()

    repost = False
    if "reposted_claim" in data:
        repost = True
        data = data["reposted_claim"]

    try:
        title = data["value"]["title"]
    except:
        title = data['name']

    if upcoming_streams and int(data.get("value",[]).get("release_time", 0)) > time.time():
        title = "[ UPCOMING "+time.ctime( int(data.get("value",[]).get("release_time", 0)))+" ] \n" + title
    
    try:
        link = data["canonical_url"]
    except:
        link = data["permanent_url"]
    
    def public_resolve(w):
        win.url.set_text(link)
        win.url.activate()

    def new_tab(w, e):

        if e.get_button()[1] == 2:
            print("MMB")
            win.resolve_tab = "new_tab"
            win.url.set_text(link)
            win.url.activate()

    namebutton = Gtk.Button()
    namebutton.set_tooltip_text(link)
    namebutton.connect("clicked", public_resolve)
    namebutton.connect("button-press-event", new_tab)
    namebutton.set_relief(Gtk.ReliefStyle.NONE)
    namebutton_box = Gtk.VBox()
    namebutton.add(namebutton_box)

    try:
        # Trying to get the thumb
        namebutton_thumb = load(win, net_image_calculation, net_image_render, data["value"]["thumbnail"]["url"], 200 , "", False)
        namebutton_thumb.set_size_request(200,200)
    except:
        try:
            # Trying to get a thumb by referencing the mimetype
            namebutton_thumb = icon(win,data["value"]["source"]["media_type"].replace("/", "-"))
            namebutton_thumb.set_size_request(200,200)
        except:
            namebutton_thumb = icon(win,"none")
            namebutton_thumb.set_size_request(200,200)

    # For overlay info on top of thumbnail image
    thumbnail_overlay = Gtk.Overlay()
    overlay_box = Gtk.HBox(spacing=5)
    thumbnail_overlay.add(namebutton_thumb)
    thumbnail_overlay.add_overlay(overlay_box)
    thumbnail_overlay.set_overlay_pass_through(overlay_box, True)
    overlay_box.set_margin_end(25)
    overlay_box.set_margin_bottom(48)
    overlay_box.set_halign(Gtk.Align.END)
    overlay_box.set_valign(Gtk.Align.END)

    # Prepare info for overlay
    overlay_items_count = 0
    seconds_length = data.get("value", {}) \
        .get("video", {}) \
        .get("duration", "0")
    downloaded_file = url.get_downloaded_file(data.get("claim_id", ""))

    # Show time
    try:
        if seconds_length != "0":
            overlay_items_count += 1
            time_label = Gtk.Label()
            # For some reason setting text color with CssProvider does not
            # work when loading thumbnail list for a second time. So we use
            # set_markup for text color.
            time_label.set_markup("<span color=\"#EFEFEF\" fgalpha=\"70%\">"+seconds_to_time(seconds_length)+"</span>")
            overlay_box.pack_start(time_label, False, False, 0)
    except:
        pass

    # Show repost indicator
    try:
        if repost:
            overlay_items_count += 1
            repost_icon = Gtk.Image.new_from_file("icons/repost.svg")
            repost_icon.set_size_request(16, 16)
            overlay_box.pack_start(repost_icon, False, False, 0)
    except:
        pass

    # Show downloaded indicator
    try:
        if downloaded_file:
            overlay_items_count += 1
            downloaded_icon = Gtk.Image.new_from_file("icons/downloaded.svg")
            downloaded_icon.set_size_request(16, 16)
            overlay_box.pack_start(downloaded_icon, False, False, 0)
    except:
        pass

    # Finishing up with thumbnail overlay
    thumbnail_overlay.show_all()
    namebutton_box.pack_start(thumbnail_overlay, False, False, 0)

    if overlay_items_count > 0:
        # Add "thumbnail_overlay" class only when there is something inside
        # the box. Meaning don't add it when empty because padding set on
        # CssProvider makes the empty box show, which is unnecessary.
        overlay_box.get_style_context().add_class("thumbnail_overlay")
    
    try:
        title_label = Gtk.Label(title)
        title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
        title_label.set_line_wrap(True)
        title_label.set_max_width_chars(20)
        namebutton_box.pack_start(title_label, True,False,0)
    except:
        pass
    
    box.pack_start(namebutton, False, False, False)
    
    
    if "signing_channel" in data and (channel_load or repost):
        box.pack_start(go_to_channel(win, data["signing_channel"]), False, False, False)


    ##### DRAGING IT OUT THE WINDOW ####

    def on_drag(widget, drag_context, send, info, time):
        # TODO: swap to FastLBRY HTML instance when ready
        librarian = link.replace("lbry://", win.settings["librarian_instance"])
        librarian = librarian.replace("#", ":")
        send.set_text(librarian, -1)
        
    namebutton.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
    namebutton.drag_source_add_text_targets()
    namebutton.connect("drag-data-get", on_drag)
        
    return box
    
def go_to_channel(win, data, resolve=True):
    try:
        try:
            channel_name = data["value"]["title"]
        except:
            channel_name = data["name"]

        try:
            channel_url = data["canonical_url"]
        except:
            channel_url = data["name"]

            try:
                channel_url = channel_url + "#" + data["claim_id"]
            except:
                pass

        channel_button = Gtk.Button()

        
        if resolve:
            def channel_resolve(w):
                win.url.set_text(channel_url)
                win.url.activate()


            try:
                channel_button.set_tooltip_text(data["canonical_url"])
            except:
                channel_button.set_tooltip_text(data["name"])
            channel_button.connect("clicked", channel_resolve)

        def new_tab(w, e):

            if e.get_button()[1] == 2:
                print("MMB")
                win.resolve_tab = "new_tab"
                win.url.set_text(channel_url)
                win.url.activate()

        channel_button.connect("button-press-event", new_tab)
            
        channel_button.set_relief(Gtk.ReliefStyle.NONE)
        channel_button_box = Gtk.HBox()
        channel_button.add(channel_button_box)

        # If channel thumbnail exists.
        try:
            channel_thumb = load(win, net_image_calculation, net_image_render, data["value"]["thumbnail"]["url"], 40 , "", False)
            channel_button_box.pack_start(channel_thumb, False,False,False)
        except:
            channel_button_box.pack_start(icon(win, "system-users"), False,False,False)
        title_label = Gtk.Label("  "+channel_name+"  ")
        title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
        title_label.set_line_wrap(True)
        title_label.set_max_width_chars(20)
        channel_button_box.pack_start(title_label, False, False, False)


        def on_drag(widget, drag_context, send, info, time):
            # TODO: swap to FastLBRY HTML instance when ready
            librarian = channel_url.replace("lbry://", win.settings["librarian_instance"])
            librarian = librarian.replace("#", ":")
            send.set_text(librarian, -1)
        
        channel_button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
        channel_button.drag_source_add_text_targets()
        channel_button.connect("drag-data-get", on_drag)
        
        return channel_button
    except Exception as e:
        print("GO TO CHANNEL:", e)
        return Gtk.Label("[anonymous]")


def notify(win, text, subtext="", force=False):


    # This function will send a notify send thingy if
    # notifications are set to True

    enabled = win.settings["notifications"]

    if enabled and (( not win.is_active()) or force ):
        Popen(["notify-send",
               "-i", os.getcwd()+"/icon.png",
               "-a", "FastLBRY GTK", text, subtext])

def select_file(was="", filter=[]):

    # This is a simple file_chooser_dialog.

    

    dialog = Gtk.FileChooserDialog("Choose a file",
                                   None,
                                   Gtk.FileChooserAction.OPEN,
                                   (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                                    Gtk.STOCK_OPEN, Gtk.ResponseType.OK))

    # Filter
    if filter:
        filter_sup = Gtk.FileFilter()
        filter_sup.set_name("Supported files")
        for i in filter:
            filter_sup.add_pattern(i)
        dialog.add_filter(filter_sup)

        filter_any = Gtk.FileFilter()
        filter_any.set_name("All files")
        filter_any.add_pattern("*")
        dialog.add_filter(filter_any)

    ############### PREVIEW CODE ###################
    # TODO:
    # Perhaps more mime-types could be added to it
    # for example .blender could be previewed using
    # blender-thumbnailer.py in each Blender install.
    # Videos by Totem.
        
    preview_image= Gtk.Image()
    dialog.set_preview_widget(preview_image)
    def update_preview(dialog):
        path= dialog.get_preview_filename()
        try:
            pixbuf= GdkPixbuf.Pixbuf.new_from_file(path)
        except Exception:
            dialog.set_preview_widget_active(False)
        else:
            maxwidth, maxheight= 300, 700
            width, height= pixbuf.get_width(), pixbuf.get_height()
            scale= min(maxwidth/width, maxheight/height)
            if scale<1:
                width, height= int(width*scale), int(height*scale)
                pixbuf= pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR)

            preview_image.set_from_pixbuf(pixbuf)
            dialog.set_preview_widget_active(True)
    dialog.connect('update-preview', update_preview)
        
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        ret = dialog.get_filename()
        dialog.destroy()
        return ret
    else:
        dialog.destroy()
        return was
    
def tags_editor(win, data, return_edit_functions=False):

    
    
    tagscont = Gtk.HBox()
    
    
    tagscrl = Gtk.ScrolledWindow()
    tagscrl.set_size_request(40,40)
    tagscont.pack_start(tagscrl, True, True, 0)

    tagsbox = Gtk.HBox()
    tagscrl.add_with_viewport(tagsbox)

    def add_tag(tag):

        if not tag:
            return
        
        if tag not in data:
            data.append(tag)
        tagb = Gtk.HBox()
        tagb.pack_start(Gtk.Label("  "+tag+"  "), False, False, 0)
        def kill(w):
            tagb.destroy()
            data.remove(tag)
        tagk = Gtk.Button()
        tagk.connect("clicked", kill)
        tagk.set_relief(Gtk.ReliefStyle.NONE)
        tagk.add(icon(win, "edit-delete"))
        tagb.pack_start(tagk, False, False, 0)
        tagb.pack_start(Gtk.VSeparator(), False, False, 5)
        tagsbox.pack_start(tagb, False, False, 0)
        tagsbox.show_all()

        # Scroll to the last
        def later():
            time.sleep(0.1)
            def now():
                a = tagscrl.get_hadjustment()
                a.set_value(a.get_upper())
            GLib.idle_add(now)
        load_thread = threading.Thread(target=later)
        load_thread.start()
        # The threading is needed, since we want to wait
        # while GTK will update the UI and only then move
        # the adjustent. Becuase else, it will move to the
        # last previous, not to the last last.
            
    addt = Gtk.Button()
    addt.set_relief(Gtk.ReliefStyle.NONE)
    addt.add(icon(win, "list-add"))
    tagscont.pack_end(addt, False, False, 0)
    def on_entry(w):
        add_tag(tagentry.get_text())
        tagentry.set_text("")
    tagentry = Gtk.Entry()
    tagentry.connect("activate", on_entry)
    addt.connect("clicked", on_entry)
    tagscont.pack_end(tagentry, False, False, False)

    
    
    for tag in data:
        add_tag(tag)

    if not return_edit_functions:
        return tagscont
    else:
        return tagscont, tagsbox, add_tag

def password_entry(win):

    # This function will create a basic entry for passwords
    # with a button to reveal text.

    box = Gtk.HBox()
    
    entry = Gtk.Entry()
    entry.set_visibility(False)

    def vis(w, e):
        e.set_visibility(w.get_active())

    button = Gtk.ToggleButton()
    button.set_relief(Gtk.ReliefStyle.NONE)
    button.add(icon(win, "video-display"))
    button.connect("clicked", vis, entry)

    box.pack_start(entry, 1,1,2)
    box.pack_end(button, 0,0,1)

    return box, entry

# Show a simple message box with only OK button
def simple_message_box(primary_text, secondary_text):
    dialog = Gtk.MessageDialog(
            transient_for=None,
            flags=0,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.OK,
            text=primary_text,
        )
    dialog.format_secondary_text(secondary_text)
    dialog.run()
    dialog.destroy()
