%D \module
%D   [       file=page-otr,
%D        version=2012.01.25,
%D          title=\CONTEXT\ Page Macros,
%D       subtitle=Output Routines,
%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.

\writestatus{loading}{ConTeXt Page Macros / Output Routines}

%D This module will get some of the code from other modules. At the same time we
%D provide a bit more control.

% When issuing two \par\penalty-\plustenthousand's, only the first triggers the
% otr. Is this an obscure feature or an optimization?

\registerctxluafile{page-otr}{autosuffix}

\unprotect

% triggerpagebuilder % defined at the lua end

\installcorenamespace{outputroutine}

\installswitchcommandhandler \??outputroutine {outputroutine} \??outputroutine

\newtoks\t_page_otr_commands
\newtoks\t_page_otr_tracers

\permanent\protected\def\defineoutputroutinecommand[#name]% doing multiple on one go saves syncing
  {\processcommalist[#name]\page_otr_commands_define}

\protected\def\page_otr_commands_define#name%
  {\ifcsname#name\endcsname \else
     \letcsname#name\endcsname\relax
     \expanded{\t_page_otr_commands{\the\t_page_otr_commands\noexpand\page_otr_commands_process{#name}}}%
   \fi}

\let\page_otr_commands_process\gobbleoneargument

\appendtoks
    \let\page_otr_commands_process\page_otr_specifics_preset
    \expand\t_page_otr_commands
    \let\page_otr_commands_process\gobbleoneargument
\to \everyswitchoutputroutine

% use \csstring

\protected\def\page_otr_specifics_preset#name%
  {\edef\page_otr_specifics_command{\directoutputroutineparameter{#name}}% no inheritance of commands
   \ifempty\page_otr_specifics_command
     \writestatus{\currentoutputroutine}{- \expandafter\strippedcsname\csname#name\endcsname}%
     \letcsname#name\endcsname\relax
   \else
     \writestatus{\currentoutputroutine}{+ \expandafter\strippedcsname\csname#name\endcsname}%
     \letcsname#name\expandafter\endcsname\page_otr_specifics_command
   \fi}

\protected\def\page_otr_specifics_preset_normal#name%
  {\edef\page_otr_specifics_command{\directoutputroutineparameter{#name}}% no inheritance of commands
   \ifempty\page_otr_specifics_command
     \letcsname#name\endcsname\relax
   \else
     \letcsname#name\expandafter\endcsname\page_otr_specifics_command
   \fi}

\protected\def\page_otr_specifics_preset_traced#name%
  {\edef\page_otr_specifics_command{\directoutputroutineparameter{#name}}% no inheritance of commands
   \ifempty\page_otr_specifics_command
     \writestatus{\currentoutputroutine}{preset: - \expandafter\strippedcsname\csname#name\endcsname}%
     \letcsname#name\endcsname\relax
   \else
     \writestatus{\currentoutputroutine}{preset: + \expandafter\strippedcsname\csname#name\endcsname}%
     \letcsname#name\expandafter\endcsname\page_otr_specifics_command
   \fi}

\let\page_otr_specifics_preset\page_otr_specifics_preset_normal

\permanent\protected\def\traceoutputroutines
  {\expand\t_page_otr_tracers}

\appendtoks
    \let\page_otr_specifics_preset\page_otr_specifics_preset_traced
\to \t_page_otr_tracers

%D We have a couple of output routines and the default one is the single column
%D routine. Then there is a multicolumn variant that can be used mixed, and a
%D columnset variant that is more exclusive.

\installcorenamespace{otrtriggers}

\newconstant\c_page_otr_eject_penalty   \c_page_otr_eject_penalty   -\plustenthousand
\newconstant\c_page_otr_super_penalty   \c_page_otr_super_penalty   -\plustwentythousand
\newinteger \c_page_otr_trigger_penalty \c_page_otr_trigger_penalty -100010

\newinteger \c_page_otr_columns % we will share this one

\newif      \ifinotr % we keep this (name) for old times sake

% \def\page_otr_update_page_goal#1#2%
%   {\global\c_page_otr_columns#2\relax
%    \pagegoal\dimexpr\vsize-\c_page_otr_columns\insertheights\relax}

\appendtoks
    \insertheights\zeropoint
\to \everyaftershipout

\protected\def\page_otr_message_b{\page_otr_message_s+}
\protected\def\page_otr_message_e{\page_otr_message_s-}

\protected\def\page_otr_message_s#sign#what%
  {\writestatus
    \currentoutputroutine
    {#sign\space           \space
     #what\space           \space
     p:\the\outputpenalty ,\space
     r:\the\realpageno    ,\space
     c:\number\mofcolumns ,\space
     v:\the\vsize         ,\space
     g:\the\pagegoal      ,\space
     t:\the\pagetotal     ,\space
     h:\the\pagelastheight,\space
     i:\the\insertheights
     \ifdim\pagetotal>\pagegoal
       ,\space
       d:\todimension{\pagetotal-\pagegoal}%
     \fi}}

\protected\def\page_otr_trigger#penalty%
  {\begingroup
   \par
   \penalty#penalty%
   \endgroup}

\permanent\protected\def\installoutputroutine#invoke#action% \invoke \action
  {\global\advanceby\c_page_otr_trigger_penalty\minusone
   \frozen\protected\edef#invoke{\page_otr_trigger{\the\c_page_otr_trigger_penalty}}%
   \defcsname\??otrtriggers\the\c_page_otr_trigger_penalty\endcsname{#action}}

\protected\def\page_otr_triggered_output_routine_traced
  {\ifcsname\??otrtriggers\the\outputpenalty\endcsname
     \page_otr_message_b{special}%
     \csname\??otrtriggers\the\outputpenalty\endcsname % \lastnamedcs can be gone
     \page_otr_message_e{special}%
   \else
     \page_otr_message_b{normal}%
     \page_otr_command_routine
     \page_otr_message_e{normal}%
   \fi}

\protected\def\page_otr_triggered_output_routine_normal
  {\ifcsname\??otrtriggers\the\outputpenalty\endcsname
     \lastnamedcs
   \else
     \page_otr_command_routine
   \fi}

\let\page_otr_triggered_output_routine\page_otr_triggered_output_routine_normal

\appendtoks
    \let\page_otr_triggered_output_routine\page_otr_triggered_output_routine_traced
\to \t_page_otr_tracers

%D The real routine handler:

\ifdefined\everybeforeoutput \else \newtoks\everybeforeoutput \fi
\ifdefined\everyafteroutput  \else \newtoks\everyafteroutput  \fi

\def\page_otr_set_engine_output_routine#content%
  {\global\output
     {\inotrtrue
      \expand\everybeforeoutput
      #content\relax
      \expand\everyafteroutput}}

\page_otr_set_engine_output_routine\page_otr_triggered_output_routine

\installoutputroutine\synchronizeoutput % use \triggerpagebuilder instead
  {\ifvoid\normalpagebox\else
     \unvbox\normalpagebox
     % not \pagediscards as it does more harm than good
   \fi}

\installoutputroutine\discardpage
  {\setbox\scratchbox\box\normalpagebox}

% todo: \resetpagebreak -> everyejectpage

\def\page_otr_trigger_output_routine
  {\par
   \ifvmode
     \penalty\c_page_otr_eject_penalty
   \fi
   \resetpagebreak}

\def\page_otr_fill_and_eject_page
  {\par
   \ifvmode
     \vfill
     \penalty\c_page_otr_eject_penalty
   \fi
   \resetpagebreak}

% In case \pagetotal is ahead of \pagegoal (\pagelastheight is the real one) we
% need to make sure that we flush but we don't do that in a ragged situation where
% we have safeguards.

% \setuplayout[limitstretch=yes] % border case 1
% \setupalign[depth]
%
% \starttext
%     \startitemize
%         \dorecurse{24}{\startitem test \stopitem}
%         \dorecurse{3}{\startitem test \stopitem}
%         \startitem test \stopitem % border case 2
%     \stopitemize
% \stoptext

% \def\page_otr_fill_page_unchecked
%   {\ifdim\pagetotal>\pagegoal \else
%      \vfil
%    \fi}

\def\page_otr_fill_page_unchecked
  {% \writestatus{!!!!!}{h=\the\pagelastheight,t=\the\pagetotal,g=\the\pagegoal}%
   \ifdim\pagetotal>\pagegoal
     % we're probably okay and are not too flexible
   \orelse\ifnum\bottomraggednessmode>\plusone
     % we actualy want to be flexible
   \else
     % we have to make sure that we don't stretch
     \vfil
   \fi}

\def\page_otr_fill_page_checked
  {\ifdim{\pagelastheight+\c_page_scale_lines\lineheight+\pageexcess}>\pagegoal\else
     \vfil
   \fi}

\def\page_otr_eject_page
  {\par
   \ifvmode
     \ifcase\c_page_scale_lines
       \page_otr_fill_page_unchecked
     \else
       \page_otr_fill_page_checked
     \fi
     \penalty\c_page_otr_eject_penalty
   \fi
   \resetpagebreak}

\def\page_otr_eject_page_and_flush_inserts % can be an installed one
  {\par
   \ifvmode
     \ifcase\c_page_scale_lines
       \page_otr_fill_page_unchecked
     \else
       \page_otr_fill_page_checked
     \fi
     \penalty\c_page_otr_super_penalty
   \fi
   \resetpagebreak}

\def\page_otr_check_for_pending_inserts
  {\ifnum\outputpenalty>\c_page_otr_super_penalty
   \orelse\ifnum\insertpenalties>\zerocount
     % something is being held over so we force a new page
     \page_otr_force_another_page
   \fi}

%D So far.

\def\page_otr_force_another_page
  {% we should actually remove the dummy line in the otr
   \hpack to \hsize{}%
   \kern-\topskip
   \nobreak
   \vfill
   \penalty\c_page_otr_super_penalty
   \resetpagebreak}

%D For those who've read the plain \TEX\ book, we provide the next macro:

\permanent\protected\def\bye
  {\writestatus\m!system{Sorry, you're not done yet, so no goodbye!}}

%D We define a few constants because that (1) provides some checking and (2) is
%D handier when aligning definitions (checks nicer). Most routines will use ard
%D codes names but sometimes we want to adapt, which is why we have these:

\definesystemconstant{page_otr_command_routine}
\definesystemconstant{page_otr_command_package_contents}
\definesystemconstant{page_otr_command_set_vsize}
\definesystemconstant{page_otr_command_set_hsize}
\definesystemconstant{page_otr_command_synchronize_hsize}
\definesystemconstant{page_otr_command_next_page}
\definesystemconstant{page_otr_command_next_page_and_inserts}
\definesystemconstant{page_otr_command_set_top_insertions}
\definesystemconstant{page_otr_command_set_bottom_insertions}
\definesystemconstant{page_otr_command_flush_top_insertions}
\definesystemconstant{page_otr_command_flush_bottom_insertions}
\definesystemconstant{page_otr_command_check_if_float_fits}
\definesystemconstant{page_otr_command_set_float_hsize}
\definesystemconstant{page_otr_command_flush_float_box}
\definesystemconstant{page_otr_command_side_float_output}
\definesystemconstant{page_otr_command_synchronize_side_floats}
\definesystemconstant{page_otr_command_flush_floats}
\definesystemconstant{page_otr_command_flush_side_floats}
\definesystemconstant{page_otr_command_flush_saved_floats}
\definesystemconstant{page_otr_command_flush_all_floats}
\definesystemconstant{page_otr_command_flush_margin_blocks}
\definesystemconstant{page_otr_command_test_column}

\definesystemconstant{singlecolumn}
\definesystemconstant{multicolumn}   % will move
\definesystemconstant{columnset}     % will move
\definesystemconstant{pagecolumn}    % will move

\defineoutputroutinecommand
  [\s!page_otr_command_routine,
   \s!page_otr_command_package_contents,
   \s!page_otr_command_set_vsize,
   \s!page_otr_command_set_hsize,
   \s!page_otr_command_synchronize_hsize, % for columns of different width
   \s!page_otr_command_next_page,
   \s!page_otr_command_next_page_and_inserts,
   \s!page_otr_command_set_top_insertions,
   \s!page_otr_command_set_bottom_insertions,
   \s!page_otr_command_flush_top_insertions,
   \s!page_otr_command_flush_bottom_insertions,
   \s!page_otr_command_check_if_float_fits,
   \s!page_otr_command_set_float_hsize,
   \s!page_otr_command_flush_float_box,
   \s!page_otr_command_side_float_output, % name will change as will hooks
   \s!page_otr_command_synchronize_side_floats,
   \s!page_otr_command_flush_floats,
   \s!page_otr_command_flush_side_floats,
   \s!page_otr_command_flush_saved_floats,
   \s!page_otr_command_flush_all_floats,
   \s!page_otr_command_flush_margin_blocks,
   \s!page_otr_command_test_column]

\appendtoks
    \setupoutputroutine[\s!singlecolumn]%
\to \everydump

\protect \endinput
