struct NoBackend <: AbstractBackend end

lazyloadPkg() = Base.require(@__MODULE__, :Pkg)

const _toml_proj = TOML.parsefile(normpath(@__DIR__, "..", "Project.toml"))
const _toml_test = TOML.parsefile(normpath(@__DIR__, "..", "test", "Project.toml"))

const _current_plots_version = VersionNumber(_toml_proj["version"])
const _plots_compats = merge(_toml_proj["compat"], _toml_test["compat"])
const _plots_deps = merge(_toml_proj["deps"], _toml_test["deps"])

const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none)
const _backendType = Dict{Symbol, DataType}(:none => NoBackend)
const _backend_packages = Dict{Symbol, Symbol}()
const _initialized_backends = Set{Symbol}()
const _backends = Symbol[]

function _check_installed(backend::Union{Module, AbstractString, Symbol}; warn = true)
    sym = Symbol(lowercase(string(backend)))
    if warn && !haskey(_backend_packages, sym)
        @warn "backend `$sym` is not compatible with `Plots`."
        return
    end
    # lowercase -> CamelCase, falling back to the given input for `PlotlyBase` ...
    str = string(get(_backend_packages, sym, backend))
    str == "Plotly" && (str *= "Base")  # FIXME: `Plots` inconsistency, `plotly` should be named `plotlybase`
    # check supported
    if warn && !haskey(_plots_compats, str)
        @warn "backend `$str` is not compatible with `Plots`."
        return
    end
    # check installed
    pkg_id = if str == "GR"
        # FIXME: remove in `Plots2.0` (`GR` won't be a hard Plots dependency anymore).
        Base.identify_package(Plots, str)  # GR can be in the Manifest or in the Project
    else
        Base.identify_package(str)  # a Project dependency
    end
    version = if pkg_id === nothing
        nothing
    else
        pkg = lazyloadPkg()
        get(Base.invokelatest(pkg.dependencies), pkg_id.uuid, (; version = nothing)).version
    end
    version === nothing && @warn "backend `$str` is not installed."
    return version
end

function _check_compat(m::Module; warn = true)
    (be_v = _check_installed(m; warn)) === nothing && return
    be_c = _plots_compats[string(m)]
    pkg = lazyloadPkg()
    semver = Base.invokelatest(pkg.Types.semver_spec, be_c)
    if Base.invokelatest(∉, be_v, semver)
        @warn "`$m` $be_v is not compatible with this version of `Plots`. The declared compatibility is $(be_c)."
    end
    return nothing
end

const _backend_paths = Dict{Symbol, RelocatableFolders.Path}(
    :gaston => @path(joinpath(@__DIR__, "backends", "gaston.jl")),
    :gr => @path(joinpath(@__DIR__, "backends", "gr.jl")),
    :hdf5 => @path(joinpath(@__DIR__, "backends", "hdf5.jl")),
    :inspectdr => @path(joinpath(@__DIR__, "backends", "inspectdr.jl")),
    :pgfplotsx => @path(joinpath(@__DIR__, "backends", "pgfplotsx.jl")),
    :plotly => @path(joinpath(@__DIR__, "backends", "plotly.jl")),
    :plotlybase => @path(joinpath(@__DIR__, "backends", "plotlybase.jl")),
    :plotlyjs => @path(joinpath(@__DIR__, "backends", "plotlyjs.jl")),
    :pythonplot => @path(joinpath(@__DIR__, "backends", "pythonplot.jl")),
    :unicodeplots => @path(joinpath(@__DIR__, "backends", "unicodeplots.jl")),
    # Deprecated backends
    :pgfplots => @path(joinpath(@__DIR__, "backends", "deprecated", "pgfplots.jl")),
    :pyplot => @path(joinpath(@__DIR__, "backends", "deprecated", "pyplot.jl")),
)

_path(sym::Symbol) = _backend_paths[sym]

"Returns a list of supported backends"
backends() = _backends

"Returns the name of the current backend"
backend_name() = CURRENT_BACKEND.sym

_backend_instance(sym::Symbol)::AbstractBackend =
    haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym")

backend_package_name(sym::Symbol = backend_name()) = _backend_packages[sym]

macro init_backend(s)
    package_str = string(s)
    str = lowercase(package_str)
    sym = Symbol(str)
    T = Symbol(string(s) * "Backend")
    return quote
        struct $T <: AbstractBackend end
        export $sym
        $sym(; kw...) = (default(; reset = false, kw...); backend($T()))
        backend_name(::$T) = Symbol($str)
        backend_package_name(::$T) = backend_package_name(Symbol($str))
        push!(_backends, Symbol($str))
        _backendType[Symbol($str)] = $T
        _backendSymbol[$T] = Symbol($str)
        _backend_packages[Symbol($str)] = Symbol($package_str)
    end |> esc
end

macro require_backend(pkg)
    be = QuoteNode(Symbol(lowercase("$pkg")))
    return quote
        backend_name() === $be || @require $pkg = $(_plots_deps["$pkg"]) begin
            include(_path($be))
        end
    end |> esc
end

# ---------------------------------------------------------

# don't do anything as a default
_create_backend_figure(plt::Plot) = nothing
_initialize_subplot(plt::Plot, sp::Subplot) = nothing

_series_added(plt::Plot, series::Series) = nothing
_series_updated(plt::Plot, series::Series) = nothing

_before_layout_calcs(plt::Plot) = nothing

