Data visualization exercise

This notebook uses #TidyTuesday week 30 Olympic Medals, data from Kaggle.

# load libraries 
library(tidyverse)
library(ggdark)
library(patchwork)
library(scales)
library(glue)
library(maps)
library(mapdata)
library(countrycode)
library(geofacet)
library(colorspace)
library(ggsci)
library(ggdist)
library(gghalves)
library(tidyquant)
library(ggtext)
library(ggmosaic)
library(ggflags)
library(DT)
# import data
olympics <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-07-27/olympics.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────
cols(
  id = col_double(),
  name = col_character(),
  sex = col_character(),
  age = col_double(),
  height = col_double(),
  weight = col_double(),
  team = col_character(),
  noc = col_character(),
  games = col_character(),
  year = col_double(),
  season = col_character(),
  city = col_character(),
  sport = col_character(),
  event = col_character(),
  medal = col_character()
)

Cumulative gold medals, by NOC (country)

gold_cumulative = olympics %>% filter(medal=="Gold") %>% 
  group_by(noc, year) %>% tally() %>%
  mutate(cumulative = cumsum(n)) 
summary(gold_cumulative$year)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1896    1952    1988    1976    2004    2016 
gold_cumulative %>% 
  group_by(noc) %>% 
  summarise(max_n = max(cumulative)) %>%
  arrange(desc(max_n))
# main plot
mainplot = gold_cumulative %>% 
  mutate(color=case_when(noc=="USA" ~ "#e63946",
                         noc=="URS" ~ "grey70",
                         noc=="GER" ~ "#fb8500",
                         noc=="GBR" ~ "#4cc9f0",
                         noc=="ITA" ~ "#90be6d",
                         TRUE~"#495057")) %>%
  ggplot() +
  aes(x=year, y= cumulative, color=color) + 
  geom_path() + 
  scale_color_identity(guide="none") + 
  scale_x_continuous(limits=c(1896,2030), expand=c(0,0), breaks=seq(1900,2016,25)) +
  annotate("text", y=2638, x=2017, label="USA: 2638", hjust=0,size=3, color="#e63946") +
  annotate("text", y=1082, x=1989, label="URS: 1082", hjust=0,size=2.5, color="grey70") +
  annotate("text", y=745, x=2017, label="GER: 745", hjust=0,size=2.5, color="#fb8500") +
  annotate("text", y=666, x=2017, label="GBR: 678", hjust=0,size=2.5, color="#4cc9f0") +
  annotate("text", y=575, x=2017, label="ITA: 575", hjust=0,size=2.5, color="#90be6d") +
  geom_segment(aes(x = 1900, y = 0, xend = 1900, yend = 200), col = "grey70", linetype = "dotted", size=.2) +
  geom_segment(aes(x = 1925, y = 0, xend = 1925, yend = 650), col = "grey70", linetype = "dotted", size=.2) +
  geom_segment(aes(x = 1950, y = 0, xend = 1950, yend = 1000), col = "grey70", linetype = "dotted", size=.2) +
  geom_segment(aes(x = 1975, y = 0, xend = 1975, yend = 1600), col = "grey70", linetype = "dotted", size=.2) +
  geom_segment(aes(x = 2000, y = 0, xend = 2000, yend = 2300), col = "grey70", linetype = "dotted", size=.2) +
  theme_minimal() +
  theme(panel.grid=element_blank(),
        plot.margin=unit(c(0.5,0.5,0.5,0.5),"cm"),
        axis.title=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks=element_blank(),
        axis.text.x=element_text(margin=margin(t=-10), size=7, color="grey90"),
        plot.caption=element_text(size=8, color="grey80"),
        plot.background = element_rect(fill = "#212529", color=NA),
        plot.title=element_text(margin=margin(t=15,b=-30),hjust=0,face="bold",size=24,color="grey90"),
        plot.subtitle=element_text(margin=margin(t=35,b=-10),hjust=0,face="bold",size=8.5,color="grey80")
        ) +
  ggtitle("Olympic Gold Medals") + 
  labs(caption="\n#TidyTuesday Week 31 | Data from Kaggle",
       subtitle="Cumulative Olympic gold medals won, by National Olympic Committee (NOC) from 1896 to 2016")
# map insert
world <- map_data("world") %>% filter(region != "Antarctica")
world2 = world %>% mutate(col = case_when(region=="UK"~"#4cc9f0",
                         region=="Germany"~"#fb8500",
                         region=="USA"~"#e63946",
                         region=="Italy"~"#90be6d",
                         TRUE~"#495057"
                         ))

map_insert = ggplot() + geom_map(data=world2, map=world2, aes(long, lat, map_id=region, fill=col)) + 
  scale_fill_identity() + 
  theme_void() + 
  theme(plot.background = element_rect(fill = "#212529", color=NA)) +
  coord_quickmap()
Ignoring unknown aesthetics: x, y
# insert map 
mainplot |inset_element(map_insert ,
                    align_to = "full",
                    clip = FALSE,
                    on_top = TRUE,
                    ignore_tag = TRUE,
                    left = 0.01, 
                    bottom = 0.15, 
                    right = 0.5, 
                    top = 1) 

ALT text: Line plot showing the cumulative gold medals won by respective NOCs from 1896 to 2016, where USA won the most gold medals (n=2638).

Ratio of medals to participation

# medal (4 levels) ratio
medal4 = olympics %>% 
  mutate(country= countrycode(noc, origin='ioc', destination='country.name')) %>% # get country name 
  count(country, year, medal) %>%
  group_by(country, year) %>% mutate(sum_medal=sum(n)) %>%
  ungroup() %>%
  mutate(ratio = n/sum_medal)
setdiff(medal2$region, world$region)
[1] "Côte d’Ivoire"       "Czechia"             "Hong Kong SAR China" "North Macedonia"    
[5] "Trinidad & Tobago"   "United Kingdom"      "United States"      
# medal (2 levels) ratio
medal2 = olympics %>%
  mutate(region= countrycode(noc, origin='ioc', destination='country.name')) %>% # get country name
  mutate(medal2 = ifelse(is.na(medal),"0","1")) %>%
  group_by(region, noc, medal2) %>% tally() %>%
  mutate(ratio=n/sum(n)*100) %>%
  filter(medal2=="1") %>%
  drop_na() %>%
  mutate(ratio_cat = cut(ratio, breaks=seq(0,30,10))) %>%
  mutate(region= case_when(region=="United States"~"USA",
                            region=="United Kingdom"~"UK",
                            region=="Côte d’Ivoire"~"Ivory Coast",
                            region=="Czechia"~"Czech Republic",
                            TRUE ~ region)) 
