local info = {
    version   = 1.002,
    comment   = "file handler for textadept for context/metafun",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files",
}

local lexer   = require("scite-context-lexer")
local context = lexer.context

local char, format, gsub = string.char, string.format, string.gsub

-- What is _CHARSET doing ... I don't want any messing with conversion at all. Scite is
-- more clever with e.g. pdf. How can I show non ascii as escapes.

-- I'll add a few more keybindings. I mostly made this file for my own use and as a
-- fallback for SciTE as it runs on all os's. It might evolve ... who knows.

io.encodings = {
    "UTF-8",
    "ASCII",
    "UTF-16",
}

-- We need this for for instance pdf files (faster too):

local sevenbitascii   = { }
for i=127,255 do
    sevenbitascii[char(i)] = format("0x%02X",i)
end

local function setsevenbitascii(buffer)
    -- we cannot directly assign sevenbitascii to buffer
    local representation = buffer.representation
    for k, v in next, sevenbitascii do
        representation[k] = v
    end
end

-- Here we rebind keys. For this we need to load the alternative runner framework. I will
-- probably change the menu.

local oldrunner = textadept.run
local runner    = require("textadept-context-runner")

-- local function userunner(runner)
--     --
--     keys [OSX and 'mr' or                  'cr'  ] = runner.process or runner.run
--     keys [OSX and 'mR' or (GUI and 'cR' or 'cmr')] = runner.check   or runner.compile
--     keys [OSX and 'mB' or (GUI and 'cB' or 'cmb')] = runner.preview or runner.build
--     keys [OSX and 'mX' or (GUI and 'cX' or 'cmx')] = runner.quit    or runner.stop
--     --
--     textadept.menu.menubar [_L['_Tools']] [_L['_Run']]     [2] = runner.process or runner.run
--     textadept.menu.menubar [_L['_Tools']] [_L['_Compile']] [2] = runner.check   or runner.compile
--     textadept.menu.menubar [_L['_Tools']] [_L['Buil_d']]   [2] = runner.preview or runner.build
--     textadept.menu.menubar [_L['_Tools']] [_L['S_top']]    [2] = runner.quit    or runner.stop
--     --
-- end

-- I played a while with supporting both systems alongsize but getting the menus
-- synchronized is a real pain and semi-random. So, I decided to just drop the
-- old. And I don't want to implement a full variant now. Anyway, after that
-- conclusion I decided to replace not only the tools menu.

local SEPARATOR = { "" }
local newmenu   = { }
local newkeys   = { }

do

    newmenu.file = {

        title = '_File',

        { '_New',          buffer.new },
        { '_Open',         io.open_file },
        { 'Open _Recent',  io.open_recent_file },
        { 'Re_load',       io.reload_file },
        { '_Save',         io.save_file },
        { 'Save _As',      io.save_file_as },
        { 'Save All',      io.save_all_files },

        SEPARATOR,

        { '_Close',        io.close_buffer },
        { 'Close All',     io.close_all_buffers },

     -- SEPARATOR,

     -- { 'Loa_d Session', textadept.session.load },
     -- { 'Sav_e Session', textadept.session.save },

        SEPARATOR,

        { _L['_Quit'],     quit },

    }

    -- maybe just the same keys on all ... or duplicate on osx

    newkeys[OSX and 'mn'  or 'cn']  = buffer.new
    newkeys[OSX and 'mo'  or 'co']  = io.open_file
    newkeys[OSX and 'cmo' or 'cao'] = io.open_recent_file
    newkeys[OSX and 'ms'  or 'cs']  = io.save_file
    newkeys[OSX and 'mS'  or 'cS']  = io.save_file_as
    newkeys[OSX and 'mw'  or 'cw']  = io.close_buffer
    newkeys[OSX and 'mW'  or 'cW']  = io.close_all_buffers

end

