*! version 1.1.0  05nov2008  Ben Jann

program define eststo, byable(onecall)
    version 8.2
    local caller : di _caller()
// --- eststo clear ---
    if `"`1'"'=="clear" {
        if `"`0'"'!="clear" {
            di as err "invalid syntax"
            exit 198
        }
        if "`_byvars'"!="" error 190
        _eststo_clear
        exit
    }
// --- update globals ---
    _eststo_cleanglobal
// --- eststo dir ---
    if `"`1'"'=="dir" {
        if `"`0'"'!="dir" {
            di as err "invalid syntax"
            exit 198
        }
        if "`_byvars'"!="" error 190
        _eststo_dir
        exit
    }
// --- eststo drop ---
    if `"`1'"'=="drop" {
        if "`_byvars'"!="" error 190
        _eststo_`0'
        exit
    }
// --- eststo store (no by) ---
    if "`_byvars'"=="" {
        version `caller': _eststo_store `0'
        exit
    }
// --- eststo store (by) ---
// - check sorting
    local sortedby : sortedby
    local i 0
    foreach byvar of local _byvars {
        local sortedbyi : word `++i' of `sortedby'
        if "`byvar'"!="`sortedbyi'" error 5
    }
// - parse command on if qualified
    capt _on_colon_parse `0'
    if _rc error 190
    if `"`s(after)'"'=="" error 190
    local estcom `"`s(after)'"'
    local 0 `"`s(before)'"'
    if substr(trim(`"`estcom'"'),1,3)=="svy" {
        di as err "svy commands not allowed with by ...: eststo:"
        exit 190
    }
    AddBygrpToIfqualifier `estcom'
// - parse syntax of _eststo_store call in order to determine
//   whether title() or missing was specified (note that
//   -estimates change- cannot be used to set the titles since
//   it does not work with -noesample-)
    TitleAndMissing `0'
// - generate byindex
    tempname _byindex
    qui egen long `_byindex' = group(`_byvars'), label `missing'
    qui su `_byindex', meanonly
    if r(N)==0 error 2000
    local Nby = r(max)
// - loop over bygroups
    forv i = 1/`Nby' {
        local ibylab: label (`_byindex') `i'
        di as txt _n "{hline}"
        di as txt `"-> `ibylab'"'   // could be improved
        if `titleopt'==0 local ibytitle
        else if `titleopt'==1 local ibytitle `" title(`ibylab')"'
        else if `titleopt'==2 local ibytitle `", title(`ibylab')"'
        capture noisily {
            version `caller': _eststo_store `0'`ibytitle' : `estcmd'
        }
        if _rc {
            if "`_byrc0'"=="" error _rc
        }
    }
end

prog TitleAndMissing
    capt syntax [anything] , Title(string) [ MISsing * ]
    if _rc==0 {
        c_local titleopt 0
        c_local missing "`missing'"
    }
    else {
        syntax [anything] [ , MISsing * ]
        if `"`missing'`options'"'!="" c_local titleopt 1
        else c_local titleopt 2
        c_local missing "`missing'"
    }
end

program AddBygrpToIfqualifier
    syntax anything(equalok) [if/] [in] [using] [fw aw pw iw] [, * ]
    local estcom `"`macval(anything)' if (\`_byindex'==\`i')"'
    if `"`macval(if)'"'!="" {
        local estcom `"`macval(estcom)' & (`macval(if)')"'
    }
    if `"`macval(in)'"'!="" {
        local estcom `"`macval(estcom)' `macval(in)'"'
    }
    if `"`macval(using)'"'!="" {
        local estcom `"`macval(estcom)' `macval(using)'"'
    }
    if `"`macval(weight)'"'!="" {
        local estcom `"`macval(estcom)' [`macval(weight)'`macval(exp)']"'
    }
    if `"`macval(options)'"'!="" {
        local estcom `"`macval(estcom)', `macval(options)'"'
    }
    c_local estcmd `"`macval(estcom)'"'
end

program define _eststo_clear
    local names $eststo
    foreach name of local names {
        capt estimates drop `name'
    }
    global eststo
    global eststo_counter
end

program define _eststo_dir
    if `"$eststo"'!="" {
        estimates dir $eststo
    }
end

