*! supershape v2.0.1 Jeremy Nighohossian 19jun2014
*! this command is an enhanced reshape command that enables retaining labels
*!   and filling in zeros

*Note: As this is a complicated program, it's pretty messy, and I haven't yet
*      cleaned it up.

*v2.0.0 fixed some issues with going wide when j is the same as labels
*		also, fixed variable naming to try to avoid errors
*v2.0.01 removed hyphen at end of variable labels
*v2.0.2 added check for the labels variable being a numeric variable w/ value label
*v2.1 modify to split up variable names and labels for ease and to correct character problems
*v2.1.01 fix mis-aligned labels issue
*v2.2 revamped labels and variables for labels option
*v2.2.1 changed temp file to circumvent access restrictions and gave ability to work through
*		missing values
*v2.2.5 revamped labels and variables for keep option
*v2.2.6 27feb2018 addressed issues with supershape long and labels stemming from vartype of _j
*v2.2.7 30mar2020 - added some error handling for when values are not unique when rshaping wide
*		and when creating varnames that will not be unique even though values are

program supershape
syntax anything, i(varlist) [j(namelist) KEEPlabels LABels(varname) Fill Zeros String]

local debug=0
tempname wl
tempvar labs jvar labelvar
tempname fdir labelfile

local maxstub=0
local badchars ", - @ & . / '"

if `debug' n display _n "Debugging supershape command"

*In case of error, revert to original dataset
tempfile orig
qui save `orig'

n capture {

*Getting arguments
local wl=substr("`anything'",1,4) //reshape wide or long
if !inlist("`wl'","wide","long") {
	n display as error "Must designate wide/long"
	exit
}
local stub_text=substr("`anything'",6,.) // stubs
qui describe `stub_text'*, varlist
local stubs = r(varlist)
local stubcount: list sizeof stubs	// number of stubs
local maxstub=0
foreach stub of local stubs {
	local temp=length("`stub'")
	if `temp'>`maxstub' local maxstub=`temp'
}

if `maxstub'>10 & "`wl'"=="wide" {
//	display "`maxstub'"
//	n display as error "Please use a shorter stub"
//	exit
}
if "`zeros'"=="zeros" & "`fill'"=="" {
	local fill="zeros"
}

n display as result	_n "`stubcount' stub(s): `stubs'" ///
			_n	"Longest stub is `maxstub' characters"

*Preliminary Error Checking
local error=0
if "`wl'"=="wide" & "`j'"=="" local error=1
if "`j'"!="" & "`wl'"=="wide" {
	local j_count: list sizeof j
	if `j_count'>1 local error=2
	local labels_count: list sizeof labels
	if `labels_count'>1 local error=3

	local j_str=0
	local j_type: type `j'
	local j_str=substr("`j_type'",1,3)=="str"
	if `j_str' count if `j'==""
	if !`j_str' count if `j'==.
	if r(N)>0 local error=4

	*Error checking for unique-ness
	if (r(N)>r(unique_value)) local error=5
}

	if `error'==1 n display as error "j option must be populated when reshaping wide"
	if `error'==2 n display as error "supershape cannot handle more than 1 variable in j"
	if `error'==3 n display as error "supershape cannot handle more than 1 variable in labels option"
	if `error'==4 n display as error "`j' contains missing values; please remove or replace"
	if `error'==5 n display as error "Values in `i' `j' not unique, please re-specify or modify data"