do

    local function complete_symbol()
        textadept.editing.autocomplete(buffer:get_lexer(true))
    end

    newmenu.edit = {

        title = '_Edit',

        SEPARATOR,

        { '_Undo',                 buffer.undo },
        { '_Redo',                 buffer.redo },

        SEPARATOR,

        { 'Cu_t',                  buffer.cut },
        { '_Copy',                 buffer.copy },
        { '_Paste',                buffer.paste },
        { '_Delete',               buffer.clear },
        { 'Select _All',           buffer.select_all },

        SEPARATOR,

        { 'Duplicate _Line',       buffer.line_duplicate },

        SEPARATOR,

        { 'Toggle _Block Comment', textadept.editing.block_comment },
        { '_Upper Case Selection', buffer.upper_case },
        { '_Lower Case Selection', buffer.lower_case },

    }

    newkeys[OSX and 'mz' or 'cz']  = buffer.undo
    newkeys[OSX and 'my' or 'cy']  = buffer.redo
    newkeys[OSX and 'mx' or 'cx']  = buffer.cut
    newkeys[OSX and 'mc' or 'cc']  = buffer.copy
    newkeys[OSX and 'mv' or 'cv']  = buffer.paste
    newkeys[OSX and 'ma' or 'ca']  = buffer.select_all

    newkeys[OSX and 'mD' or 'cD']  = textadept.editing.select_word
    newkeys[OSX and 'mN' or 'cN']  = textadept.editing.select_line
    newkeys[OSX and 'mP' or 'cP']  = textadept.editing.select_paragraph

    newkeys['del']                 = buffer.clear
    newkeys['cy']                  = buffer.redo

    newkeys[OSX and 'md' or 'cd']  = buffer.line_duplicate
    newkeys[OSX and 'cu' or 'cau'] = buffer.upper_case
    newkeys[OSX and 'cU' or 'caU'] = buffer.lower_case

    newkeys[OSX and 'mq' or 'cq']  = textadept.editing.block_comment

    -- Do I ever use these?

 -- newkeys['cf']     = buffer.char_right
 -- newkeys['cF']     = buffer.char_right_extend
 -- newkeys['cmf']    = buffer.word_right
 -- newkeys['cmF']    = buffer.word_right_extend
 -- newkeys['cb']     = buffer.char_left
 -- newkeys['cB']     = buffer.char_left_extend
 -- newkeys['cmb']    = buffer.word_left
 -- newkeys['cmB']    = buffer.word_left_extend
 -- newkeys['cn']     = buffer.line_down
 -- newkeys['cN']     = buffer.line_down_extend
 -- newkeys['cp']     = buffer.line_up
 -- newkeys['cP']     = buffer.line_up_extend
 -- newkeys['ca']     = buffer.vc_home
 -- newkeys['cA']     = buffer.vc_home_extend
 -- newkeys['ce']     = buffer.line_end
 -- newkeys['cE']     = buffer.line_end_extend
 -- newkeys['aright'] = buffer.word_right
 -- newkeys['aleft']  = buffer.word_left
 -- newkeys['cdv']    = buffer.clear
 -- newkeys['ck']     = function() buffer:line_end_extend() buffer:cut()  end
 -- newkeys['cl']     = buffer.vertical_centre_caret

    newkeys.fn = OSX and function() return true end or nil

    newkeys[OSX and 'c@' or 'c '] = complete_symbol

end

