Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.

library(knitr)
library(kableExtra)
library(tidyverse)
library(broom)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
  if(!length(cols)) {
    cols=colnames(text)
  }
  if(!col1) {
    kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
      kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
  } else {
    kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
      kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
      column_spec(1,bold=T)
  }
}
# Import Walmart data from Session_Project
library(lubridate)
preprocess_data <- function(df) {
  # Merge the data together (Pulled from outside of function -- "scoping")
  df <- inner_join(df, weekly.stores)
  df <- inner_join(df, weekly.features[,1:11])
  
  # Compress the weird markdown information to 1 variable
  df$markdown <- 0
  df[!is.na(df$MarkDown1),]$markdown <- df[!is.na(df$MarkDown1),]$MarkDown1
  df[!is.na(df$MarkDown2),]$markdown <- df[!is.na(df$MarkDown2),]$MarkDown2
  df[!is.na(df$MarkDown3),]$markdown <- df[!is.na(df$MarkDown3),]$MarkDown3
  df[!is.na(df$MarkDown4),]$markdown <- df[!is.na(df$MarkDown4),]$MarkDown4
  df[!is.na(df$MarkDown5),]$markdown <- df[!is.na(df$MarkDown5),]$MarkDown5
  
  # Fix dates and add useful time variables
  df$date <- as.Date(df$Date)
  df$week <- week(df$date)
  df$year <- year(df$date)
  df$month <- month(df$date)
  
  df
}

weekly <- read.csv("../../Data/WMT_train.csv", stringsAsFactors=FALSE)
weekly.features <- read.csv("../../Data/WMT_features.csv", stringsAsFactors=FALSE)
weekly.stores <- read.csv("../../Data/WMT_stores.csv", stringsAsFactors=FALSE)
df <- preprocess_data(weekly)
#df$id <- df$Store *10000 + df$week * 100 + df$Dept
df <- df %>%
  group_by(Store, Dept) %>% 
  mutate(store_avg=mean(Weekly_Sales, rm.na=T)) %>%
  ungroup()
# Create the binary variable from Walmart sales data
df$double <- ifelse(df$Weekly_Sales > df$store_avg*2,1,0)
fit <- glm(double ~ IsHoliday, data=df, family=binomial)
tidy(fit)
# Automating the above:
exp(coef(fit))
  (Intercept) IsHolidayTRUE 
   0.03184725    1.71367497 
c(-3.44, -2.9)
[1] -3.44 -2.90
a <- exp(-3.44)
b <- exp(-2.9)
c(a/(1+a), b/(1+b))
[1] 0.03106848 0.05215356
model2 <- glm(double ~ IsHoliday + Temperature + Fuel_Price, data=df, family=binomial)
summary(model2)

Call:
glm(formula = double ~ IsHoliday + Temperature + Fuel_Price, 
    family = binomial, data = df)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-0.4113  -0.2738  -0.2464  -0.2213   2.8562  

Coefficients:
                Estimate Std. Error z value Pr(>|z|)    
(Intercept)   -1.7764917  0.0673246  -26.39   <2e-16 ***
IsHolidayTRUE  0.3704298  0.0284395   13.03   <2e-16 ***
Temperature   -0.0108268  0.0004698  -23.04   <2e-16 ***
Fuel_Price    -0.3091950  0.0196234  -15.76   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 120370  on 421569  degrees of freedom
Residual deviance: 119146  on 421566  degrees of freedom
AIC: 119154

Number of Fisher Scoring iterations: 6
# Odds
exp(coef(model2))
  (Intercept) IsHolidayTRUE   Temperature    Fuel_Price 
    0.1692308     1.4483570     0.9892316     0.7340376 
# Typical September days
hday_sep    <- mean(predict(model2, filter(df, IsHoliday, month==9), type="response"))
no_hday_sep <-  mean(predict(model2, filter(df, !IsHoliday, month==9), type="response"))
# Typical December days
hday_dec    <- mean(predict(model2, filter(df, IsHoliday, month==12), type="response"))
no_hday_dec <-  mean(predict(model2, filter(df, !IsHoliday, month==12), type="response"))

html_df(data.frame(Month=c(9,9,12,12),
                   IsHoliday=c(FALSE,TRUE,FALSE,TRUE),
                   Probability=c(no_hday_sep, hday_sep, no_hday_dec, hday_dec)))
Month IsHoliday Probability
9 FALSE 0.0266789
9 TRUE 0.0374761
12 FALSE 0.0398377
12 TRUE 0.0586483
percent <- function(x) {
  paste0(round(x*100,2),"%")
}
library(rlang)
# Another use for a quosure :)
# You could always just use quoted arguements here though
extract_margin <- function(m, x) {
  x <- enquo(x)
  percent(filter(summary(m), factor==quo_text(x))$AME)
}
# Calculate AME marginal effects
library(margins)
m <- margins(model2)
m
summary(m) %>%
  html_df()
factor AME SE z p lower upper
Fuel_Price -0.0096438 0.0006163 -15.64800 0 -0.0108517 -0.0084359
IsHoliday 0.0133450 0.0011754 11.35372 0 0.0110413 0.0156487
Temperature -0.0003377 0.0000149 -22.71255 0 -0.0003668 -0.0003085
plot(m, which=summary(m)$factor)

margins(model2, at = list(IsHoliday = c(TRUE, FALSE)),
        variables = c("Temperature", "Fuel_Price")) %>%
  summary() %>%
  html_df()
factor IsHoliday AME SE z p lower upper
Fuel_Price FALSE -0.0093401 0.0005989 -15.59617 0 -0.0105139 -0.0081664
Fuel_Price TRUE -0.0131335 0.0008717 -15.06650 0 -0.0148420 -0.0114250
Temperature FALSE -0.0003271 0.0000146 -22.46024 0 -0.0003556 -0.0002985
Temperature TRUE -0.0004599 0.0000210 -21.92927 0 -0.0005010 -0.0004188
margins(model2, at = list(Temperature = c(0, 20, 40, 60, 80, 100)),
        variables = c("IsHoliday")) %>%
  summary() %>%
  html_df()
factor Temperature AME SE z p lower upper
IsHoliday 0 0.0234484 0.0020168 11.62643 0 0.0194955 0.0274012
IsHoliday 20 0.0194072 0.0016710 11.61387 0 0.0161320 0.0226824
IsHoliday 40 0.0159819 0.0013885 11.51001 0 0.0132604 0.0187033
IsHoliday 60 0.0131066 0.0011592 11.30623 0 0.0108345 0.0153786
IsHoliday 80 0.0107120 0.0009732 11.00749 0 0.0088046 0.0126193
IsHoliday 100 0.0087305 0.0008213 10.62977 0 0.0071207 0.0103402

