text:
include("EditorUtils.jl")
include("Actions.jl")
include("EditorTab.jl")

"     Editor <: GtkNotebook
"
mutable struct Editor <: GtkNotebook

    handle::Ptr{Gtk.GObject}
    sourcemap::Gtk.GtkWidget
    main_window::MainWindow
    search_window::SearchWindow

    function Editor(main_window::MainWindow)
        ntbook = GtkNotebook()
        set_gtk_property!(ntbook, :scrollable, true)
        set_gtk_property!(ntbook, :enable_popup, false)

        sourcemap = GtkSourceMap()
        t = new(ntbook.handle, sourcemap, main_window)

        Gtk.gobject_move_ref(t, ntbook)
    end
end

get_current_tab() = get_tab(editor, index(editor))# remove this ?

function save_project(editor::Editor)
    save(editor.main_window.project)
end

function ntbook_switch_page_cb(widgetptr::Ptr, pageptr::Ptr, pagenum::Int32, user_data)

    editor = convert(GtkNotebook, widgetptr)
    page = convert(Gtk.GtkWidget, pageptr)

    if typeof(page) == EditorTab
        set_view(editor.sourcemap, page.view)
        #visible(editor.sourcemap, opt("Editor", "show_source_map"))
    end
    nothing
end

global mousepos = zeros(Int, 2)
global mousepos_root = zeros(Int, 2)

#I need this to get the mouse position when we use the keyboard
function ntbook_motion_notify_event_cb(widget::Ptr,  eventptr::Ptr, user_data)
    event = convert(Gtk.GdkEvent, eventptr)

    mousepos[1] = round(Int, event.x)
    mousepos[2] = round(Int, event.y)
    mousepos_root[1] = round(Int, event.x_root)
    mousepos_root[2] = round(Int, event.y_root)
    return PROPAGATE
end

#this could be in the constructor but it doesn't work for some reason
function init!(editor::Editor, search_window::SearchWindow)
    signal_connect(ntbook_switch_page_cb, editor, "switch-page", Nothing, (Ptr{Gtk.GtkWidget}, Int32), false)
    signal_connect(ntbook_motion_notify_event_cb, editor, "motion-notify-event", Cint, (Ptr{Gtk.GdkEvent}, ), false)
    editor.search_window = search_window
end

function set_dir_to_file_path_cb(btn::Ptr, tab)
    editor = parent(tab)::Editor
    cd(dirname(tab.filename))
    on_path_change(editor.main_window)
    return nothing
end

function copy_path_cb(btn::Ptr, tab)
    clipboard(tab.filename)
    return nothing
end

function close_tab(editor::Editor, idx::Int)
    if editor[idx].modified
        ok = ask_dialog("Unsaved changed, close anyway?", editor.main_window)
        !ok && return
    end
    splice!(editor, idx)
    index(editor, max(idx-1, 0))
end
close_tab(editor::Editor) = close_tab(editor, index(editor))

function close_tab_cb(btn::Ptr, tab)
    editor = parent(tab)::Editor
    close_tab(editor, Gtk.pagenumber(editor, tab)+1)
    save_project(editor)
    return nothing
end

function close_other_tabs_cb(btn::Ptr, tab)
    editor = parent(tab)::Editor
    while Gtk.GAccessor.n_pages(editor) > 1
        if get_tab(editor, 1) == tab
            splice!(editor, 2)
        else
            splice!(editor, 1)
        end
    end
    save_project(editor)
    return nothing
end
function close_tabs_right_cb(btn::Ptr, tab)
    editor = parent(tab)::Editor
    idx = Gtk.pagenumber(editor, tab) +1
    while (Gtk.GAccessor.n_pages(editor) > idx)
        close_tab(editor, idx+1)
    end
    save_project(editor)
    return nothing
end
function close_all_tabs_cb(btn::Ptr, tab)
    editor = parent(tab)::Editor
    while (Gtk.GAccessor.n_pages(editor) > 0)
        close_tab(editor, 1)
    end
    save_project(editor)
    return nothing
