# julia Gtk interface
module Gtk
#=it's na 
if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel"))
    @eval Base.Experimental.@optlevel 1
end=#

# Import Binary Definitions (for building by a binary builder)
using GTK3_jll, Glib_jll # handmade collection of custom package Imports  
using Xorg_xkeyboard_config_jll, gdk_pixbuf_jll, adwaita_icon_theme_jll, hicolor_icon_theme_jll #Gnome Imports 
using Librsvg_jll #svg maker wrapper 
using JLLWrappers #Julia Wrapper
using Pkg.Artifacts #ERROR: Unable to parse `Pkg.Artifacts` as a package.Fix using Artifacts
const libgdk = libgdk3
const libgtk = libgtk3
const libgdk_pixbuf = libgdkpixbuf


const suffix = :Leaf #define :leaf # concrete
#include(safe_path("GLib.jl")) # GLib/GLib.jl") #TODO: fix 
#include("..\GLib.jl") #need to include Glib 


using .GLib
using .GLib.MutableTypes
import .GLib: set_gtk_property!, get_gtk_property, getproperty, FieldRef
import .GLib:
    signal_connect, signal_handler_disconnect,
    signal_handler_block, signal_handler_unblock, signal_handler_is_connected,
    signal_emit, unsafe_convert,
    AbstractStringLike, bytestring


import Base: convert, show, run, size, resize!, length, getindex, setindex!,
             insert!, push!, append!, pushfirst!, pop!, splice!, delete!, deleteat!,
             parent, isempty, empty!, first, last, in, popfirst!,
             eltype, copy, isvalid, string, sigatomic_begin, sigatomic_end, (:), iterate

export showall, select!, start


using Reexport
@reexport using Graphics
import .Graphics: width, height, getgc


using Cairo
import Cairo: destroy
using Serialization


const Index{I<:Integer} = Union{I, AbstractVector{I}}
export GAccessor
#=TODO: add files ending with exports 
include("basic_exports.jl")
include("long_exports.jl")
include("long_leaf_exports.jl")
=#


global const libgtk_version = VersionNumber(
      ccall((:gtk_get_major_version, libgtk), Cint, ()),
      ccall((:gtk_get_minor_version, libgtk), Cint, ()),
      ccall((:gtk_get_micro_version, libgtk), Cint, ()))


#= Required files (for Building GUI Items )
include("application.jl")
include("builder.jl") #3

include("interfaces.jl")
include("container.jl")
include("toolbar.jl") #5

include("boxes.jl")
include("buttons.jl") #4

include("gdk.jl") #1
include("gtktypes.jl")
include("cairo.jl") #2 

include("base.jl")
include("gio.jl")
include("cairo.jl")

include("events.jl")
include("layout.jl")
include("lists.jl")
include("displays.jl")

include("text.jl")
include("menus.jl")
include("selectors.jl")
include("theme.jl")
include("misc.jl")