title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt
guide_padding(axis::Axis) = Plots.get_guide(axis) == "" ? 0mm : axis[:guidefontsize] * pt

closeall(::AbstractBackend) = nothing

"Returns the (width,height) of a text label."
function text_size(lablen::Int, sz::Number, rot::Number = 0)
    # we need to compute the size of the ticks generically
    # this means computing the bounding box and then getting the width/height
    # note:
    ptsz = sz * pt
    width = 0.8lablen * ptsz

    # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
    height = abs(sind(rot)) * width + abs(cosd(rot)) * ptsz
    width = abs(sind(rot + 90)) * width + abs(cosd(rot + 90)) * ptsz
    return width, height
end
text_size(lab::AbstractString, sz::Number, rot::Number = 0) =
    text_size(length(lab), sz, rot)
text_size(lab::PlotText, sz::Number, rot::Number = 0) = text_size(length(lab.str), sz, rot)

# account for the size/length/rotation of tick labels
function tick_padding(sp::Subplot, axis::Axis)
    return if (ticks = get_ticks(sp, axis)) === nothing
        0mm
    else
        vals, labs = ticks
        isempty(labs) && return 0mm
        # ptsz = axis[:tickfont].pointsize * pt
        longest_label = maximum(length(lab) for lab in labs)

        # generalize by "rotating" y labels
        rot = axis[:rotation] + (axis[:letter] === :y ? 90 : 0)

        # # we need to compute the size of the ticks generically
        # # this means computing the bounding box and then getting the width/height
        # labelwidth = 0.8longest_label * ptsz
        #
        #
        # # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
        # hgt = abs(sind(rot)) * labelwidth + abs(cosd(rot)) * ptsz + 1mm

        # get the height of the rotated label
        text_size(longest_label, axis[:tickfontsize], rot)[2]
    end
end

# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot)
    # TODO: something different when `RecipesPipeline.is3d(sp) == true`
    leftpad = tick_padding(sp, sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
    toppad = sp[:top_margin] + title_padding(sp)
    rightpad = sp[:right_margin]
    bottompad = tick_padding(sp, sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis])

    # switch them?
    if sp[:xaxis][:mirror]
        bottompad, toppad = toppad, bottompad
    end
    if sp[:yaxis][:mirror]
        leftpad, rightpad = rightpad, leftpad
    end

    # @show (leftpad, toppad, rightpad, bottompad)
    return sp.minpad = (leftpad, toppad, rightpad, bottompad)
end

_update_plot_object(plt::Plot) = nothing

# ---------------------------------------------------------

mutable struct CurrentBackend
    sym::Symbol
    pkg::AbstractBackend
end
CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))

# ---------------------------------------------------------
const PLOTS_DEFAULT_BACKEND = "gr"

function load_default_backend()
    CURRENT_BACKEND.sym = :gr
    return backend(CURRENT_BACKEND.sym)
end

function diagnostics(io::IO = stdout)
    origin = if has_preference(Plots, "default_backend")
        "`Preferences`"
    elseif haskey(ENV, "PLOTS_DEFAULT_BACKEND")
        "environment variable"
    else
        "fallback"
    end
    if (be = backend_name()) === :none
        @info "no `Plots` backends currently initialized"
    else
        be_name = string(backend_package_name(be))
        @info "selected `Plots` backend: $be_name, from $origin"
        pkg = lazyloadPkg()
        Base.invokelatest(
            pkg.status,
            ["Plots", "RecipesBase", "RecipesPipeline", be_name];
            mode = pkg.PKGMODE_MANIFEST,
            io,
        )
    end
    return nothing
end

# ---------------------------------------------------------

"""
Returns the current plotting package name.  Initializes package on first call.
"""
function backend()
    CURRENT_BACKEND.sym === :none && load_default_backend()
    return CURRENT_BACKEND.pkg
end

initialized(sym::Symbol) = sym ∈ _initialized_backends

"""
Set the plot backend.
"""
function backend(pkg::AbstractBackend)
    sym = backend_name(pkg)
    if !initialized(sym)
        _initialize_backend(pkg)
        push!(_initialized_backends, sym)
    end
    CURRENT_BACKEND.sym = sym
    CURRENT_BACKEND.pkg = pkg
    return pkg
end

backend(sym::Symbol) =
if sym in _backends
    backend(_backend_instance(sym))
else
    @warn "`:$sym` is not a supported backend."
    backend()
end

const _deprecated_backends =
    [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize, :pgfplots]

# ---------------------------------------------------------

# these are args which every backend supports because they're not used in the backend code
const _base_supported_args = [
    :color_palette,
    :background_color,
    :background_color_subplot,
    :foreground_color,
    :foreground_color_subplot,
    :group,
    :seriestype,
    :seriescolor,
    :seriesalpha,
    :smooth,
    :xerror,
    :yerror,
    :zerror,
    :subplot,
    :x,
    :y,
    :z,
    :show,
    :size,
    :margin,
    :left_margin,
    :right_margin,
    :top_margin,
    :bottom_margin,
    :html_output_format,
    :layout,
    :link,
    :primary,
    :series_annotations,
    :subplot_index,
    :discrete_values,
    :projection,
    :show_empty_bins,
    :z_order,
    :permute,
    :unitformat,
]

function merge_with_base_supported(v::AVec)
    v = vcat(v, _base_supported_args)
    for vi in v
        if haskey(_axis_defaults, vi)
            for letter in (:x, :y, :z)
                push!(v, get_attr_symbol(letter, vi))
            end
        end
    end
    return Set(v)