end

function find_filename(model_ptr, path_ptr, iter_ptr, data_ptr)
    model = convert(Gtk.GtkTreeStoreLeaf, model_ptr)
    iter  = unsafe_load(iter_ptr)
    path  = Gtk.GtkTreePath(path_ptr)
    data  = unsafe_pointer_to_objref(data_ptr)

    if model[iter, 3] == data[1]
        data[2][1] = true
        data[2][2] = path
        return Cint(1)
    else
        return Cint(0)
    end
end

@guarded nothing function reveal_in_tree_view(btn::Ptr, tab)
    data = [false, Gtk.GtkTreePath()]
    foreach(GtkTreeModel(filespanel.list), find_filename, [tab.filename, data])
    if data[1]
        GAccessor.cursor_on_cell(filespanel.tree_view, data[2])
    end
    return nothing
end

function switch_tab_cb(btn::Ptr, user_data)
    idx, editor = user_data
    index(editor, idx)
    return nothing
end

function create_tab_menu(container, tab)

    editor = parent(tab)::Editor

    menu = buildmenu([
            MenuItem("Close Tab", close_tab_cb),
            MenuItem("Close Others Tabs", close_other_tabs_cb),
            MenuItem("Close Tabs to the Right", close_tabs_right_cb),
            MenuItem("Close All Tabs", close_all_tabs_cb),
            GtkSeparatorMenuItem,
            MenuItem("Copy Path", copy_path_cb),
            MenuItem("Reveal in Tree View", reveal_in_tree_view),
            MenuItem("Set Directory to File Path", set_dir_to_file_path_cb),
            GtkSeparatorMenuItem
            ],
            tab
        )

    #show all open tabs
    for i=1:length(editor)
        if typeof(editor[i]) == EditorTab
            s = GtkMenuItem(basename(editor[i].filename))
            push!(menu, s)
            signal_connect(switch_tab_cb, s, "activate", Nothing, (), false, (i, editor))
        end
    end

    showall(menu)
    return menu
end

@guarded (PROPAGATE) function tab_button_press_event_cb(event_box_ptr::Ptr, eventptr::Ptr, tab)
    event_box = convert(GtkEventBox, event_box_ptr)
    event     = convert(Gtk.GdkEvent, eventptr)

    if rightclick(event)
        popup(create_tab_menu(event_box, tab), event)
        return INTERRUPT
    else
        return PROPAGATE
    end
    return PROPAGATE
end

function get_tab_widget(tab, filename)

    layout = GtkBox(:h)
    event_box = GtkEventBox()
    push!(event_box, layout)
    signal_connect(tab_button_press_event_cb, event_box, "button-press-event", Cint, (Ptr{Gtk.GdkEvent}, ), false, tab)
    lbl = GtkLabel(basename(filename))
    set_gtk_property!(lbl, :name, "filename_label")
    btn = GtkButton("X")

    style_css(btn, "button {
          padding: 0px;
          margin: 0px;
          margin-left:1px;
          }")
    set_gtk_property!(btn, :relief, 2)

    signal_connect(close_tab_cb, btn, "clicked", Nothing, (), false, tab)

    push!(layout, lbl)
    push!(layout, btn)
    showall(event_box)
    return (event_box, lbl)
end

import Base.open
function open(t::EditorTab, filename::AbstractString)
    try
        if isfile(filename)
            f = Base.open(filename)
            text = read(f, String)
            set_text!(t, text)
            t.modified = false
            modified(t, t.modified)
        else
            f = Base.open(filename, "w")
            text = ""
            set_text!(t, text)
            t.modified = true
            modified(t, t.modified)
        end
        t.texthash = hash(text)
        t.filename = filename
        reset_undomanager(t.buffer)#otherwise we can undo loading the file...
        close(f)
    catch err
        @warn err
    end
    update!(project)
end

