#####################################################################
#                                                                   #
#  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.  #
#                                                                   #
#####################################################################

# Preparing the executable of lbrynet to be running. If we don't that
# there will be a nasty error of permission being denied.

import os
os.system("chmod u+x flbry/lbrynet")

# I'm not trying to make it pretty. I'm trying to make it snappy.
# It's FastLBRY not SlowLBRY.

import gi
import threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib

# It's gonna take a couple of things to make the button run
from flbry import connect
from flbry import parse
from flbry import settings
from flbry import url
from flbry import ui
from flbry import claim_search
from flbry import fetch
from flbry import publish

settings.make_sure_file_exists()

##########################################################################
#                                                                        #
#                   THE MAIN WINDOW OF FASTLBRY GTK                      #
#                                                                        #
##########################################################################

win = Gtk.Window()                    # Make a GTK window object
win.connect("destroy", Gtk.main_quit) # If you close the window, program quits
win.set_title("FastLBRY GTK")         # Title
win.set_default_icon_from_file("icon.png") # Icon
win.set_size_request(800, 900)        # Minimum size

##########################################################################
#                                                                        #
#            GLOBAL VARIABLES HACK ( adding things to 'win' )            #
#                                                                        #
##########################################################################

# This hack is very simple. Our main Gtk.Window() is a class to which we
# can add variables later on. Like the example you see below.
# This way, giving some function only 'win' and nothing else, will give
# that other function the entire list of global variable ( added this way ).

win.settings = settings.load()      # Settings
win.SDK_running = connect.check()   # Is the SDK running ( from 0 to 1  to draw progress bar)
win.resolved = {}                   # Last resolved urls
win.commenting = {}                 # Data for while chatting in comments

##########################################################################
#                                                                        #
#                              TOP PANNEL                                #
#                                                                        #
##########################################################################

# Gtk.HeaderBar() is like a box but it draws itself in the headerbar insead
# of in the window itself.

pannel = Gtk.HeaderBar()
pannel.set_show_close_button(True)
win.set_titlebar(pannel)   # Dumbasses wrote GTK

# Untill the user connected to the SDK,  search and other functions should
# not work. So we split the pannel into two parts. The other one is a
# normal box.

restofpannel = Gtk.HBox() # H for Horrizontal 
pannel.pack_end(restofpannel)


############## CONNECT BUTTON ################

# Connect button will also be a disconnect button


# Icons for both
icon_connect = ui.icon(win, "connect")
icon_disconnect = ui.icon(win, "disconnect")

# Boxes for both
connect_box = Gtk.HBox()
disconnect_box = Gtk.HBox()

# Connect box 
connect_box.pack_start(icon_connect, False, False, False)
connect_box.set_tooltip_text("Connect to LBRY Network")
connect_box.pack_start(Gtk.Label("Connect "), False, False, False)

# Disconnect box 
disconnect_box.pack_start(icon_disconnect, False, False, False)
disconnect_box.set_tooltip_text("Disconnect from LBRY Network")
disconnect_box.pack_start(Gtk.Label("Disconnect "), False, False, False)

# By whether SDK was running while the software was launched
# we present the correct button to the user and set the rest-of-pannel
# accordingly.

if win.SDK_running:
    title = disconnect_box
    restofpannel.set_sensitive(True)
else:
    title = connect_box
    restofpannel.set_sensitive(False)


connect_disconncet_button = Gtk.Button()
connect_disconncet_button.add(title)
connect_disconncet_button.set_relief(Gtk.ReliefStyle.NONE)
pannel.pack_start(connect_disconncet_button)#, False, False, False)

############### CONNECT / DISCONNECT BUTTONS FUNCTION ################

# The button should do something.

