Introduction
The goal of this project is to predict the number of cases of wine sold based on chemical characteristics of close to 13,000 wines. This report will cover the following processes: * Cleaning, standardizing, and exploring the data * Development of each of the following models * Poisson Regression * Zero-Inflated Poisson Regression * Negative Binomial Regression * Zero-Inflated Negative Binomial Regression * Multiple Linear Regression * Comparison of each model * Code to deploy the model on the test data set All code and data for this paper can be found here. # Cleaning and Exploring the Data The exploration and cleaning step involves creating flag variables for missing values and outliers, deleting the outliers, and use regression trees to impute the remaining values.
The STARS
field has the most missing data while several chemical measurements are missing 5-10% of values. In case the absence of data is an important factor in sales, a binary flag variable is produced to identify places where data was initially missing from each variable.
package <U+393C><U+3E31>ggplot2<U+393C><U+3E32> was built under R version 3.4.2

Outliers, defined as \(Q1 - (1.5*IQR)\) and \(Q3 + (1.5*IQR)\) are removed from the data set and flag variables are appended in a similar fashion to the missing data flags. Finally, all missing and deleted variables are reimputed using regression trees.
Finally, we can examine the distributions of our variables. Flags with homogenous values are removed from the training set to get a jump start on stepwise dimension reduction used in the model development stage.
fixedacidity volatileacidity citricacid residualsugar chlorides freesulfurdioxide totalsulfurdioxide
Min. :-2.405343 Min. :-2.36238 Min. :-2.406982 Min. :-2.432242 Min. :-2.3748801 Min. :-2.518626 Min. :-2.744691
1st Qu.:-0.325198 1st Qu.:-0.29839 1st Qu.:-0.361933 1st Qu.:-0.241350 1st Qu.:-0.1642604 1st Qu.:-0.283968 1st Qu.:-0.455171
Median :-0.038940 Median :-0.09039 Median : 0.003254 Median :-0.104133 Median :-0.0517827 Median :-0.030304 Median : 0.017829
Mean : 0.001865 Mean :-0.00449 Mean : 0.001676 Mean :-0.001509 Mean : 0.0002116 Mean : 0.003729 Mean : 0.003165
3rd Qu.: 0.399990 3rd Qu.: 0.43761 3rd Qu.: 0.353834 3rd Qu.: 0.357829 3rd Qu.: 0.2640202 3rd Qu.: 0.295835 3rd Qu.: 0.415349
Max. : 2.499219 Max. : 2.51761 Max. : 2.398883 Max. : 2.475539 Max. : 2.4010968 Max. : 2.554652 Max. : 2.704869
density ph sulphates alcohol labelappeal acidindex stars
Min. :-2.510270 Min. :-2.620407 Min. :-2.338310 Min. :-2.993770 Min. :-2.23427 Min. :-2.62836 Min. :-1.15425
1st Qu.:-0.260398 1st Qu.:-0.407492 1st Qu.:-0.289601 1st Qu.:-0.455298 1st Qu.:-1.11205 1st Qu.:-0.60274 1st Qu.:-1.15425
Median : 0.020346 Median :-0.009859 Median :-0.057414 Median :-0.037196 Median : 0.01017 Median : 0.41008 Median :-0.04626
Mean : 0.002849 Mean :-0.001639 Mean : 0.003905 Mean : 0.002461 Mean : 0.00000 Mean : 0.01698 Mean :-0.02669
3rd Qu.: 0.288033 3rd Qu.: 0.422351 3rd Qu.: 0.379644 3rd Qu.: 0.530227 3rd Qu.: 1.13240 3rd Qu.: 0.41008 3rd Qu.: 1.06172
Max. : 2.500037 Max. : 2.635267 Max. : 2.401037 Max. : 3.098564 Max. : 2.25462 Max. : 2.43571 Max. : 2.16970
residualsugar_na.flag chlorides_na.flag freesulfurdioxide_na.flag totalsulfurdioxide_na.flag ph_na.flag sulphates_na.flag
Min. :0.00000 Min. :0.00000 Min. :0.00000 Min. :0.0000 Min. :0.00000 Min. :0.00000
1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:0.0000 1st Qu.:0.00000 1st Qu.:0.00000
Median :0.00000 Median :0.00000 Median :0.00000 Median :0.0000 Median :0.00000 Median :0.00000
Mean :0.04814 Mean :0.04986 Mean :0.05057 Mean :0.0533 Mean :0.03087 Mean :0.09457
3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:0.0000 3rd Qu.:0.00000 3rd Qu.:0.00000
Max. :1.00000 Max. :1.00000 Max. :1.00000 Max. :1.0000 Max. :1.00000 Max. :1.00000
alcohol_na.flag stars_na.flag fixedacidity_out.flag volatileacidity_out.flag citricacid_out.flag residualsugar_out.flag
Min. :0.00000 Min. :0.0000 Min. :0.00000 Min. :0.00000 Min. :0.00000 Min. :0.0000
1st Qu.:0.00000 1st Qu.:0.0000 1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:0.0000
Median :0.00000 Median :0.0000 Median :0.00000 Median :0.00000 Median :0.00000 Median :0.0000
Mean :0.05104 Mean :0.2625 Mean :0.04158 Mean :0.05487 Mean :0.05557 Mean :0.1254
3rd Qu.:0.00000 3rd Qu.:1.0000 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:0.0000
Max. :1.00000 Max. :1.0000 Max. :1.00000 Max. :1.00000 Max. :1.00000 Max. :1.0000
chlorides_out.flag freesulfurdioxide_out.flag totalsulfurdioxide_out.flag density_out.flag ph_out.flag sulphates_out.flag
Min. :0.00000 Min. :0.0000 Min. :0.00000 Min. :0.0000 Min. :0.00000 Min. :0.00000
1st Qu.:0.00000 1st Qu.:0.0000 1st Qu.:0.00000 1st Qu.:0.0000 1st Qu.:0.00000 1st Qu.:0.00000
Median :0.00000 Median :0.0000 Median :0.00000 Median :0.0000 Median :0.00000 Median :0.00000
Mean :0.08761 Mean :0.1733 Mean :0.02876 Mean :0.1699 Mean :0.03181 Mean :0.05619
3rd Qu.:0.00000 3rd Qu.:0.0000 3rd Qu.:0.00000 3rd Qu.:0.0000 3rd Qu.:0.00000 3rd Qu.:0.00000
Max. :1.00000 Max. :1.0000 Max. :1.00000 Max. :1.0000 Max. :1.00000 Max. :1.00000
alcohol_out.flag acidindex_out.flag index target
Min. :0.00000 Min. :0.00000 Min. : 1 Min. :0.000
1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.: 4038 1st Qu.:2.000
Median :0.00000 Median :0.00000 Median : 8110 Median :3.000
Mean :0.01844 Mean :0.04103 Mean : 8070 Mean :3.029
3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:12106 3rd Qu.:4.000
Max. :1.00000 Max. :1.00000 Max. :16129 Max. :8.000
Model Development
Five models are developed for this paper. First is a Poisson regression model followed by a negative binomial regression model. Then zero-inflated versions of each model are developed as well as a multivariate linear regression model. Root mean squared error (RMSE), and mean absolute error (MAE) are calculated for each model for purposes of out of sample model evaluation.
Poisson and Negative Binomial Regression
Poisson regression models use the log link function to approximate regression processes for a count variable distributed such that the variance is equal to the mean . The backward stepwise feature selection algoritm returned a poisson model with twelve predictors. Noteably, missing values for stars
and outlier acidindex
values significantly harm sales while labelappeal
and present higher values for stars
led to higher sales. Negative binomial models use a different probability density function than Poisson regression to account for over-dispersion of the target variable (as we have in this case). The coefficients for this model are identical to that of a Poisson model.

Zero-Inflated Regression
Poisson and negative binomial models can be skewed by an overabundance of zero values. Zero-inflated models assumethe distribution has two types of values - rightful zero measurements and a separate set of values that follows a more typical distribution. These models first sort values into their proper category, then predict their outcomes using separate sets of coefficients for each. In this case, over 2,500 wines sold zero cases so zero-inflated models may improve predictive power. The same variables are used in the zero inflated models as the initial models. In this case, some of the coefficients are flipped. Missing ratings and outlier acidity values are strong positive influences on sales. Label appeal is similarly positive as in previous models, but actual star ratings are negative. Again, the coefficients for poisson and negative binomial models are identical.

Multiple Regression
The final model attempted is multiple linear regression. A new stepwise selection algorithm is used to select model variables. The same variables with relatively large coefficients in the poisson and negative binomial models stand out in the linear regression model, although additional variables with smaller coefficients are included as well.

