file_name <- 'E:/Downloads/business1.json'
business<-jsonlite::stream_in(textConnection(readLines(file_name, n=300000)), flatten = TRUE)

 Found 1 records...
 Imported 1 records. Simplifying...
file_name <- 'E:/Downloads/review1.json'
review<-jsonlite::stream_in(textConnection(readLines(file_name, n=280984)), flatten = TRUE)

 Found 1 records...
 Imported 1 records. Simplifying...
##check data types
glimpse(business)
Rows: 50,000
Columns: 58
$ business_id                           <chr> "K0i8UwxEYFv8mqHl7jAkrg", "o7cEZ~
$ name                                  <chr> "Any Lab Test Now Glendale", "Ca~
$ address                               <chr> "18205 N 51st Ave, Ste 143", "10~
$ city                                  <chr> "AZ", "Montréal", "Scarborough",~
$ state                                 <chr> "AZ", "QC", "ON", "OH", "OH", "N~
$ postal_code                           <chr> "85308", "H2Z 1J6", "M1V 4S4", "~
$ latitude                              <dbl> 33.6523, 45.5085, 43.8230, 41.15~
$ longitude                             <dbl> -112.1683, -73.5603, -79.3064, -~
$ stars                                 <dbl> 4.0, 4.0, 3.0, 2.0, 4.5, 2.0, 4.~
$ review_count                          <int> 4, 7, 87, 4, 82, 74, 5, 698, 13,~
$ is_open                               <int> 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1,~
$ categories                            <chr> "Diagnostic Services, Laboratory~
$ attributes.BusinessAcceptsCreditCards <chr> "True", NA, NA, NA, "True", "Tru~
$ attributes.BikeParking                <chr> NA, NA, "True", "False", "True",~
$ attributes.GoodForKids                <chr> NA, NA, "True", NA, "True", NA, ~
$ attributes.BusinessParking            <chr> NA, NA, "{'garage': False, 'stre~
$ attributes.RestaurantsPriceRange2     <chr> NA, NA, "1", "2", "1", NA, NA, N~
$ attributes.WiFi                       <chr> NA, NA, "u'no'", NA, "u'no'", NA~
$ attributes.RestaurantsAttire          <chr> NA, NA, "u'casual'", NA, "u'casu~
$ attributes.RestaurantsTakeOut         <chr> NA, NA, "True", NA, "True", NA, ~
$ attributes.NoiseLevel                 <chr> NA, NA, "u'average'", NA, "'aver~
$ attributes.RestaurantsReservations    <chr> NA, NA, "False", NA, "False", NA~
$ attributes.RestaurantsGoodForGroups   <chr> NA, NA, "False", NA, "False", NA~
$ attributes.HasTV                      <chr> NA, NA, "True", NA, "True", NA, ~
$ attributes.Alcohol                    <chr> NA, NA, "u'none'", NA, "'beer_an~
$ attributes.RestaurantsDelivery        <chr> NA, NA, "False", NA, "False", NA~
$ attributes.OutdoorSeating             <chr> NA, NA, "False", NA, "False", NA~
$ attributes.Caters                     <chr> NA, NA, "False", NA, "True", NA,~
$ attributes.Ambience                   <chr> NA, NA, "{'romantic': False, 'in~
$ attributes.RestaurantsTableService    <chr> NA, NA, NA, NA, "False", NA, NA,~
$ attributes.GoodForMeal                <chr> NA, NA, NA, NA, "{'dessert': Fal~
$ attributes.ByAppointmentOnly          <chr> NA, NA, NA, NA, NA, "False", NA,~
$ attributes.AcceptsInsurance           <chr> NA, NA, NA, NA, NA, "True", NA, ~
$ attributes.BusinessAcceptsBitcoin     <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.DogsAllowed                <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.HappyHour                  <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.WheelchairAccessible       <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.DriveThru                  <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.GoodForDancing             <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.CoatCheck                  <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Music                      <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Corkage                    <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.BYOBCorkage                <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.HairSpecializesIn          <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.BestNights                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Smoking                    <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.BYOB                       <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.AgesAllowed                <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.DietaryRestrictions        <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.RestaurantsCounterService  <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Open24Hours                <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ hours.Monday                          <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Tuesday                         <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Wednesday                       <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Thursday                        <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Friday                          <chr> "8:0-18:0", NA, "11:0-0:0", NA, ~
$ hours.Saturday                        <chr> "9:0-14:0", NA, "11:0-0:0", NA, ~
$ hours.Sunday                          <chr> NA, NA, "12:0-21:0", NA, NA, NA,~
### Let's examine the structure of certain columns
### We will ignore anything with "hours" or "attribute" it's the name
business %>%
select(-starts_with("hours"), -starts_with("attribute"))
### Let's count how many restaurants there are
### We will detect the term "Restaurant" using the str_detect function then retain (using "filter")only the rows that have the term restaurant.
business %>%
 select(-starts_with("hours"), -starts_with("attribute")) %>%
 filter(str_detect(categories, "Restaurant"))
### Let's examine rows in the Categories that have the term restaurant 
### Show the full list of restaurants in the categories column
business %>%
 select(categories) %>%
 filter(str_detect(categories, "Restaurant"))
#*Lets separate the items in the categories list and put each item on a separate row
#we will then display the name of each restaurant and their categories in separate rows
business %>%
 filter(str_detect(categories, "Restaurant")) %>%
 mutate(categories=str_split(categories, ",")) %>% ## Split the elements where there is a comma.This will create a list
 unnest(categories)%>%## Place each list element onto a separate row
 select(name, categories)
###*We would like to group and count the number of different categories associated with restaurants. Notice that there is a space before some of the categories
###This may be problematic if we try to group and count the observations.
###Let's remove all unnecessary spaces
business %>%
 filter(str_detect(categories, "Restaurant")) %>%
 mutate(categories=str_split(categories, ",")) %>%
 unnest(categories)%>%
 select(name, categories) %>%
 mutate(categories=str_trim(categories))%>% ## create a new variable called categories and remove whitespace from start and end of categories
 mutate(categories=str_squish(categories)) ##create a new variable called categories and reduce repeated whitespace inside a string.
### *Let's count the number of different categories associated with restaurants and arrange them from most to least
business %>%
 filter(str_detect(categories, "Restaurant")) %>%
 mutate(categories=str_split(categories, ",")) %>%
 unnest(categories)%>%
 select(name, categories) %>%
 mutate(categories=str_trim(str_squish(categories))) %>%
 count(categories)%>% ## count the unique values in the categories column
 arrange (desc(n)) ##sort (in descending order) a data frame by the number of items is each category
###Let's see which are the most popular types of categories in each state or province
###We will filter out the words "Restaurant" and "Food" since those are likely to be common for all the establishments
business %>% 
  select(-starts_with("hours"), -starts_with("attribute")) %>%
 filter(str_detect(categories, "Restaurant")) %>%
 mutate(categories=strsplit(categories, ",")) %>%
 unnest(categories)%>%
 select(name, categories) %>%
 mutate(categories=str_trim(str_squish(categories))) %>%
filter (!categories %in% c("Restaurants", "Food")) %>% ## filter out multiple categories i.e. Restaurants and Food
 count(categories)%>%
 arrange(desc(n))
### Now let's answer the following questions

##1. Show the number of different categories other than Restaurants and food, in each state/province ?