# load data
df <- read.csv("../../Data/Shipping/shipping_dataset.csv", stringsAsFactors = F, na="NA")
df_ships <- read.csv("../../Data/Shipping/index.csv", stringsAsFactors = F, na="NA")
df_ports <- read.csv("../../Data/Shipping/port_dataset.csv", stringsAsFactors = F, na="NA")
typhoon <- read.csv("../../Data/Shipping/typhoon.csv", stringsAsFactors = F)

# rename lat and lon to avoid conflicts
df_ports <- df_ports %>% rename(port_lat=lat, port_lon=lon)

# Join in ships and ports
df <- left_join(df, df_ships)
Joining, by = "imo"
df <- df %>% filter(!imo == 9528029 | reported_destination == "AU ADL")
df <- left_join(df,df_ports, by=c("left_port"="port"))
df <- df %>% rename(left_lat=port_lat, left_lon=port_lon)
df <- left_join(df,df_ports, by=c("arrive_port"="port"))
df <- df %>% rename(arrive_lat=port_lat, arrive_lon=port_lon)

# fix dates
df$last_update = as.POSIXct(df$last_update, tz="UTC",origin="1970-01-01")
df$left_time = as.POSIXct(df$left_time, tz="UTC",origin="1970-01-01")
df$arrive_time = as.POSIXct(df$arrive_time, tz="UTC",origin="1970-01-01")

# Fix typhoon dates
typhoon$date <- as.POSIXct(paste(typhoon$date,typhoon$time), format="%Y%b%d %H%M%S", tz="UTC", origin="1970-01-01")
# Fix typhoon longitude -- they have lon * -1 in the data
typhoon$lon <- typhoon$lon * -1

typhoon_all <- typhoon
# filter to dates in sample
typhoon <- typhoon %>% filter(date > as.POSIXct("2018-08-30", tz="UTC"))

df <- df %>% rename(frame=run)

df_all <- df[df$frame != 32,]
df <- df %>% filter(last_update > as.POSIXct("2018-08-30", tz="UTC"))

library(plotly)  # needed for the toRGB() function
geo <- list(
  showland = TRUE,
  showlakes = TRUE,
  showcountries = TRUE,
  showocean = TRUE,
  countrywidth = 0.5,
  landcolor = toRGB("grey90"),
  lakecolor = toRGB("aliceblue"),
  oceancolor = toRGB("aliceblue"),
  projection = list(
    type = 'orthographic',  # detailed at https://plot.ly/r/reference/#layout-geo-projection
    rotation = list(
      lon = 100,
      lat = 1,
      roll = 0
    )
  ),
  lonaxis = list(
    showgrid = TRUE,
    gridcolor = toRGB("gray40"),
    gridwidth = 0.5
  ),
  lataxis = list(
    showgrid = TRUE,
    gridcolor = toRGB("gray40"),
    gridwidth = 0.5
  )
)

typhoon_Aug31 <- typhoon[typhoon$date > as.POSIXct("2018-08-31 00:00:00", tz="UTC") & typhoon$date < as.POSIXct("2018-09-01 00:00:00", tz="UTC"),]
typhoon_Jebi <- typhoon_all[typhoon_all$typhoon_name == "JEBI_Y",]
df_Aug31 <- df[df$frame == 1,]
library(plotly)  # for plotting
library(RColorBrewer)  # for colors
# plot with boats, ports, and typhoons
# Note: geo is defined in the appendix -- it controls layout
palette = brewer.pal(8, "Dark2")[c(1,8,3,2)]
p <- plot_geo(colors=palette) %>%
  add_markers(data=df_ports, x = ~port_lon, y = ~port_lat, color = "Port") %>%
  add_markers(data=df_Aug31, x = ~lon, y = ~lat, color = ~ship_type,
              text=~paste('Ship name',shipname)) %>%
  add_markers(data=typhoon_Aug31, x = ~lon, y = ~lat, color="TYPHOON",
              text=~paste("Name", typhoon_name)) %>%
   layout(showlegend = TRUE, geo = geo,
    title = 'Singaporean owned container and tanker ships, August 31, 2018')
p
# plot with boats and typhoons
palette = brewer.pal(8, "Dark2")[c(1,3,2)]
p <- plot_geo(colors=palette) %>%
  add_markers(data=df_all[df_all$frame == 14,], x = ~lon, y = ~lat, color = ~ship_type,
              text=~paste('Ship name',shipname)) %>%
  add_markers(data=typhoon_Jebi, x = ~lon, y = ~lat, color="Typhoon Jebi", text=~paste("Name", typhoon_name, "</br>Time: ", date)) %>%
   layout(
    showlegend = TRUE, geo = geo,
    title = 'Singaporean container/tanker ships, September 4, 2018, evening')
p
library(leaflet)
library(leaflet.extras)

# typhoon icons
icons <- pulseIcons(color='red',
  heartbeat = ifelse(typhoon_Jebi$intensity_vmax > 150/1.852, 0.8,
    ifelse(typhoon$intensity_vmax < 118/1.852, 1.6, 1.2)),
  iconSize=ifelse(typhoon_Jebi$intensity_vmax > 150/1.852, 12,
    ifelse(typhoon_Jebi$intensity_vmax < 118/1.852, 3, 8)))

# ship icons
shipicons <- iconList(
  ship = makeIcon("../Figures/ship.png", NULL, 18, 18)
)
leaflet() %>%
  addTiles() %>% 
  setView(lng = 136, lat = 34, zoom=4) %>%
  addPulseMarkers(data=typhoon_Jebi[seq(1,nrow(typhoon_Jebi),5),], lng=~lon,
                  lat=~lat, label=~date, icon=icons) %>%
  addCircleMarkers(data=typhoon_Jebi[typhoon_Jebi$intensity_vmax > 150/1.852,],
    lng=~lon, lat=~lat,stroke = TRUE, radius=12, color="red", label=~date) %>%
  addCircleMarkers(data=typhoon_Jebi[typhoon_Jebi$intensity_vmax <= 150/1.852 &
    typhoon_Jebi$intensity_vmax > 118/1.852,], lng=~lon, lat=~lat,
    stroke = TRUE, radius=12, color="red", label=~date) %>%
  addCircleMarkers(data=typhoon_Jebi[typhoon_Jebi$intensity_vmax <=118/1.852,],
    lng=~lon, lat=~lat, stroke = TRUE, radius=12, color="red", label=~date) %>%
  addMarkers(data=df_all[df_all$frame == 14,], lng=~lon, lat=~lat,
             label=~shipname, icon=shipicons)