Some values were not matched unambiguously: AHO, ANZ, BOH, CRT, EUN, FRG, GDR, IOA, ISV, LIB, MAL, NBO, NFL, RHO, ROT, SAA, SCG, TCH, UAR, UNK, URS, VNM, WIF, YAR, YMD, YUG
medal2
joined = left_join(world, medal2, by="region") 

ggplot() + geom_map(data=joined, map=joined, aes(long, lat, map_id=region, fill=ratio_cat)) + 
  theme_void() + 
  coord_quickmap() + 
  theme(legend.position="top",
        plot.title=element_text(hjust=0.5, size=10, face="bold"),
        plot.margin=unit(c(0.5,1,0.5,1),"cm"),
        legend.title=element_text(size=8),
        legend.text=element_text(size = 8)) + 
  scale_fill_manual(values=c("#219ebc","#ffb703","#e63946"),na.value="grey",
                    breaks=c("(0,10]","(10,20]","(20,30]")) + # hide NA in legend
  guides(fill = guide_legend(title = "Ratio (%)",
                             title.position = "left",
                             title.hjust = 0.5,
                             label.position = "bottom",
                             label.hjust = 0.5,
                             nrow = 1,
                             keyheight = .5,
                             keywidth = 4)) + 
  labs(title="Ratio of medals to participation, by country from 1896 to 2016\n")

Distribution of medalist height by sport

sport3 = olympics %>% filter(!is.na(medal)) %>% count(sport, sort=T) %>% slice(1:5)

df_height = olympics %>% 
  filter(!is.na(medal)) %>%
  filter(sport %in% sport3$sport) %>%
  drop_na(height, sport) %>%
  mutate(sport=fct_infreq(sport)) %>%
  mutate(sport_num = as.numeric(sport))
  
df_height_stat = df_height %>%
  group_by(sport, sport_num) %>%
  summarise(median=median(height),
            max=max(height),
            n=n())
`summarise()` has grouped output by 'sport'. You can override using the `.groups` argument.
# raincloud v1: stat_half_eye and geom_half_point
df_height %>%
  ggplot(aes(x=sport_num, y=height, color=sport)) + 
  geom_half_point(side="l", size=0.7, shape=21, alpha=0.4, range_scale = 0.3) + 
  stat_summary(
    geom = "linerange",
    fun.min = function(x) -Inf,
    fun.max = function(x) median(x, na.rm = TRUE),
    linetype = "dotted",
    orientation = "x",
    size = .5
  ) +
  ggdist::stat_halfeye(
    aes(x = sport_num,color = sport,fill = after_scale(colorspace::lighten(color, .3))
    ),shape = 18,point_size = 3,interval_size = 1.8,adjust = .5,.width = c(0, 1)) + 
  geom_text(data= df_height_stat,
    aes(y = median, x=sport_num,label = median),
    color = "white",fontface = "bold",size = 3,nudge_x = .15) +
  geom_text(data= df_height_stat,
    aes(y = max, x=sport_num,label = glue::glue("n = {n}")),
    fontface = "bold",size = 3,nudge_x = .15, hjust=0) +
  scale_fill_futurama() +
  scale_color_futurama() +
  scale_x_continuous(breaks = 1:5, 
                     labels = c("Athletics","Swimming","Rowing","Gymnastics","Fencing")) +
  scale_y_continuous(expand=c(.1,.1), breaks=seq(140,210,10)) +
  coord_flip() + 
  theme(legend.position="none",
        panel.grid.minor=element_blank(),
        panel.grid.major.y=element_blank(),
        plot.margin=unit(c(0.5,2,0.5,0.5),"cm"),
        plot.title.position = "plot",
        axis.title.y=element_blank(),
        axis.title.x=element_text(size=9),
        panel.grid.major.x=element_line(size=.3),
        axis.text.y=element_text(size=9)) +
  labs(subtitle="Distribution of Olympics medalist height, by sport (5 sports with highest medal count)",
       y="Height (in cm)")

# raincloud v2: stat_half_eye, geom_boxplot and stat_dots
df_height %>% 
  ggplot(aes(x=sport, y=height, fill=sport, color=sport)) + 
  stat_halfeye(adjust=0.5, justification=-.2, .width=0, point_color=NA) + 
  geom_boxplot(width=0.1, outlier.color=NA, alpha=0.5) + 
  stat_dots(side="left", justification=1.1, binwidth=unit(c(0, 0.01), "npc")) + 
  #gghalves::geom_half_point(side="l", alpha=.3, shape="|", size=5) + #range_scale=.4, 
  geom_text(data=df_height_stat, aes(y=median, x=sport, label=median), 
            fontface="bold",color="white",nudge_x = .3,size=4) +
  geom_text(data=df_height_stat, aes(y=max, x=sport, color=sport, label=glue::glue("n = {n}")), 
            fontface="bold",nudge_x = .3,size=4, nudge_y=3) +
  theme_tq(base_size=14) +
  theme(legend.position = "none",
        plot.margin=unit(c(0.5,2,0.5,0.5),"cm")) + 
  scale_fill_futurama() + 
  scale_color_futurama() +
  coord_flip() + 
  labs(title="Distribution of Olympics medalist height by sport",
       subtitle="Top 5 sports by medal count",
       x="Sport", y="Height (in cm)")

NA

Swimming medals

olympics %>% 
  filter(!is.na(medal)) %>%
  filter(sport =="Swimming") %>%
  mutate(event_dist = parse_number(event)) %>%
  mutate(event=str_to_lower(event)) %>%
  filter(!str_detect(event, 'yard')) %>%
  drop_na() %>%
  mutate(type=case_when(str_detect(event, "relay")~"relay",
                        TRUE ~ "individual")) %>%
  mutate(noc=fct_lump(noc, 3)) %>%
  group_by(type, noc) %>% tally() %>% mutate(prop=round(n/sum(n),3)) %>%
  arrange(type, desc(n))
12 parsing failures.
row col expected                             actual
 10  -- a number Swimming Men's Plunge For Distance
536  -- a number Swimming Men's Underwater Swimming
544  -- a number Swimming Men's Plunge For Distance
745  -- a number Swimming Men's One Mile Freestyle 
805  -- a number Swimming Men's Plunge For Distance
... ... ........ ..................................
See problems(...) for more details.

Female and male athletes

mdata = olympics %>% 
  group_by(year, sex) %>%
  tally() %>% 
  mutate(prop=n/sum(n)) 

