%D \module
%D   [       file=m-scite,
%D        version=2014.04.28,
%D          title=\CONTEXT\ Extra Modules,
%D       subtitle=\SCITE\ lexers,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

% We can simplify the scite lexers, as long as we're able to return the
% lexed result table and provide alexer module with the functions that
% the lexer expects (so I need to decipher the cxx file).
%
% lexer._TOKENSTYLES : table
% lexer._CHILDREN    : flag
% lexer._EXTRASTYLES : table
% lexer._GRAMMAR     : flag
%
% lexers.load        : function
% lexers.lex         : function
%
% And some properties that map styles onto scintilla styling. I get the
% impression that we end up with something simpler, a hybrid between the
% scite lexing and the current context way, so we get an intermediate
% step, with some penalty for context, but at least I don't have to
% maintain two sets (three sets as we also have a line based series).

% TODO: as these files are in tds we can locate them and set the lexer root
% to that one. Currently we're on: we're on context/documents.

% TODO: tab

% This is an experiment: eventually we need to hook it into the verbatim code
% and deal with widow lines and so.

\startluacode

-- todo: merge with collapse
-- todo: prehash whitespaces

-- todo: hook into the pretty print code
-- todo: a simple catcode regime with only \ { }

local gsub, sub, find, lower = string.gsub, string.sub, string.find, string.lower
local concat = table.concat
local formatters = string.formatters
local lpegmatch = lpeg.match
local setmetatableindex = table.setmetatableindex

local scite   = require("util-sci")
buffers.scite = scite

-- context output:

local f_def_color = formatters["\\definecolor[slxc%s][h=%02X%02X%02X]%%"]
local f_fore_none = formatters["\\unexpanded\\def\\slx%s#1{{\\slxc%s#1}\\slxbreak}%%"]
local f_fore_bold = formatters["\\unexpanded\\def\\slx%s#1{{\\slxc%s\\slxbold#1}\\slxbreak}%%"]
local f_none_bold = formatters["\\unexpanded\\def\\slx%s#1{{\\slxbold#1}\\slxbreak}%%"]
local f_none_none = formatters["\\unexpanded\\def\\slx%s#1{{#1}\\slxbreak}%%"]
local f_texstyled = formatters["\\slx%s{%s}\\slxbreak"]
local f_hanging   = formatters["\\slxb{%r}%s\\slxe"] -- we need to round: lua 5.3

local v_none      = interfaces.variables.none

local f_mapping   = [[
\let\string\slxL\string\letterleftbrace
\let\string\slxR\string\letterrightbrace
\let\string\slxM\string\letterdollar
\let\string\slxV\string\letterbar
\let\string\slxU\string\letterhat
\let\string\slxD\string\letterunderscore
\let\string\slxH\string\letterhash
\let\string\slxB\string\letterbackslash
\let\string\slxP\string\letterpercent
\let\string\slxT\string\lettertilde
\let\string\slxS\string\fixedspace
%]]

local replacer = lpeg.replacer {
    ["{"]  = "\\slxL ",
    ["}"]  = "\\slxR ",
    ["$"]  = "\\slxM ",
    ["^"]  = "\\slxU ",
    ["_"]  = "\\slxD ",
    ["|"]  = "\\slxV ",
    ["#"]  = "\\slxH ",
    ["\\"] = "\\slxB ",
    ["%"]  = "\\slxP ",
    ["~"]  = "\\slxT ",
    [" "]  = "\\slxS ", -- can be made more efficient: \\slxF{n}
}

local colors = nil
local escape = false

local function exportcolors()
    if not colors then
        scite.loadscitelexer()
        local function black(f)
            return (f[1] == f[2]) and (f[2] == f[3]) and (f[3] == 0)
        end
     -- local result, r = { f_mapping }, 1
        local result, r = { }, 0
        for k, v in table.sortedhash(lexers.styles) do
            local fore = v.fore
            if fore and not black(fore) then
                r = r + 1
                result[r] = f_def_color(k,fore[1],fore[2] or fore[1],fore[3] or fore[1])
            end
        end
        r = r + 1
        result[r] = "%"
        for k, v in table.sortedhash(lexers.styles) do
            local bold = v.bold
            local fore = v.fore
            r = r + 1
            if fore and not black(fore) then
                if bold then
                    result[r] = f_fore_bold(k,k)
                else
                    result[r] = f_fore_none(k,k)
                end
            else
                if bold then
                    result[r] = f_none_bold(k)
                else
                    result[r] = f_none_none(k)
                end
            end
        end
        colors = concat(result,"\n")
    end
    return colors
end

local function exportwhites()
    return setmetatableindex(function(t,k)
        local v = find(k,"white") and true or false
        t[k] = v
        return v
    end)
end

local commenthandlers = {
    tex = function(txt)
        if find(txt,"^%%tex ") then
            return "{" .. gsub(txt,"^%%tex ","") .. "}"
        end
    end,
    mps = function(txt)
        if find(txt,"^%%tex ") then
            return "{" .. gsub(txt,"^%%tex ","") .. "}"
        end
    end,
    lua = function(txt)
        if find(txt,"^%-%-tex ") then
            return "{" .. gsub(txt,"^%-%-tex ","") .. "}"
        end
    end,
}

local function exportstyled(lexer,text,commenthandler)
    local result = lexers.lex(lexer,text,0)
    local start = 1
    local whites = exportwhites()
    local buffer, b = { }, 0
    for i=1,#result,2 do
        local style = result[i]
        local position = result[i+1]
        local txt = sub(text,start,position-1)
        txt = commenthandler and style == "comment" and commenthandler(txt) or lpegmatch(replacer,txt)
        if txt == "" then
            -- skip
        elseif whites[style] then
            b = b + 1 ; buffer[b] = txt
        else
            b = b + 1 ; buffer[b] = f_texstyled(style,txt)
        end
        start = position
    end
    buffer = concat(buffer)
    return buffer
end

function scite.installcommands()
    context(exportcolors())
end

local function slxF(b,e)
    local d = (e - b)/6
    if d > 0 then
        return "\\slxF{" .. d .. "}"
    else
        return " "
    end
end

local p1 = lpeg.tsplitat(lpeg.patterns.newline)
local p2 = lpeg.P("\\slxS ")
local p3 = p2^1
local p4 = lpeg.Cs( ( (lpeg.Cp() * p2 * p2^1 * lpeg.Cp()) / slxF + lpeg.P(1) )^0 )

local function indent(str)
    local l = lpegmatch(p1,str)
    for i=1,#l do
        local s = l[i]
        if #s > 0 then
            local n = lpegmatch(p3,s)
            if n then
                n = (n-1)/6 -- length of "\\slxS "
            else
                n = 0
            end
            if n > 0 then
                s = sub(s,n*6+1)
            end
            s = lpegmatch(p4,s)   -- can be combined
            l[i] = f_hanging(n,s) -- "\\slxb{%s}%s\\slxe "
        end
    end
    return concat(l,"\n")
end

local assignbuffer = buffers.assign
local getcontent   = buffers.getcontent
local loaddata     = io.loaddata
local loadedlexers = scite.loadedlexers

local function lexdata(data,lexname,handlecomment)
    if not data then
        data = ""
    elseif data ~= "" then
        if data and not find(data,"[\r\n]$") then
            -- fix for lexbyline
            data = data .. "\r"
        end
        local commenthandler = (escape or handlecomment) and commenthandlers[lexname]
        if lexname == v_none then
         -- data = string.formatters["%!tex!"](data)
            data = exportstyled(loadedlexers["dummy"] or loadedlexers.tex,data,commenthandler)
        else
            data = exportstyled(loadedlexers[lexname] or loadedlexers.tex,data,commenthandler)
        end
        data = indent(data)
    end
 -- io.savedata("temp.log",data)
    assignbuffer("lex",data)
end

scite.lexdata = lexdata

function scite.lexbuffer(name,lexname,options)
    lexdata(getcontent(name) or "",lexname or "tex",options == "escape" and handlecomment)
end

function scite.lexfile(filename,lexname,options)
    lexdata(loaddata(filename) or "",lexname or file.suffix(filename),options == "escape" and handlecomment)
end

interfaces.implement {
    name      = "scitelexfile",
    arguments = "string",
    actions   = scite.lexfile,
}

interfaces.implement {
    name      = "scitelexbuffer",
    arguments = "2 strings",
    actions   = scite.lexbuffer,
}

interfaces.implement {
    name      = "sciteinstallcommands",
    actions   = scite.installcommands,
}

local startInlineScite  = context.startInlineScite
local stopInlineScite   = context.stopInlineScite
local startDisplayScite = context.startDisplayScite
local stopDisplayScite  = context.stopDisplayScite

local lexdata           = buffers.scite.lexdata

local handler = visualizers.newhandler {
    startinline  = function(settings)
        startInlineScite(settings.name or "")
    end,
    stopinline   = function(settings)
        stopInlineScite()
    end,
    startdisplay = function(settings)
        escape = settings.escape == interfaces.variables.yes and true or false
        startDisplayScite(settings.name or "")
    end,
    stopdisplay  = function(settings)
        stopDisplayScite()
        escape = false
    end,
    noescape     = true,
}

local knownlexers = scite.knownlexers

local parser = function(content,specification)
    local method = lower(specification.method)
    lexdata(content,knownlexers[method] or method)
end

local visualizer = {
    handler = handler,
    parser  = parser,
}

visualizers.register("cld",   visualizer)
visualizers.register("tex",   visualizer)
visualizers.register("lua",   visualizer)
visualizers.register("mps",   visualizer)
visualizers.register("mp",    visualizer)
visualizers.register("pdf",   visualizer)
visualizers.register("xml",   visualizer)
visualizers.register("bibtex",visualizer)
visualizers.register("btx",   visualizer)
visualizers.register("web",   visualizer)
visualizers.register("cpp",   visualizer)
visualizers.register("txt",   visualizer)
visualizers.register("bnf",   visualizer)
visualizers.register("sql",   visualizer)
visualizers.register("json",  visualizer)

visualizers.register("sas",   visualizer)

function scite.register(name)
    visualizers.register(name,visualizer)
end

moduledata.scite = scite

\stopluacode

%definetyping[TEX] [option=cld]
\setuptyping [TEX] [option=cld]
%definetyping[LUA] [option=lua]
\setuptyping [LUA] [option=lua]
\definetyping[BTX] [option=bibtex]
\definetyping[MPS] [option=mps]
%definetyping[MP]  [option=mp]
\setuptyping [MP]  [option=mp]
\definetyping[PDF] [option=pdf]
\definetyping[CPP] [option=cpp]    % Which is kind of like the web one.
\definetyping[WEB] [option=web]
\definetyping[TXT] [option=txt]
\definetyping[BNF] [option=bnf]    % I might use this in the metafun manual.
\definetyping[SQL] [option=sql]    % To be tested in an upcoming manual.
\definetyping[JSON][option=json]   % To be tested in an upcoming manual.
\definetyping[NONE][option=none]

\definetyping[SAS] [option=sas]

% This is a preliminary interface.

\unprotect

\newdimen\scitespaceskip

\unexpanded\def\buff_scite_slxb#1%
  {% we can have a side float
   \advance\hangindent\numexpr#1+2\relax\scitespaceskip
   \begstrut\hskip#1\scitespaceskip\relax
   \ifcase\hangafter
     \hangafter\plusone\relax
   \fi}

\unexpanded\def\buff_scite_slxe
  {\endstrut\par}

\unexpanded\def\buff_scite_slxs  {\hskip\scitespaceskip\relax}
\unexpanded\def\buff_scite_slxf#1{\hskip#1\scitespaceskip\relax}

\unexpanded\def\installscitecommandsinline
  {\scitespaceskip\interwordspace % \fontcharwd\font`0\relax % brrrrr
   \let\slxb\gobbleoneargument
   \let\slxe\space
   \let\slxbreak\relax
   \let\installscitecommandsinline\relax}

\unexpanded\def\installscitecommandsdisplay
  {\scitespaceskip\interwordspace % \fontcharwd\font`0\relax % brrrrr
   \let\slxb\buff_scite_slxb
   \let\slxe\buff_scite_slxe
   \let\installscitecommandsdisplay\relax}

\clf_sciteinstallcommands

\let\slxb    \gobbleoneargument
\let\slxe    \space
\let\slxbreak\relax

\def\slxbold {\bf} % maybe contraststyle in verbatim

\let\slxL    \letterleftbrace
\let\slxR    \letterrightbrace
\let\slxM    \letterdollar
\let\slxV    \letterbar
\let\slxU    \letterhat
\let\slxD    \letterunderscore
\let\slxH    \letterhash
\let\slxB    \letterbackslash
\let\slxP    \letterpercent
\let\slxT    \lettertilde
\let\slxS    \fixedspace

%let\slxS    \buff_scite_slxs
\let\slxF    \buff_scite_slxf

\def\module_scite_inherit_typing
  {\buff_verbatim_initialize_typing_one
   \buff_verbatim_set_line_margin}

\unexpanded\def\startscite
  {\begingroup
   \dosingleempty\buff_scite_start}

\def\buff_scite_start[#1]%
  {\edef\currentscitelexer{#1}%
   \dostartbuffer[@scite@][startscite][stopscite]}

\unexpanded\def\stopscite
  {\scitebuffer[\ifx\currentscitelexer\empty tex\else\currentscitelexer\fi][@scite@]%
   \endgroup}

\definelines
  [scitelines]

\setuplines
  [scitelines]
  [\c!before=,
   \c!after=]

\unexpanded\def\scitefile
  {\dosingleargument\module_scite_file}

\unexpanded\def\module_scite_file[#1]%
  {\begingroup
   \setcatcodetable\ctxcatcodes % needed in xml
   \clf_scitelexfile{#1}%
   \tt
   \installscitecommandsdisplay
   \module_scite_inherit_typing
   \dontcomplain
   \raggedright
   \startcontextcode
   \startscitelines
   \getbuffer[lex]
   \stopscitelines
   \stopcontextcode
   \endgroup}

\unexpanded\def\scitebuffer
  {\dodoubleempty\module_scite_buffer}

\unexpanded\def\module_scite_buffer[#1][#2]%
  {\begingroup
   \setcatcodetable\ctxcatcodes % needed in xml
   \ifsecondargument
     \clf_scitelexbuffer{#2}{#1}%
   \else
     \clf_scitelexbuffer{#1}{tex}%
   \fi
   \tt
   \installscitecommandsdisplay
   \module_scite_inherit_typing
   \dontcomplain
   \raggedright
   \startscitelines
   \getbuffer[lex]
   \stopscitelines
   \endgroup}

\unexpanded\def\sciteinlinebuffer
  {\dodoubleempty\module_scite_buffer_inline}

\unexpanded\def\module_scite_buffer_inline[#1][#2]%
  {\dontleavehmode
   \begingroup
   \lettypingparameter\c!margin\zeropoint
   \setcatcodetable\ctxcatcodes % needed in xml
   \ifsecondargument
     \clf_scitelexbuffer{#2}{#1}%
   \else
     \clf_scitelexbuffer{#1}{tex}%
   \fi
   \tt
   \installscitecommandsinline
   \module_scite_inherit_typing
   \dontcomplain
   \getbuffer[lex]%
   \removeunwantedspaces
   \endgroup}

\unexpanded\def\startInlineScite#1%
  {\dontleavehmode
   \begingroup
   \setcatcodetable\ctxcatcodes % needed in xml
   \tt
   \installscitecommandsinline
   \dontcomplain
   \ignorespaces
   \clf_getbuffercontent{lex}% much faster than getbuffer
   \removeunwantedspaces}

\unexpanded\def\stopInlineScite
  {\endgroup}

\unexpanded\def\startDisplayScite#1%
  {\begingroup
   \setcatcodetable\ctxcatcodes % needed in xml
   \tt
   \installscitecommandsdisplay
   \dontcomplain
   \buff_verbatim_initialize_typing_one
   \buff_verbatim_set_line_margin
   \raggedright
   \startscitelines
   \clf_getbuffer{lex}}

\unexpanded\def\stopDisplayScite
  {\stopscitelines
   \endgroup}

\unexpanded\def\slxbreak
  {\allowbreak}

\protect

\continueifinputfile{m-scite.mkiv}

\setupbodyfont[dejavu,8pt]

\setuplayout
  [width=middle,
   height=middle,
   header=1cm,
   footer=1cm,
   topspace=1cm,
   bottomspace=1cm,
   backspace=1cm]

\startbuffer[demo]
\startsubsubject[title={oeps}]

\startMPcode
    draw fullcircle
        scaled 2cm
        withpen pencircle scaled 1mm
        withcolor .5green;
    draw textext (
        lua (
            "local function f(s) return string.upper(s) end mp.quoted(f('foo'))"
        )
    ) withcolor .5red ;
\stopMPcode

\startluacode
    context("foo")
\stopluacode

\stopsubsubject
\stopbuffer

\starttext

% \scitefile[../lexers/scite-context-lexer.lua]  \page
% \scitefile[t:/manuals/about/about-metafun.tex] \page
% \scitefile[t:/sources/strc-sec.mkiv]           \page
% \scitefile[e:/tmp/mp.w]                        \page
% \scitefile[t:/manuals/hybrid/tugboat.bib]        \page
% \scitefile[e:/tmp/test.bib]        \page

% \getbuffer[demo] \scitebuffer[demo]

% \startbuffer[foo]
% This is text. % line 1
% This is text. % line 2
% \stopbuffer

% \scitebuffer[none][foo]

% \sciteinlinebuffer[none][foo]

\starttyping[option=TEX,escape=yes]
\framed
  [width=4cm] % options
  {some text}

\framed
  [width=4cm] % \letterpercent\ \rm\bf these are options
  {some text}

\framed
  [width=4cm] %tex \letterpercent\ \rm\bf these are options
  {some text}
\stoptyping

\starttyping[option=LUA,escape=yes]
function test(x)
    return x * x  --tex -- \rm\bf these are options
end
\stoptyping

\stoptext