# Load datas
df3 <- read.csv("../../Data/Shipping/combined.csv", stringsAsFactors = F)

library(geosphere)

# Calculate distance to and from ports
df3$dist_toport <- distVincentyEllipsoid(as.matrix(df3[,c("lon","lat")]), as.matrix(df3[,c("arrive_lon","arrive_lat")]))
df3$dist_fromport <- distVincentyEllipsoid(as.matrix(df3[,c("lon","lat")]), as.matrix(df3[,c("left_lon","left_lat")]))
df3 <- df3 %>%
  arrange(imo, last_update) %>%
  group_by(imo) %>%
  mutate(delayed=ifelse(difftime(arrive_time, lead(arrive_time), units="days") > 1/8 & arrive_time > last_update & left_port == lead(left_port),1,0)) %>%
  ungroup()
library(geosphere)
x <- as.matrix(df3[,c("lon","lat")])  # ship location
y <- as.matrix(df3[,c("ty_lon","ty_lat")]) # typhoon location

df3$dist_typhoon <- distVincentyEllipsoid(x, y) / 1000
df3$typhoon_500 = ifelse(df3$dist_typhoon < 500 & 
                         df3$dist_typhoon >= 0, 1, 0)
df3$typhoon_1000 = ifelse(df3$dist_typhoon < 1000 &
                          df3$dist_typhoon >= 500, 1, 0)
df3$typhoon_2000 = ifelse(df3$dist_typhoon < 2000 &
                          df3$dist_typhoon >= 1000, 1, 0)
fit1 <- glm(delayed ~ typhoon_500 + typhoon_1000 + typhoon_2000, data=df3,
            family=binomial)
summary(fit1)

Call:
glm(formula = delayed ~ typhoon_500 + typhoon_1000 + typhoon_2000, 
    family = binomial, data = df3)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-0.2502  -0.2261  -0.2261  -0.2261   2.7127  

Coefficients:
             Estimate Std. Error  z value Pr(>|z|)    
(Intercept)  -3.65377    0.02934 -124.547   <2e-16 ***
typhoon_500   0.14073    0.16311    0.863   0.3883    
typhoon_1000  0.20539    0.12575    1.633   0.1024    
typhoon_2000  0.16059    0.07106    2.260   0.0238 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 14329  on 59184  degrees of freedom
Residual deviance: 14322  on 59181  degrees of freedom
  (3866 observations deleted due to missingness)
AIC: 14330

Number of Fisher Scoring iterations: 6
odds1 <- exp(coef(fit1))
odds1
 (Intercept)  typhoon_500 typhoon_1000 typhoon_2000 
  0.02589334   1.15111673   1.22800815   1.17420736 
m1 <- margins(fit1)
summary(m1)
# Cut makes a categorical variable out of a numerical variable using specified bins
df3$Super <- ifelse(df3$intensity_vmax * 1.852 > 185, 1, 0)
df3$Moderate <- ifelse(df3$intensity_vmax * 1.852 >= 88 &
                         df3$intensity_vmax * 1.852 < 185, 1, 0)
df3$Weak <- ifelse(df3$intensity_vmax * 1.852 >= 41 & 
                   df3$intensity_vmax * 1.852 < 88, 1, 0)
df3$HK_intensity <- cut(df3$intensity_vmax * 1.852 ,c(-1,41, 62, 87, 117, 149, 999))
table(df3$HK_intensity)

  (-1,41]   (41,62]   (62,87]  (87,117] (117,149] (149,999] 
     3398     12039     12615     11527      2255     21141 
fit2 <- glm(delayed ~ (typhoon_500 + typhoon_1000 + typhoon_2000) : 
              (Weak + Moderate + Super), data=df3,
            family=binomial)
tidy(fit2)
m2 <- margins(fit2)
summary(m2) %>%
  html_df()
factor AME SE z p lower upper
Moderate 0.0007378 0.0006713 1.0990530 0.2717449 -0.0005779 0.0020535
Super -0.0050241 0.0860163 -0.0584087 0.9534231 -0.1736129 0.1635647
typhoon_1000 0.0035473 0.0036186 0.9802921 0.3269420 -0.0035450 0.0106396
typhoon_2000 0.0039224 0.0017841 2.1985908 0.0279070 0.0004257 0.0074191
typhoon_500 -0.0440484 0.6803640 -0.0647424 0.9483791 -1.3775373 1.2894405
Weak 0.0009975 0.0005154 1.9353011 0.0529534 -0.0000127 0.0020077
margins(fit2, at = list(Weak = c(1)),
        variables = c("typhoon_500", "typhoon_1000", "typhoon_2000")) %>%
  summary() %>%
  html_df()
factor Weak AME SE z p lower upper
typhoon_1000 1 0.0073057 0.0053682 1.360938 0.1735332 -0.0032157 0.0178271
typhoon_2000 1 0.0067051 0.0031225 2.147328 0.0317671 0.0005850 0.0128251
typhoon_500 1 -0.0458116 0.7052501 -0.064958 0.9482075 -1.4280764 1.3364531
margins(fit2, at = list(Moderate = c(1)),
        variables = c("typhoon_500", "typhoon_1000", "typhoon_2000")) %>%
  summary() %>%
  html_df()
factor Moderate AME SE z p lower upper
typhoon_1000 1 0.0059332 0.0078245 0.7582856 0.4482800 -0.0094025 0.0212688
typhoon_2000 1 0.0044871 0.0039453 1.1373050 0.2554108 -0.0032457 0.0122198
typhoon_500 1 -0.0311946 0.6847130 -0.0455586 0.9636620 -1.3732074 1.3108182
margins(fit2, at = list(Super = c(1)),
        variables = c("typhoon_500", "typhoon_1000", "typhoon_2000")) %>%
  summary() %>%
  html_df()