do

    local function find_in_file()
        ui.find.in_files = false
        ui.find.focus()
    end

    local function find_in_files()
        ui.find.in_files = true
        ui.find.focus()
    end

    local function find_next_in_files()
        ui.find.goto_file_found(false,true)
    end

    local function find_previous_in_files()
        ui.find.goto_file_found(false,false)
    end

    newmenu.search = {

        title = '_Search',

        SEPARATOR,

        { '_Find',                     find_in_file },
        { 'Find _Next',                ui.find.find_next },
        { 'Find _Previous',            ui.find.find_prev },
        { '_Replace',                  ui.find.replace },
        { 'Replace _All',              ui.find.replace_all },
        { 'Find _Incremental',         ui.find.find_incremental },

        SEPARATOR,

        { 'Find in Fi_les',            find_in_files },
        { 'Goto Nex_t File Found',     find_next_in_files },
        { 'Goto Previou_s File Found', find_previous_in_files },

        SEPARATOR,

        { '_Jump to',                  textadept.editing.goto_line }

    }

    -- The few times I use osx I want the same keys ... better explicitly handle
    -- "not GUI" but I have to test the curses version first anyway.

    newkeys[OSX and 'mf' or 'cf']    = find_in_file
    newkeys[OSX and 'mg' or 'cg']    = ui.find.find_next
    newkeys[OSX and 'mG' or 'cG']    = ui.find.find_prev
    newkeys[OSX and 'mg' or 'cg']    = textadept.editing.goto_line

    newkeys['f3']                    = not OSX and ui.find.find_next or nil
    newkeys['sf3']                   = not OSX and ui.find.find_prev or nil

    newkeys[OSX and 'cr'  or 'car']  = ui.find.replace
    newkeys[OSX and 'cR'  or 'caR']  = ui.find.replace_all
    newkeys[OSX and 'cmf' or 'caf']  = ui.find.find_incremental

    newkeys[OSX and 'mF'  or 'cF']   = find_in_files
    newkeys[OSX and 'cmg' or 'cag']  = find_next_in_files
    newkeys[OSX and 'cmG' or 'caG']  = find_previous_in_files

end

do

    io.quick_open_max = 5000

    local function isdir(path)
        return path and path ~= "" and lfs.attributes(path,'mode') == 'directory'
    end

    local function resolveroot(path)
        local path = runner.resultof("mtxrun --resolve-path TEXMFCONTEXT")
        if path then
            return string.match(path,"(.-)%s$")
        end
    end

    local function opencurrentdirectory()
        local path = buffer.filename
        if path and path ~= "" then
            path = string.match(path,"^(.+)[/\\]")
            if isdir(path) then
                io.quick_open(path)
            end
        end
    end

    local function openuserdirectory()
        local path = resolveroot("TEXMFPROJECT")
        if isdir(path) then
            io.quick_open(path .. "/tex/context/user")
        end
    end

    local function openbasedirectory()
        local path = resolveroot("TEXMFCONTEXT")
        if isdir(path) then
            io.quick_open(path .. "/tex/context/base/mkiv")
        end
    end

    local started = false

    local function startservice()
        if WIN32 then
            os.execute([[cmd /c start /min "Context Documentation" mtxrun --script server --auto]])
        else
            os.execute([[mtxrun --script server --start > ~/context-wwwserver.log &]])
        end
        started = true
    end

    local function showcommand()
     -- if not started then
     --     startservice()
     -- end
        local start = buffer.selection_n_start[0]
        local stop  = buffer.selection_n_end[0]
        if start == stop then
            buffer:set_target_range(buffer:word_start_position(start,true),buffer:word_end_position(stop,true))
        else
            buffer:set_target_range(start,stop)
        end
        local word = buffer.target_text
        os.execute(format([[mtxrun --gethelp --url="http://localhost:8088/mtx-server-ctx-help.lua?command=%s"]],word or ""))
    end

    newmenu.tools = {

        title = '_Tools',

        SEPARATOR,

        { 'Check Source',            runner.check },
        { 'Process Source',          runner.process },
        { 'Preview Result',          runner.preview },
        { 'Show Log File',           runner.install("logfile") },
        { 'Quit',                    runner.quit },

        SEPARATOR,

        { 'Open Current Directory',  opencurrentdirectory },
        { 'Open User Directory',     openuserdirectory },
        { 'Open Base Directory',     openbasedirectory },

        SEPARATOR,

        { 'Purge Files',             runner.install("purge") },
        { 'Clear Cache',             runner.install("clear") },
        { 'Generate File Database',  runner.install("generate") },
        { 'Generate Font Database',  runner.install("fonts") },

        SEPARATOR,

        { 'Typeset Listing',         runner.install("listing") },
        { 'Process and Arrange',     runner.install("arrange") },

        SEPARATOR,

        { 'Start Document Service',  startservice },
        { 'Goto Document Service',   showcommand },

        SEPARATOR,

        { 'Show Unicodes',           runner.install("unicodes") },

    }

    newkeys[OSX and 'mc' or 'cc'] = runner.check
    newkeys[OSX and 'mr' or 'cr'] = runner.process
    newkeys[OSX and 'mp' or 'cp'] = runner.preview
 -- newkeys[OSX and 'mx' or 'cx'] = runner.quit -- makes no sense

    newkeys['f7']  = runner.process
    newkeys['f12'] = runner.process