end

@init_backend PyPlot
@init_backend PythonPlot
@init_backend UnicodePlots
@init_backend Plotly
@init_backend PlotlyJS
@init_backend GR
@init_backend PGFPlots
@init_backend PGFPlotsX
@init_backend InspectDR
@init_backend HDF5
@init_backend Gaston

# ---------------------------------------------------------

# create the various `is_xxx_supported` and `supported_xxxs` methods
# by default they pass through to checking membership in `_gr_xxx`
for s in (:attr, :seriestype, :marker, :style, :scale)
    f1 = Symbol("is_", s, "_supported")
    f2 = Symbol("supported_", s, "s")
    @eval begin
        $f1(::AbstractBackend, $s) = false
        $f1(be::AbstractBackend, $s::AbstractVector) = all(v -> $f1(be, v), $s)
        $f1($s) = $f1(backend(), $s)
        $f2() = $f2(backend())
    end

    for be in backends()
        be_type = typeof(_backend_instance(be))
        v = Symbol("_", be, "_", s)
        @eval begin
            $f1(::$be_type, $s::Symbol) = $s in $v
            $f2(::$be_type) = sort(collect($v))
        end
    end
end

################################################################################
# custom hooks

# @require and imports
function _pre_imports(pkg::AbstractBackend)
    @eval @require_backend $(backend_package_name(pkg))
    return nothing
end

# global definitions `const` and `include`
function _post_imports(pkg::AbstractBackend)
    name = backend_package_name(pkg)
    @eval const $name = Main.$name  # so that the module is available in `Plots`
    return nothing
end

# function calls, pointer initializations, ...
_runtime_init(::AbstractBackend) = nothing

################################################################################
# initialize the backends
function _initialize_backend(pkg::AbstractBackend)
    _pre_imports(pkg)
    name = backend_package_name(pkg)
    # NOTE: this is a hack importing in `Main` (expecting the package to be in `Project.toml`, remove in `Plots@2.0`)
    # FIXME: remove hard `GR` dependency in `Plots@2.0`
    @eval name === :GR ? Plots : Main begin
        import $name
        export $name
        if $(QuoteNode(name)) !== :GR
            $(_check_compat)($name)
        end
    end
    _post_imports(pkg)
    _runtime_init(pkg)
    return nothing
end

# ------------------------------------------------------------------------------
# gr
_post_imports(::GRBackend) = nothing

const _gr_attr = merge_with_base_supported(
    [
        :annotations,
        :annotationrotation,
        :annotationhalign,
        :annotationfontsize,
        :annotationfontfamily,
        :annotationcolor,
        :annotationvalign,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        :legend_foreground_color,
        :foreground_color_grid,
        :foreground_color_axis,
        :foreground_color_text,
        :foreground_color_border,
        :label,
        :seriescolor,
        :seriesalpha,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :fillstyle,
        :bins,
        :layout,
        :title,
        :window_title,
        :guide,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :titlefontfamily,
        :titlefontsize,
        :titlefonthalign,
        :titlefontvalign,
        :titlefontrotation,
        :titlefontcolor,
        :legend_font_family,
        :legend_font_pointsize,
        :legend_font_halign,
        :legend_font_valign,
        :legend_font_rotation,
        :legend_font_color,
        :tickfontfamily,
        :tickfontsize,
        :tickfonthalign,
        :tickfontvalign,
        :tickfontrotation,
        :tickfontcolor,
        :guidefontfamily,
        :guidefontsize,
        :guidefonthalign,
        :guidefontvalign,
        :guidefontrotation,
        :guidefontcolor,
        :grid,
        :gridalpha,
        :gridstyle,
        :gridlinewidth,
        :legend_position,
        :legend_title,
        :colorbar,
        :colorbar_title,
        :colorbar_titlefont,
        :colorbar_titlefontsize,
        :colorbar_titlefontrotation,
        :colorbar_titlefontcolor,
        :colorbar_entry,
        :colorbar_scale,
        :clims,
        :fill,
        :fill_z,
        :fontfamily,
        :fontfamily_subplot,
        :line_z,
        :marker_z,
        :legend_column,
        :legend_font,
        :legend_title,
        :legend_title_font_color,
        :legend_title_font_family,
        :legend_title_font_rotation,
        :legend_title_font_pointsize,
        :legend_title_font_valigm,
        :levels,
        :line,
        :ribbon,
        :quiver,
        :orientation,
        :overwrite_figure,
        :plot_title,
        :plot_titlefontcolor,
        :plot_titlefontfamily,
        :plot_titlefontrotation,
        :plot_titlefontsize,
        :plot_titlelocation,
        :plot_titlevspan,
        :polar,
        :aspect_ratio,
        :normalize,
        :weights,
        :inset_subplots,
        :bar_width,
        :arrow,
        :framestyle,
        :tick_direction,
        :camera,
        :contour_labels,
        :connections,
        :axis,
        :thickness_scaling,
        :minorgrid,
        :minorgridalpha,
        :minorgridlinewidth,
        :minorgridstyle,
        :minorticks,
        :mirror,
        :rotation,
        :showaxis,
        :tickfonthalign,
        :formatter,
        :mirror,
        :guidefont,
    ]
)
const _gr_seriestype = [
    :path,
    :scatter,
    :straightline,
    :heatmap,
    :image,
    :contour,
    :path3d,
    :scatter3d,
    :surface,
    :wireframe,
    :mesh3d,
    :volume,
    :shape,
]
const _gr_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _gr_marker = vcat(_allMarkers, :pixel)
const _gr_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::GRBackend, shape::Shape) = true

