*! ver 1.5 2021-05-25
*  ver 1.4 2021-05-11
*  ver 1.3 2020-05-21
*  ver 1.2 2020-05-19
*  ver 1.1 2020-05-05
*  ver 1.0 2020-04-30




program define bunching, rclass
        version 14
		syntax varlist [if] [in] [fw] ///
		,  Kink(real) M(real) s0(real) s1(real) ///
		[ GENerate(name) PCTobs(integer 9999425) POLorder(integer 9999425) ///
		DELTAM(real 9999425) DELTAP(real 9999425) ///
		Grid(numlist min=1 max=99 sort) Numiter(integer 500) BINWidth(real 9999425) ///
		SAVINGTOBIT(string asis) SAVINGBOUNDS(string asis) ///
		NOPIC VERBOSE ]

		********************************************************************************************
		* 0. SETUP
		********************************************************************************************
		* 1. observations to use
		marksample touse
        qui count if `touse'
        if r(N) == 0 error 2000

		* variables to use
		tokenize `varlist'
		local y_i `1'
		macro shift
		local covariates `*'


		* 2.1 check REQUIRED inputs
		if `s0' <= `s1' {
				di as err "value of {bf:s0} must be bigger than {bf:s1}"
				exit 198
		}
		if `m' <= 0 | `m' == . {
				di as err "strictly positive value for {bf:m} is required to run the code"
				exit 198
		}

 		* 2.2 check OPTIONAL inputs
		* "deltap" or "deltam" are entered (they should not be not equal the default value)
		if `deltap' != 9999425 | `deltam' != 9999425 {
			if `deltap' < 0 | `deltam' < 0 {
				di as err "delta values must be weakly positive"
				exit 198
		}
		if `deltap' ==. | `deltam' ==. {
				di as err "must enter both delta values"
				exit 198
			}
		}

		** "perc:_obs" or "pol:order" are entered - check if "generate(newvar)" or "deltam(# real)" or "deltap(# real)" are entered
 		if `polorder' != 9999425 & `pctobs' != 9999425 {
			if missing("`generate'") | (`deltap' == 9999425 | `deltam' == 9999425)  {
				di as err "If you'd like to run the filter with custom polorder or pctobs, you must also provide all three options: deltam, deltap, and generate"
				exit 198
			}
		}
		if (`polorder' != 9999425 & `pctobs' == 9999425) | (`polorder' == 9999425 & `pctobs' != 9999425) {
			if missing("`generate'") | (`deltap' == 9999425 | `deltam' == 9999425)  {
				di as err "If you'd like to run the filter with custom polorder or pctobs, you must also provide all three options: deltam, deltap, and generate"
				exit 198
			}

			if `pctobs' == 9999425 local pctobs = 40
			if `polorder' == 9999425 local polorder = 7
		}


		** "perc:_obs" and "pol:order" not entered; any of "generate(newvar)" or "deltam(# real)" or "deltap(# real)" are entered
		if `polorder' == 9999425 & `pctobs' == 9999425 {
			if missing("`generate'") & (`deltap' != 9999425 | `deltam' != 9999425) {
				di as err "If you'd like to run the filter, you must provide all three options deltam, deltap, and generate"
				exit 198
			}
			if !missing("`generate'") & (`deltap' == 9999425 | `deltam' == 9999425) {
				di as err "If you'd like to run the filter, you must provide all three options deltam, deltap, and generate"
				exit 198
			}

			local polorder = 7
			local pctobs = 40
		}


		* 2.4 check file names
		* check if files already exist on a disk
		CheckSaveFileOpt `savingtobit'
			local savingtobit_filename =  "`s(filename)'"
		CheckSaveFileOpt `savingbounds'
			local savingbounds_filename =  "`s(filename)'"
		* check if saved files have different names
		if ("`savingbounds_filename'" == "`savingtobit_filename'") & !missing("`savingbounds_filename'") {
				di as err "File names for {bf:Bounds} and {bf:Tobit} are the same. Please use different file names."
				exit 198
		}


		* 2.4 bunchtobit checks
		if `numiter' <= 0 {
				di as err "option {bf:numiter()} incorrectly specified"
				exit 198
		}
		if `binwidth' <= 0  {
				di as err "option {bf:binwidth()} incorrectly specified: must be strictly bigger than 0"
				exit 198
		}
		* default grid size: 10(10)90
		if missing("`grid'") {
			forvalues t = 10 (10) 90 {
				local grid `grid' `t'
			}
		}


		* 2.6 leave other options to corresponding functions



		********************************************************************************************
		* 1. RUN FUNCTIONS
		********************************************************************************************
		* collect all results in r() via -return add-
		* 1. run bunchfilter
		if (`deltap' != 9999425 & `deltam' != 9999425) & !missing("`generate'") {
			* If deltap, deltam and generate are provided, bunchfilter is run and the newly generated variable will be then used
			* as dependent variable by bounds/tobit; otherwise bounds/tobit uses the user-entered dependent variable

			funcHeader "Filter"
			bunchfilter `y_i' [`weight'`exp'] `in' `if', gen(`generate') deltam(`deltam') deltap(`deltap') kink(`kink') pctobs(`pctobs') polorder(`polorder') `nopic' binwidth(`binwidth')
			if _rc != 0 exit
			local y_i `generate'

			* use computed binwidth from filter and use that in tobit
			if missing("`nopic'") local binwidth = r(binwidth)
			return add
		}


		* 2. run bunchbounds
		funcHeader "Bounds"
		bunchbounds `y_i' [`weight'`exp'] `in' `if', kink(`kink')  s0(`s0') s1(`s1') m(`m') `nopic' savingbounds(`savingbounds')
		return add


		* 3. run bunchtobit
		funcHeader "Tobit"
		bunchtobit `y_i' `covariates' [`weight'`exp'] `in' `if', kink(`kink') s0(`s0') s1(`s1') numiter(`numiter') grid(`grid') `nopic' binwidth(`binwidth') `verbose' savingtobit(`savingtobit')
		if _rc != 0 exit
		return add

end


program CheckSaveFileOpt, sclass
/* parse the contents of the -saving- option:
 * saving(filename [, replace])
 * check if files exists
 */
	version 10
	syntax [anything] [, replace ]

	* find -replace- option
	if `"`replace'`anything'"' != "" {
		if 0`:word count `anything'' > 2 {
			di as err "option {bf:saving()} incorrectly specified"
			exit 198
		}
	}
	local filename `anything'
	local replace `replace'


	* check if file exists
	if "`filename'" != "" {
		* add dta extention
		local extpos = strpos("`filename'", ".dta")
		if (`extpos' == 0) local filename = "`filename'.dta"

		* raise an error in case a file exists
		if "`replace'" == ""  {
			* find the file in the location specified
			cap confirm file "`filename'"

			* raise an error in case a file exists
			if !_rc {
				di as err "file {bf:`filename'} already exists. Use " `"""' "replace" `"""' " option to overwrite the file"
				exit 602
			}
		}
	}

	sreturn clear
	sreturn local filename `"`filename'"'
	sreturn local replace `replace'
end

program funcHeader,
/* displays a header before each function run
 */
	version 8
	syntax [anything]

	di as text ""
	di as text "***********************************************"
	di as text "Bunching - "`anything'
	di as text "***********************************************"
	di as text ""

end