end

do

    local function use_tabs()
        buffer.use_tabs = not buffer.use_tabs
        events.emit(events.UPDATE_UI) -- for updating statusbar
    end

    local function set_eol_mode_crlf()
        set_eol_mode(buffer.EOL_CRLF)
    end

    local function set_eol_mode_lf()
        set_eol_mode(buffer.EOL_LF)
    end

    local function show_eol()
        buffer.view_eol = not buffer.view_eol
    end

    local function wrap_mode()
        buffer.wrap_mode = buffer.wrap_mode == 0 and buffer.WRAP_WHITESPACE or 0
    end

    function show_white_space()
        buffer.view_ws = buffer.view_ws == 0 and buffer.WS_VISIBLEALWAYS or 0
    end

    local function update_lexing()
        buffer:colourise(0,-1)
    end

    function set_endoding_utf8()
        set_encoding('UTF-8')
    end

    function set_encoding_ascii()
        set_encoding('ASCII')
    end

    function set_endoding_utf16le()
        set_encoding('UTF-16LE')
    end

    function set_endoding_utf16Be()
        set_encoding('UTF-16BE')
    end

    function goto_prev_buffer()
        view:goto_buffer(-1)
    end

    function goto_next_buffer()
        view:goto_buffer(1)
    end

    newmenu.buffer = {

        title = '_Buffer',

        SEPARATOR,

        { '_Previous Buffer',  goto_prev_buffer },
        { '_Next Buffer',      goto_next_buffer },
        { '_Switch to Buffer', ui.switch_buffer },

        SEPARATOR,

        { '_Toggle Use Tabs', use_tabs },
        {
            title = 'EOL Mode',

            { '_CRLF', set_eol_mode_crlf },
            { '_LF',   set_eol_mode_lf },
        },
        {
            title = 'Encoding',

            { '_ASCII',     set_encoding_ascii },
            { '_UTF-8',     set_encoding_utf8 },
            { 'UTF-16-_BE', set_encoding_utf16le },
            { 'UTF-16-_LE', set_encoding_utf16be },
        },

        SEPARATOR,

        { 'Toggle View _EOL',     show_eol },
        { 'Toggle _Wrap Mode',    wrap_mode  },
        { 'Toggle View _Spacing', show_whitespace },

        SEPARATOR,

        { 'Select _Lexer',                textadept.file_types.select_lexer },
        { 'Refresh _Syntax Highlighting', update_lexing }

    }

    newkeys['f5'] = update_lexing

    newkeys[OSX and 'mp' or 'cs\t'] = goto_prev_buffer
    newkeys[OSX and 'mn' or 'c\t']  = goto_next_buffer
    newkeys[OSX and 'mb' or 'cb']   = ui.switch_buffer

end

