# https://github.com/sisl/PGFPlots.jl

supportedArgs(::PGFPlotsBackend) = [
    # :annotation,
    # :axis,
     :background_color,
    # :color_palette,
    # :fillrange,
     :fillcolor,
     :fillalpha,
    # :foreground_color,
    # :group,
    # :label,
    # :layout,
    # :legend,
     :seriescolor, :seriesalpha,
     :linecolor,
     :linestyle,
     :linetype,
     :linewidth,
     :linealpha,
     :markershape,
     :markercolor,
     :markersize,
     :markeralpha,
    # :markerstrokewidth,
     :markerstrokecolor,
     :markerstrokestyle,
    # :n,
    # :bins,
    # :nc,
    # :nr,
    # :pos,
    # :smooth,
    # :show,
    # :size,
     :title,
    # :windowtitle,
     :x,
     :xlabel,
     :xlims,
    # :xticks,
     :y,
     :ylabel,
     :ylims,
    # :yrightlabel,
    # :yticks,
     :xscale,
     :yscale,
     :xflip,
     :yflip,
     :z,
     :zscale,
    # :tickfont,
    # :guidefont,
    # :legendfont,
     :grid,
    # :surface
    # :levels,
  ]
supportedAxes(::PGFPlotsBackend) = [:auto, :left]
supportedTypes(::PGFPlotsBackend) = [:path, :path3d, :scatter, :line, :steppre, :stepmid, :steppost, :hist, :bar, :hist2d, :sticks, :ysticks, :xsticks, :contour] #  :hexbin, :hline, :vline,]
supportedStyles(::PGFPlotsBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supportedMarkers(::PGFPlotsBackend) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape)
supportedScales(::PGFPlotsBackend) = [:identity, :log, :ln, :log2, :log10] # :asinh, :sqrt]
subplotSupported(::PGFPlotsBackend) = false


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


function _initialize_backend(::PGFPlotsBackend; kw...)
  @eval begin
    import PGFPlots
    export PGFPlots
    # TODO: other initialization that needs to be eval-ed
  end
  # TODO: other initialization
end
const _pgfplots_linestyles = KW(
    :solid => "solid",
    :dash => "dashed",
    :dot => "dotted",
    :dashdot => "dashdotted",
    :dashdotdot => "dashdotdotted"
)

const _pgfplots_markers = KW(
    :none => "mark = none,",
    :cross => "mark = +,",
    :xcross => "mark = x,",
    :utriangle => "mark = triangle*,",
    :dtriangle => "mark = triangle*,",
    :ellipse => "mark = o*,",
    :rect => "mark = square*,",
    :star5 => "mark = star,",
    :star6 => "mark = asterisk,",
    :diamond => "mark = diamond*,",
    :pentagon => "mark = pentagon*,"
)

function _pgfplots_get_color(kwargs, symb)
    c = typeof(kwargs[symb]) == Symbol ? convertColor(kwargs[symb]) : kwargs[symb].c
    "{rgb,1:red,$(float(c.r));green,$(float(c.g));blue,$(float(c.b))}"
end

function _pgfplots_get_linestyle!(kwargs, plt)
    ls = plt[:linestyle]
    if haskey(_pgfplots_linestyles, ls)
        kwargs[:style] *= _pgfplots_linestyles[ls]*","
    end

    kwargs[:style] *= "line width = $(plt[:linewidth]) pt"*","
end


function _pgfplots_get_marker!(kwargs, plt)
    # Control marker shape
    mark = plt[:markershape]
    kwargs[:style] *= _pgfplots_markers[mark]

    # Control marker size
    kwargs[:style] *= "mark size = $(plt[:markersize]/2),"

    # Control marker colors and alphas
    α = plt[:markeralpha] == nothing ? 1.0 : plt[:markeralpha]
    kwargs[:style] *= "mark options = {color=$(_pgfplots_get_color(plt, :markerstrokecolor)),"
    kwargs[:style] *= mark == :dtriangle ? "rotate=180," : ""
    kwargs[:style] *= "fill=$(_pgfplots_get_color(plt, :markercolor)),"
    kwargs[:style] *= "fill opacity = $α,"
    markstrokestyle = plt[:markerstrokestyle]
    if haskey(_pgfplots_linestyles, markstrokestyle)
        kwargs[:style] *= _pgfplots_linestyles[markstrokestyle]
    end
    kwargs[:style] *= "},"