function add_tab(filename::AbstractString, editor::Editor)

    t = EditorTab(filename, editor.main_window);
    t.scroll_target = 0.
    t.scroll_target_line = 0

    idx = index(editor)+1
    (event_box, t.label) = get_tab_widget(t, filename == "" ? "Untitled" : filename)
    insert!(editor, idx, t, event_box)
    showall(editor)
    index(editor, idx)

    Gtk.GAccessor.tooltip_text(t.label, t.filename)#t.filename is the full path

#    Gtk.create_tag(t.buffer, "debug1", font="Normal $fontsize", background="green")
#    Gtk.create_tag(t.buffer, "debug2", font="Normal $fontsize", background="blue")
    style_css(t.view, style_provider(editor.main_window))

    #we need to use the view here to capture all the keystrokes
    signal_connect(editor_tab_key_press_cb, t.view,   "key-press-event", Cint, (Ptr{Gtk.GdkEvent}, ), false, t)
    #signal_connect(editor_key_release_cb,   t.view,   "key-release-event", Cint, (Ptr{Gtk.GdkEvent}, ), false, editor)
    signal_connect(tab_button_press_cb,     t.view,   "button-press-event", Cint, (Ptr{Gtk.GdkEvent}, ), false, editor)
    signal_connect(tab_buffer_changed_cb,   t.buffer, "changed", Nothing, (), false, t)
    signal_connect(tab_adj_changed_cb, t.view.vadjustment[GtkAdjustment] , "changed", Nothing, (), false, t)

    return t
end
add_tab(editor::Editor) = add_tab("", editor)

function openfile_dialog(editor::Editor)
    f = open_dialog("Pick a file", main_window, ("*.jl", "*.md")) #TODO global
    if isfile(f)
        open_in_new_tab(f, editor)
    end
end

function load_tabs(editor::Editor, project::Project)

    #project get modified later
    files = project.files
    scroll_position = project.scroll_position
    ntbook_idx = project.ntbook_idx

    for i = 1:length(files)
        t = open_in_new_tab(files[i], editor)
        t.scroll_target = scroll_position[i]
    end

    if length(editor)==0
        open_in_new_tab(joinpath(HOMEDIR, "..", "README.md"), editor)
    elseif ntbook_idx <= length(editor)
        index(editor, ntbook_idx)
    end
    t = current_tab(editor)
    set_view(editor.sourcemap, t.view)
end


text:
using StanModels, CSV

df = CSV.read(joinpath(@__DIR__, "..", "..", "data", "WaffleDivorce.csv"), delim=';')
mean_ma = mean(df[!, :Marriage])
df[!, :Marriage_s] = convert(Vector{Float64},
  (df[!, :Marriage]) .- mean_ma)/std(df[!, :Marriage]);

mean_mam = mean(df[!, :MedianAgeMarriage])
df[!, :MedianAgeMarriage_s] = convert(Vector{Float64},
  (df[!, :MedianAgeMarriage]) .- mean_mam)/std(df[!, :MedianAgeMarriage]);

df[1:6, [1, 7, 14, 15]]

rethinking_data = "
    Location Divorce  Marriage.s MedianAgeMarriage.s
1    Alabama    12.7  0.02264406          -0.6062895
2     Alaska    12.5  1.54980162          -0.6866993
3    Arizona    10.8  0.04897436          -0.2042408
4   Arkansas    13.5  1.65512283          -1.4103870
5 California     8.0 -0.26698927           0.5998567
"

# Define the Stan language model

m5_3s = "
data {
  int N;
  vector[N] divorce;
  vector[N] marriage_z;
  vector[N] median_age_z;
}
parameters {
  real a;
  real bA;
  real bM;
  real<lower=0> sigma;
}
model {
  vector[N] mu = a + median_age_z * bA + marriage_z * bM;
  sigma ~ uniform( 0 , 10 );
  bA ~ normal( 0 , 1 );
  bM ~ normal( 0 , 1 );
  a ~ normal( 10 , 10 );
  divorce ~ normal( mu , sigma );
}
";