# ------------------------------------------------------------------------------
# plotly
_pre_imports(::PlotlyBackend) = nothing
_post_imports(::PlotlyBackend) = @eval begin
    const PlotlyBase = Main.PlotlyBase
    const PlotlyKaleido = Main.PlotlyKaleido
    # FIXME: in Plots `2.0`, `plotly` backend should be re-named to `plotlybase`
    # so that we can trigger include on `@require` instead of this
    PLOTS_DEFAULT_BACKEND == "plotly" || include(_path(:plotly))
    include(_path(:plotlybase))
end
function _initialize_backend(pkg::PlotlyBackend)
    try
        _pre_imports(pkg)
        @eval Main begin
            import PlotlyBase
            import PlotlyKaleido
            $(_check_compat)(PlotlyBase; warn = false)  # NOTE: don't warn, since those are not backends, but deps
            $(_check_compat)(PlotlyKaleido, warn = false)
        end
        _post_imports(pkg)
        _runtime_init(pkg)
    catch err
        if err isa ArgumentError
            @warn "Failed to load integration with PlotlyBase & PlotlyKaleido." exception =
                (err, catch_backtrace())
        else
            rethrow(err)
        end
        # NOTE: `plotly` is special in the way that it does not require dependencies for displaying a plot
        # as a result, we cannot rely on the `@require` mechanism for loading glue code
        # this is why it must be done here.
        PLOTS_DEFAULT_BACKEND == "plotly" || @eval include(_path(:plotly))
    end
    return @static if isdefined(Base.Experimental, :register_error_hint)
        Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs
            if exc.f === _show &&
                    length(argtypes) == 3 &&
                    argtypes[2] <: MIME"image/png" &&
                    argtypes[3] <: Plot{PlotlyBackend}
                println(
                    io,
                    "\n\nTip: For saving/rendering as png with the `Plotly` backend `PlotlyBase` and `PlotlyKaleido` need to be installed.",
                )
            end
        end
    end
end

const _plotly_attr = merge_with_base_supported(
    [
        :annotations,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        :legend_foreground_color,
        :foreground_color_guide,
        :foreground_color_grid,
        :foreground_color_axis,
        :foreground_color_text,
        :foreground_color_border,
        :foreground_color_title,
        :label,
        :seriescolor,
        :seriesalpha,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :markerstrokestyle,
        :fill,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :fontfamily,
        :fontfamily_subplot,
        :bins,
        :title,
        :titlelocation,
        :titlefontfamily,
        :titlefontsize,
        :titlefonthalign,
        :titlefontvalign,
        :titlefontcolor,
        :legend_column,
        :legend_font,
        :legend_font_family,
        :legend_font_pointsize,
        :legend_font_color,
        :legend_title,
        :legend_title_font_color,
        :legend_title_font_family,
        :legend_title_font_pointsize,
        :tickfontfamily,
        :tickfontsize,
        :tickfontcolor,
        :guidefontfamily,
        :guidefontsize,
        :guidefontcolor,
        :window_title,
        :arrow,
        :guide,
        :widen,
        :lims,
        :line,
        :ticks,
        :scale,
        :flip,
        :rotation,
        :tickfont,
        :guidefont,
        :legendfont,
        :grid,
        :gridalpha,
        :gridlinewidth,
        :legend,
        :colorbar,
        :colorbar_title,
        :colorbar_entry,
        :marker_z,
        :fill_z,
        :line_z,
        :levels,
        :ribbon,
        :quiver,
        :orientation,
        # :overwrite_figure,
        :polar,
        :plot_title,
        :plot_titlefontcolor,
        :plot_titlefontfamily,
        :plot_titlefontsize,
        :plot_titlelocation,
        :plot_titlevspan,
        :normalize,
        :weights,
        # :contours,
        :aspect_ratio,
        :hover,
        :inset_subplots,
        :bar_width,
        :clims,
        :framestyle,
        :tick_direction,
        :camera,
        :contour_labels,
        :connections,
        :xformatter,
        :xshowaxis,
        :xguidefont,
        :yformatter,
        :yshowaxis,
        :yguidefont,
        :zformatter,
        :zguidefont,
    ]
)

const _plotly_seriestype = [
    :path,
    :scatter,
    :heatmap,
    :contour,
    :surface,
    :wireframe,
    :path3d,
    :scatter3d,
    :shape,
    :scattergl,
    :straightline,
    :mesh3d,
]
const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot]
const _plotly_marker = [
    :none,
    :auto,
    :circle,
    :rect,
    :diamond,
    :utriangle,
    :dtriangle,
    :cross,
    :xcross,
    :pentagon,
    :hexagon,
    :octagon,
    :vline,
    :hline,
    :x,
]
const _plotly_scale = [:identity, :log10]

defaultOutputFormat(plt::Plot{Plots.PlotlyBackend}) = "html"

# ------------------------------------------------------------------------------
# pgfplots

