require "CLRPackage"
require "ilua"
require "CLRForm"
import "System.Windows.Forms"
import "System.Drawing"
import "System.IO"

import "TextBox.dll"

local ferr = io.stderr --debug
local append = table.insert

-- it appears necessary to force a delayed evaluation, for which we use a timer....
local timer = Timer()
timer.Interval = 10
local callback

timer.Tick:Add(function()
    timer:Stop()
    if not pcall(callback) then
        ferr:write 'callback hosed\n'
    end
end)

local function call_later (fun)
    callback = fun
    timer:Start()
end

local function readfile (file)
    local f = io.open(file,'r')
    if not f then return end
    local res = f:read("*a")
    f:close()
    return res
end

local function writefile (file,s)
    local f = io.open(file,'w')
    if not f then return end
    f:write(s)
    f:close()
    return true
end

function current_line (pane)
    return pane:GetLineFromCharIndex(pane.SelectionStart)
end

-- a useful function for selecting lines in Rich text boxes; if lno is not specified,
-- then use the current line
function select_line (pane,lno)
    if not lno then -- current line
        lno = current_line(pane)
    end
    local pos = pane.SelectionStart
    local start = pane:GetFirstCharIndexFromLine(lno)
    pane:Select(start,pos - start + 1)
end

local lines = {}
local list = ListBox()
local no_name = true
local this_dir = Environment.CurrentDirectory
local user_dir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
local session_dir = user_dir..'/'..'li-session'

if Directory.Exists(session_dir) then
    local files = Directory.GetFiles(session_dir,"*.lua")
    for i = 1,files.Length do
        list.Items:Add(Path.GetFileNameWithoutExtension(files[i-1]))
    end
else
    Directory.CreateDirectory(session_dir)
end

local function list_contains (name)
    for i = 1,list.Items.Count do
        if list.Items[i-1] == name then return true end
    end
end

local function add_to_list (name)
    list.Items:Add(name)
    list.SelectedItem = name
    current_name = name
end

local function session_file (name)
    if not name then return end
    return session_dir..'/'..name..'.lua'
end

local code = ConsoleTextBox() --RichTextBox()
code.Font = Font("Tahoma",10,FontStyle.Bold)
code.WordWrap = false

code:SetHandler(function(key)
    if key == Keys.Tab then
        code.SelectedText = "    "
        return true
    end
    return false
end)

local text = ConsoleTextBox() --RichTextBox()
text.Font = code.Font
text.WordWrap = false

-- please note that you must explicitly return false, since LuaInterface is
-- expecting a boolean return value!
text:SetHandler(function(key)
    if key == Keys.Up then
        get_history(true)
        return true
    elseif key == Keys.Down then
        get_history(false)
        return true
    end
    return false
end)

local function write (s)
    text:AppendText(s)
end

list.SelectedIndexChanged:Add(function()
    local file = session_file(list.SelectedItem)
    if not file or not File.Exists(file) then return end
    local txt = readfile(file)
    if not txt then
        ShowError ("Cannot open '"..file.."'")
        return
    end
    code.Text = txt
end)

local function load_lua_file (file)
    local oldFun = fun
    fun = function(file) end
    local res,err = pcall(dofile,file)
    if not res then -- we have an error!
        ShowError(err)
        print(err)
    end
    fun = oldFun
end

local function load_lua ()
    local dlg = OpenFileDialog()
    dlg.Filter = "Lua (*.lua)|*.lua"
    dlg.InitialDirectory = this_dir
    if dlg:ShowDialog() == DialogResult.OK then
        load_lua_file(dlg.FileName)
    end
end

local function save_session ()
    local dlg = SaveFileDialog()
    dlg.Filter = "Lua (*.lua)|*.lua"
    dlg.InitialDirectory = this_dir
    if dlg:ShowDialog() ~= DialogResult.OK then return end
    local f = io.open(dlg.FileName,"w")
    f:write(table.concat(lines,'\n'))
    f:close()
end

function clear_code ()
    code:Clear()
    no_name = true
end

function delete_list_item ()
    local path = session_file(list.SelectedItem)
    os.remove(path)
    list.Items:Remove(list.SelectedItem)
    clear_code()
end

function save_code ()
    local file
    if code.Lines.Length == 0 then return end
    if not no_name then
        file = list.SelectedItem
    else -- no name has been assigned, after clearing the code pane
        -- try make up an appropriate one!
        local firstline = code.Lines[0]
        local comment = firstline:match('%s*%-%-%s*(.*)')
        if comment then file = comment
        else file = "[current]" end
        no_name = false
    end
    local path = session_file(file)
    writefile(path,code.Text)
    if not list_contains(file) then
        add_to_list(file)
    else
        list.SelectedItem = file
    end
    return path
end

local function save_text ()
    local dlg = SaveFileDialog()
    dlg.Filter = "Text (*.txt)|*.txt"
    dlg.InitialDirectory = this_dir
    if dlg:ShowDialog() ~= DialogResult.OK then return end
    writefile(dlg.FileName,text.Text)