do

    local function toggle_current_fold()
        buffer:toggle_fold(buffer:line_from_position(buffer.current_pos))
    end

    local function toggle_show_guides()
        local off = buffer.indentation_guides == 0
        buffer.indentation_guides = off and buffer.IV_LOOKBOTH or 0
    end

    local function toggle_virtual_space()
        local off = buffer.virtual_space_options == 0
        buffer.virtual_space_options = off and buffer.VS_USERACCESSIBLE or 0
    end

    local function reset_zoom()
        buffer.zoom = 0
    end

    newmenu.view = {

        title = '_View',

        SEPARATOR,

        { 'Toggle Current _Fold' ,      toggle_current_fold },

        SEPARATOR,

        { 'Toggle Show In_dent Guides', toggle_show_guides },
        { 'Toggle _Virtual Space',      toggle_virtual_space },

        SEPARATOR,

        { 'Zoom _In',                   buffer.zoom_in },
        { 'Zoom _Out',                  buffer.zoom_out },
        { '_Reset Zoom',                reset_zoom },

    }

    newkeys[OSX and 'm=' or 'c='] = buffer.zoom_in
    newkeys[OSX and 'm-' or 'c-'] = buffer.zoom_out
    newkeys[OSX and 'm0' or 'c0'] = reset_zoom

end

do

    -- It's a pity that we can't have a proper monospaced font here so we try to make the best of it:

    local template = "\n\trelease info: %s\t\n\n\tcopyright: %s\t\n\n\tvariant: ConTeXt related editing\t\n\n\tadapted by: Hans Hagen\t"

    function show_about()
        ui.dialogs.msgbox {
            title            = "about",
            informative_text = format(template,(gsub(_RELEASE,"%s+"," ")),(gsub(_COPYRIGHT,"%s+"," ")))
        }
    end

    local function open_url(url) -- adapted from non public open_page
        local cmd = (WIN32 and 'start ""') or (OSX and 'open') or 'xdg-open'
        spawn(format('%s "%s"', cmd, url))
    end


    newmenu.help = {

        title = '_Help',

        SEPARATOR,

        { 'ConTeXt garden wiki', function() open_url("http://www.contextgarden.net") end },

     -- SEPARATOR,

        { '_About', show_about }

    }

end

do

    local function replace(oldmenu,newmenu)
        local n = #newmenu
        local o = #oldmenu
        for i=1,n do
            oldmenu[i] = newmenu[i]
        end
        for i=o,n+1,-1 do
            oldmenu[i] = nil
        end
    end

    replace(textadept.menu.menubar [_L['_File']],   newmenu.file)
    replace(textadept.menu.menubar [_L['_Edit']],   newmenu.edit)
    replace(textadept.menu.menubar [_L['_Search']], newmenu.search)
    replace(textadept.menu.menubar [_L['_Tools']],  newmenu.tools)
    replace(textadept.menu.menubar [_L['_Buffer']], newmenu.buffer)
    replace(textadept.menu.menubar [_L['_View']],   newmenu.view)
    replace(textadept.menu.menubar [_L['_Help']],   newmenu.help)

    local char = string.char

    local combi = {
        "c", "m", "a",
        "cm", "ca", "ma",
    }

    local pad   = {
        "esc", "del", "bs",
        "up", "down", "left", "right",
        "end", "home",
        "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12",
    }

    local s = "s"

    for i=1,#combi do
        local c = combi[i]
        for i=0x20,0x40 do
            local ci = char(i)
            keys[   c..ci] = nil
            keys[s..c..ci] = nil
        end
        for i=0x41,0x5A do -- A .. Z
            local ci = char(i)
            keys[   c..ci] = nil
        end
        for i=0x5B,0x60 do
            local ci = char(i)
            keys[   c..ci] = nil
            keys[s..c..ci] = nil
        end
        for i=0x61,0x7A do -- a .. z
            local ci = char(i)
            keys[   c..ci] = nil
        end
        for i=0x7B,0x7F do
            local ci = char(i)
            keys[   c..ci] = nil
            keys[s..c..ci] = nil
        end
        for i=1,#pad do
            local pi = pad[i]
            keys[   c..pi] = nil
            keys[s..c..pi] = nil
        end
    end

    for k, v in next, newkeys do
        keys[k] = v
    end