business %>%
## filter(str_detect(categories, "Restaurant")) %>%
 mutate(categories=strsplit(categories, ",")) %>%
 unnest(categories)%>%
 select(state, categories) %>%
 mutate(categories=str_trim(str_squish(categories))) %>%
 filter (!categories %in% c("Restaurants", "Food")) %>% ##filter out multiple categories
 group_by(state,categories)%>%
 count(categories)%>%
 arrange(state,desc(n))
##2. How many establishments are there in each state that have the word Restaurants as one of their categories?
business %>%
 filter(str_detect(categories, "Restaurant")) %>%
 mutate(categories=strsplit(categories, ",")) %>%
 unnest(categories)%>%
 select(state, categories) %>%
 mutate(categories=str_trim(str_squish(categories))) %>%
 filter(categories =="Restaurants") %>%
 group_by(state,categories)%>%
 count(categories)%>%
 arrange(desc(n))

#let's pivot the categories column and create a new wider dataset called business_wider that has categories as columns i.e dummyy variables from the categories column

business_wide<-business%>%
       mutate(categories = strsplit(categories, ", ")) %>%
     unnest(categories) %>% 
     arrange(categories) %>%  
     pivot_wider(names_from = categories,
            names_prefix = "categories_",
            names_repair = "universal", 
            values_from = categories, 
            values_fill = 0, 
            values_fn = length)
New names:
* `categories_& Probates` -> categories_..Probates
* `categories_3D Printing` -> categories_3D.Printing
* `categories_Acai Bowls` -> categories_Acai.Bowls
* `categories_Acne Treatment` -> categories_Acne.Treatment
* `categories_Active Life` -> categories_Active.Life
* ...
  
business_wide%>%
  filter(categories_Restaurants==1)%>%
  select(name, state )%>%
group_by(state)%>%
count(state)%>%
arrange(desc(n))
#3.How many records are there for each state/Province
business_wide%>%
  select(name, state )%>%
group_by(state)%>%
count(state)%>%
arrange(desc(n))
#4. How many establishment are open
  business_wide%>%
  select(is_open) %>%
  count(is_open)%>%
  group_by(is_open)%>%
  arrange(desc(n))
## 5. How many establishments are open and how many are closed in each state. Sort ascending by state and whether or not they are open

business_wide %>%
   select(state, is_open) %>%
  group_by(state, is_open)%>%
  count(is_open)%>%
  arrange(state,desc(is_open))
## 6. Show the top 10 states in terms of median star review scores. Organize them in descending order
business %>%
  type_convert(cols(stars = col_double()))%>% ##convert the stars column to a double
  select(state,stars) %>%
  group_by(state)%>%
  summarize(Stars=median(stars))%>%
  arrange(desc(Stars))%>%
  head(10)
##7. Show the bottom 5 states in terms of median star review scores. Also show the median number of review scores that they have received
business_wide %>%
  type_convert(cols(stars = col_double(), review_count = col_integer()))%>% ##convert the stars column to a double
   select(state,review_count, stars) %>%
  group_by(state)%>%
  summarize(Median_Stars=median(stars), Number_of_Reviews=median(review_count))%>%
  arrange(Median_Stars)%>%
  head(5)
##8. Show the establishments with the most number of 5 star reviews (top 5)
review %>%
  filter(stars == 5) %>%
  group_by(business_id) %>%
  summarise(Count = n()) %>%
  arrange(desc(Count)) %>%
  head(5)%>%
inner_join(business_wide)
Joining, by = "business_id"
### 9. Which 5 business appears the most number of times in the data set. Order the businesses by the number of time they appear
business_wide %>%
  group_by(name)%>%
  summarise(Count = n()) %>%
  arrange(desc(Count))%>%
  head(5)
## Let's analyze Starbucks  given that it the most popular company in the dataset
### we will create a new tibble called StarbucksJoined_tbl that has all the Starbucks business data and their reviews
starbucksbusiness=business %>%
  filter(name=="Starbucks")
StarbucksJoined_tbl <-tibble( inner_join(starbucksbusiness,review))
Joining, by = c("business_id", "stars")
#10. Show the number of Starbucks in each State
StarbucksJoined_tbl%>%
  group_by(state)%>%
  summarise(Count = n()) %>%
  arrange(desc(Count))
## 11. What is the number and proportion of visitors to Yelp's site who find the reviews for Starbucks useful 
StarbucksJoined_tbl %>%
  group_by(useful) %>%
  summarise(Count = n()) %>%
  arrange(desc(Count)) %>%
  mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
  head(10)
## 11. What is the number and percentage  of visitors to Yelp's site who find the reviews for Starbucks funny 
StarbucksJoined_tbl %>%
  group_by(funny) %>%
  summarise(Count = n()) %>%
  arrange(desc(Count)) %>%
  mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
  head(10)
## 11. What is the number and percentage  of visitors to Yelp's site who find the reviews for Starbucks useful 
StarbucksJoined_tbl %>%
  group_by(cool) %>%
  summarise(Count = n()) %>%
  arrange(desc(Count)) %>%
  mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
  head(10)
###Let's analyze the most negative reviews and the most positive reviews. What are customers from each group saying
### to identify the most negative and positive reviews we have to 1)assign sentiment scores to each review 2)sort the reviews scores
###1) assigning review scores and store the results in a data frame called CustomerSentiment
install.packages("textdata")
install.packages("textcat")
install.packages("tidytext")
library(textdata)
library(textcat)
library(tidytext)
get_sentiments("afinn")
CustomerSentiment<-StarbucksJoined_tbl %>%
  ## filter(textcat(text) == "english") %>% # considering only English text. Omit this line if you want faster processing.
  unnest_tokens(word, text) %>%  ##Split a text column into words/tokens
   anti_join(stop_words)%>%  #remove stopwords
   inner_join(get_sentiments("afinn"), by = "word") %>% # join the StarbucksJoined_tbl with the afinn lexicon which has 2 columns titled word and value 
   
  group_by(review_id) %>%
  summarize(sentiment = mean(value),words = n()) 
Joining, by = "word"
###2) sort the reviews scores and display desired columns(i.e. address,city, date,sentiment,text)
#### Most Negative reviews
CustomerSentiment%>%
  arrange(desc(sentiment)) %>%
  top_n(-10, sentiment) %>%  ## get the lower sentiment scores
  inner_join(StarbucksJoined_tbl, by = "review_id") %>%
  select(address,city, date,sentiment,text)
#### Most Positive reviews
CustomerSentiment%>%
  arrange(desc(sentiment)) %>%
  top_n(10, sentiment) %>% ## get the highest sentiment scores
  inner_join(StarbucksJoined_tbl, by = "review_id") %>%
  select(address,city, date,sentiment,text)
library(lubridate)
### Let's add a formatted Date Column to StarbucksJoined_tbl
## We will use Lubridate reformat the date column ( which is currently stored as chr) and create new columns to multiple columns:
### date, month,day, year,hour
StarbucksDateFormatted<-StarbucksJoined_tbl%>%
 mutate(date_formatted = as_date(date),
 month_formatted=month(date),
 day_formatted=day(date),
 year_formatted=year(date),
 hour_formatted=hour(date))
### Now lets create some plots for Starbucks

###1. Show the number of reviews for Starbucks over time(years) using a line chart
StarbucksDateFormatted%>%
  select(year_formatted)%>%
  group_by (year_formatted)%>%
  summarise(NumberofReviews = n())%>%
  ggplot(aes (x=year_formatted,y=NumberofReviews)) +
  geom_line()