def connect_disconnect_function(w):
    
    w.set_sensitive(False)

    for i in dynamic_box.get_children():
            i.destroy()

    progress_connect = Gtk.ProgressBar(show_text=True)
    dynamic_box.pack_start(progress_connect, True, True, 30)
    win.show_all()

    def do_sdk(w, pb):
        wasrunning = win.SDK_running
        if win.SDK_running == 1:
            connect.stop()
        else:
            connect.start()

        def update_pb(pb):
            pb.set_fraction(win.SDK_running)
            pb.set_text("Connecting: "+str(int(round(win.SDK_running*100)))+"%")
            
        def update_buttons(w):
            for i in dynamic_box.get_children():
                i.destroy()
            w.get_child().destroy()
            if win.SDK_running == 1:
                w.add(disconnect_box)
                restofpannel.set_sensitive(True)
            else:
                w.add(connect_box)
                restofpannel.set_sensitive(False)
            w.show_all()
            w.set_sensitive(True)
            if win.SDK_running == 1:
                load_following()
                load_channel(win)
                ui.notify(win, "Connected to LBRY")

        if wasrunning == 0:
            while True:    
                win.SDK_running = connect.check()
                if win.SDK_running == 1:
                    break
                GLib.idle_add(update_pb, pb)
            
        GLib.idle_add(update_buttons, w)
        

    load_thread = threading.Thread(target=do_sdk, args=(w, progress_connect))
    load_thread.setDaemon(True)
    load_thread.start()
            

connect_disconncet_button.connect("clicked", connect_disconnect_function)

##########################################################################
#                                                                        #
#                           SEARCH / URL BAR                             #
#                                                                        #
##########################################################################


# Note that I add this directly into the 'win' to make it a global variable.
# This let's me to start any url from anywhere in the software.

win.url = Gtk.Entry() # Gtk.SearchEntry() also looks good, but nah
win.url.set_size_request(400,40)
restofpannel.pack_start(win.url, True, True, False)

####### SEARCH FUNCTION #######

def search(w):

    # DRAG AND DROP FOR PUBLISH
    if win.url.get_text().startswith("file://") or os.path.exists(win.url.get_text()):

        # TODO: Some filenames include weird characters like %20 for spacebar and
        # stuff like that. We need to filter that out.
        
        filepath = win.url.get_text().replace("file://", "")
        publish.window(win, {"file_path":filepath})
        win.url.set_text("")
        return


    for i in dynamic_box.get_children():
        i.destroy()

    ## IF WE ARE NOT FORCING IT TO SEACH, IT WILL TRY TO RESOLVE THE LBRY URL FIRST ##
        
    if not force_search.get_active() or win.url.get_text().startswith("lbry://"):
        win.url.set_text(parse.bar(win.url.get_text()))

        resolve = ui.load(win, url.resolve, url.render_resolve, w, win,  win.url.get_text())
        dynamic_box.pack_start(resolve, True, True, True)
        win.show_all()
    else:
        resolve = ui.load(win, claim_search.find, claim_search.render, win, win.url.get_text())
        dynamic_box.pack_start(resolve, True, True, True)
        win.show_all()


# Button to activate seach for those who don't know that you can press Enter    
search_button = Gtk.Button()
search_icon = ui.icon(win, "search")
search_button.add(search_icon)
search_button.set_relief(Gtk.ReliefStyle.NONE)
search_button.connect("clicked", search)
win.url.connect("activate", search)
restofpannel.pack_start(search_button, False, False, False)
        
##########################################################################
#                                                                        #
#                           HAMBURGER MENU                               #
#                                                                        #
##########################################################################

# Popover is the new GTK menu thingy that looks like a comic book dialog
# box. Which is what we want here. Because we want it to look nice I suppose.

hamburger = Gtk.Popover()
hambutton = Gtk.MenuButton(popover=hamburger)
hambutton_icon = ui.icon(win, "actor")
hambutton.add(hambutton_icon)
hambutton.set_relief(Gtk.ReliefStyle.NONE)
pannel.pack_start(hambutton)

# Let's now pack the hamburger menu with items

hambox = Gtk.VBox()


# For the elements inside the box we need a scrolled window, to fit a lot of
# them there.

hamscrl = Gtk.ScrolledWindow()
hamscrl.set_size_request(200,200)
hamburger.add(hambox)

hambox.pack_start(hamscrl, False, False, False)

# "hamchannelbox" will be empty untill the user connects and it loads his channel
# list

hamchannelbox = Gtk.VBox()
hamscrl.add_with_viewport(hamchannelbox)
#hambox.pack_start(hamchannelbox, False, False, False)

###### CHANNELS SELECTOR ########