# Define the Stanmodel and set the output format to :mcmcchains.

sm = SampleModel("m5.3s", m5_3s);

# Input data for cmdstan

m5_3_data = Dict("N" => size(df, 1), "divorce" => df[!, :Divorce],
    "marriage_z" => df[!, :Marriage_s], "median_age_z" => df[!, :MedianAgeMarriage_s]);

# Sample using cmdstan

(sample_file, log_file) = stan_sample(sm, data=m5_3_data);

# Rethinking results

rethinking_results = "
       mean   sd  5.5% 94.5% n_eff Rhat
a      9.69 0.22  9.34 10.03  1313    1
bR    -0.12 0.30 -0.60  0.35   932    1
bA    -1.13 0.29 -1.56 -0.67   994    1
sigma  1.53 0.16  1.28  1.80  1121    1
"

if !(sample_file == nothing)
  chn = read_samples(sm)
  describe(chn)
end

ext: jl


text:
export norm!

function norm!(a::SparseMatrix{T}) where {T}
    local zero = T(0)
    local one = T(1)
    local norm = ones(T, a.column_count)

    for j = 1:a.column_count
        if a.columns[j] == a.columns[j+1]
            error("matrix should be positive defined")
        end

        local k = a.columns[j]

        if a.values[k] == zero
            a.values[k] = one
        end

        norm[j] = one / sqrt(a.values[k])

        a.values[k] = one
    end

    for j = 1:a.column_count
        local column_norm = norm[j]

        for i = (a.columns[j]+1):(a.columns[j+1]-1)
            local row_norm = norm[a.columns_rows[i]]
            a.values[i] *= column_norm * row_norm
        end
    end

    return norm
end


text:
using PyPlotly
using PyCall

using Test

py"""
import inspect
def create_symbols(mod, target):
    symbols = []
    for attr in mod.__all__:
        if attr.startswith("_"):
            continue
        satisfy = eval(f"inspect.{target}(mod.{attr})")
        if satisfy:
            symbols.append(attr)
    return symbols
"""

create_symbols(mod::PyObject, target) = Symbol.(py"create_symbols"(mod, target))

@testset "constants" begin
    @test PyPlotly.GraphObjects.go_classes ==
          create_symbols(plotly.graph_objects, "isclass")
    @test PyPlotly.Express.px_functions == create_symbols(plotly.express, "isfunction")
    @test PyPlotly.Express.px_classes == create_symbols(plotly.express, "isclass")
end

@testset "PyPlotly.jl" begin
    # Write your tests here.
end


text:
export iauRx
"""
Rotate an r-matrix about the x-axis.

This function is part of the International Astronomical Union's
SOFA (Standards Of Fundamental Astronomy) software collection.

Status:  vector/matrix support function.

Given:
   phi    double          angle (radians)

Given and returned:
   r      double[3][3]    r-matrix, rotated

Notes:

   1. Calling this function with positive phi incorporates in the
   supplied r-matrix r an additional rotation, about the x-axis,
   anticlockwise as seen looking towards the origin from positive x.

   2. The additional rotation can be represented by this matrix:

      (  1        0            0      )
      (                               )
      (  0   + cos(phi)   + sin(phi)  )
      (                               )
      (  0   - sin(phi)   + cos(phi)  )

This revision:  2013 June 18

SOFA release 2018-01-30

Copyright (C) 2018 IAU SOFA Board.  See notes at end.
"""

# void iauRx(double phi, double r[3][3])
function iauRx(phi::Real, r::AbstractMatrix{<:Real})

   # Transpose matrix upfront
   r = Array{Float64, 2}(r') # Transpose input up front

   ccall((:iauRx, libsofa_c), Cvoid,
           (Cdouble, Ptr{Cdouble}),
           phi, pointer(r))

   return convert(Array{Float64, 2}, r')
end