factor Super AME SE z p lower upper
typhoon_1000 1 0.0030638 0.0111295 0.2752891 0.7830941 -0.0187495 0.0248772
typhoon_2000 1 0.0102513 0.0041568 2.4661549 0.0136572 0.0021041 0.0183985
typhoon_500 1 -0.2241250 3.1608062 -0.0709076 0.9434713 -6.4191913 5.9709413
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gNCINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShicm9vbSkNCmh0bWxfZGYgPC0gZnVuY3Rpb24odGV4dCwgY29scz1OVUxMLCBjb2wxPUZBTFNFLCBmdWxsPUYpIHsNCiAgaWYoIWxlbmd0aChjb2xzKSkgew0KICAgIGNvbHM9Y29sbmFtZXModGV4dCkNCiAgfQ0KICBpZighY29sMSkgew0KICAgIGthYmxlKHRleHQsImh0bWwiLCBjb2wubmFtZXMgPSBjb2xzLCBhbGlnbiA9IGMoImwiLHJlcCgnYycsbGVuZ3RoKGNvbHMpLTEpKSkgJT4lDQogICAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsImhvdmVyIiksIGZ1bGxfd2lkdGg9ZnVsbCkNCiAgfSBlbHNlIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpICU+JQ0KICAgICAgY29sdW1uX3NwZWMoMSxib2xkPVQpDQogIH0NCn0NCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIEltcG9ydCBXYWxtYXJ0IGRhdGEgZnJvbSBTZXNzaW9uX1Byb2plY3QNCmxpYnJhcnkobHVicmlkYXRlKQ0KcHJlcHJvY2Vzc19kYXRhIDwtIGZ1bmN0aW9uKGRmKSB7DQogICMgTWVyZ2UgdGhlIGRhdGEgdG9nZXRoZXIgKFB1bGxlZCBmcm9tIG91dHNpZGUgb2YgZnVuY3Rpb24gLS0gInNjb3BpbmciKQ0KICBkZiA8LSBpbm5lcl9qb2luKGRmLCB3ZWVrbHkuc3RvcmVzKQ0KICBkZiA8LSBpbm5lcl9qb2luKGRmLCB3ZWVrbHkuZmVhdHVyZXNbLDE6MTFdKQ0KICANCiAgIyBDb21wcmVzcyB0aGUgd2VpcmQgbWFya2Rvd24gaW5mb3JtYXRpb24gdG8gMSB2YXJpYWJsZQ0KICBkZiRtYXJrZG93biA8LSAwDQogIGRmWyFpcy5uYShkZiRNYXJrRG93bjEpLF0kbWFya2Rvd24gPC0gZGZbIWlzLm5hKGRmJE1hcmtEb3duMSksXSRNYXJrRG93bjENCiAgZGZbIWlzLm5hKGRmJE1hcmtEb3duMiksXSRtYXJrZG93biA8LSBkZlshaXMubmEoZGYkTWFya0Rvd24yKSxdJE1hcmtEb3duMg0KICBkZlshaXMubmEoZGYkTWFya0Rvd24zKSxdJG1hcmtkb3duIDwtIGRmWyFpcy5uYShkZiRNYXJrRG93bjMpLF0kTWFya0Rvd24zDQogIGRmWyFpcy5uYShkZiRNYXJrRG93bjQpLF0kbWFya2Rvd24gPC0gZGZbIWlzLm5hKGRmJE1hcmtEb3duNCksXSRNYXJrRG93bjQNCiAgZGZbIWlzLm5hKGRmJE1hcmtEb3duNSksXSRtYXJrZG93biA8LSBkZlshaXMubmEoZGYkTWFya0Rvd241KSxdJE1hcmtEb3duNQ0KICANCiAgIyBGaXggZGF0ZXMgYW5kIGFkZCB1c2VmdWwgdGltZSB2YXJpYWJsZXMNCiAgZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJERhdGUpDQogIGRmJHdlZWsgPC0gd2VlayhkZiRkYXRlKQ0KICBkZiR5ZWFyIDwtIHllYXIoZGYkZGF0ZSkNCiAgZGYkbW9udGggPC0gbW9udGgoZGYkZGF0ZSkNCiAgDQogIGRmDQp9DQoNCndlZWtseSA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9XTVRfdHJhaW4uY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCndlZWtseS5mZWF0dXJlcyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9XTVRfZmVhdHVyZXMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCndlZWtseS5zdG9yZXMgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvV01UX3N0b3Jlcy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KZGYgPC0gcHJlcHJvY2Vzc19kYXRhKHdlZWtseSkNCiNkZiRpZCA8LSBkZiRTdG9yZSAqMTAwMDAgKyBkZiR3ZWVrICogMTAwICsgZGYkRGVwdA0KZGYgPC0gZGYgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCBEZXB0KSAlPiUgDQogIG11dGF0ZShzdG9yZV9hdmc9bWVhbihXZWVrbHlfU2FsZXMsIHJtLm5hPVQpKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3J9DQojIENyZWF0ZSB0aGUgYmluYXJ5IHZhcmlhYmxlIGZyb20gV2FsbWFydCBzYWxlcyBkYXRhDQpkZiRkb3VibGUgPC0gaWZlbHNlKGRmJFdlZWtseV9TYWxlcyA+IGRmJHN0b3JlX2F2ZyoyLDEsMCkNCmZpdCA8LSBnbG0oZG91YmxlIH4gSXNIb2xpZGF5LCBkYXRhPWRmLCBmYW1pbHk9Ymlub21pYWwpDQp0aWR5KGZpdCkNCmBgYA0KDQpgYGB7cn0NCiMgQXV0b21hdGluZyB0aGUgYWJvdmU6DQpleHAoY29lZihmaXQpKQ0KYGBgDQoNCmBgYHtyfQ0KYygtMy40NCwgLTIuOSkNCmBgYA0KDQpgYGB7cn0NCmEgPC0gZXhwKC0zLjQ0KQ0KYiA8LSBleHAoLTIuOSkNCmMoYS8oMSthKSwgYi8oMStiKSkNCmBgYA0KDQpgYGB7cn0NCm1vZGVsMiA8LSBnbG0oZG91YmxlIH4gSXNIb2xpZGF5ICsgVGVtcGVyYXR1cmUgKyBGdWVsX1ByaWNlLCBkYXRhPWRmLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KG1vZGVsMikNCmBgYA0KDQpgYGB7cn0NCiMgT2Rkcw0KZXhwKGNvZWYobW9kZWwyKSkNCmBgYA0KDQpgYGB7cn0NCiMgVHlwaWNhbCBTZXB0ZW1iZXIgZGF5cw0KaGRheV9zZXAgICAgPC0gbWVhbihwcmVkaWN0KG1vZGVsMiwgZmlsdGVyKGRmLCBJc0hvbGlkYXksIG1vbnRoPT05KSwgdHlwZT0icmVzcG9uc2UiKSkNCm5vX2hkYXlfc2VwIDwtICBtZWFuKHByZWRpY3QobW9kZWwyLCBmaWx0ZXIoZGYsICFJc0hvbGlkYXksIG1vbnRoPT05KSwgdHlwZT0icmVzcG9uc2UiKSkNCiMgVHlwaWNhbCBEZWNlbWJlciBkYXlzDQpoZGF5X2RlYyAgICA8LSBtZWFuKHByZWRpY3QobW9kZWwyLCBmaWx0ZXIoZGYsIElzSG9saWRheSwgbW9udGg9PTEyKSwgdHlwZT0icmVzcG9uc2UiKSkNCm5vX2hkYXlfZGVjIDwtICBtZWFuKHByZWRpY3QobW9kZWwyLCBmaWx0ZXIoZGYsICFJc0hvbGlkYXksIG1vbnRoPT0xMiksIHR5cGU9InJlc3BvbnNlIikpDQoNCmh0bWxfZGYoZGF0YS5mcmFtZShNb250aD1jKDksOSwxMiwxMiksDQogICAgICAgICAgICAgICAgICAgSXNIb2xpZGF5PWMoRkFMU0UsVFJVRSxGQUxTRSxUUlVFKSwNCiAgICAgICAgICAgICAgICAgICBQcm9iYWJpbGl0eT1jKG5vX2hkYXlfc2VwLCBoZGF5X3NlcCwgbm9faGRheV9kZWMsIGhkYXlfZGVjKSkpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Rn0NCnBlcmNlbnQgPC0gZnVuY3Rpb24oeCkgew0KICBwYXN0ZTAocm91bmQoeCoxMDAsMiksIiUiKQ0KfQ0KbGlicmFyeShybGFuZykNCiMgQW5vdGhlciB1c2UgZm9yIGEgcXVvc3VyZSA6KQ0KIyBZb3UgY291bGQgYWx3YXlzIGp1c3QgdXNlIHF1b3RlZCBhcmd1ZW1lbnRzIGhlcmUgdGhvdWdoDQpleHRyYWN0X21hcmdpbiA8LSBmdW5jdGlvbihtLCB4KSB7DQogIHggPC0gZW5xdW8oeCkNCiAgcGVyY2VudChmaWx0ZXIoc3VtbWFyeShtKSwgZmFjdG9yPT1xdW9fdGV4dCh4KSkkQU1FKQ0KfQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQojIENhbGN1bGF0ZSBBTUUgbWFyZ2luYWwgZWZmZWN0cw0KbGlicmFyeShtYXJnaW5zKQ0KbSA8LSBtYXJnaW5zKG1vZGVsMikNCm0NCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkobSkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02fQ0KcGxvdChtLCB3aGljaD1zdW1tYXJ5KG0pJGZhY3RvcikNCmBgYA0KDQpgYGB7cn0NCm1hcmdpbnMobW9kZWwyLCBhdCA9IGxpc3QoSXNIb2xpZGF5ID0gYyhUUlVFLCBGQUxTRSkpLA0KICAgICAgICB2YXJpYWJsZXMgPSBjKCJUZW1wZXJhdHVyZSIsICJGdWVsX1ByaWNlIikpICU+JQ0KICBzdW1tYXJ5KCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KbWFyZ2lucyhtb2RlbDIsIGF0ID0gbGlzdChUZW1wZXJhdHVyZSA9IGMoMCwgMjAsIDQwLCA2MCwgODAsIDEwMCkpLA0KICAgICAgICB2YXJpYWJsZXMgPSBjKCJJc0hvbGlkYXkiKSkgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3J9DQoNCiMgbG9hZCBkYXRhDQpkZiA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TaGlwcGluZy9zaGlwcGluZ19kYXRhc2V0LmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLCBuYT0iTkEiKQ0KZGZfc2hpcHMgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvU2hpcHBpbmcvaW5kZXguY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIG5hPSJOQSIpDQpkZl9wb3J0cyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TaGlwcGluZy9wb3J0X2RhdGFzZXQuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIG5hPSJOQSIpDQp0eXBob29uIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1NoaXBwaW5nL3R5cGhvb24uY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQoNCiMgcmVuYW1lIGxhdCBhbmQgbG9uIHRvIGF2b2lkIGNvbmZsaWN0cw0KZGZfcG9ydHMgPC0gZGZfcG9ydHMgJT4lIHJlbmFtZShwb3J0X2xhdD1sYXQsIHBvcnRfbG9uPWxvbikNCg0KIyBKb2luIGluIHNoaXBzIGFuZCBwb3J0cw0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9zaGlwcykNCmRmIDwtIGRmICU+JSBmaWx0ZXIoIWltbyA9PSA5NTI4MDI5IHwgcmVwb3J0ZWRfZGVzdGluYXRpb24gPT0gIkFVIEFETCIpDQpkZiA8LSBsZWZ0X2pvaW4oZGYsZGZfcG9ydHMsIGJ5PWMoImxlZnRfcG9ydCI9InBvcnQiKSkNCmRmIDwtIGRmICU+JSByZW5hbWUobGVmdF9sYXQ9cG9ydF9sYXQsIGxlZnRfbG9uPXBvcnRfbG9uKQ0KZGYgPC0gbGVmdF9qb2luKGRmLGRmX3BvcnRzLCBieT1jKCJhcnJpdmVfcG9ydCI9InBvcnQiKSkNCmRmIDwtIGRmICU+JSByZW5hbWUoYXJyaXZlX2xhdD1wb3J0X2xhdCwgYXJyaXZlX2xvbj1wb3J0X2xvbikNCg0KIyBmaXggZGF0ZXMNCmRmJGxhc3RfdXBkYXRlID0gYXMuUE9TSVhjdChkZiRsYXN0X3VwZGF0ZSwgdHo9IlVUQyIsb3JpZ2luPSIxOTcwLTAxLTAxIikNCmRmJGxlZnRfdGltZSA9IGFzLlBPU0lYY3QoZGYkbGVmdF90aW1lLCB0ej0iVVRDIixvcmlnaW49IjE5NzAtMDEtMDEiKQ0KZGYkYXJyaXZlX3RpbWUgPSBhcy5QT1NJWGN0KGRmJGFycml2ZV90aW1lLCB0ej0iVVRDIixvcmlnaW49IjE5NzAtMDEtMDEiKQ0KDQojIEZpeCB0eXBob29uIGRhdGVzDQp0eXBob29uJGRhdGUgPC0gYXMuUE9TSVhjdChwYXN0ZSh0eXBob29uJGRhdGUsdHlwaG9vbiR0aW1lKSwgZm9ybWF0PSIlWSViJWQgJUglTSVTIiwgdHo9IlVUQyIsIG9yaWdpbj0iMTk3MC0wMS0wMSIpDQojIEZpeCB0eXBob29uIGxvbmdpdHVkZSAtLSB0aGV5IGhhdmUgbG9uICogLTEgaW4gdGhlIGRhdGENCnR5cGhvb24kbG9uIDwtIHR5cGhvb24kbG9uICogLTENCg0KdHlwaG9vbl9hbGwgPC0gdHlwaG9vbg0KIyBmaWx0ZXIgdG8gZGF0ZXMgaW4gc2FtcGxlDQp0eXBob29uIDwtIHR5cGhvb24gJT4lIGZpbHRlcihkYXRlID4gYXMuUE9TSVhjdCgiMjAxOC0wOC0zMCIsIHR6PSJVVEMiKSkNCg0KZGYgPC0gZGYgJT4lIHJlbmFtZShmcmFtZT1ydW4pDQoNCmRmX2FsbCA8LSBkZltkZiRmcmFtZSAhPSAzMixdDQpkZiA8LSBkZiAlPiUgZmlsdGVyKGxhc3RfdXBkYXRlID4gYXMuUE9TSVhjdCgiMjAxOC0wOC0zMCIsIHR6PSJVVEMiKSkNCg0KbGlicmFyeShwbG90bHkpICAjIG5lZWRlZCBmb3IgdGhlIHRvUkdCKCkgZnVuY3Rpb24NCmdlbyA8LSBsaXN0KA0KICBzaG93bGFuZCA9IFRSVUUsDQogIHNob3dsYWtlcyA9IFRSVUUsDQogIHNob3djb3VudHJpZXMgPSBUUlVFLA0KICBzaG93b2NlYW4gPSBUUlVFLA0KICBjb3VudHJ5d2lkdGggPSAwLjUsDQogIGxhbmRjb2xvciA9IHRvUkdCKCJncmV5OTAiKSwNCiAgbGFrZWNvbG9yID0gdG9SR0IoImFsaWNlYmx1ZSIpLA0KICBvY2VhbmNvbG9yID0gdG9SR0IoImFsaWNlYmx1ZSIpLA0KICBwcm9qZWN0aW9uID0gbGlzdCgNCiAgICB0eXBlID0gJ29ydGhvZ3JhcGhpYycsICAjIGRldGFpbGVkIGF0IGh0dHBzOi8vcGxvdC5seS9yL3JlZmVyZW5jZS8jbGF5b3V0LWdlby1wcm9qZWN0aW9uDQogICAgcm90YXRpb24gPSBsaXN0KA0KICAgICAgbG9uID0gMTAwLA0KICAgICAgbGF0ID0gMSwNCiAgICAgIHJvbGwgPSAwDQogICAgKQ0KICApLA0KICBsb25heGlzID0gbGlzdCgNCiAgICBzaG93Z3JpZCA9IFRSVUUsDQogICAgZ3JpZGNvbG9yID0gdG9SR0IoImdyYXk0MCIpLA0KICAgIGdyaWR3aWR0aCA9IDAuNQ0KICApLA0KICBsYXRheGlzID0gbGlzdCgNCiAgICBzaG93Z3JpZCA9IFRSVUUsDQogICAgZ3JpZGNvbG9yID0gdG9SR0IoImdyYXk0MCIpLA0KICAgIGdyaWR3aWR0aCA9IDAuNQ0KICApDQopDQoNCnR5cGhvb25fQXVnMzEgPC0gdHlwaG9vblt0eXBob29uJGRhdGUgPiBhcy5QT1NJWGN0KCIyMDE4LTA4LTMxIDAwOjAwOjAwIiwgdHo9IlVUQyIpICYgdHlwaG9vbiRkYXRlIDwgYXMuUE9TSVhjdCgiMjAxOC0wOS0wMSAwMDowMDowMCIsIHR6PSJVVEMiKSxdDQp0eXBob29uX0plYmkgPC0gdHlwaG9vbl9hbGxbdHlwaG9vbl9hbGwkdHlwaG9vbl9uYW1lID09ICJKRUJJX1kiLF0NCmRmX0F1ZzMxIDwtIGRmW2RmJGZyYW1lID09IDEsXQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9RiwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OH0NCmxpYnJhcnkocGxvdGx5KSAgIyBmb3IgcGxvdHRpbmcNCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAgIyBmb3IgY29sb3JzDQojIHBsb3Qgd2l0aCBib2F0cywgcG9ydHMsIGFuZCB0eXBob29ucw0KIyBOb3RlOiBnZW8gaXMgZGVmaW5lZCBpbiB0aGUgYXBwZW5kaXggLS0gaXQgY29udHJvbHMgbGF5b3V0DQpwYWxldHRlID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKVtjKDEsOCwzLDIpXQ0KcCA8LSBwbG90X2dlbyhjb2xvcnM9cGFsZXR0ZSkgJT4lDQogIGFkZF9tYXJrZXJzKGRhdGE9ZGZfcG9ydHMsIHggPSB+cG9ydF9sb24sIHkgPSB+cG9ydF9sYXQsIGNvbG9yID0gIlBvcnQiKSAlPiUNCiAgYWRkX21hcmtlcnMoZGF0YT1kZl9BdWczMSwgeCA9IH5sb24sIHkgPSB+bGF0LCBjb2xvciA9IH5zaGlwX3R5cGUsDQogICAgICAgICAgICAgIHRleHQ9fnBhc3RlKCdTaGlwIG5hbWUnLHNoaXBuYW1lKSkgJT4lDQogIGFkZF9tYXJrZXJzKGRhdGE9dHlwaG9vbl9BdWczMSwgeCA9IH5sb24sIHkgPSB+bGF0LCBjb2xvcj0iVFlQSE9PTiIsDQogICAgICAgICAgICAgIHRleHQ9fnBhc3RlKCJOYW1lIiwgdHlwaG9vbl9uYW1lKSkgJT4lDQogICBsYXlvdXQoc2hvd2xlZ2VuZCA9IFRSVUUsIGdlbyA9IGdlbywNCiAgICB0aXRsZSA9ICdTaW5nYXBvcmVhbiBvd25lZCBjb250YWluZXIgYW5kIHRhbmtlciBzaGlwcywgQXVndXN0IDMxLCAyMDE4JykNCnANCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUYsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9DQojIHBsb3Qgd2l0aCBib2F0cyBhbmQgdHlwaG9vbnMNCnBhbGV0dGUgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpW2MoMSwzLDIpXQ0KcCA8LSBwbG90X2dlbyhjb2xvcnM9cGFsZXR0ZSkgJT4lDQogIGFkZF9tYXJrZXJzKGRhdGE9ZGZfYWxsW2RmX2FsbCRmcmFtZSA9PSAxNCxdLCB4ID0gfmxvbiwgeSA9IH5sYXQsIGNvbG9yID0gfnNoaXBfdHlwZSwNCiAgICAgICAgICAgICAgdGV4dD1+cGFzdGUoJ1NoaXAgbmFtZScsc2hpcG5hbWUpKSAlPiUNCiAgYWRkX21hcmtlcnMoZGF0YT10eXBob29uX0plYmksIHggPSB+bG9uLCB5ID0gfmxhdCwgY29sb3I9IlR5cGhvb24gSmViaSIsIHRleHQ9fnBhc3RlKCJOYW1lIiwgdHlwaG9vbl9uYW1lLCAiPC9icj5UaW1lOiAiLCBkYXRlKSkgJT4lDQogICBsYXlvdXQoDQogICAgc2hvd2xlZ2VuZCA9IFRSVUUsIGdlbyA9IGdlbywNCiAgICB0aXRsZSA9ICdTaW5nYXBvcmVhbiBjb250YWluZXIvdGFua2VyIHNoaXBzLCBTZXB0ZW1iZXIgNCwgMjAxOCwgZXZlbmluZycpDQpwDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD05fQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShsZWFmbGV0LmV4dHJhcykNCg0KIyB0eXBob29uIGljb25zDQppY29ucyA8LSBwdWxzZUljb25zKGNvbG9yPSdyZWQnLA0KICBoZWFydGJlYXQgPSBpZmVsc2UodHlwaG9vbl9KZWJpJGludGVuc2l0eV92bWF4ID4gMTUwLzEuODUyLCAwLjgsDQogICAgaWZlbHNlKHR5cGhvb24kaW50ZW5zaXR5X3ZtYXggPCAxMTgvMS44NTIsIDEuNiwgMS4yKSksDQogIGljb25TaXplPWlmZWxzZSh0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPiAxNTAvMS44NTIsIDEyLA0KICAgIGlmZWxzZSh0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPCAxMTgvMS44NTIsIDMsIDgpKSkNCg0KIyBzaGlwIGljb25zDQpzaGlwaWNvbnMgPC0gaWNvbkxpc3QoDQogIHNoaXAgPSBtYWtlSWNvbigiLi4vRmlndXJlcy9zaGlwLnBuZyIsIE5VTEwsIDE4LCAxOCkNCikNCmxlYWZsZXQoKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUgDQogIHNldFZpZXcobG5nID0gMTM2LCBsYXQgPSAzNCwgem9vbT00KSAlPiUNCiAgYWRkUHVsc2VNYXJrZXJzKGRhdGE9dHlwaG9vbl9KZWJpW3NlcSgxLG5yb3codHlwaG9vbl9KZWJpKSw1KSxdLCBsbmc9fmxvbiwNCiAgICAgICAgICAgICAgICAgIGxhdD1+bGF0LCBsYWJlbD1+ZGF0ZSwgaWNvbj1pY29ucykgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YT10eXBob29uX0plYmlbdHlwaG9vbl9KZWJpJGludGVuc2l0eV92bWF4ID4gMTUwLzEuODUyLF0sDQogICAgbG5nPX5sb24sIGxhdD1+bGF0LHN0cm9rZSA9IFRSVUUsIHJhZGl1cz0xMiwgY29sb3I9InJlZCIsIGxhYmVsPX5kYXRlKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhPXR5cGhvb25fSmViaVt0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPD0gMTUwLzEuODUyICYNCiAgICB0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPiAxMTgvMS44NTIsXSwgbG5nPX5sb24sIGxhdD1+bGF0LA0KICAgIHN0cm9rZSA9IFRSVUUsIHJhZGl1cz0xMiwgY29sb3I9InJlZCIsIGxhYmVsPX5kYXRlKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhPXR5cGhvb25fSmViaVt0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPD0xMTgvMS44NTIsXSwNCiAgICBsbmc9fmxvbiwgbGF0PX5sYXQsIHN0cm9rZSA9IFRSVUUsIHJhZGl1cz0xMiwgY29sb3I9InJlZCIsIGxhYmVsPX5kYXRlKSAlPiUNCiAgYWRkTWFya2VycyhkYXRhPWRmX2FsbFtkZl9hbGwkZnJhbWUgPT0gMTQsXSwgbG5nPX5sb24sIGxhdD1+bGF0LA0KICAgICAgICAgICAgIGxhYmVsPX5zaGlwbmFtZSwgaWNvbj1zaGlwaWNvbnMpDQpgYGANCg0KYGBge3J9DQojIExvYWQgZGF0YXMNCmRmMyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TaGlwcGluZy9jb21iaW5lZC5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCg0KbGlicmFyeShnZW9zcGhlcmUpDQoNCiMgQ2FsY3VsYXRlIGRpc3RhbmNlIHRvIGFuZCBmcm9tIHBvcnRzDQpkZjMkZGlzdF90b3BvcnQgPC0gZGlzdFZpbmNlbnR5RWxsaXBzb2lkKGFzLm1hdHJpeChkZjNbLGMoImxvbiIsImxhdCIpXSksIGFzLm1hdHJpeChkZjNbLGMoImFycml2ZV9sb24iLCJhcnJpdmVfbGF0IildKSkNCmRmMyRkaXN0X2Zyb21wb3J0IDwtIGRpc3RWaW5jZW50eUVsbGlwc29pZChhcy5tYXRyaXgoZGYzWyxjKCJsb24iLCJsYXQiKV0pLCBhcy5tYXRyaXgoZGYzWyxjKCJsZWZ0X2xvbiIsImxlZnRfbGF0IildKSkNCmRmMyA8LSBkZjMgJT4lDQogIGFycmFuZ2UoaW1vLCBsYXN0X3VwZGF0ZSkgJT4lDQogIGdyb3VwX2J5KGltbykgJT4lDQogIG11dGF0ZShkZWxheWVkPWlmZWxzZShkaWZmdGltZShhcnJpdmVfdGltZSwgbGVhZChhcnJpdmVfdGltZSksIHVuaXRzPSJkYXlzIikgPiAxLzggJiBhcnJpdmVfdGltZSA+IGxhc3RfdXBkYXRlICYgbGVmdF9wb3J0ID09IGxlYWQobGVmdF9wb3J0KSwxLDApKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdlb3NwaGVyZSkNCnggPC0gYXMubWF0cml4KGRmM1ssYygibG9uIiwibGF0IildKSAgIyBzaGlwIGxvY2F0aW9uDQp5IDwtIGFzLm1hdHJpeChkZjNbLGMoInR5X2xvbiIsInR5X2xhdCIpXSkgIyB0eXBob29uIGxvY2F0aW9uDQoNCmRmMyRkaXN0X3R5cGhvb24gPC0gZGlzdFZpbmNlbnR5RWxsaXBzb2lkKHgsIHkpIC8gMTAwMA0KYGBgDQoNCmBgYHtyfQ0KZGYzJHR5cGhvb25fNTAwID0gaWZlbHNlKGRmMyRkaXN0X3R5cGhvb24gPCA1MDAgJiANCiAgICAgICAgICAgICAgICAgICAgICAgICBkZjMkZGlzdF90eXBob29uID49IDAsIDEsIDApDQpkZjMkdHlwaG9vbl8xMDAwID0gaWZlbHNlKGRmMyRkaXN0X3R5cGhvb24gPCAxMDAwICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZGYzJGRpc3RfdHlwaG9vbiA+PSA1MDAsIDEsIDApDQpkZjMkdHlwaG9vbl8yMDAwID0gaWZlbHNlKGRmMyRkaXN0X3R5cGhvb24gPCAyMDAwICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZGYzJGRpc3RfdHlwaG9vbiA+PSAxMDAwLCAxLCAwKQ0KYGBgDQoNCmBgYHtyfQ0KZml0MSA8LSBnbG0oZGVsYXllZCB+IHR5cGhvb25fNTAwICsgdHlwaG9vbl8xMDAwICsgdHlwaG9vbl8yMDAwLCBkYXRhPWRmMywNCiAgICAgICAgICAgIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZml0MSkNCmBgYA0KDQpgYGB7cn0NCm9kZHMxIDwtIGV4cChjb2VmKGZpdDEpKQ0Kb2RkczENCmBgYA0KDQpgYGB7cn0NCm0xIDwtIG1hcmdpbnMoZml0MSkNCnN1bW1hcnkobTEpDQpgYGANCg0KYGBge3J9DQojIEN1dCBtYWtlcyBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIG91dCBvZiBhIG51bWVyaWNhbCB2YXJpYWJsZSB1c2luZyBzcGVjaWZpZWQgYmlucw0KZGYzJFN1cGVyIDwtIGlmZWxzZShkZjMkaW50ZW5zaXR5X3ZtYXggKiAxLjg1MiA+IDE4NSwgMSwgMCkNCmRmMyRNb2RlcmF0ZSA8LSBpZmVsc2UoZGYzJGludGVuc2l0eV92bWF4ICogMS44NTIgPj0gODggJg0KICAgICAgICAgICAgICAgICAgICAgICAgIGRmMyRpbnRlbnNpdHlfdm1heCAqIDEuODUyIDwgMTg1LCAxLCAwKQ0KZGYzJFdlYWsgPC0gaWZlbHNlKGRmMyRpbnRlbnNpdHlfdm1heCAqIDEuODUyID49IDQxICYgDQogICAgICAgICAgICAgICAgICAgZGYzJGludGVuc2l0eV92bWF4ICogMS44NTIgPCA4OCwgMSwgMCkNCmRmMyRIS19pbnRlbnNpdHkgPC0gY3V0KGRmMyRpbnRlbnNpdHlfdm1heCAqIDEuODUyICxjKC0xLDQxLCA2MiwgODcsIDExNywgMTQ5LCA5OTkpKQ0KdGFibGUoZGYzJEhLX2ludGVuc2l0eSkNCmBgYA0KDQpgYGB7cn0NCmZpdDIgPC0gZ2xtKGRlbGF5ZWQgfiAodHlwaG9vbl81MDAgKyB0eXBob29uXzEwMDAgKyB0eXBob29uXzIwMDApIDogDQogICAgICAgICAgICAgIChXZWFrICsgTW9kZXJhdGUgKyBTdXBlciksIGRhdGE9ZGYzLA0KICAgICAgICAgICAgZmFtaWx5PWJpbm9taWFsKQ0KdGlkeShmaXQyKQ0KYGBgDQoNCmBgYHtyfQ0KbTIgPC0gbWFyZ2lucyhmaXQyKQ0Kc3VtbWFyeShtMikgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KbWFyZ2lucyhmaXQyLCBhdCA9IGxpc3QoV2VhayA9IGMoMSkpLA0KICAgICAgICB2YXJpYWJsZXMgPSBjKCJ0eXBob29uXzUwMCIsICJ0eXBob29uXzEwMDAiLCAidHlwaG9vbl8yMDAwIikpICU+JQ0KICBzdW1tYXJ5KCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KbWFyZ2lucyhmaXQyLCBhdCA9IGxpc3QoTW9kZXJhdGUgPSBjKDEpKSwNCiAgICAgICAgdmFyaWFibGVzID0gYygidHlwaG9vbl81MDAiLCAidHlwaG9vbl8xMDAwIiwgInR5cGhvb25fMjAwMCIpKSAlPiUNCiAgc3VtbWFyeSgpICU+JQ0KICBodG1sX2RmKCkNCmBgYA0KDQpgYGB7cn0NCm1hcmdpbnMoZml0MiwgYXQgPSBsaXN0KFN1cGVyID0gYygxKSksDQogICAgICAgIHZhcmlhYmxlcyA9IGMoInR5cGhvb25fNTAwIiwgInR5cGhvb25fMTAwMCIsICJ0eXBob29uXzIwMDAiKSkgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0K