const _pgfplots_attr = merge_with_base_supported(
    [
        :annotations,
        :legend_background_color,
        :background_color_inside,
        # :background_color_outside,
        # :legend_foreground_color,
        :foreground_color_grid,
        :foreground_color_axis,
        :foreground_color_text,
        :foreground_color_border,
        :label,
        :seriescolor,
        :seriesalpha,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :markerstrokestyle,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :bins,
        # :bar_width, :bar_edges,
        :title,
        # :window_title,
        :guide,
        :guide_position,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :rotation,
        :tickfont,
        :guidefont,
        :legendfont,
        :grid,
        :legend,
        :colorbar,
        :colorbar_title,
        :fill_z,
        :line_z,
        :marker_z,
        :levels,
        # :ribbon, :quiver, :arrow,
        # :orientation,
        # :overwrite_figure,
        :polar,
        # :normalize, :weights, :contours,
        :aspect_ratio,
        :tick_direction,
        :framestyle,
        :camera,
        :contour_labels,
    ]
)
const _pgfplots_seriestype = [
    :path,
    :path3d,
    :scatter,
    :steppre,
    :stepmid,
    :steppost,
    :histogram2d,
    :ysticks,
    :xsticks,
    :contour,
    :shape,
    :straightline,
]
const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _pgfplots_marker = [
    :none,
    :auto,
    :circle,
    :rect,
    :diamond,
    :utriangle,
    :dtriangle,
    :cross,
    :xcross,
    :star5,
    :pentagon,
    :hline,
    :vline,
] #vcat(_allMarkers, Shape)
const _pgfplots_scale = [:identity, :ln, :log2, :log10]

# ------------------------------------------------------------------------------
# plotlyjs
_post_imports(::PlotlyJSBackend) = @eval begin
    const PlotlyJS = Main.PlotlyJS
    if Plots.bool_env("PLOTS_PLOTLYJS_UNSAFE_ELECTRON", "false")
        (Sys.islinux() && isdefined(PlotlyJS, :unsafe_electron)) &&
            PlotlyJS.unsafe_electron()
    end
end

const _plotlyjs_attr = _plotly_attr
const _plotlyjs_seriestype = _plotly_seriestype
const _plotlyjs_style = _plotly_style
const _plotlyjs_marker = _plotly_marker
const _plotlyjs_scale = _plotly_scale

# ------------------------------------------------------------------------------
# pyplot

_post_imports(::PyPlotBackend) = @eval begin
    const PyPlot = Main.PyPlot
    const PyCall = Main.PyPlot.PyCall
end
_runtime_init(::PyPlotBackend) = @eval begin
    pycolors = PyCall.pyimport("matplotlib.colors")
    pypath = PyCall.pyimport("matplotlib.path")
    mplot3d = PyCall.pyimport("mpl_toolkits.mplot3d")
    axes_grid1 = PyCall.pyimport("mpl_toolkits.axes_grid1")
    pypatches = PyCall.pyimport("matplotlib.patches")
    pyticker = PyCall.pyimport("matplotlib.ticker")
    pycmap = PyCall.pyimport("matplotlib.cm")
    pynp = PyCall.pyimport("numpy")

    pynp."seterr"(invalid = "ignore")

    PyPlot.ioff()  # we don't want every command to update the figure
end

function _initialize_backend(pkg::PyPlotBackend)
    _pre_imports(pkg)
    @eval Main begin
        import PyPlot
        export PyPlot
        $(_check_compat)(PyPlot)
    end
    _post_imports(pkg)
    return _runtime_init(pkg)
end

const _pyplot_attr = merge_with_base_supported(
    [
        :annotations,
        :annotationrotation,
        :annotationhalign,
        :annotationfontsize,
        :annotationfontfamily,
        :annotationcolor,
        :annotationvalign,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        :foreground_color_grid,
        :legend_foreground_color,
        :foreground_color_title,
        :foreground_color_axis,
        :foreground_color_border,
        :foreground_color_guide,
        :foreground_color_text,
        :label,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :fillstyle,
        :bins,
        :bar_width,
        :bar_edges,
        :bar_position,
        :title,
        :titlelocation,
        :titlefont,
        :window_title,
        :guide,
        :guide_position,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :rotation,
        :titlefontfamily,
        :titlefontsize,
        :titlefontcolor,
        :legend_font_family,
        :legend_font_pointsize,
        :legend_font_color,
        :tickfontfamily,
        :tickfontsize,
        :tickfontcolor,
        :guidefontfamily,
        :guidefontsize,
        :guidefontcolor,
        :grid,
        :gridalpha,
        :gridstyle,
        :gridlinewidth,
        :legend_position,
        :legend_title,
        :colorbar,
        :colorbar_title,
        :colorbar_entry,
        :colorbar_ticks,
        :colorbar_tickfontfamily,
        :colorbar_tickfontsize,
        :colorbar_tickfonthalign,
        :colorbar_tickfontvalign,
        :colorbar_tickfontrotation,
        :colorbar_tickfontcolor,
        :colorbar_titlefontcolor,
        :colorbar_titlefontsize,
        :colorbar_scale,
        :marker_z,
        :line,
        :line_z,
        :fill,
        :fill_z,
        :fontfamily,
        :fontfamily_subplot,
        :legend_column,
        :legend_font,
        :legend_title,
        :legend_title_font_color,
        :legend_title_font_family,
        :legend_title_font_pointsize,
        :levels,
        :ribbon,
        :quiver,
        :arrow,
        :orientation,
        :overwrite_figure,
        :polar,
        :plot_title,
        :plot_titlefontcolor,
        :plot_titlefontfamily,
        :plot_titlefontsize,
        :plot_titlelocation,
        :plot_titlevspan,
        :normalize,
        :weights,
        :contours,
        :aspect_ratio,
        :clims,
        :inset_subplots,
        :dpi,
        :stride,
        :framestyle,
        :tick_direction,
        :thickness_scaling,
        :camera,
        :contour_labels,
        :connections,
        :thickness_scaling,
        :axis,
        :minorgrid,
        :minorgridalpha,
        :minorgridlinewidth,
        :minorgridstyle,
        :minorticks,
        :mirror,
        :showaxis,
        :tickfontrotation,
        :formatter,
        :guidefont,
    ]
)
const _pyplot_seriestype = [
    :path,
    :steppre,
    :stepmid,
    :steppost,
    :shape,
    :straightline,
    :scatter,
    :hexbin,
    :heatmap,
    :image,
    :contour,
    :contour3d,
    :path3d,
    :scatter3d,
    :mesh3d,
    :surface,
    :wireframe,
]
const _pyplot_style = [:auto, :solid, :dash, :dot, :dashdot]
const _pyplot_marker = vcat(_allMarkers, :pixel)
const _pyplot_scale = [:identity, :ln, :log2, :log10]