ggplot(olympics) +
  geom_mosaic(aes(x=product(sex, year), fill=sex), show.legend=F) + 
  scale_y_continuous(position="right") +
  theme(axis.text.x=element_text(angle=90, vjust=0.5,size=7, color="black"),
        axis.text.y=element_text(color="black",size=7),
        panel.grid.minor=element_blank(),
        axis.title=element_blank(),
        axis.ticks=element_line(color="grey"),
        axis.ticks.length = unit(.4, "cm"),
        panel.grid.major.x=element_blank(),
        plot.margin=unit(c(1,1,1,1),"cm"),
        plot.title=element_markdown(face="bold",size=12),
        plot.subtitle = element_text(size=8, color="grey30")) + 
  coord_cartesian(expand = FALSE,clip="off") + 
  scale_fill_manual(values=c("#ee9b00","#656d4a")) + 
  labs(title="Olympics <span style = 'color:#ee9b00'>female</span> and <span style = 'color:#656d4a'>male</span> athletes, by year",
       subtitle="Cross sectional proportion of athletes by gender, from 1896 to 2016. Winter and Summer Games were held in the same year up until\n1992. After that, they staggered them such that Winter Games occur on a four year cycle starting with 1994, then Summer in 1996,\nthenWinter in 1998, and so on.\n")

Event count by season and year

olympics %>%
  count(season, year, event) %>%
  group_by(season, year) %>% tally(n) %>%
  ggplot(aes(x=year, y=n, fill=season)) + 
  geom_col() + 
  scale_x_continuous(breaks=seq(1896,2016,20)) + 
  scale_y_continuous(labels=scales::comma) + 
  theme(panel.grid.minor=element_blank(),
        legend.position = "top",
        plot.margin=unit(c(1,2,1,1),"cm"),
        axis.title=element_text(size=9),
        plot.title.position="plot",
        legend.justification = "left",
        legend.margin = margin(l=-45),
        legend.key.width =unit(.25,"cm"),
        panel.grid.major=element_line(size=.3)) + 
  scale_fill_npg() + 
  labs(fill="", x="Year", y="Event count",
       subtitle="Olympic event count, by season and year") 

10 countries with most summer olympics medal

s10 = olympics %>% 
  filter(season=="Summer",year>1990,!is.na(medal)) %>% 
  count(noc, sort=T) %>%
  slice(1:10)
# reference: https://kpress.dev/blog/2021-07-26-tidy-tuesday-olympic-medals/
# reference: https://github.com/davidsjoberg/ggbump
df_rank = olympics %>% 
  filter(season=="Summer",year>1990,!is.na(medal)) %>%
  filter(noc %in% s10$noc) %>%
  mutate(region= countrycode(noc, origin='ioc', destination='genc2c')) %>% 
  mutate(region= str_to_lower(region)) %>%
  group_by(year, region) %>% tally() %>%
  arrange(year, -n) %>%
  mutate(rank = dense_rank(desc(n)))

df_rank %>%
  ggplot(aes(year, rank, color=region)) + 
  geom_point(size=2) +
  geom_bump(size=1) + 
  geom_flag(data= df_rank %>% filter(year==2016),aes(country=region),size=8) +
  #geom_text(data= df_rank %>% filter(year==2016), aes(x=year+0.5, y=rank, label=country, hjust=0),size=3) +
  scale_y_reverse(breaks=seq(1,10,1)) + 
  scale_x_continuous(breaks=seq(1992, 2016,4)) + 
  scale_color_d3() +
  theme(panel.grid.minor=element_blank(),
        panel.grid.major=element_blank(),
        plot.margin=unit(c(.5,1,.5,.5),"cm"),
        legend.position="none",
        plot.background=element_rect(fill="#e9ecef", color="NA"),
        axis.title=element_text(size=8.5),
        plot.title.position = "plot",
        axis.text=element_text(color="black")) + 
  labs(x="Year", y="Rank",
       subtitle="Countries with most summer Olympics medals\n") 

Sport: min year, max year, event count

olympics %>% group_by(sport) %>%
  summarise(min_year= min(year), max_year=max(year), year_count = n_distinct(year)) %>%
  arrange(min_year, max_year) %>%
  ungroup() %>%
  DT::datatable(rownames=FALSE,options = list(order = list(list(1, 'asc'))))
