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

-- This is an adapted version of the run code by mitchell.att.foicica.corunner. The main
-- reason I started patching is that long lines got broken in the middle so we needed
-- to have a more clever line splitter that saves half of a line for later. Then I
-- decided to come up with a few more variants so in the end ... it's just too tempting
-- make something that exactly suits out needs. In fact, maybe I'll do that some day:
-- take core textadept and make a dedicated variant for the kind of processing that we
-- do and make it suitable for document authors (the manual says that is doable). In that
-- case I can also use a lot of already written helpers.
--
-- The error scanner is not needed. If I need one, it will be using a lexers applied
-- afterwards because working on half lines is not going to work out well anyway.
--
-- Here I removed iconv calls as in context we use utf (less hassle with fonts too). One
-- can always use the original approach.
--
-- The events seems to have hard coded names, Also, the name of the message buffer cannot
-- be changes because otherwise we get a message when the session is restored. I don't
-- care about locales.
--
-- Somehow the process hangs when I refresh the pdf viewer, this doesn't happen in scite so
-- the underlying code is for the moment less reliant.

local match, gsub, find, format = string.match, string.gsub, string.find, string.format
local assert, type = assert, type

local original            = textadept.run
local runner              = { }

runner.MARK_WARNING       = original.MARK_WARNING
runner.MARK_ERROR         = original.MARK_ERROR

local specifications      = { }
runner.specifications     = specifications

----- RUNNER_EVENT        = "[Context Runner]"
local OUTPUT_BUFFER       = '[Message Buffer]' -- CONSOLE

----- events.RUNNER_EVENT = RUNNER_EVENT

local currentprocess      = nil
local xbuffer             = nil

local function find_buffer(buffer_type)
    for i=1,#_BUFFERS do
        local buffer = _BUFFERS[i]
        if buffer._type == buffer_type then
            return buffer
        end
    end
end

local function print_output(str)
    local print_buffer = find_buffer(OUTPUT_BUFFER)
    -- some simplified magic copied from the adeptext runner
    if not print_buffer then
        if not ui.tabs then
            view:split()
        end
        print_buffer = buffer.new()
        print_buffer._type = OUTPUT_BUFFER
        events.emit(events.FILE_OPENED)
    else
        for i=1,#_VIEWS do
            local view = _VIEWS[i]
            if view.buffer._type == OUTPUT_BUFFER then
                ui.goto_view(view)
                break
            end
        end
        if view.buffer._type ~= OUTPUT_BUFFER then
            view:goto_buffer(print_buffer)
        end
    end
    print_buffer:append_text(str)
    print_buffer:goto_pos(buffer.length)
    print_buffer:set_save_point()
    --
    return true -- quits
end

local function clear_output()
    xbuffer = buffer
    local print_buffer = find_buffer(OUTPUT_BUFFER)
    if print_buffer then
        print_buffer:clear_all()
    end
end

local function is_output(buffer)
    return buffer._type == OUTPUT_BUFFER
end

-- Instead of events we will have out own interceptors so that we don't have
-- interference. The main problem is that we don't hav emuch control over the
-- order. If we have much actions I can always come up with something.