end

function _pgfplots_get_series_color!(kwargs, plt)
    α = plt[:seriesalpha] == nothing ? 1.0 : plt[:seriesalpha]
    kwargs[:style] *= "color=$(_pgfplots_get_color(plt, :seriescolor)),"
    kwargs[:style] *= "draw opacity = $α,"
end

function _pgfplots_get_line_color!(kwargs, plt)
    α = plt[:linealpha] == nothing ? 1.0 : plt[:linealpha]
    kwargs[:style] *= "color=$(_pgfplots_get_color(plt, :linecolor)),"
    kwargs[:style] *= "draw opacity = $α,"
end

function _pgfplots_get_fill_color!(kwargs, plt)
    α = plt[:fillalpha] == nothing ? 1.0 : plt[:fillalpha]
    kwargs[:style] *= "fill=$(_pgfplots_get_color(plt, :fillcolor)),"
    kwargs[:style] *= "fill opacity = $α,"
end

function _pgfplots_get_plot_kwargs(plt)
    kwargs = KW()
    kwargs[:style] = ""
    _pgfplots_get_linestyle!(kwargs, plt)
    _pgfplots_get_marker!(kwargs, plt)
    _pgfplots_get_series_color!(kwargs, plt)
    kwargs
end

function _pgfplots_axis(plt_series)
    line_type = plt_series[:linetype]
    plt_kwargs = _pgfplots_get_plot_kwargs(plt_series)
        if line_type == :path
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :path3d
        PGFPlots.Linear3(plt_series[:x], plt_series[:y], plt_series[:z]; plt_kwargs...)
    elseif line_type == :scatter
        PGFPlots.Scatter(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :steppre
        plt_kwargs[:style] *= "const plot mark right,"
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :stepmid
        plt_kwargs[:style] *= "const plot mark mid,"
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :steppost
        plt_kwargs[:style] *= "const plot,"
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :hist
        #TODO patch this in PGFPlots.jl instead; the problem is that PGFPlots will
        # save _all_ data points in the figure which can be quite heavy
        plt_hist = hist(plt_series[:y])
        plt_kwargs[:style] *= "ybar interval,"
        _pgfplots_get_line_color!(plt_kwargs, plt_series)
        _pgfplots_get_fill_color!(plt_kwargs, plt_series)
        PGFPlots.Linear(plt_hist[1][1:end-1]+plt_hist[1].step/2, plt_hist[2]; plt_kwargs...)
    elseif line_type == :hist2d
        PGFPlots.Histogram2(plt_series[:x], plt_series[:y])
    elseif line_type == :bar
        plt_kwargs[:style] *= "ybar,"
        _pgfplots_get_line_color!(plt_kwargs, plt_series)
        _pgfplots_get_fill_color!(plt_kwargs, plt_series)
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :sticks || line_type == :ysticks
        plt_kwargs[:style] *= "ycomb"
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :xsticks
        plt_kwargs[:style] *= "xcomb"
        PGFPlots.Linear(plt_series[:x], plt_series[:y]; plt_kwargs...)
    elseif line_type == :contour
        PGFPlots.Contour(plt_series[:z].surf, plt_series[:x], plt_series[:y])
    end
end

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

function _create_plot(pkg::PGFPlotsBackend, d::KW)
  # TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
  # TODO: initialize the plot... title, xlabel, bgcolor, etc
  Plot(nothing, pkg, 0, d, KW[])
end


function _add_series(::PGFPlotsBackend, plt::Plot, d::KW)
  # TODO: add one series to the underlying package
  push!(plt.seriesargs, d)
  plt
end

function _add_annotations{X,Y,V}(plt::Plot{PGFPlotsBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
  # set or add to the annotation_list
  if haskey(plt.plotargs, :annotation_list)
    append!(plt.plotargs[:annotation_list], anns)
  else
    plt.plotargs[:annotation_list] = anns
  end
end

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

function _before_update_plot(plt::Plot{PGFPlotsBackend})
end

# TODO: override this to update plot items (title, xlabel, etc) after creation
function _update_plot(plt::Plot{PGFPlotsBackend}, d::KW)
end

function _update_plot_pos_size(plt::AbstractPlot{PGFPlotsBackend}, d::KW)
end

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

# accessors for x/y data

# function getxy(plt::Plot{PGFPlotsBackend}, i::Int)
#   d = plt.seriesargs[i]
#   d[:x], d[:y]
# end
#
# function setxy!{X,Y}(plt::Plot{PGFPlotsBackend}, xy::Tuple{X,Y}, i::Integer)
#   d = plt.seriesargs[i]
#   d[:x], d[:y] = xy
#   plt
# end

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

function _create_subplot(subplt::Subplot{PGFPlotsBackend}, isbefore::Bool)
  # TODO: build the underlying Subplot object.  this is where you might layout the panes within a GUI window, for example
  true
end

function _expand_limits(lims, plt::Plot{PGFPlotsBackend}, isx::Bool)
  # TODO: call expand limits for each plot data
end

function _remove_axis(plt::Plot{PGFPlotsBackend}, isx::Bool)
  # TODO: if plot is inner subplot, might need to remove ticks or axis labels
end

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

function _pgfplots_get_axis_kwargs(d)
    axisargs = KW()
    for arg in (:xlabel, :ylabel, :zlabel, :title)
        axisargs[arg] = d[arg]
    end
    axisargs[:style] = ""
    axisargs[:style] *= d[:xflip] == true ? "x dir=reverse," : ""
    axisargs[:style] *= d[:yflip] == true ? "y dir=reverse," : ""
    if d[:xscale] in (:log, :log2, :ln, :log10)
        axisargs[:xmode] = "log"
        if d[:xscale] == :log2
            axisargs[:style] *= "log basis x=2,"
        elseif d[:xscale] in (:log, :log10)
            axisargs[:style] *= "log basis x=10,"
        end
    end
    if d[:yscale] in (:log, :log2, :ln, :log10)
        axisargs[:ymode] = "log"
        if d[:yscale] == :log2
            axisargs[:style] *= "log basis y=2,"
        elseif d[:yscale] in (:log, :log10)
            axisargs[:style] *= "log basis x=10,"
        end
    end
    if d[:zscale] in (:log, :log2, :ln, :log10)
        axisargs[:zmode] = "log"
        if d[:zscale] == :log2
            axisargs[:style] *= "log basis z=2,"
        elseif d[:zscale] in (:log, :log10)
            axisargs[:style] *= "log basis x=10,"
        end
    end

    # Control background color
    axisargs[:style] *= "axis background/.style={fill=$(_pgfplots_get_color(d, :background_color))},"
    # Control x/y-limits
    if d[:xlims] !== :auto
        axisargs[:xmin] = d[:xlims][1]
        axisargs[:xmax] = d[:xlims][2]
    end
    if d[:ylims] !== :auto
        axisargs[:ymin] = d[:ylims][1]
        axisargs[:ymax] = d[:ylims][2]
    end
    if d[:grid] == true
        axisargs[:style] *= "grid = major"
    elseif d[:grid] == false

    end

    axisargs
end

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

#################  This is the important method to implement!!! #################
function _make_pgf_plot(plt::Plot{PGFPlotsBackend})
    os = [_pgfplots_axis(plt_series) for plt_series in plt.seriesargs]
    axisargs  =_pgfplots_get_axis_kwargs(plt.plotargs)
    plt.o = PGFPlots.Axis([os...]; axisargs...)
end

function Base.writemime(io::IO, mime::MIME"image/svg+xml", plt::AbstractPlot{PGFPlotsBackend})
  plt.o = _make_pgf_plot(plt)
  writemime(io, mime, plt.o)
end

# function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot{PGFPlotsBackend})
# end

function Base.display(::PlotsDisplay, plt::AbstractPlot{PGFPlotsBackend})
  plt.o = _make_pgf_plot(plt)
  display(plt.o)
end

# function Base.display(::PlotsDisplay, plt::Subplot{PGFPlotsBackend})
#   # TODO: display/show the subplot
# end