###2. Show the number of reviews by State using a column/bar chart
StarbucksDateFormatted%>%
  select(state)%>%
  group_by(state)%>%
  ggplot(aes (x=state)) +
  geom_bar()

###3. Create a donut chart that Shows the proportion of businesses that are open vs those that are closed
business %>%
 group_by(is_open) %>%
 summarise(Count = n())%>%
 mutate(is_open=as.factor(is_open), percentage = round(Count/sum(Count)*100,2), LabelPosition = cumsum(percentage)-.1*percentage) %>%
 ###plot the pie chart with geom_bar() and then convert the bar into pie with the coord_polar() function. to make a donut plot we must specify the x = 2 in aes() and add the xlim() as code 
 ggplot(
 aes(x = 2, y = percentage, fill = is_open))+
 geom_bar(stat = "identity")+
 coord_polar("y" ) +
 geom_text(aes(y = LabelPosition, label = paste(percentage,"%", sep = "")), col = "white") +
 theme_void() +
 scale_fill_brewer(palette = "Dark2")+
 xlim(.2,2.5)

##4. Create a column chart that shows the median number of words used in Starbucks reviews each month
StarbucksDateFormatted %>%
  mutate(NumberOfWords=str_count(text, boundary("word"))) %>% ## counts the number of words since boundary is set to word
  mutate(MonthName=month(ymd(date_formatted), label = TRUE)) %>%
  group_by(MonthName) %>%
  summarize(NumberOfWords=median(NumberOfWords)) %>%
  ggplot(aes (x=MonthName, y=NumberOfWords)) +
  geom_col() +
  coord_flip()

### 5. Create a chart that shows the relationship between funny and useful reviews of Starbucks.
StarbucksDateFormatted%>%
  ggplot(aes(x=funny, y=useful)) + 
  geom_point()+
  geom_smooth(method=lm)
`geom_smooth()` using formula 'y ~ x'

### 6. Create a wordcloud to show the words that are used the most frequently in reviews

library(wordcloud)
createWordCloud = function(x)
{
  StarbucksDateFormatted %>%
    unnest_tokens(word, text) %>%
    
    filter(!word %in% stop_words$word, !word %in% 'starbucks') %>%
    ### filter(!word = 'starbucks') %>%
    count(word,sort = TRUE) %>%
    ungroup() %>%
    head(30) %>%
    with(wordcloud(word, n, max.words = 30,colors=brewer.pal(8, "Dark2")))
}

createWordCloud(review)

### 7.Sentiment Analysis : The Positive and negative words associate with Starbucks reviews

positiveWordsBarGraph <- function(SC) {
  contributions <- SC %>%
    unnest_tokens(word, text) %>%
    count(word,sort = TRUE) %>%
    ungroup() %>%
    
    inner_join(get_sentiments("afinn"), by = "word") %>%
    group_by(word) %>%
    summarize(contribution = sum(value), n=n())
  
  contributions %>%
    top_n(20, abs(contribution)) %>%
    mutate(word = reorder(word, contribution)) %>%
    head(20) %>%
    ggplot(aes(word, contribution, fill = contribution > 0)) +
    geom_col(show.legend = FALSE) +
    coord_flip() + theme_bw()
}

positiveWordsBarGraph(StarbucksJoined_tbl)

###8. Show all the Starbucks locations on a map
### we will use the starbucksbusiness data set that we created earlier because it does not have repeating reviews each record is unique
library(leaflet)
### this function will color each location based on the star rating it has
pal <- colorFactor(c("purple", "red", "orange", "black","blue"),
                   domain = unique(starbucksbusiness$stars))
### this draws the map
map <- leaflet(starbucksbusiness) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    color = ~pal(starbucksbusiness$stars),
    stroke = FALSE, fillOpacity = 0.5,
    lat = starbucksbusiness$latitude,
    lng = starbucksbusiness$longitude,
    clusterOptions = markerClusterOptions(),
    popup = as.character(starbucksbusiness$address))
