%D \module
%D   [       file=m-timing,
%D        version=2007.12.23,
%D          title=\CONTEXT\ Modules,
%D       subtitle=Timing,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright=Hans Hagen]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\ifx\ShowNamedUsage\undefined \else \endinput \fi

%D Written at the end of 2007, this module is dedicated to Taco. Reaching this
%D point in \LUATEX\ was a non trivial effort. By visualizing a bit what happens
%D when pages come out of \LUATEX, you may get an idea what is involved. It took
%D much time an dedication to reach this point in the development. Add to that
%D those daily Skype intense discussion, testing and debugging moments. Time flies
%D but progress is impressive. The motto of this module could be: what you see
%D is what you get. An there is much more to come \unknown.

% \usemodule[timing]
% \setupcolors[state=start]
% \starttext
%     \dorecurse{200}{\input tufte \par} \ShowUsage{}
% \stoptext

\definecolor[usage:line] [darkred]
\definecolor[usage:time] [darkblue]
\definecolor[usage:frame][darkgray]

\startluacode
do

    document          = document          or { }
    document.progress = document.progress or { }

    local defaultfilename = tex.jobname .. "-luatex-progress"

    local params = {
        "cs_count",
        "dyn_used",
        "elapsed_time",
        "luabytecode_bytes",
        "luastate_bytes",
        "max_buf_stack",
        "obj_ptr",
        "pdf_mem_ptr",
        "pdf_mem_size",
        "pdf_os_cntr",
        "pool_ptr",
        "str_ptr",
    }

    -- storage

    local last  = os.clock()
    local data  = { }

    function document.progress.save()
        local f = io.open((name or defaultfilename) .. ".lut","w")
        if f then
            f:write(table.serialize(data,true))
            f:close()
            data = { }
        end
    end

    function document.progress.store()
        local c = os.clock()
        local t = {
            elapsed_time = c - last,
            node_memory  = tex.node_mem_status(),
        }
        for k, v in pairs(params) do
            if status[v] then t[v] = status[v] end
        end
        data[#data+1] = t
        last = c
    end

    -- conversion

    local processed = { }

    function document.progress.bot(name,tag)
        local d = document.progress.convert(name)
        return d.bot[tag] or 0
    end
    function document.progress.top(name,tag)
        local d = document.progress.convert(name)
        return d.top[tag] or 0
    end
    function document.progress.pages(name,tag)
        local d = document.progress.convert(name)
        return d.pages or 0
    end
    function document.progress.path(name,tag)
        local d = document.progress.convert(name)
        return d.paths[tag] or "origin"
    end
    function document.progress.nodes(name)
        local d = document.progress.convert(name)
        return d.names or { }
    end
    function document.progress.parameters(name)
        local d = document.progress.convert(name)
        return params -- shared
    end

    function document.progress.convert(name)
        name = ((name ~= "") and name) or defaultfilename
        if not processed[name] then
            local names, top, bot, pages, paths, keys = { }, { }, { }, 0, { }, { }
            local data = io.loaddata(name .. ".lut")
            if data then data = loadstring(data) end
            if data then data = data() end
            if data then
                pages = #data
                if pages > 1 then
                    local factor = 100
                    for k,v in ipairs(data) do
                        for k,v in pairs(v.node_memory) do
                            keys[k] = true
                        end
                    end
                    for k,v in ipairs(data) do
                        local m = v.node_memory
                        for k, _ in pairs(keys) do
                            if not m[k] then m[k] = 0 end
                        end
                    end
                    local function path(tag,subtag)
                        local b, t, s = nil, nil, { }
                        for k,v in ipairs(data) do
                            local v = (subtag and v[tag][subtag]) or v[tag]
                            if v then
                                v = tonumber(v)
                                if b then
                                    if v > t then t = v end
                                    if v < b then b = v end
                                else
                                    t = v
                                    b = v
                                end
                                s[k] = v
                            else
                                s[k] = 0
                            end
                        end
                        local tagname = subtag or tag
                        top[tagname] = (string.format("%.3f",t)):gsub("%.000$","")
                        bot[tagname] = (string.format("%.3f",b)):gsub("%.000$","")
                        local delta = t-b
                        if delta == 0 then
                            delta = 1
                        else
                            delta = factor/delta
                        end
                        for k, v in ipairs(s) do
                            s[k] = "(" .. k .. "," .. (v-b)*delta .. ")"
                        end
                        paths[tagname] = table.concat(s,"--")
                    end
                    for _, tag in pairs(params) do
                        path(tag)
                    end
                    for tag, _ in pairs(keys) do
                        path("node_memory",tag)
                        names[#names+1] = tag
                    end
                    pages = pages - 1
                end
            end
            table.sort(names)
            processed[name] = {
                names = names,
                top = top,
                bot = bot,
                pages = pages,
                paths = paths,
            }
        end
        return processed[name]
    end

    function document.progress.show(filename,parameters,nodes,other)
        for n, name in pairs(parameters or document.progress.parameters(filename)) do
            tex.sprint(tex.ctxcatcodes,string.format("\\ShowNamedUsage{%s}{%s}{%s}",filename or defaultfilename,name,other or ""))
        end
        for n, name in pairs(nodes or document.progress.nodes(filename)) do
            tex.sprint(tex.ctxcatcodes,string.format("\\ShowNamedUsage{%s}{%s}{%s}",filename or defaultfilename,name,other or ""))
        end
    end

end
\stopluacode

% \everyfirstshipout

\appendtoks\ctxlua{document.progress.store()}\to\everystarttext
\appendtoks\ctxlua{document.progress.store()}\to\everyshipout

\ctxlua{table.insert(input.stop_actions, function() document.progress.save() end)}

\def\ShowNamedUsage#1#2#3%
  {\setbox\scratchbox\vbox\bgroup\startMPcode
      begingroup ; save p, q, b, h, w ;
      path p, q, b ; numeric h, w ;
      p := \ctxlua{tex.sprint(document.progress.path("#1","#2"))} ;
      if bbwidth(p) > 1 :
        h := 100 ; w := 2 * h ;
        w := \the\textwidth-3pt ; % correct for pen
        p := p xstretched w ;
        b := boundingbox (llcorner p -- llcorner p shifted (w,h)) ;
        pickup pencircle scaled 3pt ; linecap := butt ;
        draw b withcolor \MPcolor{usage:frame} ;
        draw p withcolor \MPcolor{usage:line} ;
        if ("#3" <> "") and ("#3" <> "#2") :
          q := \ctxlua{tex.sprint(document.progress.path("#1","#3"))} ;
          if bbwidth(q) > 1 :
            q := q xstretched w ;
            pickup pencircle scaled 1.5pt ; linecap := butt ;
            draw q withcolor \MPcolor{usage:time} ;
          fi ;
        fi ;
      fi ;
      endgroup ;
   \stopMPcode\egroup
   \scratchdimen\wd\scratchbox
   \ifdim\scratchdimen>\zeropoint
     \startlinecorrection
         \box\scratchbox \endgraf
         \hbox to \scratchdimen{\tttf\strut\detokenize{#2}\hss
          min:\ctxlua{tex.sprint(document.progress.bot("#1","\detokenize{#2}"))}, %
          max:\ctxlua{tex.sprint(document.progress.top("#1","\detokenize{#2}"))}, %
          pages:\ctxlua{tex.sprint(document.progress.pages("#1"))}%
         }%
     \stoplinecorrection
   \fi}

\def\LoadUsage      #1{\ctxlua{document.progress.convert("#1")}}
\def\ShowUsage      #1{\ctxlua{document.progress.show("#1",nil,nil,"elapsed_time")}}
\def\ShowMemoryUsage#1{\ctxlua{document.progress.show("#1",nil,{}, "elapsed_time")}}
\def\ShowNodeUsage  #1{\ctxlua{document.progress.show("#1",{},nil, "elapsed_time")}}

\endinput