local function process(buffer,filename,action)
    if not filename then
        filename = buffer.filename
    end
    if not filename then
        return
    end
    if filename == buffer.filename then
        buffer:annotation_clear_all() -- needed ?
        io.save_file()
    end
    if filename == "" then
        return
    end
    local suffix        = match(filename,'[^/\\.]+$')
    local specification = specifications[suffix]
    if not specification then
        return
    end
    local action = specification[action]
    local quitter = nil
    if type(action) == "table" then
        action  = action.command
        quitter = action.quitter
    end
    if type(action) ~= "string" then
        return
    end
    clear_output()
    local pathpart = ''
    local basename = filename
    if find(filename,'[/\\]') then
        pathpart, basename = match(filename,'^(.+[/\\])([^/\\]+)$')
    end
    -- beter strip one from the end
    local nameonly = match(basename,'^(.+)%.')
    -- more in sync which what we normally do (i'd rather use the ctx template mechanism)
    local command = gsub(action,'%%(.-)%%', {
        filename  = filename,
        pathname  = dirname,
        dirname   = dirname,
        pathpart  = dirname,
        basename  = basename,
        nameonly  = nameonly,
        suffix    = suffix,
        selection = function() return match(buffer.get_sel_text(),"%s*([A-Za-z]+)") end,
    })
    -- for fun i'll add a ansi escape sequence lexer some day
    local function emit_output(output)
        print_output(output) -- events.emit(RUNNER_EVENT,...)
        -- afaik there is no way to check if we're waiting for input (no input callback)
        if quitter then
            local quit, message = quitter(interceptor)
            if quit then
                if message then
                    print_output(format("\n\n> quit: %s\n",message))
                end
                runner.quit()
            end
        end
    end
    local function exit_output(status)
        print_output(format("\n\n> exit: %s, press esc to return to source\n",status)) -- events.emit(RUNNER_EVENT,...)
    end
    print_output(format("> command: %s\n",command)) -- events.emit(RUNNER_EVENT,...)
    currentprocess = assert(spawn(command, pathpart, emit_output, emit_output, exit_output))
end

function runner.install(name)
    return function(filename)
        process(buffer,filename,name)
    end
end

runner.check   = runner.install("check")
runner.process = runner.install("process")
runner.preview = runner.install("preview")

function runner.resultof(command) -- from l-os.lua
    local handle = io.popen(command,"r")
    if handle then
        local result = handle:read("*all") or ""
        handle:close()
        return result
    else
        return ""
    end
end

function runner.quit()
    if currentprocess then
        assert(currentprocess:kill())
    end
end

local function char_added(code)
    if code == 10 and currentprocess and currentprocess:status() == 'running' and buffer._type == OUTPUT_BUFFER then
        local line_num = buffer:line_from_position(buffer.current_pos) - 1
        currentprocess:write((buffer:get_line(line_num)))
    end
    return true -- quits
end

function runner.goto_error(line, next)
    -- see original code for how to do it
end

local function key_press(code)
    if xbuffer and keys.KEYSYMS[code] == 'esc' then
        view:goto_buffer(xbuffer)
        return true
    end
end

local function double_click()
    if xbuffer and is_output(buffer) then
        view:goto_buffer(xbuffer)
        return true
    end
end

-- Tricky: we can't reset an event (because we need to know the function which is
-- local. So, a first solution injected a false into the table which will trigger
-- a break and then I found out that returning true has the same effect. Then I
-- found out that we can have our own events and next decided not to use them at
-- all.

-- events.connect(events.RUNNER_EVENT,   print_output, 1)

events.connect(events.CHAR_ADDED,     char_added,   1)
events.connect(events.KEYPRESS,       key_press,    1)
events.connect(events.DOUBLE_CLICK,   double_click, 1)

return runner

-- The ui.print function is a bit heavy as each flush will parse the whole list of buffers.
-- Also it does some tab magic that we don't need or want. There is the original ui.print for
-- that. FWIW, speed is not an issue. Some optimizations:

-- function _print(buffer_type,one,two,...)
--     ...
--     print_buffer:append_text(one)
--     if two then
--         print_buffer:append_text(two)
--         for i=1, select('#', ...) do
--             print_buffer:append_text((select(i,...)))
--         end
--     end
--     print_buffer:append_text('\n')
--     ...
-- end
--
-- And a better splitter:
--     ...
--     local rest
--     local function emit_output(output)
--         for line, lineend in output:gmatch('([^\r\n]+)([\r\n]?)') do
--             if rest then
--                 line = rest .. line
--                 rest = nil
--             end
--             if lineend and lineend ~= "" then
--                 events.emit(event, line, ext_or_lexer)
--             else
--                 rest = line
--             end
--         end
--     end
--     ...
--         if rest then
--             events.emit(event,rest,ext_or_lexer)
--         end
--         events.emit(event, '> exit status: '..status)
--     ...