map
#Let's join the business and review tables so that we can create a dashboard
business_reviews <- inner_join(business,review)
Joining, by = c("business_id", "stars")
LS0tDQp0aXRsZTogIkV4cGxvcmluZyB0aGUgWWVscCBEYXRhc2V0Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQpgYGB7cn0NCmZpbGVfbmFtZSA8LSAnRTovRG93bmxvYWRzL2J1c2luZXNzMS5qc29uJw0KYnVzaW5lc3M8LWpzb25saXRlOjpzdHJlYW1faW4odGV4dENvbm5lY3Rpb24ocmVhZExpbmVzKGZpbGVfbmFtZSwgbj0zMDAwMDApKSwgZmxhdHRlbiA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQpmaWxlX25hbWUgPC0gJ0U6L0Rvd25sb2Fkcy9yZXZpZXcxLmpzb24nDQpyZXZpZXc8LWpzb25saXRlOjpzdHJlYW1faW4odGV4dENvbm5lY3Rpb24ocmVhZExpbmVzKGZpbGVfbmFtZSwgbj0yODA5ODQpKSwgZmxhdHRlbiA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQojI2NoZWNrIGRhdGEgdHlwZXMNCmdsaW1wc2UoYnVzaW5lc3MpDQpgYGANCg0KYGBge3J9DQojIyMgTGV0J3MgZXhhbWluZSB0aGUgc3RydWN0dXJlIG9mIGNlcnRhaW4gY29sdW1ucw0KIyMjIFdlIHdpbGwgaWdub3JlIGFueXRoaW5nIHdpdGggImhvdXJzIiBvciAiYXR0cmlidXRlIiBpdCdzIHRoZSBuYW1lDQpidXNpbmVzcyAlPiUNCnNlbGVjdCgtc3RhcnRzX3dpdGgoImhvdXJzIiksIC1zdGFydHNfd2l0aCgiYXR0cmlidXRlIikpDQpgYGANCg0KYGBge3J9DQojIyMgTGV0J3MgY291bnQgaG93IG1hbnkgcmVzdGF1cmFudHMgdGhlcmUgYXJlDQojIyMgV2Ugd2lsbCBkZXRlY3QgdGhlIHRlcm0gIlJlc3RhdXJhbnQiIHVzaW5nIHRoZSBzdHJfZGV0ZWN0IGZ1bmN0aW9uIHRoZW4gcmV0YWluICh1c2luZyAiZmlsdGVyIilvbmx5IHRoZSByb3dzIHRoYXQgaGF2ZSB0aGUgdGVybSByZXN0YXVyYW50Lg0KYnVzaW5lc3MgJT4lDQogc2VsZWN0KC1zdGFydHNfd2l0aCgiaG91cnMiKSwgLXN0YXJ0c193aXRoKCJhdHRyaWJ1dGUiKSkgJT4lDQogZmlsdGVyKHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIlJlc3RhdXJhbnQiKSkNCmBgYA0KYGBge3J9DQojIyMgTGV0J3MgZXhhbWluZSByb3dzIGluIHRoZSBDYXRlZ29yaWVzIHRoYXQgaGF2ZSB0aGUgdGVybSByZXN0YXVyYW50IA0KIyMjIFNob3cgdGhlIGZ1bGwgbGlzdCBvZiByZXN0YXVyYW50cyBpbiB0aGUgY2F0ZWdvcmllcyBjb2x1bW4NCmJ1c2luZXNzICU+JQ0KIHNlbGVjdChjYXRlZ29yaWVzKSAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKQ0KYGBgDQpgYGB7cn0NCiMqTGV0cyBzZXBhcmF0ZSB0aGUgaXRlbXMgaW4gdGhlIGNhdGVnb3JpZXMgbGlzdCBhbmQgcHV0IGVhY2ggaXRlbSBvbiBhIHNlcGFyYXRlIHJvdw0KI3dlIHdpbGwgdGhlbiBkaXNwbGF5IHRoZSBuYW1lIG9mIGVhY2ggcmVzdGF1cmFudCBhbmQgdGhlaXIgY2F0ZWdvcmllcyBpbiBzZXBhcmF0ZSByb3dzDQpidXNpbmVzcyAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfc3BsaXQoY2F0ZWdvcmllcywgIiwiKSkgJT4lICMjIFNwbGl0IHRoZSBlbGVtZW50cyB3aGVyZSB0aGVyZSBpcyBhIGNvbW1hLlRoaXMgd2lsbCBjcmVhdGUgYSBsaXN0DQogdW5uZXN0KGNhdGVnb3JpZXMpJT4lIyMgUGxhY2UgZWFjaCBsaXN0IGVsZW1lbnQgb250byBhIHNlcGFyYXRlIHJvdw0KIHNlbGVjdChuYW1lLCBjYXRlZ29yaWVzKQ0KYGBgDQpgYGB7cn0NCiMjIypXZSB3b3VsZCBsaWtlIHRvIGdyb3VwIGFuZCBjb3VudCB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCBjYXRlZ29yaWVzIGFzc29jaWF0ZWQgd2l0aCByZXN0YXVyYW50cy4gTm90aWNlIHRoYXQgdGhlcmUgaXMgYSBzcGFjZSBiZWZvcmUgc29tZSBvZiB0aGUgY2F0ZWdvcmllcw0KIyMjVGhpcyBtYXkgYmUgcHJvYmxlbWF0aWMgaWYgd2UgdHJ5IHRvIGdyb3VwIGFuZCBjb3VudCB0aGUgb2JzZXJ2YXRpb25zLg0KIyMjTGV0J3MgcmVtb3ZlIGFsbCB1bm5lY2Vzc2FyeSBzcGFjZXMNCmJ1c2luZXNzICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl9zcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3QobmFtZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oY2F0ZWdvcmllcykpJT4lICMjIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgY2F0ZWdvcmllcyBhbmQgcmVtb3ZlIHdoaXRlc3BhY2UgZnJvbSBzdGFydCBhbmQgZW5kIG9mIGNhdGVnb3JpZXMNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfc3F1aXNoKGNhdGVnb3JpZXMpKSAjI2NyZWF0ZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgY2F0ZWdvcmllcyBhbmQgcmVkdWNlIHJlcGVhdGVkIHdoaXRlc3BhY2UgaW5zaWRlIGEgc3RyaW5nLg0KYGBgDQpgYGB7cn0NCiMjIyAqTGV0J3MgY291bnQgdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgY2F0ZWdvcmllcyBhc3NvY2lhdGVkIHdpdGggcmVzdGF1cmFudHMgYW5kIGFycmFuZ2UgdGhlbSBmcm9tIG1vc3QgdG8gbGVhc3QNCmJ1c2luZXNzICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl9zcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3QobmFtZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KIGNvdW50KGNhdGVnb3JpZXMpJT4lICMjIGNvdW50IHRoZSB1bmlxdWUgdmFsdWVzIGluIHRoZSBjYXRlZ29yaWVzIGNvbHVtbg0KIGFycmFuZ2UgKGRlc2MobikpICMjc29ydCAoaW4gZGVzY2VuZGluZyBvcmRlcikgYSBkYXRhIGZyYW1lIGJ5IHRoZSBudW1iZXIgb2YgaXRlbXMgaXMgZWFjaCBjYXRlZ29yeQ0KYGBgDQoNCmBgYHtyfQ0KIyMjTGV0J3Mgc2VlIHdoaWNoIGFyZSB0aGUgbW9zdCBwb3B1bGFyIHR5cGVzIG9mIGNhdGVnb3JpZXMgaW4gZWFjaCBzdGF0ZSBvciBwcm92aW5jZQ0KIyMjV2Ugd2lsbCBmaWx0ZXIgb3V0IHRoZSB3b3JkcyAiUmVzdGF1cmFudCIgYW5kICJGb29kIiBzaW5jZSB0aG9zZSBhcmUgbGlrZWx5IHRvIGJlIGNvbW1vbiBmb3IgYWxsIHRoZSBlc3RhYmxpc2htZW50cw0KYnVzaW5lc3MgJT4lIA0KICBzZWxlY3QoLXN0YXJ0c193aXRoKCJob3VycyIpLCAtc3RhcnRzX3dpdGgoImF0dHJpYnV0ZSIpKSAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJzcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3QobmFtZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KZmlsdGVyICghY2F0ZWdvcmllcyAlaW4lIGMoIlJlc3RhdXJhbnRzIiwgIkZvb2QiKSkgJT4lICMjIGZpbHRlciBvdXQgbXVsdGlwbGUgY2F0ZWdvcmllcyBpLmUuIFJlc3RhdXJhbnRzIGFuZCBGb29kDQogY291bnQoY2F0ZWdvcmllcyklPiUNCiBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KYGBge3J9DQojIyMgTm93IGxldCdzIGFuc3dlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9ucw0KDQojIzEuIFNob3cgdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgY2F0ZWdvcmllcyBvdGhlciB0aGFuIFJlc3RhdXJhbnRzIGFuZCBmb29kLCBpbiBlYWNoIHN0YXRlL3Byb3ZpbmNlID8NCg0KDQpidXNpbmVzcyAlPiUNCiMjIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cnNwbGl0KGNhdGVnb3JpZXMsICIsIikpICU+JQ0KIHVubmVzdChjYXRlZ29yaWVzKSU+JQ0KIHNlbGVjdChzdGF0ZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KIGZpbHRlciAoIWNhdGVnb3JpZXMgJWluJSBjKCJSZXN0YXVyYW50cyIsICJGb29kIikpICU+JSAjI2ZpbHRlciBvdXQgbXVsdGlwbGUgY2F0ZWdvcmllcw0KIGdyb3VwX2J5KHN0YXRlLGNhdGVnb3JpZXMpJT4lDQogY291bnQoY2F0ZWdvcmllcyklPiUNCiBhcnJhbmdlKHN0YXRlLGRlc2MobikpDQpgYGANCmBgYHtyfQ0KIyMyLiBIb3cgbWFueSBlc3RhYmxpc2htZW50cyBhcmUgdGhlcmUgaW4gZWFjaCBzdGF0ZSB0aGF0IGhhdmUgdGhlIHdvcmQgUmVzdGF1cmFudHMgYXMgb25lIG9mIHRoZWlyIGNhdGVnb3JpZXM/DQpidXNpbmVzcyAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJzcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3Qoc3RhdGUsIGNhdGVnb3JpZXMpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl90cmltKHN0cl9zcXVpc2goY2F0ZWdvcmllcykpKSAlPiUNCiBmaWx0ZXIoY2F0ZWdvcmllcyA9PSJSZXN0YXVyYW50cyIpICU+JQ0KIGdyb3VwX2J5KHN0YXRlLGNhdGVnb3JpZXMpJT4lDQogY291bnQoY2F0ZWdvcmllcyklPiUNCiBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KYGBge3J9DQoNCiNsZXQncyBwaXZvdCB0aGUgY2F0ZWdvcmllcyBjb2x1bW4gYW5kIGNyZWF0ZSBhIG5ldyB3aWRlciBkYXRhc2V0IGNhbGxlZCBidXNpbmVzc193aWRlciB0aGF0IGhhcyBjYXRlZ29yaWVzIGFzIGNvbHVtbnMgaS5lIGR1bW15eSB2YXJpYWJsZXMgZnJvbSB0aGUgY2F0ZWdvcmllcyBjb2x1bW4NCg0KYnVzaW5lc3Nfd2lkZTwtYnVzaW5lc3MlPiUNCiAgICAgICBtdXRhdGUoY2F0ZWdvcmllcyA9IHN0cnNwbGl0KGNhdGVnb3JpZXMsICIsICIpKSAlPiUNCiAgICAgdW5uZXN0KGNhdGVnb3JpZXMpICU+JSANCiAgICAgYXJyYW5nZShjYXRlZ29yaWVzKSAlPiUgIA0KICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY2F0ZWdvcmllcywNCiAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJjYXRlZ29yaWVzXyIsDQogICAgICAgICAgICBuYW1lc19yZXBhaXIgPSAidW5pdmVyc2FsIiwgDQogICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IGNhdGVnb3JpZXMsIA0KICAgICAgICAgICAgdmFsdWVzX2ZpbGwgPSAwLCANCiAgICAgICAgICAgIHZhbHVlc19mbiA9IGxlbmd0aCkNCiAgDQpgYGANCg0KYGBge3J9DQojIzIuIEhvdyBtYW55IGVzdGFibGlzaG1lbnRzIGFyZSB0aGVyZSBpbiBlYWNoIHN0YXRlIHRoYXQgaGF2ZSB0aGUgd29yZCBSZXN0YXVyYW50cyBhcyBvbmUgb2YgdGhlaXIgY2F0ZWdvcmllcz8NCg0KYnVzaW5lc3Nfd2lkZSU+JQ0KICBmaWx0ZXIoY2F0ZWdvcmllc19SZXN0YXVyYW50cz09MSklPiUNCiAgc2VsZWN0KG5hbWUsIHN0YXRlICklPiUNCmdyb3VwX2J5KHN0YXRlKSU+JQ0KY291bnQoc3RhdGUpJT4lDQphcnJhbmdlKGRlc2MobikpDQpgYGANCmBgYHtyfQ0KIzMuSG93IG1hbnkgcmVjb3JkcyBhcmUgdGhlcmUgZm9yIGVhY2ggc3RhdGUvUHJvdmluY2UNCmJ1c2luZXNzX3dpZGUlPiUNCiAgc2VsZWN0KG5hbWUsIHN0YXRlICklPiUNCmdyb3VwX2J5KHN0YXRlKSU+JQ0KY291bnQoc3RhdGUpJT4lDQphcnJhbmdlKGRlc2MobikpDQpgYGANCmBgYHtyfQ0KIzQuIEhvdyBtYW55IGVzdGFibGlzaG1lbnQgYXJlIG9wZW4NCiAgYnVzaW5lc3Nfd2lkZSU+JQ0KICBzZWxlY3QoaXNfb3BlbikgJT4lDQogIGNvdW50KGlzX29wZW4pJT4lDQogIGdyb3VwX2J5KGlzX29wZW4pJT4lDQogIGFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KYGBge3J9DQojIyA1LiBIb3cgbWFueSBlc3RhYmxpc2htZW50cyBhcmUgb3BlbiBhbmQgaG93IG1hbnkgYXJlIGNsb3NlZCBpbiBlYWNoIHN0YXRlLiBTb3J0IGFzY2VuZGluZyBieSBzdGF0ZSBhbmQgd2hldGhlciBvciBub3QgdGhleSBhcmUgb3Blbg0KDQpidXNpbmVzc193aWRlICU+JQ0KICAgc2VsZWN0KHN0YXRlLCBpc19vcGVuKSAlPiUNCiAgZ3JvdXBfYnkoc3RhdGUsIGlzX29wZW4pJT4lDQogIGNvdW50KGlzX29wZW4pJT4lDQogIGFycmFuZ2Uoc3RhdGUsZGVzYyhpc19vcGVuKSkNCmBgYA0KYGBge3J9DQojIyA2LiBTaG93IHRoZSB0b3AgMTAgc3RhdGVzIGluIHRlcm1zIG9mIG1lZGlhbiBzdGFyIHJldmlldyBzY29yZXMuIE9yZ2FuaXplIHRoZW0gaW4gZGVzY2VuZGluZyBvcmRlcg0KYnVzaW5lc3Nfd2lkZSAlPiUNCiAgdHlwZV9jb252ZXJ0KGNvbHMoc3RhcnMgPSBjb2xfZG91YmxlKCkpKSU+JSAjI2NvbnZlcnQgdGhlIHN0YXJzIGNvbHVtbiB0byBhIGRvdWJsZQ0KICBzZWxlY3Qoc3RhdGUsc3RhcnMpICU+JQ0KICBncm91cF9ieShzdGF0ZSklPiUNCiAgc3VtbWFyaXplKFN0YXJzPW1lZGlhbihzdGFycykpJT4lDQogIGFycmFuZ2UoZGVzYyhTdGFycykpJT4lDQogIGhlYWQoMTApDQpgYGANCg0KYGBge3J9DQojIzcuIFNob3cgdGhlIGJvdHRvbSA1IHN0YXRlcyBpbiB0ZXJtcyBvZiBtZWRpYW4gc3RhciByZXZpZXcgc2NvcmVzLiBBbHNvIHNob3cgdGhlIG1lZGlhbiBudW1iZXIgb2YgcmV2aWV3IHNjb3JlcyB0aGF0IHRoZXkgaGF2ZSByZWNlaXZlZA0KYnVzaW5lc3Nfd2lkZSAlPiUNCiAgdHlwZV9jb252ZXJ0KGNvbHMoc3RhcnMgPSBjb2xfZG91YmxlKCksIHJldmlld19jb3VudCA9IGNvbF9pbnRlZ2VyKCkpKSU+JSAjI2NvbnZlcnQgdGhlIHN0YXJzIGNvbHVtbiB0byBhIGRvdWJsZQ0KICAgc2VsZWN0KHN0YXRlLHJldmlld19jb3VudCwgc3RhcnMpICU+JQ0KICBncm91cF9ieShzdGF0ZSklPiUNCiAgc3VtbWFyaXplKE1lZGlhbl9TdGFycz1tZWRpYW4oc3RhcnMpLCBOdW1iZXJfb2ZfUmV2aWV3cz1tZWRpYW4ocmV2aWV3X2NvdW50KSklPiUNCiAgYXJyYW5nZShNZWRpYW5fU3RhcnMpJT4lDQogIGhlYWQoNSkNCmBgYA0KYGBge3J9DQojIzguIFNob3cgdGhlIGVzdGFibGlzaG1lbnRzIHdpdGggdGhlIG1vc3QgbnVtYmVyIG9mIDUgc3RhciByZXZpZXdzICh0b3AgNSkNCnJldmlldyAlPiUNCiAgZmlsdGVyKHN0YXJzID09IDUpICU+JQ0KICBncm91cF9ieShidXNpbmVzc19pZCkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBoZWFkKDUpJT4lDQppbm5lcl9qb2luKGJ1c2luZXNzX3dpZGUpDQoNCmBgYA0KDQpgYGB7cn0NCiMjIyA5LiBXaGljaCA1IGJ1c2luZXNzIGFwcGVhcnMgdGhlIG1vc3QgbnVtYmVyIG9mIHRpbWVzIGluIHRoZSBkYXRhIHNldC4gT3JkZXIgdGhlIGJ1c2luZXNzZXMgYnkgdGhlIG51bWJlciBvZiB0aW1lIHRoZXkgYXBwZWFyDQpidXNpbmVzc193aWRlICU+JQ0KICBncm91cF9ieShuYW1lKSU+JQ0KICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoQ291bnQpKSU+JQ0KICBoZWFkKDUpDQpgYGANCmBgYHtyfQ0KIyMgTGV0J3MgYW5hbHl6ZSBTdGFyYnVja3MgIGdpdmVuIHRoYXQgaXQgdGhlIG1vc3QgcG9wdWxhciBjb21wYW55IGluIHRoZSBkYXRhc2V0DQojIyMgd2Ugd2lsbCBjcmVhdGUgYSBuZXcgdGliYmxlIGNhbGxlZCBTdGFyYnVja3NKb2luZWRfdGJsIHRoYXQgaGFzIGFsbCB0aGUgU3RhcmJ1Y2tzIGJ1c2luZXNzIGRhdGEgYW5kIHRoZWlyIHJldmlld3MNCnN0YXJidWNrc2J1c2luZXNzPWJ1c2luZXNzICU+JQ0KICBmaWx0ZXIobmFtZT09IlN0YXJidWNrcyIpDQpTdGFyYnVja3NKb2luZWRfdGJsIDwtdGliYmxlKCBpbm5lcl9qb2luKHN0YXJidWNrc2J1c2luZXNzLHJldmlldykpDQpgYGANCmBgYHtyfQ0KIzEwLiBTaG93IHRoZSBudW1iZXIgb2YgU3RhcmJ1Y2tzIGluIGVhY2ggU3RhdGUNClN0YXJidWNrc0pvaW5lZF90YmwlPiUNCiAgZ3JvdXBfYnkoc3RhdGUpJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpDQpgYGANCg0KYGBge3J9DQojIyAxMS4gV2hhdCBpcyB0aGUgbnVtYmVyIGFuZCBwZXJjZW50YWdlICBvZiB2aXNpdG9ycyB0byBZZWxwJ3Mgc2l0ZSB3aG8gZmluZCB0aGUgcmV2aWV3cyBmb3IgU3RhcmJ1Y2tzIHVzZWZ1bCANClN0YXJidWNrc0pvaW5lZF90YmwgJT4lDQogIGdyb3VwX2J5KHVzZWZ1bCkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpKSAlPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjIDExLiBXaGF0IGlzIHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2UgIG9mIHZpc2l0b3JzIHRvIFllbHAncyBzaXRlIHdobyBmaW5kIHRoZSByZXZpZXdzIGZvciBTdGFyYnVja3MgZnVubnkgDQpTdGFyYnVja3NKb2luZWRfdGJsICU+JQ0KICBncm91cF9ieShmdW5ueSkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpKSAlPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjIDExLiBXaGF0IGlzIHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2UgIG9mIHZpc2l0b3JzIHRvIFllbHAncyBzaXRlIHdobyBmaW5kIHRoZSByZXZpZXdzIGZvciBTdGFyYnVja3MgdXNlZnVsIA0KU3RhcmJ1Y2tzSm9pbmVkX3RibCAlPiUNCiAgZ3JvdXBfYnkoY29vbCkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpKSAlPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjI0xldCdzIGFuYWx5emUgdGhlIG1vc3QgbmVnYXRpdmUgcmV2aWV3cyBhbmQgdGhlIG1vc3QgcG9zaXRpdmUgcmV2aWV3cy4gV2hhdCBhcmUgY3VzdG9tZXJzIGZyb20gZWFjaCBncm91cCBzYXlpbmcNCiMjIyB0byBpZGVudGlmeSB0aGUgbW9zdCBuZWdhdGl2ZSBhbmQgcG9zaXRpdmUgcmV2aWV3cyB3ZSBoYXZlIHRvIDEpYXNzaWduIHNlbnRpbWVudCBzY29yZXMgdG8gZWFjaCByZXZpZXcgMilzb3J0IHRoZSByZXZpZXdzIHNjb3Jlcw0KIyMjMSkgYXNzaWduaW5nIHJldmlldyBzY29yZXMgYW5kIHN0b3JlIHRoZSByZXN1bHRzIGluIGEgZGF0YSBmcmFtZSBjYWxsZWQgQ3VzdG9tZXJTZW50aW1lbnQNCmluc3RhbGwucGFja2FnZXMoInRleHRkYXRhIikNCmluc3RhbGwucGFja2FnZXMoInRleHRjYXQiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXRleHQiKQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmxpYnJhcnkodGV4dGNhdCkNCmxpYnJhcnkodGlkeXRleHQpDQoNCmBgYA0KDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJhZmlubiIpDQpgYGANCg0KDQpgYGB7cn0NCkN1c3RvbWVyU2VudGltZW50PC1TdGFyYnVja3NKb2luZWRfdGJsICU+JQ0KICAjIyBmaWx0ZXIodGV4dGNhdCh0ZXh0KSA9PSAiZW5nbGlzaCIpICU+JSAjIGNvbnNpZGVyaW5nIG9ubHkgRW5nbGlzaCB0ZXh0LiBPbWl0IHRoaXMgbGluZSBpZiB5b3Ugd2FudCBmYXN0ZXIgcHJvY2Vzc2luZy4NCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgICMjU3BsaXQgYSB0ZXh0IGNvbHVtbiBpbnRvIHdvcmRzL3Rva2Vucw0KICAgYW50aV9qb2luKHN0b3Bfd29yZHMpJT4lICAjcmVtb3ZlIHN0b3B3b3Jkcw0KICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSwgYnkgPSAid29yZCIpICU+JSAjIGpvaW4gdGhlIFN0YXJidWNrc0pvaW5lZF90Ymwgd2l0aCB0aGUgYWZpbm4gbGV4aWNvbiB3aGljaCBoYXMgMiBjb2x1bW5zIHRpdGxlZCB3b3JkIGFuZCB2YWx1ZSANCiAgIGdyb3VwX2J5KHJldmlld19pZCkgJT4lDQogIHN1bW1hcml6ZShzZW50aW1lbnQgPSBtZWFuKHZhbHVlKSx3b3JkcyA9IG4oKSkgJT4lDQogIGZpbHRlcih3b3JkcyA+PSA1KSAjIHdlIHdpbGwgZXhjbHVkZSByZXZpZXdzIHdpdGggbGVzcyB0aGFuIDUgd29yZHMNCmBgYA0KYGBge3J9DQojIyMyKSBzb3J0IHRoZSByZXZpZXdzIHNjb3JlcyBhbmQgZGlzcGxheSBkZXNpcmVkIGNvbHVtbnMoaS5lLiBhZGRyZXNzLGNpdHksIGRhdGUsc2VudGltZW50LHRleHQpDQojIyMjIE1vc3QgTmVnYXRpdmUgcmV2aWV3cw0KQ3VzdG9tZXJTZW50aW1lbnQlPiUNCiAgYXJyYW5nZShkZXNjKHNlbnRpbWVudCkpICU+JQ0KICB0b3BfbigtMTAsIHNlbnRpbWVudCkgJT4lICAjIyBnZXQgdGhlIGxvd2VyIHNlbnRpbWVudCBzY29yZXMNCiAgaW5uZXJfam9pbihTdGFyYnVja3NKb2luZWRfdGJsLCBieSA9ICJyZXZpZXdfaWQiKSAlPiUNCiAgc2VsZWN0KGFkZHJlc3MsY2l0eSwgZGF0ZSxzZW50aW1lbnQsdGV4dCkNCmBgYA0KDQpgYGB7cn0NCiMjIyMgTW9zdCBQb3NpdGl2ZSByZXZpZXdzDQpDdXN0b21lclNlbnRpbWVudCU+JQ0KICBhcnJhbmdlKGRlc2Moc2VudGltZW50KSkgJT4lDQogIHRvcF9uKDEwLCBzZW50aW1lbnQpICU+JSAjIyBnZXQgdGhlIGhpZ2hlc3Qgc2VudGltZW50IHNjb3Jlcw0KICBpbm5lcl9qb2luKFN0YXJidWNrc0pvaW5lZF90YmwsIGJ5ID0gInJldmlld19pZCIpICU+JQ0KICBzZWxlY3QoYWRkcmVzcyxjaXR5LCBkYXRlLHNlbnRpbWVudCx0ZXh0KQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShsdWJyaWRhdGUpDQpgYGANCg0KYGBge3J9DQojIyMgTGV0J3MgYWRkIGEgZm9ybWF0dGVkIERhdGUgQ29sdW1uIHRvIFN0YXJidWNrc0pvaW5lZF90YmwNCiMjIFdlIHdpbGwgdXNlIEx1YnJpZGF0ZSByZWZvcm1hdCB0aGUgZGF0ZSBjb2x1bW4gKCB3aGljaCBpcyBjdXJyZW50bHkgc3RvcmVkIGFzIGNocikgYW5kIGNyZWF0ZSBuZXcgY29sdW1ucyB0byBtdWx0aXBsZSBjb2x1bW5zOg0KIyMjIGRhdGUsIG1vbnRoLGRheSwgeWVhcixob3VyDQpTdGFyYnVja3NEYXRlRm9ybWF0dGVkPC1TdGFyYnVja3NKb2luZWRfdGJsJT4lDQogbXV0YXRlKGRhdGVfZm9ybWF0dGVkID0gYXNfZGF0ZShkYXRlKSwNCiBtb250aF9mb3JtYXR0ZWQ9bW9udGgoZGF0ZSksDQogZGF5X2Zvcm1hdHRlZD1kYXkoZGF0ZSksDQogeWVhcl9mb3JtYXR0ZWQ9eWVhcihkYXRlKSwNCiBob3VyX2Zvcm1hdHRlZD1ob3VyKGRhdGUpKQ0KYGBgDQoNCmBgYHtyfQ0KIyMjIE5vdyBsZXRzIGNyZWF0ZSBzb21lIHBsb3RzIGZvciBTdGFyYnVja3MNCg0KIyMjMS4gU2hvdyB0aGUgbnVtYmVyIG9mIHJldmlld3MgZm9yIFN0YXJidWNrcyBvdmVyIHRpbWUoeWVhcnMpIHVzaW5nIGEgbGluZSBjaGFydA0KU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCU+JQ0KICBzZWxlY3QoeWVhcl9mb3JtYXR0ZWQpJT4lDQogIGdyb3VwX2J5ICh5ZWFyX2Zvcm1hdHRlZCklPiUNCiAgc3VtbWFyaXNlKE51bWJlcm9mUmV2aWV3cyA9IG4oKSklPiUNCiAgZ2dwbG90KGFlcyAoeD15ZWFyX2Zvcm1hdHRlZCx5PU51bWJlcm9mUmV2aWV3cykpICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KDQpgYGB7cn0NCiMjIzIuIFNob3cgdGhlIG51bWJlciBvZiByZXZpZXdzIGJ5IFN0YXRlIHVzaW5nIGEgY29sdW1uL2JhciBjaGFydA0KU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCU+JQ0KICBzZWxlY3Qoc3RhdGUpJT4lDQogIGdyb3VwX2J5KHN0YXRlKSU+JQ0KICBnZ3Bsb3QoYWVzICh4PXN0YXRlKSkgKw0KICBnZW9tX2JhcigpDQpgYGANCg0KYGBge3J9DQojIyMzLiBDcmVhdGUgYSBkb251dCBjaGFydCB0aGF0IFNob3dzIHRoZSBwcm9wb3J0aW9uIG9mIGJ1c2luZXNzZXMgdGhhdCBhcmUgb3BlbiB2cyB0aG9zZSB0aGF0IGFyZSBjbG9zZWQNCmJ1c2luZXNzICU+JQ0KIGdyb3VwX2J5KGlzX29wZW4pICU+JQ0KIHN1bW1hcmlzZShDb3VudCA9IG4oKSklPiUNCiBtdXRhdGUoaXNfb3Blbj1hcy5mYWN0b3IoaXNfb3BlbiksIHBlcmNlbnRhZ2UgPSByb3VuZChDb3VudC9zdW0oQ291bnQpKjEwMCwyKSwgTGFiZWxQb3NpdGlvbiA9IGN1bXN1bShwZXJjZW50YWdlKS0uMSpwZXJjZW50YWdlKSAlPiUNCiAjIyNwbG90IHRoZSBwaWUgY2hhcnQgd2l0aCBnZW9tX2JhcigpIGFuZCB0aGVuIGNvbnZlcnQgdGhlIGJhciBpbnRvIHBpZSB3aXRoIHRoZSBjb29yZF9wb2xhcigpIGZ1bmN0aW9uLiB0byBtYWtlIGEgZG9udXQgcGxvdCB3ZSBtdXN0IHNwZWNpZnkgdGhlIHggPSAyIGluIGFlcygpIGFuZCBhZGQgdGhlIHhsaW0oKSBhcyBjb2RlIA0KIGdncGxvdCgNCiBhZXMoeCA9IDIsIHkgPSBwZXJjZW50YWdlLCBmaWxsID0gaXNfb3BlbikpKw0KIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSsNCiBjb29yZF9wb2xhcigieSIgKSArDQogZ2VvbV90ZXh0KGFlcyh5ID0gTGFiZWxQb3NpdGlvbiwgbGFiZWwgPSBwYXN0ZShwZXJjZW50YWdlLCIlIiwgc2VwID0gIiIpKSwgY29sID0gIndoaXRlIikgKw0KIHRoZW1lX3ZvaWQoKSArDQogc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpKw0KIHhsaW0oLjIsMi41KQ0KYGBgDQoNCmBgYHtyfQ0KIyM0LiBDcmVhdGUgYSBjb2x1bW4gY2hhcnQgdGhhdCBzaG93cyB0aGUgbWVkaWFuIG51bWJlciBvZiB3b3JkcyB1c2VkIGluIFN0YXJidWNrcyByZXZpZXdzIGVhY2ggbW9udGgNClN0YXJidWNrc0RhdGVGb3JtYXR0ZWQgJT4lDQogIG11dGF0ZShOdW1iZXJPZldvcmRzPXN0cl9jb3VudCh0ZXh0LCBib3VuZGFyeSgid29yZCIpKSkgJT4lICMjIGNvdW50cyB0aGUgbnVtYmVyIG9mIHdvcmRzIHNpbmNlIGJvdW5kYXJ5IGlzIHNldCB0byB3b3JkDQogIG11dGF0ZShNb250aE5hbWU9bW9udGgoeW1kKGRhdGVfZm9ybWF0dGVkKSwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KE1vbnRoTmFtZSkgJT4lDQogIHN1bW1hcml6ZShOdW1iZXJPZldvcmRzPW1lZGlhbihOdW1iZXJPZldvcmRzKSkgJT4lDQogIGdncGxvdChhZXMgKHg9TW9udGhOYW1lLCB5PU51bWJlck9mV29yZHMpKSArDQogIGdlb21fY29sKCkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KYGBge3J9DQojIyMgNS4gQ3JlYXRlIGEgY2hhcnQgdGhhdCBzaG93cyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZnVubnkgYW5kIHVzZWZ1bCByZXZpZXdzIG9mIFN0YXJidWNrcy4NClN0YXJidWNrc0RhdGVGb3JtYXR0ZWQlPiUNCiAgZ2dwbG90KGFlcyh4PWZ1bm55LCB5PXVzZWZ1bCkpICsgDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWxtKQ0KYGBgDQoNCg0KYGBge3J9DQojIyMgNi4gQ3JlYXRlIGEgd29yZGNsb3VkIHRvIHNob3cgdGhlIHdvcmRzIHRoYXQgYXJlIHVzZWQgdGhlIG1vc3QgZnJlcXVlbnRseSBpbiByZXZpZXdzDQoNCmxpYnJhcnkod29yZGNsb3VkKQ0KYGBgDQoNCmBgYHtyfQ0KY3JlYXRlV29yZENsb3VkID0gZnVuY3Rpb24oeCkNCnsNCiAgU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCAlPiUNCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQ0KICAgIA0KICAgIGZpbHRlcighd29yZCAlaW4lIHN0b3Bfd29yZHMkd29yZCwgIXdvcmQgJWluJSAnc3RhcmJ1Y2tzJykgJT4lDQogICAgIyMjIGZpbHRlcighd29yZCA9ICdzdGFyYnVja3MnKSAlPiUNCiAgICBjb3VudCh3b3JkLHNvcnQgPSBUUlVFKSAlPiUNCiAgICB1bmdyb3VwKCkgJT4lDQogICAgaGVhZCgzMCkgJT4lDQogICAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMzAsY29sb3JzPWJyZXdlci5wYWwoOCwgIkRhcmsyIikpKQ0KfQ0KDQpjcmVhdGVXb3JkQ2xvdWQocmV2aWV3KQ0KYGBgDQoNCmBgYHtyfQ0KIyMjIDcuU2VudGltZW50IEFuYWx5c2lzIDogVGhlIFBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyBhc3NvY2lhdGUgd2l0aCBTdGFyYnVja3MgcmV2aWV3cw0KDQpwb3NpdGl2ZVdvcmRzQmFyR3JhcGggPC0gZnVuY3Rpb24oU0MpIHsNCiAgY29udHJpYnV0aW9ucyA8LSBTQyAlPiUNCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQ0KICAgIGNvdW50KHdvcmQsc29ydCA9IFRSVUUpICU+JQ0KICAgIHVuZ3JvdXAoKSAlPiUNCiAgICANCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpLCBieSA9ICJ3b3JkIikgJT4lDQogICAgZ3JvdXBfYnkod29yZCkgJT4lDQogICAgc3VtbWFyaXplKGNvbnRyaWJ1dGlvbiA9IHN1bSh2YWx1ZSksIG49bigpKQ0KICANCiAgY29udHJpYnV0aW9ucyAlPiUNCiAgICB0b3BfbigyMCwgYWJzKGNvbnRyaWJ1dGlvbikpICU+JQ0KICAgIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBjb250cmlidXRpb24pKSAlPiUNCiAgICBoZWFkKDIwKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHdvcmQsIGNvbnRyaWJ1dGlvbiwgZmlsbCA9IGNvbnRyaWJ1dGlvbiA+IDApKSArDQogICAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICAgIGNvb3JkX2ZsaXAoKSArIHRoZW1lX2J3KCkNCn0NCg0KcG9zaXRpdmVXb3Jkc0JhckdyYXBoKFN0YXJidWNrc0pvaW5lZF90YmwpDQpgYGANCg0KYGBge3J9DQojIyM4LiBTaG93IGFsbCB0aGUgU3RhcmJ1Y2tzIGxvY2F0aW9ucyBvbiBhIG1hcA0KIyMjIHdlIHdpbGwgdXNlIHRoZSBzdGFyYnVja3NidXNpbmVzcyBkYXRhIHNldCB0aGF0IHdlIGNyZWF0ZWQgZWFybGllciBiZWNhdXNlIGl0IGRvZXMgbm90IGhhdmUgcmVwZWF0aW5nIHJldmlld3MgZWFjaCByZWNvcmQgaXMgdW5pcXVlDQpsaWJyYXJ5KGxlYWZsZXQpDQojIyMgdGhpcyBmdW5jdGlvbiB3aWxsIGNvbG9yIGVhY2ggbG9jYXRpb24gYmFzZWQgb24gdGhlIHN0YXIgcmF0aW5nIGl0IGhhcw0KcGFsIDwtIGNvbG9yRmFjdG9yKGMoInB1cnBsZSIsICJyZWQiLCAib3JhbmdlIiwgImJsYWNrIiwiYmx1ZSIpLA0KICAgICAgICAgICAgICAgICAgIGRvbWFpbiA9IHVuaXF1ZShzdGFyYnVja3NidXNpbmVzcyRzdGFycykpDQojIyMgdGhpcyBkcmF3cyB0aGUgbWFwDQptYXAgPC0gbGVhZmxldChzdGFyYnVja3NidXNpbmVzcykgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycygNCiAgICBjb2xvciA9IH5wYWwoc3RhcmJ1Y2tzYnVzaW5lc3Mkc3RhcnMpLA0KICAgIHN0cm9rZSA9IEZBTFNFLCBmaWxsT3BhY2l0eSA9IDAuNSwNCiAgICBsYXQgPSBzdGFyYnVja3NidXNpbmVzcyRsYXRpdHVkZSwNCiAgICBsbmcgPSBzdGFyYnVja3NidXNpbmVzcyRsb25naXR1ZGUsDQogICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpLA0KICAgIHBvcHVwID0gYXMuY2hhcmFjdGVyKHN0YXJidWNrc2J1c2luZXNzJGFkZHJlc3MpKQ0KbWFwDQpgYGANCg0KDQpgYGB7cn0NCiNMZXQncyBqb2luIHRoZSBidXNpbmVzcyBhbmQgcmV2aWV3IHRhYmxlcyBzbyB0aGF0IHdlIGNhbiBjcmVhdGUgYSBkYXNoYm9hcmQNCmJ1c2luZXNzX3Jldmlld3MgPC0gaW5uZXJfam9pbihidXNpbmVzcyxyZXZpZXcpDQpgYGANCg0KDQo=