end

local function save_and_go ()
    local file = save_code()
    if not file then return end
    local res,err = pcall(dofile,file)
    --ferr:write(file,'\n')
    if not res then
        local i1,i2,line = err:find(':(%d+):')
        if i1 then
            print(err:sub(i2+1))
            write '\n> '
            code:Focus()
            select_line(code,tonumber(line)-1)
            return
        end
    end
    write '\n> '
    append(lines,'dofile[['..file..']]')
    text:Focus()
end

function fun (fn)
    if not fn then -- prompt for a function name
        fn = PromptForString("Lua Interface Console","Function name","")
        if not fn then	return end
    end
    if list_contains(fn) then
        ShowError("'"..fn.."' already exists. Pick another name")
        return
    end
    no_name = false
    local txt = "function "..fn.."( )\n\nend\n"
    code.Text = txt
    add_to_list(fn)
    code:Focus()
end

---------------------- Main Menu --------------------------------------
local menu = main_menu {
    "File",{
        "Load Lua(CtrlO)",load_lua,
        "Save Session(CtrlS)",save_session,
        "Save As Text",save_text,
        "E&xit(CtrlX)",function() os.exit(0) end,
    },
    "Run",{
        "Save and Go(F5)",save_and_go,
        "Create Function",function() fun() end,
        "Delete Item",delete_list_item,
        "Clear Code Pane",clear_code,
    },
    "History", {
        "Last(AltUpArrow)", function() get_history(true) end,
        "Previous(AltDownArrow)", function() get_history(false) end
    }
}

local function method (obj,fun)
    return function()
        fun(obj)
    end
end

local popup = popup_menu {
    "Copy",method(text,text.Copy),
    "Paste",method(text,text.Paste),
    "Cut",method(text,text.Cut),
}

------------ Managing Command History -----------
local help_idx = 1

function get_history (up)
    call_later(function()
        local delta
        -- awful hack, cancelling out the last up/down arrow movement!
        if up then
            delta = -1
        else
            delta = 1
        end
        key_sent = true
        call_later(function()
            help_idx = help_idx + delta
            local txt = lines[help_idx]
            if not txt then
                help_idx = help_idx - delta
                return
            end
            select_line(text)
            text.SelectedText = '> '..txt
        end)
    end)
end

------------ Special Key Handling ------------------
local lastLine = -1

text.KeyDown:Add(function(sender,args)
    if args.KeyCode == Keys.Enter then
        local lineNo = text:GetLineFromCharIndex(text.SelectionStart)
        if lineNo ~= lastLine then -- for some reason, happens twice!
            if lineNo >= text.Lines.Length then
                lineNo = text.Lines.Length - 1
                --ferr:write(lineNo,' ',text.Lines.Length,' goofed\n')
            end
            do
                local line = text.Lines[lineNo]
                line = line:gsub('^> ','')
                lastLine = lineNo
                call_later(function()
                    eval_lua(line)
                    append(lines,line)
                    help_idx = #lines + 1
                    write '> '
                end)
            end
        end
    end
end)

----------------------- Ouput Redirection ---------------------------------
function write_out (expand,...)
    local t = {...}
    local n = #t - 1
    for i = 1,n do
        write(tostring(t[i]))
        if expand then write '\t' end
    end
    write(tostring(t[n+1]))
end

function writer (...)
    write_out(false,...)
end

ilua.set_writer(writer)
function print (...)
    write_out(true,...)
    write '\r\n'
end

-------- Layout Controls ---------------------------------------------
local form = Form()
form.Menu = menu
form.Text = "LuaInterface GUI Prompt"
form.Size = Size(500,500)
form.Closing:Add(function()
    os.exit(0)
end)

local panel = Panel()
panel.Dock = DockStyle.Top

local hsplitter = Splitter()
hsplitter.Dock = DockStyle.Left
hsplitter.MinSize = 70

code.Dock = DockStyle.Fill
code.Height = 70

list.Dock = DockStyle.Left
list.Width = 70

panel.Controls:Add(code)
panel.Controls:Add(hsplitter)
panel.Controls:Add(list)

-- note the particular order!
local splitter = Splitter()
splitter.Dock = DockStyle.Top
splitter.MinSize = 70
splitter.MinExtra = 100
text.Dock = DockStyle.Fill
text.ContextMenu = popup
form.Controls:Add(text)
form.Controls:Add(splitter)
form.Controls:Add(panel)

-- stuff exported to the interactive console
gettype = luanet.import_type
app = {code=code,text=text, list=list, form=form}
function cd (path)
    if not path or #path == 0 then
        print(Directory.GetCurrentDirectory())
    else
        Directory.SetCurrentDirectory(path)
    end
end

write 'Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio\r\n'
write '> '

if arg[1] and File.Exists(arg[1]) then
    load_lua_file(arg[1])
end

text.WordWrap = true
console = text

form:ShowDialog()