end

-- We have a different way to set up files and runners. Less distributed and morein the way we
-- do things in context.

local dummyrunner    = function() end
local extensions     = textadept.file_types.extensions
local specifications = runner.specifications
local setters        = { }
local defaults       = {
    check   = dummyrunner,
    process = dummyrunner,
    preview = dummyrunner,
}

setmetatable(specifications, { __index = defaults })

function context.install(specification)
    local suffixes = specification.suffixes
    if suffixes then
        local lexer    = specification.lexer
        local setter   = specification.setter
        local encoding = specification.encoding
        for i=1,#suffixes do
            local suffix = suffixes[i]
            if lexer and extensions then
                extensions[suffix] = lexer
            end
            specifications[suffix] = specification
            if lexer then
                setters[lexer] = function()
                    if encoding == "7-BIT-ASCII" then
                        setsevenbitascii(buffer)
                    end
                    if setter then
                        setter(lexer)
                    end
                end
            end
        end
    end
end

-- Too much interference so I might drop all the old stuff eventually.

local function synchronize(lexer)
    if lexer then
        local setter = lexer and setters[lexer]
        if setter then
            local action = context.synchronize
            if action then
                action()
            end
         -- userunner()
            setter(lexer)
        else
         -- useoldrunner()
        end
    end
end

events.connect(events.FILE_OPENED,function(filename)
    synchronize(buffer.get_lexer(buffer))
end)

events.connect(events.LEXER_LOADED,function(lexer)
    synchronize(lexer)
end)

-- obsolete

-- events.connect(events.BUFFER_AFTER_SWITCH,function()
--     synchronize(buffer.get_lexer(buffer))
-- end)

-- events.connect(events.VIEW_AFTER_SWITCH,function()
--     synchronize(buffer.get_lexer(buffer))
-- end)

-- events.connect(events.BUFFER_NEW,function()
--     synchronize(buffer.get_lexer(buffer))
-- end)

-- events.connect(events.VIEW_NEW,function()
--     synchronize(buffer.get_lexer(buffer))
-- end)

-- events.connect(events.RESET_AFTER,function()
--     synchronize(buffer.get_lexer(buffer))
-- end)

-- local oldtools  = { }
-- local usingold  = false
-- local toolsmenu = textadept.menu.menubar [_L['_Tools']]
--
-- for i=1,#toolsmenu do
--     oldtools[i] = toolsmenu[i]
-- end
--
-- local function replace(tools)
--     local n = #toolsmenu
--     local m = #tools
--     for i=1,m do
--         toolsmenu[i] = tools[i]
--     end
--     for i=n,m+1,-1 do
--         toolsmenu[i] = nil
--     end
-- end
--
-- local function useoldrunner()
--     if not usingold then
--         keys [OSX and 'mr' or                  'cr'  ] = oldrunner.run
--         keys [OSX and 'mR' or (GUI and 'cR' or 'cmr')] = oldrunner.compile
--         keys [OSX and 'mB' or (GUI and 'cB' or 'cmb')] = oldrunner.build
--         keys [OSX and 'mX' or (GUI and 'cX' or 'cmx')] = oldrunner.stop
--         --
--         replace(oldtools)
--         --
--         usingold = true
--     end
-- end
--
-- local function userunner()
--     if usingold then
--         keys [OSX and 'mr' or                  'cr'  ] = runner.process
--         keys [OSX and 'mR' or (GUI and 'cR' or 'cmr')] = runner.check
--         keys [OSX and 'mB' or (GUI and 'cB' or 'cmb')] = runner.preview
--         keys [OSX and 'mX' or (GUI and 'cX' or 'cmx')] = runner.quit
--         --
--         replace(newtools)
--         --
--         usingold = false
--     end
-- end
--
-- userunner()