Model Evaluation
To assess the accuracy of the various models, each model is repeatedly trained on random samples of the data and tested out of sample. Iterative resampling is used to measure the error rates of each model and attempt to account for uncertainty in error measurements by producing distributions of possible error measures rather than single point estimates. The distributions of MAE and RMSE for each model are presented below. Similar to the above coefficients, the error rates for the poisson and negative binomial models are identical with the two zero-inflated models strongly outperforming the non-adjusted models. Multiple regression also made a strong showing, but did not improve predictive accuracy compared to the two zero-inflated models. Of the two winning models, zero-inflated negative binomial is preferred due to the slight overdispersion of the target variable.

Implementation
The following function applies the zero-inflated negative binomial regression model to out of sample data and generates a CSV file in the proper submission format.
apply_model <- function(csv){
train.raw <- read_csv(csv)
colnames(train_raw) <- tolower(colnames(train_raw))
train.flagged <- train_raw%>%
mutate_all(funs(na.flag = ifelse(is.na(.),1,0)))
int_df <- train.flagged%>%
dplyr::select(-index, -target, -index_na.flag, -target_na.flag)%>%
dplyr::select_if(is.numeric)
cleaned_cols <- list()
for(c in colnames(train_raw%>%
dplyr::select(-index, -target)%>%
dplyr::select_if(is.numeric))){
column <- train.flagged%>%select_(col = c)
iqr <- quantile(column$col, na.rm = T)[4] - quantile(column$col, na.rm = T)[2]
low <- quantile(column$col, na.rm = T)[2] - iqr
high <- quantile(column$col, na.rm = T)[4] + iqr
vals <- c()
for(i in seq(1:nrow(int_df))){
ifelse(between(column$col[i], low - (1.5*iqr), high + (1.5*iqr)),
vals[i] <- column$col[i],
ifelse(is.na(column$col[i]), vals[i] <- NA, vals[i] <- NA))
}
ifelse(length(vals) == nrow(int_df),
cleaned_cols[[c]] <- vals,
cleaned_cols[[c]] <- c(vals,NA))
}
df2 <- bind_cols(
bind_cols(cleaned_cols)%>%
scale(center = TRUE)%>%
data.frame(),
train.flagged%>%
dplyr::select(ends_with('na.flag'))%>%
dplyr::select(-index_na.flag, -target_na.flag)
)
df3 <- df2%>%
mutate(
fixedacidity_out.flag = ifelse(is.na(fixedacidity) & fixedacidity_na.flag ==0,1,0),
volatileacidity_out.flag = ifelse(is.na(volatileacidity) & volatileacidity_na.flag ==0,1,0),
citricacid_out.flag = ifelse(is.na(citricacid) & citricacid_na.flag ==0,1,0),
residualsugar_out.flag = ifelse(is.na(residualsugar) & residualsugar_na.flag ==0,1,0),
chlorides_out.flag = ifelse(is.na(chlorides) & chlorides_na.flag ==0,1,0),
freesulfurdioxide_out.flag = ifelse(is.na(freesulfurdioxide) & freesulfurdioxide_na.flag ==0,1,0),
totalsulfurdioxide_out.flag = ifelse(is.na(totalsulfurdioxide) & totalsulfurdioxide_na.flag ==0,1,0),
density_out.flag = ifelse(is.na(density) & density_na.flag ==0,1,0),
ph_out.flag = ifelse(is.na(ph) & ph_na.flag ==0,1,0),
sulphates_out.flag = ifelse(is.na(sulphates) & sulphates_na.flag ==0,1,0),
alcohol_out.flag = ifelse(is.na(alcohol) & alcohol_na.flag ==0,1,0),
labelappeal_out.flag = ifelse(is.na(labelappeal) & labelappeal_na.flag ==0,1,0),
acidindex_out.flag = ifelse(is.na(acidindex) & acidindex_na.flag ==0,1,0),
stars_out.flag = ifelse(is.na(stars) & stars_na.flag ==0,1,0)
)
library(mice)
temp_df <- mice(df3, method = 'cart', maxit = 1)
train <- complete(temp_df)%>%
bind_cols(train_raw%>%dplyr::select(index))%>%
dplyr::select(-stars_out.flag, -labelappeal_out.flag, -density_na.flag,
-labelappeal_na.flag, -acidindex_na.flag, -fixedacidity_na.flag,
-volatileacidity_na.flag, -citricacid_na.flag)
return(data.frame(
index = train$index,
target_p = predict(zinng.mod, newdata = train)
)
)
}
test <- apply_model('Wine_Random_Test.csv')
write.csv(test, 'yazman.csv', row.names = FALSE)
Citations
LS0tDQp0aXRsZTogIlByZWRpY3QgNDEzIEZpbmFsOiBXaW5lIFNhbGVzIFByZWRpY3Rpb25zIg0KYXV0aG9yOiAnSm9zaCBZYXptYW4nDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIEludHJvZHVjdGlvbg0KVGhlIGdvYWwgb2YgdGhpcyBwcm9qZWN0IGlzIHRvIHByZWRpY3QgdGhlIG51bWJlciBvZiBjYXNlcyBvZiB3aW5lIHNvbGQgYmFzZWQgb24gY2hlbWljYWwgY2hhcmFjdGVyaXN0aWNzIG9mIGNsb3NlIHRvIDEzLDAwMCB3aW5lcy4gVGhpcyByZXBvcnQgd2lsbCBjb3ZlciB0aGUgZm9sbG93aW5nIHByb2Nlc3NlczoNCiogQ2xlYW5pbmcsIHN0YW5kYXJkaXppbmcsIGFuZCBleHBsb3JpbmcgdGhlIGRhdGENCiogRGV2ZWxvcG1lbnQgb2YgZWFjaCBvZiB0aGUgZm9sbG93aW5nIG1vZGVscw0KICAqIFBvaXNzb24gUmVncmVzc2lvbg0KICAqIFplcm8tSW5mbGF0ZWQgUG9pc3NvbiBSZWdyZXNzaW9uDQogICogTmVnYXRpdmUgQmlub21pYWwgUmVncmVzc2lvbg0KICAqIFplcm8tSW5mbGF0ZWQgTmVnYXRpdmUgQmlub21pYWwgUmVncmVzc2lvbg0KICAqIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uIA0KKiBDb21wYXJpc29uIG9mIGVhY2ggbW9kZWwNCiogQ29kZSB0byBkZXBsb3kgdGhlIG1vZGVsIG9uIHRoZSB0ZXN0IGRhdGEgc2V0DQpBbGwgY29kZSBhbmQgZGF0YSBmb3IgdGhpcyBwYXBlciBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qb3NoeWF6bWFuL25vcnRod2VzdGVybi90cmVlL21hc3Rlci9wcmVkaWN0LTQxMS93aW5lKS4NCiMgQ2xlYW5pbmcgYW5kIEV4cGxvcmluZyB0aGUgRGF0YQ0KVGhlIGV4cGxvcmF0aW9uIGFuZCBjbGVhbmluZyBzdGVwIGludm9sdmVzIGNyZWF0aW5nIGZsYWcgdmFyaWFibGVzIGZvciBtaXNzaW5nIHZhbHVlcyBhbmQgb3V0bGllcnMsIGRlbGV0aW5nIHRoZSBvdXRsaWVycywgYW5kIHVzZSByZWdyZXNzaW9uIHRyZWVzIHRvIGltcHV0ZSB0aGUgcmVtYWluaW5nIHZhbHVlcy4gDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQpsaWJyYXJ5KHJlYWRyKQ0KdHJhaW5fcmF3IDwtIHJlYWRfY3N2KCdXaW5lX1RyYWluaW5nLmNzdicpDQpgYGANCg0KVGhlIGBTVEFSU2AgZmllbGQgaGFzIHRoZSBtb3N0IG1pc3NpbmcgZGF0YSB3aGlsZSBzZXZlcmFsIGNoZW1pY2FsIG1lYXN1cmVtZW50cyBhcmUgbWlzc2luZyA1LTEwJSBvZiB2YWx1ZXMuIEluIGNhc2UgdGhlIGFic2VuY2Ugb2YgZGF0YSBpcyBhbiBpbXBvcnRhbnQgZmFjdG9yIGluIHNhbGVzLCBhIGJpbmFyeSBmbGFnIHZhcmlhYmxlIGlzIHByb2R1Y2VkIHRvIGlkZW50aWZ5IHBsYWNlcyB3aGVyZSBkYXRhIHdhcyBpbml0aWFsbHkgbWlzc2luZyBmcm9tIGVhY2ggdmFyaWFibGUuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh5YXp0aGVtZSkNCm51bGxzIDwtIGRhdGEuZnJhbWUoY29sID0gYXMuY2hhcmFjdGVyKGNvbG5hbWVzKHRyYWluX3JhdykpLCANCiAgICAgICAgICAgICAgICAgICAgcGN0X251bGwgPSBjb2xTdW1zKGlzLm5hKHRyYWluX3JhdykpKjEwMC8oY29sU3Vtcyhpcy5uYSh0cmFpbl9yYXcpKStjb2xTdW1zKCFpcy5uYSh0cmFpbl9yYXcpKSkpJT4lDQogIGZpbHRlcihjb2wgIT0gJ0lOREVYJykNCmdncGxvdChudWxscywgYWVzKHggPSBjb2wsIHkgPSBwY3RfbnVsbCkpKw0KICBnZW9tX2JhcihmaWxsID0geWF6X2NvbHNbMV0sIHN0YXQgPSAnaWRlbnRpdHknKSsNCiAgY29vcmRfZmxpcCgpKw0KICBsYWJzKHRpdGxlID0gJ0Rpc3RyaWJ1dGlvbiBvZiBNaXNzaW5nIERhdGEnLA0KICAgICAgIHggPSBlbGVtZW50X2JsYW5rKCksIHkgPSAnUGVyY2VudCBvZiBJbmZvcm1hdGlvbiBNaXNzaW5nJykrDQogIHRoZW1lX3lheigpKw0KICB5bGltKDAsMTAwKQ0KYGBgDQoNCk91dGxpZXJzLCBkZWZpbmVkIGFzICRRMSAtICgxLjUqSVFSKSQgYW5kICRRMyArICgxLjUqSVFSKSQgYXJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YSBzZXQgYW5kIGZsYWcgdmFyaWFibGVzIGFyZSBhcHBlbmRlZCBpbiBhIHNpbWlsYXIgZmFzaGlvbiB0byB0aGUgbWlzc2luZyBkYXRhIGZsYWdzLiBGaW5hbGx5LCBhbGwgbWlzc2luZyBhbmQgZGVsZXRlZCB2YXJpYWJsZXMgYXJlIHJlaW1wdXRlZCB1c2luZyByZWdyZXNzaW9uIHRyZWVzLg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0KY29sbmFtZXModHJhaW5fcmF3KSA8LSB0b2xvd2VyKGNvbG5hbWVzKHRyYWluX3JhdykpDQoNCnRyYWluLmZsYWdnZWQgPC0gdHJhaW5fcmF3JT4lDQogIG11dGF0ZV9hbGwoZnVucyhuYS5mbGFnID0gaWZlbHNlKGlzLm5hKC4pLDEsMCkpKQ0KaW50X2RmIDwtIHRyYWluLmZsYWdnZWQlPiUNCiAgZHBseXI6OnNlbGVjdCgtaW5kZXgsIC10YXJnZXQsIC1pbmRleF9uYS5mbGFnLCAtdGFyZ2V0X25hLmZsYWcpJT4lDQogIGRwbHlyOjpzZWxlY3RfaWYoaXMubnVtZXJpYykNCiMgbWQucGF0dGVybihpbnRfZGYpDQpjbGVhbmVkX2NvbHMgPC0gbGlzdCgpDQpmb3IoYyBpbiBjb2xuYW1lcyh0cmFpbl9yYXclPiUNCiAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoLWluZGV4LCAtdGFyZ2V0KSU+JQ0KICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdF9pZihpcy5udW1lcmljKSkpew0KICBjb2x1bW4gPC0gdHJhaW4uZmxhZ2dlZCU+JXNlbGVjdF8oY29sID0gYykNCiAgaXFyIDwtIHF1YW50aWxlKGNvbHVtbiRjb2wsIG5hLnJtID0gVClbNF0gLSBxdWFudGlsZShjb2x1bW4kY29sLCBuYS5ybSA9IFQpWzJdDQogIGxvdyA8LSBxdWFudGlsZShjb2x1bW4kY29sLCBuYS5ybSA9IFQpWzJdIC0gaXFyDQogIGhpZ2ggPC0gcXVhbnRpbGUoY29sdW1uJGNvbCwgbmEucm0gPSBUKVs0XSArIGlxcg0KICANCiAgdmFscyA8LSBjKCkNCiAgZm9yKGkgaW4gc2VxKDE6bnJvdyhpbnRfZGYpKSl7DQogICAgaWZlbHNlKGJldHdlZW4oY29sdW1uJGNvbFtpXSwgbG93IC0gKDEuNSppcXIpLCBoaWdoICsgKDEuNSppcXIpKSwNCiAgICAgICAgICAgdmFsc1tpXSA8LSBjb2x1bW4kY29sW2ldLCANCiAgICAgICAgICAgaWZlbHNlKGlzLm5hKGNvbHVtbiRjb2xbaV0pLCB2YWxzW2ldIDwtIE5BLCB2YWxzW2ldIDwtIE5BKSkNCiAgfQ0KICANCiAgaWZlbHNlKGxlbmd0aCh2YWxzKSA9PSBucm93KGludF9kZiksDQogICAgICAgICBjbGVhbmVkX2NvbHNbW2NdXSA8LSB2YWxzLCANCiAgICAgICAgIGNsZWFuZWRfY29sc1tbY11dIDwtIGModmFscyxOQSkpDQp9DQoNCmRmMiA8LSBiaW5kX2NvbHMoDQogIGJpbmRfY29scyhjbGVhbmVkX2NvbHMpJT4lDQogICAgc2NhbGUoY2VudGVyID0gVFJVRSklPiUNCiAgICBkYXRhLmZyYW1lKCksDQogIHRyYWluLmZsYWdnZWQlPiUNCiAgICBkcGx5cjo6c2VsZWN0KGVuZHNfd2l0aCgnbmEuZmxhZycpKSU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoLWluZGV4X25hLmZsYWcsIC10YXJnZXRfbmEuZmxhZykNCikNCg0KZGYzIDwtIGRmMiU+JQ0KICBtdXRhdGUoDQogICAgZml4ZWRhY2lkaXR5X291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGZpeGVkYWNpZGl0eSkgJiBmaXhlZGFjaWRpdHlfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICB2b2xhdGlsZWFjaWRpdHlfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEodm9sYXRpbGVhY2lkaXR5KSAmIHZvbGF0aWxlYWNpZGl0eV9uYS5mbGFnID09MCwxLDApLA0KICAgIGNpdHJpY2FjaWRfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEoY2l0cmljYWNpZCkgJiBjaXRyaWNhY2lkX25hLmZsYWcgPT0wLDEsMCksDQogICAgcmVzaWR1YWxzdWdhcl9vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShyZXNpZHVhbHN1Z2FyKSAmIHJlc2lkdWFsc3VnYXJfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICBjaGxvcmlkZXNfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEoY2hsb3JpZGVzKSAmIGNobG9yaWRlc19uYS5mbGFnID09MCwxLDApLA0KICAgIGZyZWVzdWxmdXJkaW94aWRlX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGZyZWVzdWxmdXJkaW94aWRlKSAmIGZyZWVzdWxmdXJkaW94aWRlX25hLmZsYWcgPT0wLDEsMCksDQogICAgdG90YWxzdWxmdXJkaW94aWRlX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKHRvdGFsc3VsZnVyZGlveGlkZSkgJiB0b3RhbHN1bGZ1cmRpb3hpZGVfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICBkZW5zaXR5X291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGRlbnNpdHkpICYgZGVuc2l0eV9uYS5mbGFnID09MCwxLDApLA0KICAgIHBoX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKHBoKSAmIHBoX25hLmZsYWcgPT0wLDEsMCksDQogICAgc3VscGhhdGVzX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKHN1bHBoYXRlcykgJiBzdWxwaGF0ZXNfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICBhbGNvaG9sX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGFsY29ob2wpICYgYWxjb2hvbF9uYS5mbGFnID09MCwxLDApLA0KICAgIGxhYmVsYXBwZWFsX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGxhYmVsYXBwZWFsKSAmIGxhYmVsYXBwZWFsX25hLmZsYWcgPT0wLDEsMCksDQogICAgYWNpZGluZGV4X291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGFjaWRpbmRleCkgJiBhY2lkaW5kZXhfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICBzdGFyc19vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShzdGFycykgJiBzdGFyc19uYS5mbGFnID09MCwxLDApDQopDQoNCmxpYnJhcnkobWljZSkNCnRlbXBfZGYgPC0gbWljZShkZjMsIG1ldGhvZCA9ICdjYXJ0JywgbWF4aXQgPSAxKQ0KdHJhaW4gPC0gY29tcGxldGUodGVtcF9kZiklPiUNCiAgYmluZF9jb2xzKHRyYWluX3JhdyU+JWRwbHlyOjpzZWxlY3QoaW5kZXgsIHRhcmdldCkpJT4lDQogIGRwbHlyOjpzZWxlY3QoLXN0YXJzX291dC5mbGFnLCAtbGFiZWxhcHBlYWxfb3V0LmZsYWcsIC1kZW5zaXR5X25hLmZsYWcsDQogICAgICAgICAgICAgICAgLWxhYmVsYXBwZWFsX25hLmZsYWcsIC1hY2lkaW5kZXhfbmEuZmxhZywgLWZpeGVkYWNpZGl0eV9uYS5mbGFnLA0KICAgICAgICAgICAgICAgIC12b2xhdGlsZWFjaWRpdHlfbmEuZmxhZywgLWNpdHJpY2FjaWRfbmEuZmxhZykNCmBgYA0KDQpGaW5hbGx5LCB3ZSBjYW4gZXhhbWluZSAgdGhlIGRpc3RyaWJ1dGlvbnMgb2Ygb3VyIHZhcmlhYmxlcy4gRmxhZ3Mgd2l0aCBob21vZ2Vub3VzIHZhbHVlcyBhcmUgcmVtb3ZlZCBmcm9tIHRoZSB0cmFpbmluZyBzZXQgdG8gZ2V0IGEganVtcCBzdGFydCBvbiBzdGVwd2lzZSBkaW1lbnNpb24gcmVkdWN0aW9uIHVzZWQgaW4gdGhlIG1vZGVsIGRldmVsb3BtZW50IHN0YWdlLiANCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCnN1bW1hcnkodHJhaW4pDQpgYGANCg0KIyBNb2RlbCBEZXZlbG9wbWVudA0KRml2ZSBtb2RlbHMgYXJlIGRldmVsb3BlZCBmb3IgdGhpcyBwYXBlci4gRmlyc3QgaXMgYSBQb2lzc29uIHJlZ3Jlc3Npb24gbW9kZWwgZm9sbG93ZWQgYnkgYSBuZWdhdGl2ZSBiaW5vbWlhbCByZWdyZXNzaW9uIG1vZGVsLiBUaGVuIHplcm8taW5mbGF0ZWQgdmVyc2lvbnMgb2YgZWFjaCBtb2RlbCBhcmUgZGV2ZWxvcGVkIGFzIHdlbGwgYXMgYSBtdWx0aXZhcmlhdGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuIFJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIChSTVNFKSwgYW5kIG1lYW4gYWJzb2x1dGUgZXJyb3IgKE1BRSkgYXJlIGNhbGN1bGF0ZWQgZm9yIGVhY2ggbW9kZWwgZm9yIHB1cnBvc2VzIG9mIG91dCBvZiBzYW1wbGUgbW9kZWwgZXZhbHVhdGlvbi4gDQoNCiMjIFBvaXNzb24gYW5kIE5lZ2F0aXZlIEJpbm9taWFsIFJlZ3Jlc3Npb24NClBvaXNzb24gcmVncmVzc2lvbiBtb2RlbHMgdXNlIHRoZSBsb2cgbGluayBmdW5jdGlvbiB0byBhcHByb3hpbWF0ZSByZWdyZXNzaW9uIHByb2Nlc3NlcyBmb3IgYSBjb3VudCB2YXJpYWJsZSBkaXN0cmlidXRlZCBzdWNoIHRoYXQgdGhlIHZhcmlhbmNlIGlzIGVxdWFsIHRvIHRoZSBtZWFuIFteMV0uIFRoZSBiYWNrd2FyZCBzdGVwd2lzZSBmZWF0dXJlIHNlbGVjdGlvbiBhbGdvcml0bSByZXR1cm5lZCBhIHBvaXNzb24gbW9kZWwgd2l0aCB0d2VsdmUgcHJlZGljdG9yc1teMl0uIE5vdGVhYmx5LCBtaXNzaW5nIHZhbHVlcyBmb3IgYHN0YXJzYCBhbmQgb3V0bGllciBgYWNpZGluZGV4YCB2YWx1ZXMgc2lnbmlmaWNhbnRseSBoYXJtIHNhbGVzIHdoaWxlIGBsYWJlbGFwcGVhbGAgYW5kIHByZXNlbnQgaGlnaGVyIHZhbHVlcyBmb3IgYHN0YXJzYCBsZWQgdG8gaGlnaGVyIHNhbGVzLiBOZWdhdGl2ZSBiaW5vbWlhbCBtb2RlbHMgdXNlIGEgZGlmZmVyZW50IHByb2JhYmlsaXR5IGRlbnNpdHkgZnVuY3Rpb24gdGhhbiBQb2lzc29uIHJlZ3Jlc3Npb24gdG8gYWNjb3VudCBmb3Igb3Zlci1kaXNwZXJzaW9uIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUgKGFzIHdlIGhhdmUgaW4gdGhpcyBjYXNlKVteM10uIFRoZSBjb2VmZmljaWVudHMgZm9yIHRoaXMgbW9kZWwgYXJlIGlkZW50aWNhbCB0byB0aGF0IG9mIGEgUG9pc3NvbiBtb2RlbC4NCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCmxpYnJhcnkoTUFTUykNCg0KYmFzZV9wb2lzcyA8LSBnbG0odGFyZ2V0IH4gLiwgZmFtaWx5PSJwb2lzc29uIiwgZGF0YT10cmFpbikNCnBvaXNzLmJhY2sgPC0gc3RlcEFJQyhiYXNlX3BvaXNzLCBkaXJlY3Rpb24gPSAnYmFja3dhcmQnKQ0KIyBzdW1tYXJ5KHBvaXNzLmJhY2spJGNhbGwNCnBvaXNzLm1vZCA8LSBnbG0oZm9ybXVsYSA9IHRhcmdldCB+IHZvbGF0aWxlYWNpZGl0eSArIHRvdGFsc3VsZnVyZGlveGlkZSArIA0KICAgIGRlbnNpdHkgKyBwaCArIHN1bHBoYXRlcyArIGFsY29ob2wgKyBsYWJlbGFwcGVhbCArIGFjaWRpbmRleCArIA0KICAgIHN0YXJzICsgc3RhcnNfbmEuZmxhZyArIHZvbGF0aWxlYWNpZGl0eV9vdXQuZmxhZyArIGFjaWRpbmRleF9vdXQuZmxhZywgDQogICAgZmFtaWx5ID0gInBvaXNzb24iLCBkYXRhID0gdHJhaW4pDQoNCnBvaXNzLmNvZWZmcyA8LSBkYXRhLmZyYW1lKHZhciA9IG5hbWVzKHBvaXNzLm1vZCRjb2VmZmljaWVudHMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZmZpY2llbnQgPSBwb2lzcy5tb2QkY29lZmZpY2llbnRzKSU+JQ0KICBtdXRhdGUobWV0aG9kID0gJ1BvaXNzb24nKQ0KDQpuZWdiaW4ubW9kIDwtIGdsbS5uYihmb3JtdWxhID0gdGFyZ2V0IH4gdm9sYXRpbGVhY2lkaXR5ICsgdG90YWxzdWxmdXJkaW94aWRlICsgDQogICAgZGVuc2l0eSArIHBoICsgc3VscGhhdGVzICsgYWxjb2hvbCArIGxhYmVsYXBwZWFsICsgYWNpZGluZGV4ICsgDQogICAgc3RhcnMgKyBzdGFyc19uYS5mbGFnICsgdm9sYXRpbGVhY2lkaXR5X291dC5mbGFnICsgYWNpZGluZGV4X291dC5mbGFnLCANCiAgICBkYXRhID0gdHJhaW4pDQoNCm5lZ2Jpbi5jb2VmZnMgPC0gZGF0YS5mcmFtZSh2YXIgPSBuYW1lcyhuZWdiaW4ubW9kJGNvZWZmaWNpZW50cyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZmZpY2llbnQgPSBuZWdiaW4ubW9kJGNvZWZmaWNpZW50cyklPiUNCiAgbXV0YXRlKG1ldGhvZCA9ICdOZWdhdGl2ZSBCaW5vbWlhbCcpDQoNCg0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh5YXp0aGVtZSkNCg0KZ2dwbG90KGJpbmRfcm93cyhuZWdiaW4uY29lZmZzLCBwb2lzcy5jb2VmZnMpLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcih2YXIsIGNvZWZmaWNpZW50KSwgeSA9IGNvZWZmaWNpZW50LCBmaWxsID0gbWV0aG9kKSkrDQogIGdlb21fY29sKHBvc2l0aW9uID0gJ2RvZGdlJykrDQogIGNvb3JkX2ZsaXAoKSsNCiAgbGFicyh5ID0gJ0NvZWZmaWNpZW50JywNCiAgICAgICB4ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgIHRpdGxlID0gJ1JlZ3Jlc3Npb24gQ29lZmZpY2llbnRzJywNCiAgICAgICBzdWJ0aXRsZSA9ICdGZWF0dXJlcyBzZWxlY3RlZCB2aWEgYmFja3dhcmRzIHZhcmlhYmxlIHNlbGVjdGlvbicpKw0KICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gJ01ldGhvZCcsIHZhbHVlcyA9IHlhel9jb2xzWzQ6NV0pKw0KICB0aGVtZV95YXooKQ0KYGBgDQoNCiMjIFplcm8tSW5mbGF0ZWQgUmVncmVzc2lvbg0KUG9pc3NvbiBhbmQgbmVnYXRpdmUgYmlub21pYWwgbW9kZWxzIGNhbiBiZSBza2V3ZWQgYnkgYW4gb3ZlcmFidW5kYW5jZSBvZiB6ZXJvIHZhbHVlcy4gWmVyby1pbmZsYXRlZCBtb2RlbHMgYXNzdW1ldGhlIGRpc3RyaWJ1dGlvbiBoYXMgdHdvIHR5cGVzIG9mIHZhbHVlcyAtIHJpZ2h0ZnVsIHplcm8gbWVhc3VyZW1lbnRzIGFuZCBhIHNlcGFyYXRlIHNldCBvZiB2YWx1ZXMgdGhhdCBmb2xsb3dzIGEgbW9yZSB0eXBpY2FsIGRpc3RyaWJ1dGlvbi4gVGhlc2UgbW9kZWxzIGZpcnN0IHNvcnQgdmFsdWVzIGludG8gdGhlaXIgcHJvcGVyIGNhdGVnb3J5LCB0aGVuIHByZWRpY3QgdGhlaXIgb3V0Y29tZXMgdXNpbmcgc2VwYXJhdGUgc2V0cyBvZiBjb2VmZmljaWVudHMgZm9yIGVhY2hbXjRdLiBJbiB0aGlzIGNhc2UsIG92ZXIgMiw1MDAgd2luZXMgc29sZCB6ZXJvIGNhc2VzIHNvIHplcm8taW5mbGF0ZWQgbW9kZWxzIG1heSBpbXByb3ZlIHByZWRpY3RpdmUgcG93ZXIuIFRoZSBzYW1lIHZhcmlhYmxlcyBhcmUgdXNlZCBpbiB0aGUgemVybyBpbmZsYXRlZCBtb2RlbHMgYXMgdGhlIGluaXRpYWwgbW9kZWxzW141XS4gSW4gdGhpcyBjYXNlLCBzb21lIG9mIHRoZSBjb2VmZmljaWVudHMgYXJlIGZsaXBwZWQuIE1pc3NpbmcgcmF0aW5ncyBhbmQgb3V0bGllciBhY2lkaXR5IHZhbHVlcyBhcmUgc3Ryb25nIHBvc2l0aXZlIGluZmx1ZW5jZXMgb24gc2FsZXMuIExhYmVsIGFwcGVhbCBpcyBzaW1pbGFybHkgcG9zaXRpdmUgYXMgaW4gcHJldmlvdXMgbW9kZWxzLCBidXQgYWN0dWFsIHN0YXIgcmF0aW5ncyBhcmUgbmVnYXRpdmUuIEFnYWluLCB0aGUgY29lZmZpY2llbnRzIGZvciBwb2lzc29uIGFuZCBuZWdhdGl2ZSBiaW5vbWlhbCBtb2RlbHMgYXJlIGlkZW50aWNhbC4gDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQp6aW5wLm1vZCA8LSBwc2NsOjp6ZXJvaW5mbChmb3JtdWxhID0gdGFyZ2V0IH4gdm9sYXRpbGVhY2lkaXR5ICsgdG90YWxzdWxmdXJkaW94aWRlICsgDQogICAgZGVuc2l0eSArIHBoICsgc3VscGhhdGVzICsgYWxjb2hvbCArIGxhYmVsYXBwZWFsICsgYWNpZGluZGV4ICsgDQogICAgc3RhcnMgKyBzdGFyc19uYS5mbGFnICsgdm9sYXRpbGVhY2lkaXR5X291dC5mbGFnICsgYWNpZGluZGV4X291dC5mbGFnLCANCiAgICBkYXRhID0gdHJhaW4pDQoNCnppbnAuY29lZmZzIDwtIGRhdGEuZnJhbWUodmFyID0gbmFtZXMoemlucC5tb2QkY29lZmZpY2llbnRzJHplcm8pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvZWZmaWNpZW50ID0gemlucC5tb2QkY29lZmZpY2llbnRzJHplcm8pJT4lDQogIG11dGF0ZShtZXRob2QgPSAnWmVyby1JbmZsYXRlZCBQb2lzc29uJykNCg0KemlubmcubW9kIDwtIHBzY2w6Onplcm9pbmZsKGZvcm11bGEgPSB0YXJnZXQgfiB2b2xhdGlsZWFjaWRpdHkgKyB0b3RhbHN1bGZ1cmRpb3hpZGUgKyANCiAgICBkZW5zaXR5ICsgcGggKyBzdWxwaGF0ZXMgKyBhbGNvaG9sICsgbGFiZWxhcHBlYWwgKyBhY2lkaW5kZXggKyANCiAgICBzdGFycyArIHN0YXJzX25hLmZsYWcgKyB2b2xhdGlsZWFjaWRpdHlfb3V0LmZsYWcgKyBhY2lkaW5kZXhfb3V0LmZsYWcsIA0KICAgIGRhdGEgPSB0cmFpbiwgZGlzdCA9ICJuZWdiaW4iLCBFTSA9IFRSVUUpDQoNCnppbm5nLmNvZWZmcyA8LSBkYXRhLmZyYW1lKHZhciA9IG5hbWVzKHppbm5nLm1vZCRjb2VmZmljaWVudHMkemVybyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZmZpY2llbnQgPSB6aW5uZy5tb2QkY29lZmZpY2llbnRzJHplcm8pJT4lDQogIG11dGF0ZShtZXRob2QgPSAnWmVyby1JbmZsYXRlZCBOZWdhdGl2ZSBCaW5vbWlhbCcpDQoNCmdncGxvdChiaW5kX3Jvd3MoemlucC5jb2VmZnMsIHppbm5nLmNvZWZmcyksDQogICAgICAgYWVzKHggPSByZW9yZGVyKHZhciwgY29lZmZpY2llbnQpLCB5ID0gY29lZmZpY2llbnQsIGZpbGwgPSBtZXRob2QpKSsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAnZG9kZ2UnKSsNCiAgY29vcmRfZmxpcCgpKw0KICBsYWJzKHkgPSAnQ29lZmZpY2llbnQnLA0KICAgICAgIHggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgdGl0bGUgPSAnUmVncmVzc2lvbiBDb2VmZmljaWVudHMnLA0KICAgICAgIHN1YnRpdGxlID0gJ0ZlYXR1cmVzIHNlbGVjdGVkIHZpYSBiYWNrd2FyZHMgdmFyaWFibGUgc2VsZWN0aW9uJykrDQogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAnTWV0aG9kJywgdmFsdWVzID0geWF6X2NvbHNbNDo1XSkrDQogIHRoZW1lX3lheigpDQpgYGANCg0KIyMgTXVsdGlwbGUgUmVncmVzc2lvbg0KVGhlIGZpbmFsIG1vZGVsIGF0dGVtcHRlZCBpcyBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbi4gQSBuZXcgc3RlcHdpc2Ugc2VsZWN0aW9uIGFsZ29yaXRobSBpcyB1c2VkIHRvIHNlbGVjdCBtb2RlbCB2YXJpYWJsZXMuIFRoZSBzYW1lIHZhcmlhYmxlcyB3aXRoIHJlbGF0aXZlbHkgbGFyZ2UgY29lZmZpY2llbnRzIGluIHRoZSBwb2lzc29uIGFuZCBuZWdhdGl2ZSBiaW5vbWlhbCBtb2RlbHMgc3RhbmQgb3V0IGluIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCwgYWx0aG91Z2ggYWRkaXRpb25hbCB2YXJpYWJsZXMgd2l0aCBzbWFsbGVyIGNvZWZmaWNpZW50cyBhcmUgaW5jbHVkZWQgYXMgd2VsbC4gDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQptbHIuYmFzZSA8LSBsbShmb3JtdWxhID0gdGFyZ2V0IH4gLiwgZGF0YSA9IHRyYWluKQ0KbWxyLnN0ZXAgPC0gc3RlcEFJQyhtbHIuYmFzZSwgZGlyZWN0aW9uID0gJ2JhY2t3YXJkJykNCm1sci5tb2QgPC0gbG0oZm9ybXVsYSA9IHRhcmdldCB+IHZvbGF0aWxlYWNpZGl0eSArIGNpdHJpY2FjaWQgKyBjaGxvcmlkZXMgKyANCiAgICB0b3RhbHN1bGZ1cmRpb3hpZGUgKyBkZW5zaXR5ICsgcGggKyBzdWxwaGF0ZXMgKyBhbGNvaG9sICsgDQogICAgbGFiZWxhcHBlYWwgKyBhY2lkaW5kZXggKyBzdGFycyArIHJlc2lkdWFsc3VnYXJfbmEuZmxhZyArIA0KICAgIHN0YXJzX25hLmZsYWcgKyB2b2xhdGlsZWFjaWRpdHlfb3V0LmZsYWcgKyBhY2lkaW5kZXhfb3V0LmZsYWcsIA0KICAgIGRhdGEgPSB0cmFpbikNCg0KbWxyLmNvZWZmcyA8LSBkYXRhLmZyYW1lKHZhciA9IG5hbWVzKG1sci5tb2QkY29lZmZpY2llbnRzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmZmljaWVudCA9IG1sci5tb2QkY29lZmZpY2llbnRzKQ0KDQpnZ3Bsb3QobWxyLmNvZWZmcywgYWVzKHggPSByZW9yZGVyKHZhciwgY29lZmZpY2llbnQpLCB5ID0gY29lZmZpY2llbnQpKSsNCiAgZ2VvbV9jb2woZmlsbCA9IHlhel9jb2xzWzVdKSsNCiAgY29vcmRfZmxpcCgpKw0KICBsYWJzKHkgPSAnQ29lZmZpY2llbnQnLA0KICAgICAgIHggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgdGl0bGUgPSAnTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24gUmVncmVzc2lvbiBDb2VmZmljaWVudHMnLA0KICAgICAgIHN1YnRpdGxlID0gJ0ZlYXR1cmVzIHNlbGVjdGVkIHZpYSBiYWNrd2FyZHMgdmFyaWFibGUgc2VsZWN0aW9uJykrDQogIHRoZW1lX3lheigpDQpgYGANCg0KIyBNb2RlbCBFdmFsdWF0aW9uDQpUbyBhc3Nlc3MgdGhlIGFjY3VyYWN5IG9mIHRoZSB2YXJpb3VzIG1vZGVscywgZWFjaCBtb2RlbCBpcyByZXBlYXRlZGx5IHRyYWluZWQgb24gcmFuZG9tIHNhbXBsZXMgb2YgdGhlIGRhdGEgYW5kIHRlc3RlZCBvdXQgb2Ygc2FtcGxlLiBJdGVyYXRpdmUgcmVzYW1wbGluZyBpcyB1c2VkIHRvIG1lYXN1cmUgdGhlIGVycm9yIHJhdGVzIG9mIGVhY2ggbW9kZWwgYW5kIGF0dGVtcHQgdG8gYWNjb3VudCBmb3IgdW5jZXJ0YWludHkgaW4gZXJyb3IgbWVhc3VyZW1lbnRzIGJ5IHByb2R1Y2luZyBkaXN0cmlidXRpb25zIG9mIHBvc3NpYmxlIGVycm9yIG1lYXN1cmVzIHJhdGhlciB0aGFuIHNpbmdsZSBwb2ludCBlc3RpbWF0ZXMuIFRoZSBkaXN0cmlidXRpb25zIG9mIE1BRSBhbmQgUk1TRSBmb3IgZWFjaCBtb2RlbCBhcmUgcHJlc2VudGVkIGJlbG93LiBTaW1pbGFyIHRvIHRoZSBhYm92ZSBjb2VmZmljaWVudHMsIHRoZSBlcnJvciByYXRlcyBmb3IgdGhlIHBvaXNzb24gYW5kIG5lZ2F0aXZlIGJpbm9taWFsIG1vZGVscyBhcmUgaWRlbnRpY2FsIHdpdGggdGhlIHR3byB6ZXJvLWluZmxhdGVkIG1vZGVscyBzdHJvbmdseSBvdXRwZXJmb3JtaW5nIHRoZSBub24tYWRqdXN0ZWQgbW9kZWxzLiBNdWx0aXBsZSByZWdyZXNzaW9uIGFsc28gbWFkZSBhIHN0cm9uZyBzaG93aW5nLCBidXQgZGlkIG5vdCBpbXByb3ZlIHByZWRpY3RpdmUgYWNjdXJhY3kgY29tcGFyZWQgdG8gdGhlIHR3byB6ZXJvLWluZmxhdGVkIG1vZGVscy4gT2YgdGhlIHR3byB3aW5uaW5nIG1vZGVscywgemVyby1pbmZsYXRlZCBuZWdhdGl2ZSBiaW5vbWlhbCBpcyBwcmVmZXJyZWQgZHVlIHRvIHRoZSBzbGlnaHQgb3ZlcmRpc3BlcnNpb24gb2YgdGhlIHRhcmdldCB2YXJpYWJsZS4NCg0KYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSA4LCBlY2hvID0gRkFMU0V9DQp0ZXN0X21vZHMgPC0gZnVuY3Rpb24oZGYsIGl0ZXJhdGlvbnMpew0KICBtZXRob2QgPC0gYyhyZXAoJ1BvaXNzb24nLCBpdGVyYXRpb25zKSwgcmVwKCdOZWdhdGl2ZSBCaW5vbWlhbCcsaXRlcmF0aW9ucyksDQogICAgICAgICAgICAgIHJlcCgnWmVyby1JbmZsYXRlZCBQb2lzc29uJywgaXRlcmF0aW9ucyksDQogICAgICAgICAgICAgIHJlcCgnWmVyby1JbmZsYXRlZCBOZWdhdGl2ZSBCaW5vbWlhbCcsIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICByZXAoJ011bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uJyxpdGVyYXRpb25zKSkNCiAgcG9pc3Mucm1zZSA8LSBjKCkNCiAgcG9pc3MubWFlIDwtIGMoKQ0KICBuZWdiaW4ucm1zZSA8LSBjKCkNCiAgbmVnYmluLm1hZSA8LSBjKCkNCiAgemlucC5ybXNlIDwtIGMoKQ0KICB6aW5wLm1hZSA8LSBjKCkNCiAgemlubmcucm1zZSA8LSBjKCkNCiAgemlubmcubWFlIDwtIGMoKQ0KICBtbHIucm1zZSA8LSBjKCkNCiAgbWxyLm1hZSA8LSBjKCkNCiAgZm9yKGkgaW4gc2VxKDEsaXRlcmF0aW9ucykpew0KICAgIHRlbXBkZiA8LSBzYW1wbGVfZnJhYyhkZiwgLjgpDQogICAgdGVzdCA8LSBzZXRkaWZmKGRmLCB0ZW1wZGYpDQogICAgcG9pc3MubW9kIDwtIGdsbShmb3JtdWxhID0gdGFyZ2V0IH4gdm9sYXRpbGVhY2lkaXR5ICsgdG90YWxzdWxmdXJkaW94aWRlICsNCiAgICAgICAgZGVuc2l0eSArIHBoICsgc3VscGhhdGVzICsgYWxjb2hvbCArIGxhYmVsYXBwZWFsICsgYWNpZGluZGV4ICsNCiAgICAgICAgc3RhcnMgKyBzdGFyc19uYS5mbGFnICsgdm9sYXRpbGVhY2lkaXR5X291dC5mbGFnICsgYWNpZGluZGV4X291dC5mbGFnLA0KICAgICAgICBmYW1pbHkgPSAicG9pc3NvbiIsIGRhdGEgPSB0ZW1wZGYpDQoNCiAgICBwb2lzcy5ybXNlW2ldIDwtIHNxcnQobWVhbigocHJlZGljdC5nbG0ocG9pc3MubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KV4yKSkNCiAgICBwb2lzcy5tYWVbaV0gPC0gbWVhbihhYnMocHJlZGljdC5nbG0ocG9pc3MubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KSkNCg0KDQogICAgbmVnYmluLm1vZCA8LSBnbG0ubmIoZm9ybXVsYSA9IHRhcmdldCB+IHZvbGF0aWxlYWNpZGl0eSArIHRvdGFsc3VsZnVyZGlveGlkZSArDQogICAgICAgIGRlbnNpdHkgKyBwaCArIHN1bHBoYXRlcyArIGFsY29ob2wgKyBsYWJlbGFwcGVhbCArIGFjaWRpbmRleCArDQogICAgICAgIHN0YXJzICsgc3RhcnNfbmEuZmxhZyArIHZvbGF0aWxlYWNpZGl0eV9vdXQuZmxhZyArIGFjaWRpbmRleF9vdXQuZmxhZywNCiAgICAgICAgZGF0YSA9IHRlbXBkZikNCg0KICAgIG5lZ2Jpbi5ybXNlW2ldIDwtIHNxcnQobWVhbigocHJlZGljdC5nbG0obmVnYmluLm1vZCwgdGVzdCkgLSB0ZXN0JHRhcmdldCleMikpDQogICAgbmVnYmluLm1hZVtpXSA8LSBtZWFuKGFicyhwcmVkaWN0LmdsbShuZWdiaW4ubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KSkNCg0KICAgIHppbnAubW9kIDwtIHBzY2w6Onplcm9pbmZsKGZvcm11bGEgPSB0YXJnZXQgfiB2b2xhdGlsZWFjaWRpdHkgKyB0b3RhbHN1bGZ1cmRpb3hpZGUgKw0KICAgICAgICBkZW5zaXR5ICsgcGggKyBzdWxwaGF0ZXMgKyBhbGNvaG9sICsgbGFiZWxhcHBlYWwgKyBhY2lkaW5kZXggKw0KICAgICAgICBzdGFycyArIHN0YXJzX25hLmZsYWcgKyB2b2xhdGlsZWFjaWRpdHlfb3V0LmZsYWcgKyBhY2lkaW5kZXhfb3V0LmZsYWcsDQogICAgICAgIGRhdGEgPSB0ZW1wZGYpDQoNCiAgICB6aW5wLnJtc2VbaV0gPC0gc3FydChtZWFuKChwcmVkaWN0KHppbnAubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KV4yKSkNCiAgICB6aW5wLm1hZVtpXSA8LSBtZWFuKGFicyhwcmVkaWN0KHppbnAubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KSkNCg0KICAgIHppbm5nLm1vZCA8LSBwc2NsOjp6ZXJvaW5mbChmb3JtdWxhID0gdGFyZ2V0IH4gdm9sYXRpbGVhY2lkaXR5ICsgdG90YWxzdWxmdXJkaW94aWRlICsNCiAgICAgICAgZGVuc2l0eSArIHBoICsgc3VscGhhdGVzICsgYWxjb2hvbCArIGxhYmVsYXBwZWFsICsgYWNpZGluZGV4ICsNCiAgICAgICAgc3RhcnMgKyBzdGFyc19uYS5mbGFnICsgdm9sYXRpbGVhY2lkaXR5X291dC5mbGFnICsgYWNpZGluZGV4X291dC5mbGFnLA0KICAgICAgICBkYXRhID0gdGVtcGRmLCBkaXN0ID0gIm5lZ2JpbiIsIEVNID0gVFJVRSkNCg0KICAgIHppbm5nLnJtc2VbaV0gPC0gc3FydChtZWFuKChwcmVkaWN0KHppbm5nLm1vZCwgdGVzdCkgLSB0ZXN0JHRhcmdldCleMikpDQogICAgemlubmcubWFlW2ldIDwtIG1lYW4oYWJzKHByZWRpY3QoemlubmcubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KSkNCg0KICAgIG1sci5tb2QgPC0gbG0oZm9ybXVsYSA9IHRhcmdldCB+IHZvbGF0aWxlYWNpZGl0eSArIGNpdHJpY2FjaWQgKyBjaGxvcmlkZXMgKw0KICAgICAgICB0b3RhbHN1bGZ1cmRpb3hpZGUgKyBkZW5zaXR5ICsgcGggKyBzdWxwaGF0ZXMgKyBhbGNvaG9sICsNCiAgICAgICAgbGFiZWxhcHBlYWwgKyBhY2lkaW5kZXggKyBzdGFycyArIHJlc2lkdWFsc3VnYXJfbmEuZmxhZyArDQogICAgICAgIHN0YXJzX25hLmZsYWcgKyB2b2xhdGlsZWFjaWRpdHlfb3V0LmZsYWcgKyBhY2lkaW5kZXhfb3V0LmZsYWcsDQogICAgICAgIGRhdGEgPSB0ZW1wZGYpDQogICAgbWxyLnJtc2VbaV0gPC0gc3FydChtZWFuKChwcmVkaWN0KG1sci5tb2QsIHRlc3QpIC0gdGVzdCR0YXJnZXQpXjIpKQ0KICAgIG1sci5tYWVbaV0gPC0gbWVhbihhYnMocHJlZGljdChtbHIubW9kLCB0ZXN0KSAtIHRlc3QkdGFyZ2V0KSkNCiAgfQ0KICByZXR1cm4oZGF0YS5mcmFtZSgNCiAgICBtZXRob2QsDQogICAgcm1zZSA9IGMocG9pc3Mucm1zZSwgbmVnYmluLnJtc2UsIHppbnAucm1zZSwgemlubmcucm1zZSwgbWxyLnJtc2UpLA0KICAgIG1hZSA9IGMocG9pc3MubWFlLCBuZWdiaW4ubWFlLCB6aW5wLm1hZSwgemlubmcubWFlLCBtbHIubWFlKQ0KICApKQ0KfQ0KbW9kZWxfZGlhZ25vc3RpY3MgPC0gdGVzdF9tb2RzKHRyYWluLCAyNTApDQoNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShnZ3JpZGdlcykNCmxpYnJhcnkocmVzaGFwZTIpDQoNCmdncGxvdChtb2RlbF9kaWFnbm9zdGljcyU+JW1lbHQoaWQudmFycyA9ICdtZXRob2QnKSwgYWVzKHggPSB2YWx1ZSwgeSA9IG1ldGhvZCwgZmlsbCA9IHZhcmlhYmxlKSkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAuNzUpKw0KICBmYWNldF93cmFwKH50b3VwcGVyKHZhcmlhYmxlKSwgc2NhbGVzID0gJ2ZyZWUnKSsNCiAgdGhlbWVfeWF6KCkrDQogIGxhYnModGl0bGUgPSAnTW9kZWwgRGlhZ25vc3RpY3MgYnkgTWV0aG9kJywNCiAgICAgICB5ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgIHggPSBlbGVtZW50X2JsYW5rKCkpKw0KICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gJ01ldHJpYycsIHZhbHVlcyA9IHlhel9jb2xzWzQ6NV0sIGxhYmVscyA9IGMoJ1JNU0UnLCAnTUFFJykpDQpgYGANCg0KIyBJbXBsZW1lbnRhdGlvbg0KVGhlIGZvbGxvd2luZyBmdW5jdGlvbiBhcHBsaWVzIHRoZSB6ZXJvLWluZmxhdGVkIG5lZ2F0aXZlIGJpbm9taWFsIHJlZ3Jlc3Npb24gbW9kZWwgdG8gb3V0IG9mIHNhbXBsZSBkYXRhIGFuZCBnZW5lcmF0ZXMgYSBDU1YgZmlsZSBpbiB0aGUgcHJvcGVyIHN1Ym1pc3Npb24gZm9ybWF0LiANCmBgYHtyLCBlY2hvID0gVFJVRX0NCmFwcGx5X21vZGVsIDwtIGZ1bmN0aW9uKGNzdil7DQogIHRyYWluLnJhdyA8LSByZWFkX2Nzdihjc3YpDQogIGNvbG5hbWVzKHRyYWluX3JhdykgPC0gdG9sb3dlcihjb2xuYW1lcyh0cmFpbl9yYXcpKQ0KICANCiAgdHJhaW4uZmxhZ2dlZCA8LSB0cmFpbl9yYXclPiUNCiAgICBtdXRhdGVfYWxsKGZ1bnMobmEuZmxhZyA9IGlmZWxzZShpcy5uYSguKSwxLDApKSkNCiAgaW50X2RmIDwtIHRyYWluLmZsYWdnZWQlPiUNCiAgICBkcGx5cjo6c2VsZWN0KC1pbmRleCwgLXRhcmdldCwgLWluZGV4X25hLmZsYWcsIC10YXJnZXRfbmEuZmxhZyklPiUNCiAgICBkcGx5cjo6c2VsZWN0X2lmKGlzLm51bWVyaWMpDQogIA0KICBjbGVhbmVkX2NvbHMgPC0gbGlzdCgpDQogIGZvcihjIGluIGNvbG5hbWVzKHRyYWluX3JhdyU+JQ0KICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KC1pbmRleCwgLXRhcmdldCklPiUNCiAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdF9pZihpcy5udW1lcmljKSkpew0KICAgIGNvbHVtbiA8LSB0cmFpbi5mbGFnZ2VkJT4lc2VsZWN0Xyhjb2wgPSBjKQ0KICAgIGlxciA8LSBxdWFudGlsZShjb2x1bW4kY29sLCBuYS5ybSA9IFQpWzRdIC0gcXVhbnRpbGUoY29sdW1uJGNvbCwgbmEucm0gPSBUKVsyXQ0KICAgIGxvdyA8LSBxdWFudGlsZShjb2x1bW4kY29sLCBuYS5ybSA9IFQpWzJdIC0gaXFyDQogICAgaGlnaCA8LSBxdWFudGlsZShjb2x1bW4kY29sLCBuYS5ybSA9IFQpWzRdICsgaXFyDQogICAgDQogICAgdmFscyA8LSBjKCkNCiAgICBmb3IoaSBpbiBzZXEoMTpucm93KGludF9kZikpKXsNCiAgICAgIGlmZWxzZShiZXR3ZWVuKGNvbHVtbiRjb2xbaV0sIGxvdyAtICgxLjUqaXFyKSwgaGlnaCArICgxLjUqaXFyKSksDQogICAgICAgICAgICAgdmFsc1tpXSA8LSBjb2x1bW4kY29sW2ldLCANCiAgICAgICAgICAgICBpZmVsc2UoaXMubmEoY29sdW1uJGNvbFtpXSksIHZhbHNbaV0gPC0gTkEsIHZhbHNbaV0gPC0gTkEpKQ0KICAgIH0NCiAgICANCiAgICBpZmVsc2UobGVuZ3RoKHZhbHMpID09IG5yb3coaW50X2RmKSwNCiAgICAgICAgICAgY2xlYW5lZF9jb2xzW1tjXV0gPC0gdmFscywgDQogICAgICAgICAgIGNsZWFuZWRfY29sc1tbY11dIDwtIGModmFscyxOQSkpDQogIH0NCiAgDQogIGRmMiA8LSBiaW5kX2NvbHMoDQogICAgYmluZF9jb2xzKGNsZWFuZWRfY29scyklPiUNCiAgICAgIHNjYWxlKGNlbnRlciA9IFRSVUUpJT4lDQogICAgICBkYXRhLmZyYW1lKCksDQogICAgdHJhaW4uZmxhZ2dlZCU+JQ0KICAgICAgZHBseXI6OnNlbGVjdChlbmRzX3dpdGgoJ25hLmZsYWcnKSklPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoLWluZGV4X25hLmZsYWcsIC10YXJnZXRfbmEuZmxhZykNCiAgKQ0KICANCiAgZGYzIDwtIGRmMiU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIGZpeGVkYWNpZGl0eV9vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShmaXhlZGFjaWRpdHkpICYgZml4ZWRhY2lkaXR5X25hLmZsYWcgPT0wLDEsMCksDQogICAgICB2b2xhdGlsZWFjaWRpdHlfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEodm9sYXRpbGVhY2lkaXR5KSAmIHZvbGF0aWxlYWNpZGl0eV9uYS5mbGFnID09MCwxLDApLA0KICAgICAgY2l0cmljYWNpZF9vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShjaXRyaWNhY2lkKSAmIGNpdHJpY2FjaWRfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICAgIHJlc2lkdWFsc3VnYXJfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEocmVzaWR1YWxzdWdhcikgJiByZXNpZHVhbHN1Z2FyX25hLmZsYWcgPT0wLDEsMCksDQogICAgICBjaGxvcmlkZXNfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEoY2hsb3JpZGVzKSAmIGNobG9yaWRlc19uYS5mbGFnID09MCwxLDApLA0KICAgICAgZnJlZXN1bGZ1cmRpb3hpZGVfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEoZnJlZXN1bGZ1cmRpb3hpZGUpICYgZnJlZXN1bGZ1cmRpb3hpZGVfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICAgIHRvdGFsc3VsZnVyZGlveGlkZV9vdXQuZmxhZyA9IGlmZWxzZShpcy5uYSh0b3RhbHN1bGZ1cmRpb3hpZGUpICYgdG90YWxzdWxmdXJkaW94aWRlX25hLmZsYWcgPT0wLDEsMCksDQogICAgICBkZW5zaXR5X291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGRlbnNpdHkpICYgZGVuc2l0eV9uYS5mbGFnID09MCwxLDApLA0KICAgICAgcGhfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEocGgpICYgcGhfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICAgIHN1bHBoYXRlc19vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShzdWxwaGF0ZXMpICYgc3VscGhhdGVzX25hLmZsYWcgPT0wLDEsMCksDQogICAgICBhbGNvaG9sX291dC5mbGFnID0gaWZlbHNlKGlzLm5hKGFsY29ob2wpICYgYWxjb2hvbF9uYS5mbGFnID09MCwxLDApLA0KICAgICAgbGFiZWxhcHBlYWxfb3V0LmZsYWcgPSBpZmVsc2UoaXMubmEobGFiZWxhcHBlYWwpICYgbGFiZWxhcHBlYWxfbmEuZmxhZyA9PTAsMSwwKSwNCiAgICAgIGFjaWRpbmRleF9vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShhY2lkaW5kZXgpICYgYWNpZGluZGV4X25hLmZsYWcgPT0wLDEsMCksDQogICAgICBzdGFyc19vdXQuZmxhZyA9IGlmZWxzZShpcy5uYShzdGFycykgJiBzdGFyc19uYS5mbGFnID09MCwxLDApDQogICkNCiAgDQogIGxpYnJhcnkobWljZSkNCiAgdGVtcF9kZiA8LSBtaWNlKGRmMywgbWV0aG9kID0gJ2NhcnQnLCBtYXhpdCA9IDEpDQogIHRyYWluIDwtIGNvbXBsZXRlKHRlbXBfZGYpJT4lDQogICAgYmluZF9jb2xzKHRyYWluX3JhdyU+JWRwbHlyOjpzZWxlY3QoaW5kZXgpKSU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoLXN0YXJzX291dC5mbGFnLCAtbGFiZWxhcHBlYWxfb3V0LmZsYWcsIC1kZW5zaXR5X25hLmZsYWcsDQogICAgICAgICAgICAgICAgICAtbGFiZWxhcHBlYWxfbmEuZmxhZywgLWFjaWRpbmRleF9uYS5mbGFnLCAtZml4ZWRhY2lkaXR5X25hLmZsYWcsDQogICAgICAgICAgICAgICAgICAtdm9sYXRpbGVhY2lkaXR5X25hLmZsYWcsIC1jaXRyaWNhY2lkX25hLmZsYWcpDQogIHJldHVybihkYXRhLmZyYW1lKA0KICAgIGluZGV4ID0gdHJhaW4kaW5kZXgsDQogICAgdGFyZ2V0X3AgPSBwcmVkaWN0KHppbm5nLm1vZCwgbmV3ZGF0YSA9IHRyYWluKQ0KICAgICkNCiAgKQ0KfQ0KDQp0ZXN0IDwtIGFwcGx5X21vZGVsKCdXaW5lX1JhbmRvbV9UZXN0LmNzdicpDQp3cml0ZS5jc3YodGVzdCwgJ3lhem1hbi5jc3YnLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQojIENpdGF0aW9ucw0KW14xXTogSG9mZm1hbm4sIEpvaG4gUC4gR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsczogYW4gQXBwbGllZCBBcHByb2FjaC4gUGVhcnNvbi9BbGx5biAmIEJhY29uLCAyMDA0Lg0KW14yXTogaHR0cHM6Ly9zdGF0cy5pZHJlLnVjbGEuZWR1L3IvZGFlL3BvaXNzb24tcmVncmVzc2lvbi8NClteM106IGh0dHBzOi8vc3RhdHMuaWRyZS51Y2xhLmVkdS9yL2RhZS9uZWdhdGl2ZS1iaW5vbWlhbC1yZWdyZXNzaW9uLw0KW140XTogaHR0cHM6Ly9zdGF0aXN0aWNhbGhvcml6b25zLmNvbS96ZXJvLWluZmxhdGVkLW1vZGVscw0KW141XTogaHR0cHM6Ly9zdGF0cy5pZHJlLnVjbGEuZWR1L3IvZGFlL3ppcC8=