if `error' exit
*Pre-processing

**Reshape Wide
***Data types for j and labels
if "`wl'"=="wide" {
	if `debug' n display "Preparing variables for reshape wide"
	tempvar j_temp labels_temp
	local j_labels:	value label `j'
	*If j is str, all is good
	if `j_str' g `j_temp'=`j'
	*If j is numeric w/o value labels, string it
	if !`j_str' qui tostring(`j'), g(`j_temp')
	*If j is numeric w/ value labels, decode it
	foreach char of local badchars {
		qui replace `j_temp'=subinstr(`j_temp',"`char'","",.)
	}
	qui replace `j_temp'=subinstr(`j_temp'," ","_",.)
	qui replace `j_temp'=substr(`j_temp',1,32-`maxstub')

	*Check for duplicate values now that names are abbreviated
	qui duplicates report `i' `j_temp'
	if r(N)>r(unique_value) local error=5
	if `error'==5 {
		tempvar jind_full j_uniq jind jind_max
		bysort `j': g `j_uniq'=_n==1
		g `jind'=_n
		bysort `j_temp' (`jind'): g `jind_full'=sum(`j_uniq')
		bysort `j_temp': egen `jind_max'=max(`jind_full')
		replace `j_temp'=substr(`j_temp',1,31-`maxstub')+string(`jind_full') if inrange(`jind_max',2,9)
	}

	if `labels_count' {
	if `debug' n display "Preparing labels for reshape wide"
		local labels_str=0
		local labels_type: type `labels'
		local labels_str=substr("`labels_type'",1,3)=="str"
		local labels_labels:	value label `labels'
		*If j is str, all is good
		if `labels_str' g `labels_temp'=`labels'
		*If j is numeric w/o value labels, string it
		if !`labels_str' & "`labels_labels'"=="" qui tostring(`labels'), g(`labels_temp')
		*If j is numeric w/ value labels, decode it
		if !`labels_str' & "`labels_labels'"!="" decode `labels', g(`labels_temp')
	}
}

tokenize "`stubs'"

*For reshape long: code to retain labels
if "`keeplabels'"!="" {
	if `debug' n display "saving labels"
	local `fdir'=c(tmpdir)
	if `debug' n display "File saved ``fdir''/`labelfile'.txt"
	n display "Stubcount is `stubcount'"
	file open lblfl using ``fdir''/`labelfile'.txt, write replace
	forv s=1/`stubcount' {
		if `debug' n display "Working on Stub ``s''"
		local stub="``s''"
		local stub=subinstr("``s''","@","*",1)
		foreach var of varlist `stub'* {
			local `var': var label `var'
			display "`stub'"
			local var2=regexr("`var'","^`stub_text'","")
			n display "`var'|``var''" _n
			file write lblfl "`var2'|``var''" _n
		}
	}
	file close lblfl
	preserve
		import delimited using "``fdir''/`labelfile'.txt", delimiter("|") clear
		rename (v1 v2) (_j lbl)
		local jtype: type _j
		if substr("`jtype'",1,3)=="str" drop if _j==""
		else drop if _j==.
		duplicates drop
		save lblfl, replace
	restore
	n display "`varlabels'"
}

*Reshape Wide: Prepare variable names & Save labels for later application
if "`wl'"=="wide" {
if `debug' n display "Saving labels file"
preserve
	if `labels_count' keep `j_temp' `labels_temp'
		else keep `j_temp'
	qui duplicates drop
	local `fdir'=c(tmpdir)
	qui export delimited ``fdir''/`labelfile'.txt, delimiter("|") replace
restore
}

if `debug' n display "List of variables: `varsmacro'"

if `debug' n display "Reshaping..."
keep `stubs' `i' `j_temp'
if substr("`jtype'",1,3)=="str" local string="string"
if "`j'"=="" {
	n display _n "reshape `wl' `stub_text', i(`i') `string'"  _n
	n reshape `wl' `stub_text', i(`i') "`string'"
}
else {
	n display _n "reshape `wl' `stub_text', i(`i') j(`j_temp') `string'" _n
	capture reshape `wl' `stub_text', i(`i') j(`j_temp') string
	if `debug' n display "Error Handling"
	*Error Handling
	if (_rc==9) {
		n display as error "Error when values converted to variable names. Try encoding `j' and setting original to label."
	}
	else {
		if (_rc>0) n display as error "Error with Reshape. Review Setup and try again."
	}
	qui compress
}

if `debug' n display "Reshaping Complete"

if "`keeplabels'"!="" {
	n display "Looping through `stubcount' stubs"
	forval s=1/`stubcount' {
		n display "On Stub `s' cleaning up ``s''"
	}
	merge m:1 _j using lblfl, nogen
	erase ``fdir''/`labelfile'.txt
}

if "`wl'"=="long" & "`j'"!="" rename _j `j'

if "`labels'"!="" {
	file open labels_file using ``fdir''/`labelfile'.txt, read text
	file read labels_file line_red
	while r(eof)==0 {
		file read labels_file line_red
		local temp_var=substr("`line_red'",1,strpos("`line_red'","|")-1)
		local temp_label=substr("`line_red'",strpos("`line_red'","|")+1,.)
		//if `debug' n display "label var `temp_var' `temp_label'"
		capture label var `stub_text'`temp_var' `"`temp_label'"'
	}
	file close labels_file
	erase ``fdir''/`labelfile'.txt
	if `debug' n display "Labels Applied"
}

*fill missing numerical variables with zeros
if "`wl'"=="wide" & "`fill'"!="" {
	forv s=1/`stubcount' {
		foreach var of varlist `stub_text'* {
			local vtype: type `var'
			if substr("`vtype'",1,3)!="str" qui replace `var'=0 if `var'==.
		}
	}
}
}

if !`debug' & _rc use `orig', replace
end