# ------------------------------------------------------------------------------
# pythonplot

_post_imports(::PythonPlotBackend) = @eval begin
    const PythonPlot = Main.PythonPlot
    const PythonCall = Main.PythonPlot.PythonCall
    const mpl_toolkits = PythonPlot.pyimport("mpl_toolkits")
    const mpl = PythonPlot.pyimport("matplotlib")
    const numpy = PythonPlot.pyimport("numpy")

    PythonPlot.pyimport("mpl_toolkits.axes_grid1")
    numpy.seterr(invalid = "ignore")

    const pyisnone = if isdefined(PythonCall, :pyisnone)
        PythonCall.pyisnone
    else
        PythonCall.Core.pyisnone
    end

    PythonPlot.ioff() # we don't want every command to update the figure
end
_runtime_init(::PythonPlotBackend) = nothing

function _initialize_backend(pkg::PythonPlotBackend)
    _pre_imports(pkg)
    @eval Main begin
        import PythonPlot
        $(_check_compat)(PythonPlot)
    end
    _post_imports(pkg)
    return _runtime_init(pkg)
end

const _pythonplot_seriestype = _pyplot_seriestype
const _pythonplot_marker = _pyplot_marker
const _pythonplot_style = _pyplot_style
const _pythonplot_scale = _pyplot_scale

const _pythonplot_attr = merge_with_base_supported(
    [
        :annotations,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        :foreground_color_grid,
        :legend_foreground_color,
        :foreground_color_title,
        :foreground_color_axis,
        :foreground_color_border,
        :foreground_color_guide,
        :foreground_color_text,
        :label,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :fillstyle,
        :bins,
        :bar_width,
        :bar_edges,
        :bar_position,
        :title,
        :titlelocation,
        :titlefont,
        :window_title,
        :guide,
        :guide_position,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :rotation,
        :titlefontfamily,
        :titlefontsize,
        :titlefontcolor,
        :legend_font_family,
        :legend_font_pointsize,
        :legend_font_color,
        :tickfontfamily,
        :tickfontsize,
        :tickfontcolor,
        :guidefontfamily,
        :guidefontsize,
        :guidefontcolor,
        :grid,
        :gridalpha,
        :gridstyle,
        :gridlinewidth,
        :legend_position,
        :legend_title,
        :colorbar,
        :colorbar_title,
        :colorbar_entry,
        :colorbar_ticks,
        :colorbar_tickfontfamily,
        :colorbar_tickfontsize,
        :colorbar_tickfonthalign,
        :colorbar_tickfontvalign,
        :colorbar_tickfontrotation,
        :colorbar_tickfontcolor,
        :colorbar_titlefontcolor,
        :colorbar_titlefontsize,
        :colorbar_scale,
        :marker_z,
        :line,
        :line_z,
        :fill,
        :fill_z,
        :fontfamily,
        :fontfamily_subplot,
        :legend_column,
        :legend_font,
        :legend_title,
        :legend_title_font_color,
        :legend_title_font_family,
        :legend_title_font_pointsize,
        :levels,
        :ribbon,
        :quiver,
        :arrow,
        :orientation,
        :overwrite_figure,
        :polar,
        :normalize,
        :weights,
        :contours,
        :aspect_ratio,
        :clims,
        :inset_subplots,
        :dpi,
        :stride,
        :framestyle,
        :tick_direction,
        :camera,
        :contour_labels,
        :connections,
    ]
)

# ------------------------------------------------------------------------------
# gaston

const _gaston_attr = merge_with_base_supported(
    [
        :annotations,
        # :background_color_legend,
        # :background_color_inside,
        # :background_color_outside,
        # :foreground_color_legend,
        # :foreground_color_grid, :foreground_color_axis,
        # :foreground_color_text, :foreground_color_border,
        :label,
        :seriescolor,
        :seriesalpha,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        # :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
        # :fillrange, :fillcolor, :fillalpha,
        # :bins,
        # :bar_width, :bar_edges,
        :title,
        :window_title,
        :guide,
        :guide_position,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :rotation,
        :tickfont,
        :guidefont,
        :legendfont,
        :grid,
        :legend,
        # :colorbar, :colorbar_title,
        # :fill_z, :line_z, :marker_z, :levels,
        # :ribbon,
        :quiver,
        :arrow,
        # :orientation, :overwrite_figure,
        :polar,
        # :normalize, :weights, :contours,
        :aspect_ratio,
        :tick_direction,
        # :framestyle,
        # :camera,
        # :contour_labels,
        :connections,
    ]
)