=#
function __init__()
    # Set XDG_DATA_DIRS so that Gtk can find its icons and schemas
    ENV["XDG_DATA_DIRS"] = join(filter(x -> x !== nothing, [
            dirname(adwaita_icons_dir),
            dirname(hicolor_icons_dir),
            joinpath(dirname(GTK3_jll.libgdk3_path::String), "..", "share"),
            get(ENV, "XDG_DATA_DIRS", nothing)::Union{String,Nothing},
        ]), Sys.iswindows() ? ";" : ":")


    # Next, ensure that gdk-pixbuf has its loaders.cache file; we generate a
    # MutableArtifacts.toml file that maps in a loaders.cache we dynamically
    # generate by running `gdk-pixbuf-query-loaders:`
    mutable_artifacts_toml = joinpath(dirname(@__DIR__), "MutableArtifacts.toml")
    loaders_cache_name = "gdk-pixbuf-loaders-cache"
    loaders_cache_hash = artifact_hash(loaders_cache_name, mutable_artifacts_toml)
    loaders_dir_name = "gdk-pixbuf-loaders-dir"
    loaders_dir_hash = artifact_hash(loaders_dir_name, mutable_artifacts_toml)
 
    if loaders_cache_hash === nothing
        if Librsvg_jll.is_available()
            # Copy loaders into a directory
            loaders_dir_hash = create_artifact() do art_dir
                loaders_dir = mkdir(joinpath(art_dir, "loaders_dir"))
                pixbuf_loaders = joinpath.(gdk_pixbuf_loaders_dir, readdir(gdk_pixbuf_loaders_dir))
                push!(pixbuf_loaders, Librsvg_jll.libpixbufloader_svg)
                cp.(pixbuf_loaders, joinpath.(loaders_dir, basename.(pixbuf_loaders)))
            end
 
            loaders_dir = joinpath(artifact_path(loaders_dir_hash), "loaders_dir")
            # Pkg removes "execute" permissions on Windows
            Sys.iswindows() && chmod(artifact_path(loaders_dir_hash), 0o755; recursive = true)#grant access to msft
            # Run gdk-pixbuf-query-loaders, capture output
            loader_cache_contents = gdk_pixbuf_query_loaders() do gpql
                withenv("GDK_PIXBUF_MODULEDIR" => loaders_dir, JLLWrappers.LIBPATH_env => Librsvg_jll.LIBPATH[]) do
                    return String(readchomp(`$gpql`))
                end
            end

            bind_artifact!(mutable_artifacts_toml,
                loaders_dir_name,
                loaders_dir_hash;
                force = true
            )
        else  # just use the gdk_pixbuf directory
            loader_cache_contents = gdk_pixbuf_query_loaders() do gpql
                withenv("GDK_PIXBUF_MODULEDIR" => gdk_pixbuf_loaders_dir) do
                    return String(read(`$gpql`))
                end
            end
        end
        # Write cache out to file in new artifact
        loaders_cache_hash = create_artifact() do art_dir
            open(joinpath(art_dir, "loaders.cache"), "w") do io
                write(io, loader_cache_contents)
            end
        end
        bind_artifact!(mutable_artifacts_toml,
            loaders_cache_name,
            loaders_cache_hash;
            force = true
        )
    end

    # Point gdk to our cached loaders
    ENV["GDK_PIXBUF_MODULE_FILE"] = joinpath(artifact_path(loaders_cache_hash), "loaders.cache")
    ENV["GDK_PIXBUF_MODULEDIR"] = Librsvg_jll.is_available() && loaders_dir_hash !== nothing ?
                                  joinpath(artifact_path(loaders_dir_hash), "loaders_dir") :
                                  gdk_pixbuf_loaders_dir

    if Sys.islinux() || Sys.isfreebsd()
        # Needed by xkbcommon:
        # https://xkbcommon.org/doc/current/group__include-path.html.  Related
        # to issue https://github.com/JuliaGraphics/Gtk.jl/issues/469
        ENV["XKB_CONFIG_ROOT"] = joinpath(Xorg_xkeyboard_config_jll.artifact_dir::String,
            "share", "X11", "xkb")
    end

    GError() do error_check #Gerror not Defined 
        ccall((:gtk_init_with_args, libgtk), Bool,
            (Ptr{Nothing}, Ptr{Nothing}, Ptr{UInt8}, Ptr{Nothing}, Ptr{UInt8}, Ptr{Ptr{GError}}),
            C_NULL, C_NULL, "Julia Gtk Bindings", C_NULL, C_NULL, error_check)
    end

    # if g_main_depth > 0, a glib main-loop is already running
    # unfortunately, this call does not reliably reflect the state after the
    # loop has been stopped (or restarted), so only use it once at the start
    gtk_main_running[] = ccall((:g_main_depth, GLib.libglib), Cint, ()) > 0

    # Given GLib provides `g_idle_add` to specify what happens during idle, this allows
    # that call to also start the eventloop
    GLib.gtk_eventloop_f[] = enable_eventloop

    auto_idle[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true"

    # by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called (during Idle)
    # this allows that call to also start the eventloop
    #GLib.gtk_eventloop_f[] = enable_eventloop
    enable_eventloop(!auto_idle[])
end

const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"] #TODO:check if changed in latest version 
const gtk_main_running = Ref{Bool}(false)
const quit_task = Ref{Task}()
const enable_eventloop_lock = Base.ReentrantLock()

"""  
1. thread handling: the use of lock:  
    Gtk.enable_eventloop(b::Bool = true)
Shortcomming: on lock, only one thread can passs by; in this function (it's gotta be the event loop)

2.Set whether Gtk's event loop is running 
"""
function enable_eventloop(b::Bool = true; wait_stopped::Bool = false)
    lock(enable_eventloop_lock) do # handle widgets (that are being displayed/destructed by different threads)

        isassigned(quit_task) && wait(quit_task[]) #flag check - on special event: Prevents starting, while the asynch is still stopping
                    if b
                        if !is_eventloop_running()
                            global gtk_main_task = schedule(Task(gtk_main)) #Warning: changed: gtk_main_task → gtk_main
                            gtk_main_running[] = true #set `all` true
                        end

        else
            if is_eventloop_running()
                # @async & short sleep as needed on MacOS, otherwise
                # the Window doesn't always finish closing before the eventloop stops
                # @async and short sleep is needer on MacOS at least, otherwise
                # the window doesn't always finish closing before the eventloop stops.
                quit_task[] = @async begin
                    sleep(0.2)
                    gtk_quit()
                    gtk_main_running[] = false # negate `all` running threads 
                end
                wait_stopped && wait(quit_task[])
            end
        end
    end
end
 

# Given GLib provides `g_idle_add` to specify what happens during idle, this allows
# that call to also start the eventloop
GLib.gtk_eventloop_f[] = enable_eventloop

#const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"]
auto_idle[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true"

    #const enable_eventloop_lock = Base.ReentrantLock()
    # by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called
                quit_task[] = @async begin

                    sleep(0.2)
                    gtk_quit()
                    gtk_main_running[] = false
                end
                wait_stopped && wait(quit_task[])
            #=end
        end
    end=#
end 

"""
    Gtk.pause_eventloop(f; force = false)

Pauses the eventloop around a function. Restores the state of the eventloop after
pausing. Respects whether Gtk.jl is configured to allow auto-stopping of the
eventloop, unless `force = true`.
"""
function pause_eventloop(f; force = false)
    was_running = is_eventloop_running()
    (force || auto_idle[]) && enable_eventloop(false, wait_stopped = true)
    try
        f()
    finally
        (force || auto_idle[]) && enable_eventloop(was_running)
    end
end

"""
    Gtk.is_eventloop_running()::Bool

Check whether Gtk's event loop is running.
"""
is_eventloop_running() = gtk_main_running[]

const ser_version = Serialization.ser_version
let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen")
    fastgtkcache = joinpath(cachedir, "gtk$(libgtk_version.major)_julia_ser$(ser_version)")
    if isfile(fastgtkcache) & true #old error && was a boolean Literal
        open(fastgtkcache) do cache
            while !eof(cache)
                Core.eval(Gtk, deserialize(cache))
            end
        end
    else
        gboxcache = joinpath(cachedir, "gbox$(libgtk_version.major)")
        map(eval, include(gboxcache).args)
        constcache = joinpath(cachedir, "gconsts$(libgtk_version.major)")
        map(eval, include(constcache).args)
    end
end
const _ = GAccessor #TODO: No Definition for GAcessor (Gnome Accessor) #Infer: Gnome wrapper package 
using .GConstants
#= TODO:UncommentMe
include("windows.jl")
include("gl_area.jl")
=#
if Base.VERSION >= v"1.4.2"
  #=  include("precompile.jl") =#
    _precompile_()
end

# Alternative Interface (`using Gtk.ShortNames`)
module ShortNames
using .Gtk 
import GLib:
    signal_connect, signal_handler_disconnect,
    signal_handler_block, signal_handler_unblock, signal_handler_is_connected,
    signal_emit
import GLib.@g_type_delegate
import Gtk: suffix
#export Gtk
#= TODO: fix the following files before Uncomenting #UncommentMe
    include("basic_exports.jl")
    include("short_exports.jl")
    include("short_leaf_exports.jl")
=##enforce Using the .ShortNames 
end
using .ShortNames
end