program define _eststo_cleanglobal
    local enames $eststo
    if `"`enames'"'!="" {
        tempname hcurrent
        _return hold `hcurrent'
        qui _estimates dir
        local snames `r(names)'
        _return restore `hcurrent'
    }
    local names: list enames & snames
    global eststo `names'
    if "`names'"=="" global eststo_counter
end

program define _eststo_drop
    local droplist `0'
    if `"`droplist'"'=="" {
        di as error "someting required"
        exit 198
    }
    local names $eststo
    foreach item of local droplist {
        capt confirm integer number `item'
        if _rc {
            local dropname `item'
        }
        else {
            if `item'<1 {
                di as error "`item' not allowed"
                exit 198
            }
            local dropname est`item'
        }
        local found 0
        foreach name in `names' {
            if match("`name'",`"`dropname'"') {
                local found 1
                estimates drop `name'
                local names: list names - name
                di as txt "(" as res "`name'" as txt " dropped)"
            }
        }
        if `found'==0 {
            di as txt "(no matches found for " as res `"`dropname'"' as txt ")"
        }
    }
    global eststo `names'
end


program define _eststo_store, eclass
    local caller : di _caller()
    capt _on_colon_parse `0'
    if !_rc {
        local command `"`s(after)'"'
        local 0 `"`s(before)'"'
    }
    syntax [name] [, ///
        Title(passthru) ///
        Prefix(name) ///
        Refresh Refresh2(numlist integer max=1 >0) ///
        ADDscalars(string asis) ///
        noEsample ///
        noCopy ///
        MISsing svy /// doesn't do anything
        ]
    if `"`prefix'"'=="" local prefix "est"

// get previous eststo names and counter
    local names $eststo
    local counter $eststo_counter
    if `"`counter'"'=="" local counter 0

// if name provided; set refresh on if name already in list
    if "`namelist'"!="" {
        if "`refresh2'"!="" {
            di as error "refresh() not allowed"
            exit 198
        }
        local name `namelist'
        if `:list name in names' local refresh refresh
        else {
            if "`refresh'"!="" {
                di as txt "(" as res "`name'" as txt " not found)"
            }
            local refresh
        }
        if "`refresh'"=="" local ++counter
    }
// if no name provided
    else {
        if "`refresh2'"!="" local refresh refresh
        if "`refresh'"!="" {
// refresh2 not provided => refresh last (if available)
            if "`refresh2'"=="" {
                if "`names'"=="" {
                    di as txt "(nothing to refresh)"
                    local refresh
                }
                else local name: word `:list sizeof names' of `names'
            }
// refresh2 provided => check availability
            else {
                if `:list posof "`prefix'`refresh2'" in names' {
                    local name `prefix'`refresh2'
                }
                else {
                    di as txt "(" as res "`prefix'`refresh2'" as txt " not found)"
                    local refresh
                }
            }
        }
        if "`refresh'"=="" local ++counter
// set default name
        if "`name'"=="" local name `prefix'`counter'
    }

// run estimation command if provided
    if `"`command'"'!="" {
        version `caller': `command'
    }

// add scalars to e()
    if `"`addscalars'"'!="" {
        capt ParseAddscalars `addscalars'
        if _rc {
            di as err `"addscalars() invalid"'
            exit 198
        }
        if "`replace'"=="" {
            local elist `: e(scalars)' `: e(macros)' `: e(matrices)' `: e(functions)'
        }
        local forbidden b V sample
        while (1) {
            gettoken lhs rest: rest
            if `:list lhs in forbidden' {
                di as err `"`lhs' not allowed in addscalars()"'
                exit 198
            }
            if "`replace'"=="" {
                if `:list lhs in elist' {
                    di as err `"e(`lhs') already defined"'
                    exit 110
                }
            }
            gettoken rhs rest: rest, bind
            capt eret scalar `lhs' = `rhs'
            if _rc {
                di as err `"addscalars() invalid"'
                exit 198
            }
            capture local result = e(`lhs')
            di as txt "(e(" as res `"`lhs'"' as txt ") = " ///
             as res `result' as txt " added)"
            if `"`rest'"'=="" continue, break
        }
    }
// add e(cmd) if missing
    if `"`e(cmd)'"'=="" {
        if `"`: e(scalars)'`: e(macros)'`: e(matrices)'`: e(functions)'"'!="" {
            eret local cmd "."
        }
    }

// store estimates with e(sample)
    estimates store `name' , `copy' `title'

// remove e(sample) if -noesample- specified
    if "`esample'"!="" {
        capt confirm new var _est_`name'
        if _rc {
            tempname hcurrent
            _est hold `hcurrent', restore estsystem nullok
            qui replace _est_`name' = . in 1
            _est unhold `name'
            capt confirm new var _est_`name'
            if _rc qui drop _est_`name'
            else {
                di as error "somethings wrong; please contact author of -eststo- " ///
                 "(see e-mail in help {help eststo})"
                exit 498
            }
            _est hold `name', estimates varname(_est_`name')
                // varname() only needed so that _est hold does not return error
                // if variable `name' exists
        }
    }

// report
    if "`refresh'"=="" {
        global eststo `names' `name'
        global eststo_counter `counter'
        if `"`namelist'"'=="" {
            di as txt "(" as res "`name'" as txt " stored)"
        }
    }
    else {
        if `"`namelist'"'=="" {
            di as txt "(" as res "`name'" as txt " refreshed)"
        }
    }
end

program ParseAddscalars
    syntax anything [ , Replace ]
    c_local rest `"`anything'"'
    c_local replace `replace'
end