const _gaston_seriestype = [
    :path,
    :path3d,
    :scatter,
    :steppre,
    :stepmid,
    :steppost,
    :ysticks,
    :xsticks,
    :contour,
    :shape,
    :straightline,
    :scatter3d,
    :contour3d,
    :wireframe,
    :heatmap,
    :surface,
    :mesh3d,
    :image,
]

const _gaston_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]

const _gaston_marker = [
    :none,
    :auto,
    :pixel,
    :cross,
    :xcross,
    :+,
    :x,
    :star5,
    :rect,
    :circle,
    :utriangle,
    :dtriangle,
    :diamond,
    :pentagon,
    # :hline,
    # :vline,
]

const _gaston_scale = [:identity, :ln, :log2, :log10]

# ------------------------------------------------------------------------------
# unicodeplots

const _unicodeplots_attr = merge_with_base_supported(
    [
        :annotations,
        :bins,
        :guide,
        :widen,
        :grid,
        :label,
        :layout,
        :legend,
        :legend_title_font_color,
        :lims,
        :line,
        :linealpha,
        :linecolor,
        :linestyle,
        :markershape,
        :plot_title,
        :quiver,
        :arrow,
        :seriesalpha,
        :seriescolor,
        :scale,
        :flip,
        :title,
        # :marker_z,
        :line_z,
    ]
)
const _unicodeplots_seriestype = [
    :path,
    :path3d,
    :scatter,
    :scatter3d,
    :straightline,
    # :bar,
    :shape,
    :histogram2d,
    :heatmap,
    :contour,
    # :contour3d,
    :image,
    :spy,
    :surface,
    :wireframe,
    :mesh3d,
]
const _unicodeplots_style = [:auto, :solid]
const _unicodeplots_marker = [
    :none,
    :auto,
    :pixel,
    # vvvvvvvvvv shapes
    :circle,
    :rect,
    :star5,
    :diamond,
    :hexagon,
    :cross,
    :xcross,
    :utriangle,
    :dtriangle,
    :rtriangle,
    :ltriangle,
    :pentagon,
    # :heptagon,
    # :octagon,
    :star4,
    :star6,
    # :star7,
    :star8,
    :vline,
    :hline,
    :+,
    :x,
]
const _unicodeplots_scale = [:identity, :ln, :log2, :log10]

# ------------------------------------------------------------------------------
# hdf5

const _hdf5_attr = merge_with_base_supported(
    [
        :annotations,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        :foreground_color_grid,
        :legend_foreground_color,
        :foreground_color_title,
        :foreground_color_axis,
        :foreground_color_border,
        :foreground_color_guide,
        :foreground_color_text,
        :label,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :bins,
        :bar_width,
        :bar_edges,
        :bar_position,
        :title,
        :titlelocation,
        :titlefont,
        :window_title,
        :guide,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :rotation,
        :tickfont,
        :guidefont,
        :legendfont,
        :grid,
        :legend,
        :colorbar,
        :marker_z,
        :line_z,
        :fill_z,
        :levels,
        :ribbon,
        :quiver,
        :arrow,
        :orientation,
        :overwrite_figure,
        :polar,
        :normalize,
        :weights,
        :contours,
        :aspect_ratio,
        :clims,
        :inset_subplots,
        :dpi,
        :colorbar_title,
    ]
)
const _hdf5_seriestype = [
    :path,
    :steppre,
    :stepmid,
    :steppost,
    :shape,
    :straightline,
    :scatter,
    :hexbin,
    :heatmap,
    :image,
    :contour,
    :contour3d,
    :path3d,
    :scatter3d,
    :surface,
    :wireframe,
]
const _hdf5_style = [:auto, :solid, :dash, :dot, :dashdot]
const _hdf5_marker = vcat(_allMarkers, :pixel)
const _hdf5_scale = [:identity, :ln, :log2, :log10]

# Additional constants
# Dict has problems using "Types" as keys.  Initialize in "_initialize_backend":
const HDF5PLOT_MAP_STR2TELEM = Dict{String, Type}()
const HDF5PLOT_MAP_TELEM2STR = Dict{Type, String}()

# Don't really like this global variable... Very hacky
mutable struct HDF5Plot_PlotRef
    ref::Union{Plot, Nothing}
end
const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing)

# ------------------------------------------------------------------------------
# inspectdr

const _inspectdr_attr = merge_with_base_supported(
    [
        :annotations,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        # :foreground_color_grid,
        :legend_foreground_color,
        :foreground_color_title,
        :foreground_color_axis,
        :foreground_color_border,
        :foreground_color_guide,
        :foreground_color_text,
        :label,
        :seriescolor,
        :seriesalpha,
        :line,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :markerstrokestyle, #Causes warning not to have it... what is this?
        :fillcolor,
        :fillalpha, #:fillrange,
        #    :bins, :bar_width, :bar_edges, :bar_position,
        :title,
        :titlelocation,
        :window_title,
        :guide,
        :widen,
        :lims,
        :scale, #:ticks, :flip, :rotation,
        :titlefontfamily,
        :titlefontsize,
        :titlefontcolor,
        :legend_font_family,
        :legend_font_pointsize,
        :legend_font_color,
        :tickfontfamily,
        :tickfontsize,
        :tickfontcolor,
        :guidefontfamily,
        :guidefontsize,
        :guidefontcolor,
        :grid,
        :legend_position, #:colorbar,
        #    :marker_z,
        #    :line_z,
        #    :levels,
        #   :ribbon, :quiver, :arrow,
        #    :orientation,
        :overwrite_figure,
        :polar,
        #    :normalize, :weights,
        #    :contours, :aspect_ratio,
        #    :clims,
        #    :inset_subplots,
        :dpi,
        #    :colorbar_title,
    ]
)
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
const _inspectdr_seriestype = [
    :path,
    :scatter,
    :shape,
    :straightline, #, :steppre, :stepmid, :steppost
]
#see: _allMarkers, _shape_keys
const _inspectdr_marker = Symbol[
    :none,
    :auto,
    :circle,
    :rect,
    :diamond,
    :cross,
    :xcross,
    :utriangle,
    :dtriangle,
    :rtriangle,
    :ltriangle,
    :pentagon,
    :hexagon,
    :heptagon,
    :octagon,
    :star4,
    :star5,
    :star6,
    :star7,
    :star8,
    :vline,
    :hline,
    :+,
    :x,
]