win.channel = False
def load_channel(win):

    def change_channel(w, win, channel ):
        win.channel = channel
        win.settings["channel"] = win.channel["claim_id"]
        settings.save(win.settings)

        try:
            hambutton.get_child().destroy()
            try:
                newicon = ui.load(win, ui.net_image_calculation, ui.net_image_render, channel["value"]["thumbnail"]["url"], 40, "", False )
            except:
                newicon = ui.icon(win, "actor")
            hambutton.add(newicon)
            pannel.show_all()
        except Exception as e:
            print("What?", e)
    
    out = fetch.lbrynet("channel_list")
    win.my_channels = out

    # We are chooseing first only if there is no channel set in the settings
    
    
    try:
        first = out["items"][0]
        win.channel = first
    except:
        pass

    if "channel" in win.settings:

        try:
            for i in out["items"]:
                if i["claim_id"] == win.settings["channel"]:
                    first = i
                    break
        except:
            pass
        
    try:
        change_channel(False, win, first)
    except:
        pass
    try:

        ####### Go to channel button #####

        def go_to_channel(w):

            channel_url = win.channel["name"]
            try:
                channel_url = channel_url + "#" + win.channel["claim_id"]
            except:
                pass
            
            win.url.set_text(channel_url)
            win.url.activate()
        go = Gtk.Button("  Go To Channel  ")
        go.set_relief(Gtk.ReliefStyle.NONE)
        go.connect("clicked", go_to_channel)
        hamchannelbox.pack_start(go, False, False, False)
        
        
        for i in out["items"]:
            channel_button = ui.go_to_channel(win, i, resolve=False)
            channel_button.connect("clicked", change_channel, win, i)
            hamchannelbox.pack_start(channel_button, False, False, False)
        hamchannelbox.show_all()
    except Exception as e:
        print("CHANNEL ERROR:", e)
  


######## SETTINGS BUTTON ############

settings_button = Gtk.Button("Settings")
settings_button.set_relief(Gtk.ReliefStyle.NONE)
settings_button.connect("clicked", settings.dialogue, win)
hamchannelbox.pack_end(settings_button, False, False, False)


######## FOLLOWING BUTTON ###########

def load_following(  w=False):

    for i in dynamic_box.get_children():
        i.destroy()
    
    try:
        out = fetch.lbrynet("preference_get")
        subs_raw = out["shared"]["value"]["subscriptions"]
        subs_raw = fetch.lbrynet("resolve", {"urls":subs_raw})
        subs = []
        for i in subs_raw:
            try:
                subs.append(subs_raw[i]["claim_id"])
            except:
                pass
    except Exception as e:
        print("\n\nERROR:", e, "\n\n")
        subs = []
    
    
    resolve = ui.load(win, claim_search.find, claim_search.render, win, "", subs)
    dynamic_box.pack_start(resolve, True, True, True)
    dynamic_box.show_all()


following = Gtk.Button("Following")
following.set_relief(Gtk.ReliefStyle.NONE)
following.connect("clicked", load_following)
hamchannelbox.pack_end(following, False, False,False)


########## FORCE SEARCH BUTTON #######

force_search = Gtk.CheckButton("  Force Search  ")
force_search.set_tooltip_text("If not activated it will try to resolve the claim first.")
hamchannelbox.pack_end(force_search, False, False, False)
hambox.show_all()


##########################################################################
#                                                                        #
#                           DYNAMIC BOX ( REST )                         #
#                                                                        #
##########################################################################

# Dynamic box is a box contents of which will change depending on the "page"
# that the user is loading.

box = Gtk.VBox() # I'm making a top level box just in case
win.add(box)

dynamic_box = Gtk.VBox() # And here our dynamic box
scrl = Gtk.ScrolledWindow()
box.pack_start(dynamic_box, True, True, False)



# Just something to run if we are connected

if win.SDK_running == 1:
    load_following()
    load_channel(win)
else:
    dynamic_box.pack_start(Gtk.Label("LBRY is not connected. Please connect."), True, True, 20)

##########################################################################
#                                                                        #
#                              DRAG AND DROP                             #
#                                                                        #
##########################################################################

def on_drop(widget, drag_context, x, y, data, info, time):

    win.url.set_text(data.get_text())
    win.url.activate()

    
enforce_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(4), 129)
box.drag_dest_set(Gtk.DestDefaults.ALL, [enforce_target], Gdk.DragAction.COPY)
box.connect("drag-data-received", on_drop)
    
# Starting Everything
    
win.show_all()
Gtk.main()

#### Clearing the cache of images ###

ui.clean_image_cache()
