#delim ;
program define ltop, sortpreserve;
version 16.0;
*
 Input a weight expression containing line weights
 and (optionally) a list of internal by-variables
 and output a numeric variable containing page sequence numbers.
*!Author: Roger Newson
*!Date: 24 April 2023
*;

syntax newvarname  [if] [in] [fweight pweight aweight iweight /]
  [ , MAXlperp(numlist min=1 max=1 >0) by(varlist) iby(varlist) ];
/*
  maxlperp() gives maximum number of lines per page.
  by() specifies a varlist specifying by-groups,
    within which page numbers are defined.
  iby() specifies a varlist specifying inner by-groups,
    which must be within pages and not split between pages,
    and which defaults to the virtual variable _n.
*/

*
 Set defaults
*;
if `"`exp'"'=="" local exp "1";
if "`maxlperp'"=="" local maxlperp=50;

*
 Mark sample and sort by to-use indicator and by-group
*;
marksample touse, novarlist;
sort `touse' `by', stable;

*
 Set default iby() iption
*;
if "`iby'"=="" {;
  tempvar ibyvar;
  qui {;
   by `touse' `by': gene long `ibyvar'=_n if `touse';
    compress `ibyvar';
  };
  local iby "`ibyvar'";
};

*
 Sort dataset by to-use indicator, by-group, and iby-group
*;
sort `touse' `by' `iby', stable;

*
 Generate sum of weights variable by iby-group,
 and fail if it is ever greater than maxlperp() option.
*;
tempvar sumwtvar;
qui by `touse' `by' `iby': egen `sumwtvar'=total(`exp') if `touse';
cap assert `sumwtvar'<=`maxlperp' if `touse';
if _rc {;
  disp as error
    "At least 1 iby-group total weight"
    _n "is greater than the maximum lines per page of:"
    _n "`maxlperp'";
  error 498;
};

*
 Generate iby-group first-observation indicator
 and iby-group start-observation position within by-group.
*;
tempvar ifirst istart;
qui {;
  by `touse' `by' `iby': gene byte `ifirst'=_n==1 if `touse';
  by `touse' `by': gene long `istart'=_n if `touse' & `ifirst';
  compress `istart';
  by `touse' `by' `iby': replace `istart'=`istart'[1] if `touse';
};

*
 Create cumulative sum of weights variable (within page),
 new page indicator variable,
 and page sequence number variable.
*;
* Cumulative sum of weights variable (within page) *;
tempvar cumwtvar;
qui {;
  gene double `cumwtvar'=.;
  by `touse' `by': replace `cumwtvar'=cond(_n==1,
    `sumwtvar',
    cond(`ifirst',
      cond(`sumwtvar'+`cumwtvar'[_n-1]>`maxlperp',
        `sumwtvar',
        `sumwtvar'+`cumwtvar'[_n-1]
        ),
      `cumwtvar'[`istart']
      )
    )
    if `touse';
  compress `cumwtvar';
};
* New page indicator variable *;
tempvar newpagevar;
qui {;
  gene byte `newpagevar'=0 if `touse';
  by `touse' `by': replace `newpagevar'=cond(_n==1,
       1,
       cond(`ifirst' & (`sumwtvar'+`cumwtvar'[_n-1]>`maxlperp'),
         1,
         0
       )
     )
     if `touse';
};
* Page number variable *;
tempvar pagenumvar;
qui {;
  by `touse' `by': gene double `pagenumvar'=sum(`newpagevar') if `touse';
  compress `pagenumvar';
};

*
 Rename page number variable
*;
rename `pagenumvar' `varlist';
lab var `varlist' "Page number";

end;