LS0tCnRpdGxlOiAiVGlkeSBUdWVzZGF5IFdlZWsgMzEvMjAyMSIKZGF0ZTogIjIwMjEvMDcvMjciCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkRhdGEgdmlzdWFsaXphdGlvbiBleGVyY2lzZQoKVGhpcyBub3RlYm9vayB1c2VzIFsjVGlkeVR1ZXNkYXldKGh0dHBzOi8vZ2l0aHViLmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkpIHdlZWsgMzAgW09seW1waWMgTWVkYWxzXShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L2Jsb2IvbWFzdGVyL2RhdGEvMjAyMS8yMDIxLTA3LTI3L3JlYWRtZS5tZCksIGRhdGEgZnJvbSBbS2FnZ2xlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2hlZXNvbzM3LzEyMC15ZWFycy1vZi1vbHltcGljLWhpc3RvcnktYXRobGV0ZXMtYW5kLXJlc3VsdHMpLgoKYGBge3J9CiMgbG9hZCBsaWJyYXJpZXMgCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdnZGFyaykKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGdsdWUpCmxpYnJhcnkobWFwcykKbGlicmFyeShtYXBkYXRhKQpsaWJyYXJ5KGNvdW50cnljb2RlKQpsaWJyYXJ5KGdlb2ZhY2V0KQpsaWJyYXJ5KGNvbG9yc3BhY2UpCmxpYnJhcnkoZ2dzY2kpCmxpYnJhcnkoZ2dkaXN0KQpsaWJyYXJ5KGdnaGFsdmVzKQpsaWJyYXJ5KHRpZHlxdWFudCkKbGlicmFyeShnZ3RleHQpCmxpYnJhcnkoZ2dtb3NhaWMpCmxpYnJhcnkoZ2dmbGFncykKbGlicmFyeShEVCkKYGBgCgpgYGB7cn0KIyBpbXBvcnQgZGF0YQpvbHltcGljcyA8LSByZWFkcjo6cmVhZF9jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvbWFzdGVyL2RhdGEvMjAyMS8yMDIxLTA3LTI3L29seW1waWNzLmNzdicpCmBgYAoKIyMjIyBDdW11bGF0aXZlIGdvbGQgbWVkYWxzLCBieSBOT0MgKGNvdW50cnkpCiogc2hhcmVkIG9uIFtUd2l0dGVyXShodHRwczovL3R3aXR0ZXIuY29tL2xlZW9sbmV5My9zdGF0dXMvMTQxOTkxODQyMzc0NDc4MjM3OSkKCmBgYHtyfQpnb2xkX2N1bXVsYXRpdmUgPSBvbHltcGljcyAlPiUgZmlsdGVyKG1lZGFsPT0iR29sZCIpICU+JSAKICBncm91cF9ieShub2MsIHllYXIpICU+JSB0YWxseSgpICU+JQogIG11dGF0ZShjdW11bGF0aXZlID0gY3Vtc3VtKG4pKSAKYGBgCgpgYGB7cn0Kc3VtbWFyeShnb2xkX2N1bXVsYXRpdmUkeWVhcikKYGBgCgpgYGB7cn0KZ29sZF9jdW11bGF0aXZlICU+JSAKICBncm91cF9ieShub2MpICU+JSAKICBzdW1tYXJpc2UobWF4X24gPSBtYXgoY3VtdWxhdGl2ZSkpICU+JQogIGFycmFuZ2UoZGVzYyhtYXhfbikpCmBgYAoKYGBge3J9CiMgbWFpbiBwbG90Cm1haW5wbG90ID0gZ29sZF9jdW11bGF0aXZlICU+JSAKICBtdXRhdGUoY29sb3I9Y2FzZV93aGVuKG5vYz09IlVTQSIgfiAiI2U2Mzk0NiIsCiAgICAgICAgICAgICAgICAgICAgICAgICBub2M9PSJVUlMiIH4gImdyZXk3MCIsCiAgICAgICAgICAgICAgICAgICAgICAgICBub2M9PSJHRVIiIH4gIiNmYjg1MDAiLAogICAgICAgICAgICAgICAgICAgICAgICAgbm9jPT0iR0JSIiB+ICIjNGNjOWYwIiwKICAgICAgICAgICAgICAgICAgICAgICAgIG5vYz09IklUQSIgfiAiIzkwYmU2ZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFfiIjNDk1MDU3IikpICU+JQogIGdncGxvdCgpICsKICBhZXMoeD15ZWFyLCB5PSBjdW11bGF0aXZlLCBjb2xvcj1jb2xvcikgKyAKICBnZW9tX3BhdGgoKSArIAogIHNjYWxlX2NvbG9yX2lkZW50aXR5KGd1aWRlPSJub25lIikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzPWMoMTg5NiwyMDMwKSwgZXhwYW5kPWMoMCwwKSwgYnJlYWtzPXNlcSgxOTAwLDIwMTYsMjUpKSArCiAgYW5ub3RhdGUoInRleHQiLCB5PTI2MzgsIHg9MjAxNywgbGFiZWw9IlVTQTogMjYzOCIsIGhqdXN0PTAsc2l6ZT0zLCBjb2xvcj0iI2U2Mzk0NiIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHk9MTA4MiwgeD0xOTg5LCBsYWJlbD0iVVJTOiAxMDgyIiwgaGp1c3Q9MCxzaXplPTIuNSwgY29sb3I9ImdyZXk3MCIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHk9NzQ1LCB4PTIwMTcsIGxhYmVsPSJHRVI6IDc0NSIsIGhqdXN0PTAsc2l6ZT0yLjUsIGNvbG9yPSIjZmI4NTAwIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeT02NjYsIHg9MjAxNywgbGFiZWw9IkdCUjogNjc4IiwgaGp1c3Q9MCxzaXplPTIuNSwgY29sb3I9IiM0Y2M5ZjAiKSArCiAgYW5ub3RhdGUoInRleHQiLCB5PTU3NSwgeD0yMDE3LCBsYWJlbD0iSVRBOiA1NzUiLCBoanVzdD0wLHNpemU9Mi41LCBjb2xvcj0iIzkwYmU2ZCIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSAxOTAwLCB5ID0gMCwgeGVuZCA9IDE5MDAsIHllbmQgPSAyMDApLCBjb2wgPSAiZ3JleTcwIiwgbGluZXR5cGUgPSAiZG90dGVkIiwgc2l6ZT0uMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDE5MjUsIHkgPSAwLCB4ZW5kID0gMTkyNSwgeWVuZCA9IDY1MCksIGNvbCA9ICJncmV5NzAiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplPS4yKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMTk1MCwgeSA9IDAsIHhlbmQgPSAxOTUwLCB5ZW5kID0gMTAwMCksIGNvbCA9ICJncmV5NzAiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplPS4yKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMTk3NSwgeSA9IDAsIHhlbmQgPSAxOTc1LCB5ZW5kID0gMTYwMCksIGNvbCA9ICJncmV5NzAiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplPS4yKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMjAwMCwgeSA9IDAsIHhlbmQgPSAyMDAwLCB5ZW5kID0gMjMwMCksIGNvbCA9ICJncmV5NzAiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplPS4yKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShwYW5lbC5ncmlkPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90Lm1hcmdpbj11bml0KGMoMC41LDAuNSwwLjUsMC41KSwiY20iKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KG1hcmdpbj1tYXJnaW4odD0tMTApLCBzaXplPTcsIGNvbG9yPSJncmV5OTAiKSwKICAgICAgICBwbG90LmNhcHRpb249ZWxlbWVudF90ZXh0KHNpemU9OCwgY29sb3I9ImdyZXk4MCIpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiMyMTI1MjkiLCBjb2xvcj1OQSksCiAgICAgICAgcGxvdC50aXRsZT1lbGVtZW50X3RleHQobWFyZ2luPW1hcmdpbih0PTE1LGI9LTMwKSxoanVzdD0wLGZhY2U9ImJvbGQiLHNpemU9MjQsY29sb3I9ImdyZXk5MCIpLAogICAgICAgIHBsb3Quc3VidGl0bGU9ZWxlbWVudF90ZXh0KG1hcmdpbj1tYXJnaW4odD0zNSxiPS0xMCksaGp1c3Q9MCxmYWNlPSJib2xkIixzaXplPTguNSxjb2xvcj0iZ3JleTgwIikKICAgICAgICApICsKICBnZ3RpdGxlKCJPbHltcGljIEdvbGQgTWVkYWxzIikgKyAKICBsYWJzKGNhcHRpb249IlxuI1RpZHlUdWVzZGF5IFdlZWsgMzEgfCBEYXRhIGZyb20gS2FnZ2xlIiwKICAgICAgIHN1YnRpdGxlPSJDdW11bGF0aXZlIE9seW1waWMgZ29sZCBtZWRhbHMgd29uLCBieSBOYXRpb25hbCBPbHltcGljIENvbW1pdHRlZSAoTk9DKSBmcm9tIDE4OTYgdG8gMjAxNiIpCmBgYAoKCmBgYHtyfQojIG1hcCBpbnNlcnQKd29ybGQgPC0gbWFwX2RhdGEoIndvcmxkIikgJT4lIGZpbHRlcihyZWdpb24gIT0gIkFudGFyY3RpY2EiKQp3b3JsZDIgPSB3b3JsZCAlPiUgbXV0YXRlKGNvbCA9IGNhc2Vfd2hlbihyZWdpb249PSJVSyJ+IiM0Y2M5ZjAiLAogICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uPT0iR2VybWFueSJ+IiNmYjg1MDAiLAogICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uPT0iVVNBIn4iI2U2Mzk0NiIsCiAgICAgICAgICAgICAgICAgICAgICAgICByZWdpb249PSJJdGFseSJ+IiM5MGJlNmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgVFJVRX4iIzQ5NTA1NyIKICAgICAgICAgICAgICAgICAgICAgICAgICkpCgptYXBfaW5zZXJ0ID0gZ2dwbG90KCkgKyBnZW9tX21hcChkYXRhPXdvcmxkMiwgbWFwPXdvcmxkMiwgYWVzKGxvbmcsIGxhdCwgbWFwX2lkPXJlZ2lvbiwgZmlsbD1jb2wpKSArIAogIHNjYWxlX2ZpbGxfaWRlbnRpdHkoKSArIAogIHRoZW1lX3ZvaWQoKSArIAogIHRoZW1lKHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiMyMTI1MjkiLCBjb2xvcj1OQSkpICsKICBjb29yZF9xdWlja21hcCgpCmBgYAoKCmBgYHtyfQojIGluc2VydCBtYXAgCm1haW5wbG90IHxpbnNldF9lbGVtZW50KG1hcF9pbnNlcnQgLAogICAgICAgICAgICAgICAgICAgIGFsaWduX3RvID0gImZ1bGwiLAogICAgICAgICAgICAgICAgICAgIGNsaXAgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICBvbl90b3AgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIGlnbm9yZV90YWcgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIGxlZnQgPSAwLjAxLCAKICAgICAgICAgICAgICAgICAgICBib3R0b20gPSAwLjE1LCAKICAgICAgICAgICAgICAgICAgICByaWdodCA9IDAuNSwgCiAgICAgICAgICAgICAgICAgICAgdG9wID0gMSkgCmBgYApBTFQgdGV4dDogTGluZSBwbG90IHNob3dpbmcgdGhlIGN1bXVsYXRpdmUgZ29sZCBtZWRhbHMgd29uIGJ5IHJlc3BlY3RpdmUgTk9DcyBmcm9tIDE4OTYgdG8gMjAxNiwgd2hlcmUgVVNBIHdvbiB0aGUgbW9zdCBnb2xkIG1lZGFscyAobj0yNjM4KS4gCgojIyMjIFJhdGlvIG9mIG1lZGFscyB0byBwYXJ0aWNpcGF0aW9uCmBgYHtyfQojIG1lZGFsICg0IGxldmVscykgcmF0aW8KbWVkYWw0ID0gb2x5bXBpY3MgJT4lIAogIG11dGF0ZShjb3VudHJ5PSBjb3VudHJ5Y29kZShub2MsIG9yaWdpbj0naW9jJywgZGVzdGluYXRpb249J2NvdW50cnkubmFtZScpKSAlPiUgIyBnZXQgY291bnRyeSBuYW1lIAogIGNvdW50KGNvdW50cnksIHllYXIsIG1lZGFsKSAlPiUKICBncm91cF9ieShjb3VudHJ5LCB5ZWFyKSAlPiUgbXV0YXRlKHN1bV9tZWRhbD1zdW0obikpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUocmF0aW8gPSBuL3N1bV9tZWRhbCkKYGBgCgpgYGB7cn0Kc2V0ZGlmZihtZWRhbDIkcmVnaW9uLCB3b3JsZCRyZWdpb24pCmBgYAoKYGBge3J9CiMgbWVkYWwgKDIgbGV2ZWxzKSByYXRpbwptZWRhbDIgPSBvbHltcGljcyAlPiUKICBtdXRhdGUocmVnaW9uPSBjb3VudHJ5Y29kZShub2MsIG9yaWdpbj0naW9jJywgZGVzdGluYXRpb249J2NvdW50cnkubmFtZScpKSAlPiUgIyBnZXQgY291bnRyeSBuYW1lCiAgbXV0YXRlKG1lZGFsMiA9IGlmZWxzZShpcy5uYShtZWRhbCksIjAiLCIxIikpICU+JQogIGdyb3VwX2J5KHJlZ2lvbiwgbm9jLCBtZWRhbDIpICU+JSB0YWxseSgpICU+JQogIG11dGF0ZShyYXRpbz1uL3N1bShuKSoxMDApICU+JQogIGZpbHRlcihtZWRhbDI9PSIxIikgJT4lCiAgZHJvcF9uYSgpICU+JQogIG11dGF0ZShyYXRpb19jYXQgPSBjdXQocmF0aW8sIGJyZWFrcz1zZXEoMCwzMCwxMCkpKSAlPiUKICBtdXRhdGUocmVnaW9uPSBjYXNlX3doZW4ocmVnaW9uPT0iVW5pdGVkIFN0YXRlcyJ+IlVTQSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdpb249PSJVbml0ZWQgS2luZ2RvbSJ+IlVLIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2lvbj09IkPDtHRlIGTigJlJdm9pcmUifiJJdm9yeSBDb2FzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdpb249PSJDemVjaGlhIn4iQ3plY2ggUmVwdWJsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHJlZ2lvbikpIAptZWRhbDIKYGBgCgoKYGBge3IsIHdhcm5pbmc9Rn0Kam9pbmVkID0gbGVmdF9qb2luKHdvcmxkLCBtZWRhbDIsIGJ5PSJyZWdpb24iKSAKCmdncGxvdCgpICsgZ2VvbV9tYXAoZGF0YT1qb2luZWQsIG1hcD1qb2luZWQsIGFlcyhsb25nLCBsYXQsIG1hcF9pZD1yZWdpb24sIGZpbGw9cmF0aW9fY2F0KSkgKyAKICB0aGVtZV92b2lkKCkgKyAKICBjb29yZF9xdWlja21hcCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiLAogICAgICAgIHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xMCwgZmFjZT0iYm9sZCIpLAogICAgICAgIHBsb3QubWFyZ2luPXVuaXQoYygwLjUsMSwwLjUsMSksImNtIiksCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplID0gOCkpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMyMTllYmMiLCIjZmZiNzAzIiwiI2U2Mzk0NiIpLG5hLnZhbHVlPSJncmV5IiwKICAgICAgICAgICAgICAgICAgICBicmVha3M9YygiKDAsMTBdIiwiKDEwLDIwXSIsIigyMCwzMF0iKSkgKyAjIGhpZGUgTkEgaW4gbGVnZW5kCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiUmF0aW8gKCUpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZS5wb3NpdGlvbiA9ICJsZWZ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZS5oanVzdCA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsLmhqdXN0ID0gMC41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleWhlaWdodCA9IC41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleXdpZHRoID0gNCkpICsgCiAgbGFicyh0aXRsZT0iUmF0aW8gb2YgbWVkYWxzIHRvIHBhcnRpY2lwYXRpb24sIGJ5IGNvdW50cnkgZnJvbSAxODk2IHRvIDIwMTZcbiIpCmBgYAoKCgoKIyMjIyBEaXN0cmlidXRpb24gb2YgbWVkYWxpc3QgaGVpZ2h0IGJ5IHNwb3J0IAoKYGBge3J9CnNwb3J0MyA9IG9seW1waWNzICU+JSBmaWx0ZXIoIWlzLm5hKG1lZGFsKSkgJT4lIGNvdW50KHNwb3J0LCBzb3J0PVQpICU+JSBzbGljZSgxOjUpCgpkZl9oZWlnaHQgPSBvbHltcGljcyAlPiUgCiAgZmlsdGVyKCFpcy5uYShtZWRhbCkpICU+JQogIGZpbHRlcihzcG9ydCAlaW4lIHNwb3J0MyRzcG9ydCkgJT4lCiAgZHJvcF9uYShoZWlnaHQsIHNwb3J0KSAlPiUKICBtdXRhdGUoc3BvcnQ9ZmN0X2luZnJlcShzcG9ydCkpICU+JQogIG11dGF0ZShzcG9ydF9udW0gPSBhcy5udW1lcmljKHNwb3J0KSkKICAKZGZfaGVpZ2h0X3N0YXQgPSBkZl9oZWlnaHQgJT4lCiAgZ3JvdXBfYnkoc3BvcnQsIHNwb3J0X251bSkgJT4lCiAgc3VtbWFyaXNlKG1lZGlhbj1tZWRpYW4oaGVpZ2h0KSwKICAgICAgICAgICAgbWF4PW1heChoZWlnaHQpLAogICAgICAgICAgICBuPW4oKSkKYGBgCgoKYGBge3J9CiMgcmFpbmNsb3VkIHYxOiBzdGF0X2hhbGZfZXllIGFuZCBnZW9tX2hhbGZfcG9pbnQKIyByZWZlcmVuY2U6IGh0dHBzOi8vejN0dC5naXRodWIuaW8vT3V0bGllckNvbmYyMDIxLwpkZl9oZWlnaHQgJT4lCiAgZ2dwbG90KGFlcyh4PXNwb3J0X251bSwgeT1oZWlnaHQsIGNvbG9yPXNwb3J0KSkgKyAKICBnZW9tX2hhbGZfcG9pbnQoc2lkZT0ibCIsIHNpemU9MC43LCBzaGFwZT0yMSwgYWxwaGE9MC40LCByYW5nZV9zY2FsZSA9IDAuMykgKyAKICBzdGF0X3N1bW1hcnkoCiAgICBnZW9tID0gImxpbmVyYW5nZSIsCiAgICBmdW4ubWluID0gZnVuY3Rpb24oeCkgLUluZiwKICAgIGZ1bi5tYXggPSBmdW5jdGlvbih4KSBtZWRpYW4oeCwgbmEucm0gPSBUUlVFKSwKICAgIGxpbmV0eXBlID0gImRvdHRlZCIsCiAgICBvcmllbnRhdGlvbiA9ICJ4IiwKICAgIHNpemUgPSAuNQogICkgKwogIGdnZGlzdDo6c3RhdF9oYWxmZXllKAogICAgYWVzKHggPSBzcG9ydF9udW0sY29sb3IgPSBzcG9ydCxmaWxsID0gYWZ0ZXJfc2NhbGUoY29sb3JzcGFjZTo6bGlnaHRlbihjb2xvciwgLjMpKQogICAgKSxzaGFwZSA9IDE4LHBvaW50X3NpemUgPSAzLGludGVydmFsX3NpemUgPSAxLjgsYWRqdXN0ID0gLjUsLndpZHRoID0gYygwLCAxKSkgKyAKICBnZW9tX3RleHQoZGF0YT0gZGZfaGVpZ2h0X3N0YXQsCiAgICBhZXMoeSA9IG1lZGlhbiwgeD1zcG9ydF9udW0sbGFiZWwgPSBtZWRpYW4pLAogICAgY29sb3IgPSAid2hpdGUiLGZvbnRmYWNlID0gImJvbGQiLHNpemUgPSAzLG51ZGdlX3ggPSAuMTUpICsKICBnZW9tX3RleHQoZGF0YT0gZGZfaGVpZ2h0X3N0YXQsCiAgICBhZXMoeSA9IG1heCwgeD1zcG9ydF9udW0sbGFiZWwgPSBnbHVlOjpnbHVlKCJuID0ge259IikpLAogICAgZm9udGZhY2UgPSAiYm9sZCIsc2l6ZSA9IDMsbnVkZ2VfeCA9IC4xNSwgaGp1c3Q9MCkgKwogIHNjYWxlX2ZpbGxfZnV0dXJhbWEoKSArCiAgc2NhbGVfY29sb3JfZnV0dXJhbWEoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDE6NSwgCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkF0aGxldGljcyIsIlN3aW1taW5nIiwiUm93aW5nIiwiR3ltbmFzdGljcyIsIkZlbmNpbmciKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQ9YyguMSwuMSksIGJyZWFrcz1zZXEoMTQwLDIxMCwxMCkpICsKICBjb29yZF9mbGlwKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5tYXJnaW49dW5pdChjKDAuNSwyLDAuNSwwLjUpLCJjbSIpLAogICAgICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIsCiAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KHNpemU9OSksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54PWVsZW1lbnRfbGluZShzaXplPS4zKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT05KSkgKwogIGxhYnMoc3VidGl0bGU9IkRpc3RyaWJ1dGlvbiBvZiBPbHltcGljcyBtZWRhbGlzdCBoZWlnaHQsIGJ5IHNwb3J0ICg1IHNwb3J0cyB3aXRoIGhpZ2hlc3QgbWVkYWwgY291bnQpIiwKICAgICAgIHk9IkhlaWdodCAoaW4gY20pIikKYGBgCgoKYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD0zLjh9CiMgcmFpbmNsb3VkIHYyOiBzdGF0X2hhbGZfZXllLCBnZW9tX2JveHBsb3QgYW5kIHN0YXRfZG90cwojIHJlZmVyZW5jZTogaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vMjAyMS8wNy9nZ2Rpc3QtbWFrZS1hLXJhaW5jbG91ZC1wbG90LXRvLXZpc3VhbGl6ZS1kaXN0cmlidXRpb24taW4tZ2dwbG90Mi8KZGZfaGVpZ2h0ICU+JSAKICBnZ3Bsb3QoYWVzKHg9c3BvcnQsIHk9aGVpZ2h0LCBmaWxsPXNwb3J0LCBjb2xvcj1zcG9ydCkpICsgCiAgc3RhdF9oYWxmZXllKGFkanVzdD0wLjUsIGp1c3RpZmljYXRpb249LS4yLCAud2lkdGg9MCwgcG9pbnRfY29sb3I9TkEpICsgCiAgZ2VvbV9ib3hwbG90KHdpZHRoPTAuMSwgb3V0bGllci5jb2xvcj1OQSwgYWxwaGE9MC41KSArIAogIHN0YXRfZG90cyhzaWRlPSJsZWZ0IiwganVzdGlmaWNhdGlvbj0xLjEsIGJpbndpZHRoPXVuaXQoYygwLCAwLjAxKSwgIm5wYyIpKSArIAogICNnZ2hhbHZlczo6Z2VvbV9oYWxmX3BvaW50KHNpZGU9ImwiLCBhbHBoYT0uMywgc2hhcGU9InwiLCBzaXplPTUpICsgI3JhbmdlX3NjYWxlPS40LCAKICBnZW9tX3RleHQoZGF0YT1kZl9oZWlnaHRfc3RhdCwgYWVzKHk9bWVkaWFuLCB4PXNwb3J0LCBsYWJlbD1tZWRpYW4pLCAKICAgICAgICAgICAgZm9udGZhY2U9ImJvbGQiLGNvbG9yPSJ3aGl0ZSIsbnVkZ2VfeCA9IC4zLHNpemU9NCkgKwogIGdlb21fdGV4dChkYXRhPWRmX2hlaWdodF9zdGF0LCBhZXMoeT1tYXgsIHg9c3BvcnQsIGNvbG9yPXNwb3J0LCBsYWJlbD1nbHVlOjpnbHVlKCJuID0ge259IikpLCAKICAgICAgICAgICAgZm9udGZhY2U9ImJvbGQiLG51ZGdlX3ggPSAuMyxzaXplPTQsIG51ZGdlX3k9MykgKwogIHRoZW1lX3RxKGJhc2Vfc2l6ZT0xNCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBwbG90Lm1hcmdpbj11bml0KGMoMC41LDIsMC41LDAuNSksImNtIikpICsgCiAgc2NhbGVfZmlsbF9mdXR1cmFtYSgpICsgCiAgc2NhbGVfY29sb3JfZnV0dXJhbWEoKSArCiAgY29vcmRfZmxpcCgpICsgCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIE9seW1waWNzIG1lZGFsaXN0IGhlaWdodCBieSBzcG9ydCIsCiAgICAgICBzdWJ0aXRsZT0iVG9wIDUgc3BvcnRzIGJ5IG1lZGFsIGNvdW50IiwKICAgICAgIHg9IlNwb3J0IiwgeT0iSGVpZ2h0IChpbiBjbSkiKQogIApgYGAKCiMjIyMgU3dpbW1pbmcgbWVkYWxzIApgYGB7cn0Kb2x5bXBpY3MgJT4lIAogIGZpbHRlcighaXMubmEobWVkYWwpKSAlPiUKICBmaWx0ZXIoc3BvcnQgPT0iU3dpbW1pbmciKSAlPiUKICBtdXRhdGUoZXZlbnRfZGlzdCA9IHBhcnNlX251bWJlcihldmVudCkpICU+JQogIG11dGF0ZShldmVudD1zdHJfdG9fbG93ZXIoZXZlbnQpKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3QoZXZlbnQsICd5YXJkJykpICU+JQogIGRyb3BfbmEoKSAlPiUKICBtdXRhdGUodHlwZT1jYXNlX3doZW4oc3RyX2RldGVjdChldmVudCwgInJlbGF5Iil+InJlbGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJpbmRpdmlkdWFsIikpICU+JQogIG11dGF0ZShub2M9ZmN0X2x1bXAobm9jLCAzKSkgJT4lCiAgZ3JvdXBfYnkodHlwZSwgbm9jKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3A9cm91bmQobi9zdW0obiksMykpICU+JQogIGFycmFuZ2UodHlwZSwgZGVzYyhuKSkKYGBgCgojIyMjIEZlbWFsZSBhbmQgbWFsZSBhdGhsZXRlcwpgYGB7cn0KbWRhdGEgPSBvbHltcGljcyAlPiUgCiAgZ3JvdXBfYnkoeWVhciwgc2V4KSAlPiUKICB0YWxseSgpICU+JSAKICBtdXRhdGUocHJvcD1uL3N1bShuKSkgCgpnZ3Bsb3Qob2x5bXBpY3MpICsKICBnZW9tX21vc2FpYyhhZXMoeD1wcm9kdWN0KHNleCwgeWVhciksIGZpbGw9c2V4KSwgc2hvdy5sZWdlbmQ9RikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMocG9zaXRpb249InJpZ2h0IikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCwgdmp1c3Q9MC41LHNpemU9NywgY29sb3I9ImJsYWNrIiksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KGNvbG9yPSJibGFjayIsc2l6ZT03KSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzPWVsZW1lbnRfbGluZShjb2xvcj0iZ3JleSIpLAogICAgICAgIGF4aXMudGlja3MubGVuZ3RoID0gdW5pdCguNCwgImNtIiksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90Lm1hcmdpbj11bml0KGMoMSwxLDEsMSksImNtIiksCiAgICAgICAgcGxvdC50aXRsZT1lbGVtZW50X21hcmtkb3duKGZhY2U9ImJvbGQiLHNpemU9MTIpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04LCBjb2xvcj0iZ3JleTMwIikpICsgCiAgY29vcmRfY2FydGVzaWFuKGV4cGFuZCA9IEZBTFNFLGNsaXA9Im9mZiIpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNlZTliMDAiLCIjNjU2ZDRhIikpICsgCiAgbGFicyh0aXRsZT0iT2x5bXBpY3MgPHNwYW4gc3R5bGUgPSAnY29sb3I6I2VlOWIwMCc+ZmVtYWxlPC9zcGFuPiBhbmQgPHNwYW4gc3R5bGUgPSAnY29sb3I6IzY1NmQ0YSc+bWFsZTwvc3Bhbj4gYXRobGV0ZXMsIGJ5IHllYXIiLAogICAgICAgc3VidGl0bGU9IkNyb3NzIHNlY3Rpb25hbCBwcm9wb3J0aW9uIG9mIGF0aGxldGVzIGJ5IGdlbmRlciwgZnJvbSAxODk2IHRvIDIwMTYuIFdpbnRlciBhbmQgU3VtbWVyIEdhbWVzIHdlcmUgaGVsZCBpbiB0aGUgc2FtZSB5ZWFyIHVwIHVudGlsXG4xOTkyLiBBZnRlciB0aGF0LCB0aGV5IHN0YWdnZXJlZCB0aGVtIHN1Y2ggdGhhdCBXaW50ZXIgR2FtZXMgb2NjdXIgb24gYSBmb3VyIHllYXIgY3ljbGUgc3RhcnRpbmcgd2l0aCAxOTk0LCB0aGVuIFN1bW1lciBpbiAxOTk2LFxudGhlbldpbnRlciBpbiAxOTk4LCBhbmQgc28gb24uXG4iKQoKYGBgCgojIyMjIEV2ZW50IGNvdW50IGJ5IHNlYXNvbiBhbmQgeWVhcgoKYGBge3J9Cm9seW1waWNzICU+JQogIGNvdW50KHNlYXNvbiwgeWVhciwgZXZlbnQpICU+JQogIGdyb3VwX2J5KHNlYXNvbiwgeWVhcikgJT4lIHRhbGx5KG4pICU+JQogIGdncGxvdChhZXMoeD15ZWFyLCB5PW4sIGZpbGw9c2Vhc29uKSkgKyAKICBnZW9tX2NvbCgpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTg5NiwyMDE2LDIwKSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPXNjYWxlczo6Y29tbWEpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgcGxvdC5tYXJnaW49dW5pdChjKDEsMiwxLDEpLCJjbSIpLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSksCiAgICAgICAgcGxvdC50aXRsZS5wb3NpdGlvbj0icGxvdCIsCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAibGVmdCIsCiAgICAgICAgbGVnZW5kLm1hcmdpbiA9IG1hcmdpbihsPS00NSksCiAgICAgICAgbGVnZW5kLmtleS53aWR0aCA9dW5pdCguMjUsImNtIiksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoc2l6ZT0uMykpICsgCiAgc2NhbGVfZmlsbF9ucGcoKSArIAogIGxhYnMoZmlsbD0iIiwgeD0iWWVhciIsIHk9IkV2ZW50IGNvdW50IiwKICAgICAgIHN1YnRpdGxlPSJPbHltcGljIGV2ZW50IGNvdW50LCBieSBzZWFzb24gYW5kIHllYXIiKSAKYGBgCgoKIyMjIyAxMCBjb3VudHJpZXMgd2l0aCBtb3N0IHN1bW1lciBvbHltcGljcyBtZWRhbAoKYGBge3J9CnMxMCA9IG9seW1waWNzICU+JSAKICBmaWx0ZXIoc2Vhc29uPT0iU3VtbWVyIix5ZWFyPjE5OTAsIWlzLm5hKG1lZGFsKSkgJT4lIAogIGNvdW50KG5vYywgc29ydD1UKSAlPiUKICBzbGljZSgxOjEwKQpgYGAKCmBgYHtyfQojIHJlZmVyZW5jZTogaHR0cHM6Ly9rcHJlc3MuZGV2L2Jsb2cvMjAyMS0wNy0yNi10aWR5LXR1ZXNkYXktb2x5bXBpYy1tZWRhbHMvCiMgcmVmZXJlbmNlOiBodHRwczovL2dpdGh1Yi5jb20vZGF2aWRzam9iZXJnL2dnYnVtcApkZl9yYW5rID0gb2x5bXBpY3MgJT4lIAogIGZpbHRlcihzZWFzb249PSJTdW1tZXIiLHllYXI+MTk5MCwhaXMubmEobWVkYWwpKSAlPiUKICBmaWx0ZXIobm9jICVpbiUgczEwJG5vYykgJT4lCiAgbXV0YXRlKHJlZ2lvbj0gY291bnRyeWNvZGUobm9jLCBvcmlnaW49J2lvYycsIGRlc3RpbmF0aW9uPSdnZW5jMmMnKSkgJT4lIAogIG11dGF0ZShyZWdpb249IHN0cl90b19sb3dlcihyZWdpb24pKSAlPiUKICBncm91cF9ieSh5ZWFyLCByZWdpb24pICU+JSB0YWxseSgpICU+JQogIGFycmFuZ2UoeWVhciwgLW4pICU+JQogIG11dGF0ZShyYW5rID0gZGVuc2VfcmFuayhkZXNjKG4pKSkKCmRmX3JhbmsgJT4lCiAgZ2dwbG90KGFlcyh5ZWFyLCByYW5rLCBjb2xvcj1yZWdpb24pKSArIAogIGdlb21fcG9pbnQoc2l6ZT0yKSArCiAgZ2VvbV9idW1wKHNpemU9MSkgKyAKICBnZW9tX2ZsYWcoZGF0YT0gZGZfcmFuayAlPiUgZmlsdGVyKHllYXI9PTIwMTYpLGFlcyhjb3VudHJ5PXJlZ2lvbiksc2l6ZT04KSArCiAgI2dlb21fdGV4dChkYXRhPSBkZl9yYW5rICU+JSBmaWx0ZXIoeWVhcj09MjAxNiksIGFlcyh4PXllYXIrMC41LCB5PXJhbmssIGxhYmVsPWNvdW50cnksIGhqdXN0PTApLHNpemU9MykgKwogIHNjYWxlX3lfcmV2ZXJzZShicmVha3M9c2VxKDEsMTAsMSkpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTk5MiwgMjAxNiw0KSkgKyAKICBzY2FsZV9jb2xvcl9kMygpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90Lm1hcmdpbj11bml0KGMoLjUsMSwuNSwuNSksImNtIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICBwbG90LmJhY2tncm91bmQ9ZWxlbWVudF9yZWN0KGZpbGw9IiNlOWVjZWYiLCBjb2xvcj0iTkEiKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTguNSksCiAgICAgICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgICAgICBheGlzLnRleHQ9ZWxlbWVudF90ZXh0KGNvbG9yPSJibGFjayIpKSArIAogIGxhYnMoeD0iWWVhciIsIHk9IlJhbmsiLAogICAgICAgc3VidGl0bGU9IkNvdW50cmllcyB3aXRoIG1vc3Qgc3VtbWVyIE9seW1waWNzIG1lZGFsc1xuIikgCmBgYAoKCiMjIyMgU3BvcnQ6IG1pbiB5ZWFyLCBtYXggeWVhciwgZXZlbnQgY291bnQKICAKYGBge3J9Cm9seW1waWNzICU+JSBncm91cF9ieShzcG9ydCkgJT4lCiAgc3VtbWFyaXNlKG1pbl95ZWFyPSBtaW4oeWVhciksIG1heF95ZWFyPW1heCh5ZWFyKSwgeWVhcl9jb3VudCA9IG5fZGlzdGluY3QoeWVhcikpICU+JQogIGFycmFuZ2UobWluX3llYXIsIG1heF95ZWFyKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgRFQ6OmRhdGF0YWJsZShyb3duYW1lcz1GQUxTRSxvcHRpb25zID0gbGlzdChvcmRlciA9IGxpc3QobGlzdCgxLCAnYXNjJykpKSkKYGBgCgoK