const _inspectdr_scale = [:identity, :ln, :log2, :log10]
# ------------------------------------------------------------------------------
# pgfplotsx

_pre_imports(::PGFPlotsXBackend) = @eval Plots begin
    import LaTeXStrings: LaTeXString
    import UUIDs: uuid4
    import Latexify
    import Contour
    @require_backend PGFPlotsX
end

function _initialize_backend(pkg::PGFPlotsXBackend)
    _pre_imports(pkg)
    @eval Main begin
        import PGFPlotsX
        export PGFPlotsX
        $(_check_compat)(PGFPlotsX)
    end
    _post_imports(pkg)
    return _runtime_init(pkg)
end

const _pgfplotsx_attr = merge_with_base_supported(
    [
        :annotations,
        :annotationrotation,
        :annotationhalign,
        :annotationfontsize,
        :annotationfontfamily,
        :annotationcolor,
        :legend_background_color,
        :background_color_inside,
        :background_color_outside,
        :legend_foreground_color,
        :foreground_color_grid,
        :foreground_color_axis,
        :foreground_color_text,
        :foreground_color_border,
        :label,
        :seriescolor,
        :seriesalpha,
        :line,
        :linecolor,
        :linestyle,
        :linewidth,
        :linealpha,
        :markershape,
        :markercolor,
        :markersize,
        :markeralpha,
        :markerstrokewidth,
        :markerstrokecolor,
        :markerstrokealpha,
        :fillrange,
        :fillcolor,
        :fillalpha,
        :bins,
        :layout,
        :title,
        :window_title,
        :guide,
        :widen,
        :lims,
        :ticks,
        :scale,
        :flip,
        :titlefontfamily,
        :titlefontsize,
        :titlefonthalign,
        :titlefontvalign,
        :titlefontrotation,
        :titlefontcolor,
        :legend_font_family,
        :legend_font_pointsize,
        :legend_font_halign,
        :legend_font_valign,
        :legend_font_rotation,
        :legend_font_color,
        :tickfontfamily,
        :tickfontsize,
        :tickfonthalign,
        :tickfontvalign,
        :tickfontrotation,
        :tickfontcolor,
        :guidefontfamily,
        :guidefontsize,
        :guidefonthalign,
        :guidefontvalign,
        :guidefontrotation,
        :guidefontcolor,
        :grid,
        :gridalpha,
        :gridstyle,
        :gridlinewidth,
        :legend_position,
        :legend_title,
        :colorbar,
        :colorbar_title,
        :colorbar_titlefontsize,
        :colorbar_titlefontcolor,
        :colorbar_titlefontrotation,
        :colorbar_entry,
        :fill,
        :fill_z,
        :line_z,
        :marker_z,
        :levels,
        :legend_column,
        :legend_title,
        :legend_title_font_color,
        :legend_title_font_pointsize,
        :ribbon,
        :quiver,
        :orientation,
        :overwrite_figure,
        :polar,
        :plot_title,
        :plot_titlefontcolor,
        :plot_titlefontrotation,
        :plot_titlefontsize,
        :plot_titlevspan,
        :aspect_ratio,
        :normalize,
        :weights,
        :inset_subplots,
        :bar_width,
        :arrow,
        :framestyle,
        :tick_direction,
        :thickness_scaling,
        :camera,
        :contour_labels,
        :connections,
        :thickness_scaling,
        :axis,
        :draw_arrow,
        :minorgrid,
        :minorgridalpha,
        :minorgridlinewidth,
        :minorgridstyle,
        :minorticks,
        :mirror,
        :rotation,
        :showaxis,
        :tickfontrotation,
        :draw_arrow,
    ]
)
const _pgfplotsx_seriestype = [
    :path,
    :scatter,
    :straightline,
    :path3d,
    :scatter3d,
    :surface,
    :wireframe,
    :heatmap,
    :mesh3d,
    :contour,
    :contour3d,
    :quiver,
    :shape,
    :steppre,
    :stepmid,
    :steppost,
    :ysticks,
    :xsticks,
]
const _pgfplotsx_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _pgfplotsx_marker = [
    :none,
    :auto,
    :circle,
    :rect,
    :diamond,
    :utriangle,
    :dtriangle,
    :ltriangle,
    :rtriangle,
    :cross,
    :xcross,
    :x,
    :+,
    :star5,
    :star6,
    :pentagon,
    :hline,
    :vline,
]
const _pgfplotsx_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::PGFPlotsXBackend, shape::Shape) = true

# additional constants
const _pgfplotsx_series_ids = KW()
