Note: This project involves getting data ready for analysis and doing some preliminary investigations. Project 2 will involve modeling and predictions, and will be released at a later date. Both projects will have equal weightage towards your grade.

Data

In this project, you will explore a dataset that contains information about movies, including ratings, budget, gross revenue and other attributes. It was prepared by Dr. Guy Lebanon, and here is his description of the dataset:

The file movies_merged contains a dataframe with the same name that has 40K rows and 39 columns. Each row represents a movie title and each column represents a descriptor such as Title, Actors, and Budget. I collected the data by querying IMDb’s API (see www.omdbapi.com) and joining it with a separate dataset of movie budgets and gross earnings (unknown to you). The join key was the movie title. This data is available for personal use, but IMDb’s terms of service do not allow it to be used for commercial purposes or for creating a competing repository.

Objective

Your goal is to investigate the relationship between the movie descriptors and the box office success of movies, as represented by the variable Gross. This task is extremely important as it can help a studio decide which titles to fund for production, how much to bid on produced movies, when to release a title, how much to invest in marketing and PR, etc. This information is most useful before a title is released, but it is still very valuable after the movie is already released to the public (for example it can affect additional marketing spend or how much a studio should negotiate with on-demand streaming companies for “second window” streaming rights).

Instructions

This is an R Markdown Notebook. Open this file in RStudio to get started.

When you execute code within the notebook, the results appear beneath the code. Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

x = 1:10
print(x^2)
 [1]   1   4   9  16  25  36  49  64  81 100

Plots appear inline too:

plot(x, x^2, 'o')

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Cmd+Option+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Cmd+Shift+K to preview the HTML file).

Please complete the tasks below and submit this R Markdown file (as pr1.Rmd) as well as a PDF export of it (as pr1.pdf). Both should contain all the code, output, plots and written responses for each task.

Setup

Load data

Make sure you’ve downloaded the movies_merged file and it is in the current working directory. Now load it into memory:

load('movies_merged')

This creates an object of the same name (movies_merged). For convenience, you can copy it to df and start using it:

df = movies_merged
cat("Dataset has", dim(df)[1], "rows and", dim(df)[2], "columns", end="\n", file="")
Dataset has 40789 rows and 39 columns 
colnames(df)
 [1] "Title"             "Year"              "Rated"             "Released"          "Runtime"          
 [6] "Genre"             "Director"          "Writer"            "Actors"            "Plot"             
[11] "Language"          "Country"           "Awards"            "Poster"            "Metascore"        
[16] "imdbRating"        "imdbVotes"         "imdbID"            "Type"              "tomatoMeter"      
[21] "tomatoImage"       "tomatoRating"      "tomatoReviews"     "tomatoFresh"       "tomatoRotten"     
[26] "tomatoConsensus"   "tomatoUserMeter"   "tomatoUserRating"  "tomatoUserReviews" "tomatoURL"        
[31] "DVD"               "BoxOffice"         "Production"        "Website"           "Response"         
[36] "Budget"            "Domestic_Gross"    "Gross"             "Date"             

Load R packages

Load any R packages that you will need to use. You can come back to this chunk, edit it and re-run to load any additional packages later.

install.packages("tokenizers")
Installing package into 㤼㸱C:/Users/BrunoWan/Documents/R/win-library/3.2㤼㸲
(as 㤼㸱lib㤼㸲 is unspecified)
also installing the dependency 㤼㸱SnowballC㤼㸲

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/SnowballC_0.5.1.zip'
Content type 'application/zip' length 3076520 bytes (2.9 MB)
downloaded 2.9 MB

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/tokenizers_0.1.4.zip'
Content type 'application/zip' length 493089 bytes (481 KB)
downloaded 481 KB
package ‘SnowballC’ successfully unpacked and MD5 sums checked
package ‘tokenizers’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\BrunoWan\AppData\Local\Temp\Rtmp86LeP0\downloaded_packages
library("tokenizers")
package 㤼㸱tokenizers㤼㸲 was built under R version 3.2.5
library(ggplot2)
library(GGally)
library(stringr)

If you are loading any non-standard packages (ones that have not been discussed in class or explicitly allowed for this project), please mention them below. Include any special instructions if they cannot be installed using the regular install.packages('<pkg name>') command.

Non-standard packages used: None

Tasks

Each task below is worth 10 points, and is meant to be performed sequentially, i.e. do step 2 after you have processed the data as described in step 1. Total points: 100

Complete each task by implementing code chunks as described by TODO comments, and by responding to questions (“Q:”) with written answers (“A:”). If you are unable to find a meaningful or strong relationship in any of the cases when requested, explain why not by referring to appropriate plots/statistics.

It is OK to handle missing values below by omission, but please omit as little as possible. It is worthwhile to invest in reusable and clear code as you may need to use it or modify it in project 2.

1. Remove non-movie rows

The variable Type captures whether the row is a movie, a TV series, or a game. Remove all rows from df that do not correspond to movies.

# TODO: Remove all rows from df that do not correspond to movies
df=df[df$Type=="movie",]
nrow(df)
[1] 40000

Q: How many rows are left after removal? Enter your response below.

A: 40000

2. Process Runtime column

The variable Runtime represents the length of the title as a string. Write R code to convert it to a numeric value (in minutes) and replace df$Runtime with the new numeric column.

# TODO: Replace df$Runtime with a numeric column containing the runtime in minutes
# Check the text feature of the Runtime column
unique(str_extract_all(df$Runtime, "[A-z]+"))
[[1]]
[1] "min"

[[2]]
[1] "N" "A"

[[3]]
[1] "h"   "min"

[[4]]
[1] "h"
#Replace df$Runtime with a numeric column containing the runtime in minutes
for(i in c(1:nrow(df))){
  if(is.na(str_extract_all(df[i,"Runtime"], "[A-z]+"))){
    df[i,"Runtime"]=as.numeric(str_extract_all(df[i,"Runtime"], "[[:digit:]]+"))
  }else if(length(str_extract_all(df[i,"Runtime"], "[A-z]+")[[1]])==2){
    df[i,"Runtime"]=(as.numeric(str_extract_all(df[i,"Runtime"], "[[:digit:]]+")[[1]][1]))*60 +(as.numeric(str_extract_all(df[i,"Runtime"], "[[:digit:]]+")[[1]][2]))
  }else if(str_extract_all(df[i,"Runtime"],"[A-z]+")=="h"){
    df[i,"Runtime"]=(as.numeric(str_extract_all(df[i,"Runtime"], "[[:digit:]]+")))*60
  }else{
    df[i,"Runtime"]=as.numeric(str_extract_all(df[i,"Runtime"], "[[:digit:]]+"))
  }
}
# Convert the data type of Runtime from character to numeric
df$Runtime=as.numeric(df$Runtime)

Now investigate the distribution of Runtime values and how it changes over years (variable Year, which you can bucket into decades) and in relation to the budget (variable Budget). Include any plots that illustrate.

# TODO: Investigate the distribution of Runtime values and how it varies by Year and Budget
summary(df$Runtime)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   1.00   72.00   90.00   81.79  101.00  873.00     751 
hist(df$Runtime, xlab="Runtime", main="Histogram of Runtime")

# Subset the original dataset to get a new dataset for the analysis
runtime=df[,c("Year", "Budget", "Runtime")]
# Get some statistical features of the three columns
min(runtime$Year)
[1] 1888
max(runtime$Year)
[1] 2018
min(runtime[!is.na(runtime$Budget), "Budget"])
[1] 1100
max(runtime[!is.na(runtime$Budget), "Budget"])
[1] 425000000
nrow(runtime[!is.na(runtime$Budget),])
[1] 4558
nrow(runtime[!is.na(runtime$Year),])
[1] 40000
# From the dataset, we can discover that
# Budget only contains 4520 valid records
# Year contain 400000 valid records
# Year span is from 1888 to 2017
# To check the distribution of Runtime over Year and Budget. We need to omit the record with null values
runtime=runtime[!is.na(runtime$Runtime),]
# To fairly compare the Runtime over Year, we need to aggregate the Runtime by Year and Mean
plot(aggregate(runtime[, "Runtime"], list(runtime$Year), mean), type="o")

# From the plot above, we can discover that there fluctuations over years and there are some years missing
# To get an insight over the general trend of Runtime over Year, we can smooth the data by cut Year into Bins of decades
runtime$Year_bin=cut(runtime$Year, c(1880, seq(1890, 2020, by=10)), dig.lab=0, labels=FALSE, right=FALSE)
# Change the bin Bin number to the start year of that decade
runtime$Year_cut=1880+(runtime$Year_bin-1)*10
# Then aggregate the "Runtime" by decade by mean
aggregate(runtime[, "Runtime"], list(runtime$Year_cut), mean, na.action=na.omit)
#Display the average Runtime data for the movies in different decades
#need to double check how to omit the NA values at the first place then plot them
plot(aggregate(runtime[,"Runtime"], list(runtime$Year_cut), mean), type="o")

#Check the distribution of Budget, we can discover that the Budget is right skewed. Most movie cost less than 100 million dollars.
hist(runtime[!is.na(runtime$Budget), "Budget"], main=paste("Histogram of Budget"), xlab="Budget")

# For analysis purpose, we use log of Budget instead of Budget because the log of the Budget is more closed to normal distribution
runtime$Budget_log=log10(runtime$Budget)
min(runtime[!is.na(runtime$Budget_log), "Budget_log"])
[1] 3.041393
max(runtime[!is.na(runtime$Budget_log), "Budget_log"])
[1] 8.628389
hist(runtime[!is.na(runtime$Budget_log), "Budget_log"], xlab="log10(Budget)", main="Histogram of log10(Budget)")

# For analysis purpose, we cut the log of Budget into Bins
runtime$Budget_log_bin=cut(runtime$Budget_log, c(3, seq(3.5, 9,by=0.5)), dig.lab=0, labels=FALSE, right=FALSE)
# Then aggregate the Runtime by Budget by Budget log by means
aggregate(runtime[,3], list(runtime$Budget_log_bin*0.5+3), mean)
plot(aggregate(runtime[,3], list(runtime$Budget_log_bin),mean), type="o", xlab="log10(Budget)", ylab="Runtime")

Feel free to insert additional code chunks as necessary.

Q: Comment on the distribution as well as relationships. Are there any patterns or trends that you can observe?

A: The distribution of Runtime of movies is close to normal distribution with some outliers. The median is 90 and mean is 81.44. The runtime of movies is centred on an hour and a half. After process, there is a obvious positive correlation between Budget and Runtime. As Budget increases, the runtime increases as well.

3. Encode Genre column

The column Genre represents a list of genres associated with the movie in a string format. Write code to parse each text string into a binary vector with 1s representing the presence of a genre and 0s the absence, and add it to the dataframe as additional columns. Then remove the original Genre column.

For example, if there are a total of 3 genres: Drama, Comedy, and Action, a movie that is both Action and Comedy should be represented by a binary vector <0, 1, 1>. Note that you need to first compile a dictionary of all possible genres and then figure out which movie has which genres (you can use the R tm package to create the dictionary).

# TODO: Replace Genre with a collection of binary columns
# We list all of the genres
unique(unlist(tokenize_regex(df$Genre,pattern=", ")))
 [1] "Documentary" "Biography"   "Romance"     "Short"       "Thriller"    "Drama"       "War"        
 [8] "Comedy"      "Horror"      "Sci-Fi"      "Adventure"   "Family"      "History"     "Crime"      
[15] "Action"      "Music"       "Mystery"     "Fantasy"     "Sport"       "Animation"   "Musical"    
[22] "N/A"         "Talk-Show"   "Adult"       "Western"     "Film-Noir"   "Reality-TV"  "News"       
[29] "Game-Show"  
#Combine the genre with the original dataset
column_genre_names=unique(unlist(tokenize_regex(df$Genre,pattern=", ")))
genre=data.frame(matrix(nrow=nrow(df), ncol=length(column_genre_names)))
colnames(genre)=column_genre_names
genre[]=0
for(val in column_genre_names){
  genre[grep(val, df[, "Genre"]), val]=1
}
genre_vectored=cbind(df, genre)

Plot the relative proportions of movies having the top 10 most common genres.

# TODO: Select movies from top 10 most common genres and plot their relative proportions
genre_sum=colSums(genre)
print(genre_sum)
Documentary   Biography     Romance       Short    Thriller       Drama         War      Comedy      Horror 
       3051        1109        4975        6516        3380       15859        1070       12849        2716 
     Sci-Fi   Adventure      Family     History       Crime      Action       Music     Mystery     Fantasy 
       1624        2928        2653         830        4062        4413        2550        1642        1400 
      Sport   Animation     Musical         N/A   Talk-Show       Adult     Western   Film-Noir  Reality-TV 
        525        2788        1387         986           4         422        1314         352           6 
       News   Game-Show 
         20           2 
#Sort the genres based on the number of occurences
genre_sort=sort(genre_sum, decreasing=TRUE)
print(genre_sort)
      Drama      Comedy       Short     Romance      Action       Crime    Thriller Documentary   Adventure 
      15859       12849        6516        4975        4413        4062        3380        3051        2928 
  Animation      Horror      Family       Music     Mystery      Sci-Fi     Fantasy     Musical     Western 
       2788        2716        2653        2550        1642        1624        1400        1387        1314 
  Biography         War         N/A     History       Sport       Adult   Film-Noir        News  Reality-TV 
       1109        1070         986         830         525         422         352          20           6 
  Talk-Show   Game-Show 
          4           2 
#Take the top 10 genres
genre_sort_names=names(genre_sort)
genre_top=genre_sort[1:10]
genre_prop=genre_top/nrow(df)
print(genre_prop)
      Drama      Comedy       Short     Romance      Action       Crime    Thriller Documentary   Adventure 
   0.396475    0.321225    0.162900    0.124375    0.110325    0.101550    0.084500    0.076275    0.073200 
  Animation 
   0.069700 
library(ggplot2)
pie(genre_prop)

Examine how the distribution of Runtime changes across genres for the top 10 most common genres.

# TODO: Plot Runtime distribution for top 10 most common genres
# Create individual data frame for each genre
for(gen in genre_sort_names[1:10]){
  assign(gen, genre_vectored[genre_vectored[, gen]==1,])
}
# Create histogram for each genre and display their runtime distribution
for( gen in genre_sort_names[1:10]){
  hist(get(gen)[!is.na(get(gen)$Runtime), "Runtime"], main=paste("Histogram of", gen, "Runtime"), xlab="Runtime")
    
}

Q: Describe the interesting relationship(s) you observe. Are there any expected or unexpected trends that are evident?

A: For most of genre, runtime has a normal-like distribution Expected: Most of genre have a distribution close to normal distribution. Unexpected: Comedy and Animation has a distribution showing a strong skewness.

4. Eliminate mismatched rows

The dataframe was put together by merging two different sources of data and it is possible that the merging process was inaccurate in some cases (the merge was done based on movie title, but there are cases of different movies with the same title). The first source’s release time was represented by the column Year (numeric representation of the year) and the second by the column Released (string representation of release date).

Find and remove all rows where you suspect a merge error occurred based on a mismatch between these two variables. To make sure subsequent analysis and modeling work well, avoid removing more than 10% of the rows that have a Gross value present.

# TODO: Remove rows with Released-Year mismatch
# omit NA rows
test=df[setdiff(rownames(df), rownames(df[is.na(df$Released),])),]
test_match=test[abs((test$Year-as.numeric(substr(test$Released, 1, 4))))<=1,]
test_dismatch=df[setdiff(rownames(df), rownames(df[rownames(test_match),])),]
#Count the records with Gross value in matched records and dismatched records to ensure the "less than 10% removement" requirement is met
nrow(test_match[!is.na(test_match$Gross), ])
[1] 4422
nrow(test_dismatch[!is.na(test_dismatch$Gross),])
[1] 136
nrow(test_dismatch[!is.na(test_dismatch$Gross),])/nrow(test_match[!is.na(test_match$Gross), ])
[1] 0.03075531

Q: What is your precise removal logic and how many rows did you end up removing?

A: At first, we exclude the records with missing Released column, then we do the calculation of the difference between “Year” column and “Released” column. Then we exclude the records which the difference of “Year” column and the “Released” column is bigger than 1.

5. Explore Gross revenue

For the commercial success of a movie, production houses want to maximize Gross revenue. Investigate if Gross revenue is related to Budget, Runtime or Genre in any way.

Note: To get a meaningful relationship, you may have to partition the movies into subsets such as short vs. long duration, or by genre, etc.

# TODO: Investigate if Gross Revenue is related to Budget, Runtime or Genre
g_revenue=df[!is.na(df$Gross),]
g_revenue=g_revenue[!is.na(g_revenue$Budget),]
g_revenue$Budget_log=log10(g_revenue$Budget)
g_revenue$Budget_log_bin=cut(g_revenue$Budget_log, c(3, seq(3.5, 9, by=0.5)), dig.lab=0, labels=FALSE,right=FALSE)
g_revenue_agg=aggregate(g_revenue[,"Gross"], list(g_revenue$Budget_log_bin*0.5+3), mean)
g_revenue_agg$Gross_log=log2(g_revenue_agg[,2])
print(g_revenue_agg)
plot(g_revenue_agg[,c(1,3)], type="o", xlab="log(Budget)", ylab="log(Gross)")

Q: Did you find any observable relationships or combinations of Budget/Runtime/Genre that result in high Gross revenue? If you divided the movies into different subsets, you may get different answers for them - point out interesting ones.

A: We can discover there is a positive correlation between the Budget and Gross value

# TODO: Investigate if Gross Revenue is related to Release Month
# Retrieve the month from the "Released" column
g_revenue$month=substr(g_revenue$Released, 6, 7)
# Aggregate the Gross by month, we can discover the movies are much more successful in summer and Year End
plot(aggregate(g_revenue$Gross, list(g_revenue$month), mean), type="o", xlab="Month", ylab="Gross")

6. Process Awards column

The variable Awards describes nominations and awards in text format. Convert it to 2 numeric columns, the first capturing the number of wins, and the second capturing nominations. Replace the Awards column with these new columns, and then study the relationship of Gross revenue with respect to them.

Note that the format of the Awards column is not standard; you may have to use regular expressions to find the relevant values. Try your best to process them, and you may leave the ones that don’t have enough information as NAs or set them to 0s.

# TODO: Convert Awards to 2 numeric columns: wins and nominations
# Process the "Awards" column and fill in the "wins" and "nominations" columns
for (i in c(1:nrow(df))){
    win=0
    nomination=0
  if(grepl("Won",df[i, "Awards"])){
    win=as.numeric(str_extract(str_extract(df[i,"Awards"], "Won ([\\d]+)"), "[[:digit:]]+"))
    win=win+as.numeric(str_extract(str_extract(df[i,"Awards"], "([\\d]+) win"), "[[:digit:]]+"))
    nomination=nomination+as.numeric(str_extract(str_extract(df[i,"Awards"], "([\\d]+) nomination"), "[[:digit:]]+"))
  }else if(grepl("Nominated",df[i, "Awards"])){
    nomination=as.numeric(str_extract(str_extract(df[i,"Awards"], "Nominated for ([\\d]+)"), "[[:digit:]]+"))
    win=win+as.numeric(str_extract(str_extract(df[i,"Awards"], "([\\d]+) win"), "[[:digit:]]+"))
    nomination=nomination+as.numeric(str_extract(str_extract(df[i,"Awards"], "([\\d]+) nomination"), "[[:digit:]]+"))
  }else{
    win=win+as.numeric(str_extract(str_extract(df[i,"Awards"], "([\\d]+) win"), "[[:digit:]]+"))
    nomination=nomination+as.numeric(str_extract(str_extract(df[i,"Awards"], "([\\d]+) nomination"), "[[:digit:]]+"))
  }
    df[i,"wins"]=win
    df[i, "nominations"]=nomination
    if(is.na(df[i,"wins"])){df[i,"wins"]=0}
    if(is.na(df[i,"nominations"])){df[i,"nominations"]=0}
}

Q: How did you construct your conversion mechanism? How many rows had valid/non-zero wins or nominations?

A: 1. If the records contains the word “Won”, then take the number and add it to the variable “win”. Then check the number after the word “win”, add it to the variable “win”. Then check the number after the word “nomination”, add it to the variable “nomination”. 2. Else if the record contains the word “Nominated”, then take the number and add it to the variable “nomination”. Then check the number after the word “win”, add it to the variable “win”. Then check the number after the word “nomination”, add it to the variable “nomination” 3. Else Then check the number after the word “win”, add it to the variable “win”. Then check the number after the word “nomination”, add it to the variable “nomination” 4. Set rest as 0

# TODO: Plot Gross revenue against wins and nominations
gross_awards=df[!is.na(df$Gross),]
aggregate(gross_awards[,"Gross"], list(gross_awards[,"wins"]), mean)
plot(aggregate(gross_awards[,"Gross"], list(gross_awards[,"wins"]), mean), type="o", xlab="Wins", ylab="Gross")

aggregate(gross_awards[,"Gross"], list(gross_awards[,"nominations"]), mean)
plot(aggregate(gross_awards[,"Gross"], list(gross_awards[,"nominations"]), mean), type="o", xlab="Nominations", ylab="Gross")

Q: How does the gross revenue vary by number of awards won and nominations received?

A: We can discover that movies with nomination and wins around 70 to 150 have highest Gross revenue. As nomination and wins decreases or increases from this range, the Gross revenue decreases.

7. Movie ratings from IMDb and Rotten Tomatoes

There are several variables that describe ratings, including IMDb ratings (imdbRating represents average user ratings and imdbVotes represents the number of user ratings), and multiple Rotten Tomatoes ratings (represented by several variables pre-fixed by tomato). Read up on such ratings on the web (for example rottentomatoes.com/about and www.imdb.com/help/show_leaf?votestopfaq).

Investigate the pairwise relationships between these different descriptors using graphs.

# TODO: Illustrate how ratings from IMDb and Rotten Tomatoes are related
pairs(df[,c("imdbRating", "imdbVotes", "tomatoMeter", "tomatoRating","tomatoReviews" , "tomatoFresh"       ,"tomatoRotten" ,"tomatoUserMeter" , "tomatoUserRating","tomatoUserReviews")])

Q: Comment on the similarities and differences between the user ratings of IMDb and the critics ratings of Rotten Tomatoes.

A: IMDB ratings does not have a strong self correlation, however, tomato rating shows a very strong self correlations. For example, TomatoReviews almost form a linear relationship with TomatoFresh. Similarities: IMDB and Rotten Tomatoes all centralized over a range of rating scores. Diffirences: There are some Rotten Tomatoes ratings which distributed over two polar, like TomotoUserReviews.

8. Ratings and awards

These ratings typically reflect the general appeal of the movie to the public or gather opinions from a larger body of critics. Whereas awards are given by professional societies that may evaluate a movie on specific attributes, such as artistic performance, screenplay, sound design, etc.

Study the relationship between ratings and awards using graphs (awards here refers to wins and/or nominations).

# TODO: Show how ratings and awards are related
ratings=c("imdbRating", "imdbVotes", "tomatoMeter", "tomatoRating","tomatoReviews" , "tomatoFresh"       ,"tomatoRotten" ,"tomatoUserMeter" , "tomatoUserRating","tomatoUserReviews")
for(rating in ratings){
  assign(rating, df[!is.na(df[,rating]),c(rating, "wins","nominations")])
}
# For each rating, display their distribution over wins and nominations
for(rating in ratings){
  plot(aggregate(get(rating)[,rating], list(get(rating)[,"wins"]), mean), type="o", xlab="wins", ylab=rating)
  plot(aggregate(get(rating)[,rating], list(get(rating)[,"nominations"]), mean), type="o", xlab="nominations", ylab=rating)  
}

Q: How good are these ratings in terms of predicting the success of a movie in winning awards or nominations? Is there a high correlation between two variables?

A: We can find that, except “tomatoUserReviews” and “tomatoRotten”, all of other reviews has a strong positive correlation between wins/nominations and review scores.

9. Expected insights

Come up with two new insights (backed up by data and graphs) that is expected. Here “new” means insights that are not an immediate consequence of one of the above tasks. You may use any of the columns already explored above or a different one in the dataset, such as Title, Actors, etc.

# TODO: Find and illustrate two expected insights
unique(unlist(tokenize_regex(df$Country,pattern=", ")))
  [1] "USA"                              "UK"                               "France"                          
  [4] "Canada"                           "Germany"                          "Norway"                          
  [7] "Spain"                            "India"                            "Switzerland"                     
 [10] "Algeria"                          "N/A"                              "New Zealand"                     
 [13] "Russia"                           "Philippines"                      "Mexico"                          
 [16] "Italy"                            "Netherlands"                      "Denmark"                         
 [19] "Australia"                        "Finland"                          "Sweden"                          
 [22] "Brazil"                           "Argentina"                        "Portugal"                        
 [25] "Morocco"                          "Hong Kong"                        "Belgium"                         
 [28] "South Africa"                     "Palestine"                        "Israel"                          
 [31] "Singapore"                        "Bulgaria"                         "Panama"                          
 [34] "Turkey"                           "Ireland"                          "Austria"                         
 [37] "Iran"                             "Guatemala"                        "China"                           
 [40] "Luxembourg"                       "Egypt"                            "Taiwan"                          
 [43] "Hungary"                          "Romania"                          "Greece"                          
 [46] "Chile"                            "Costa Rica"                       "Japan"                           
 [49] "South Korea"                      "Colombia"                         "Tunisia"                         
 [52] "Croatia"                          "Czech Republic"                   "West Germany"                    
 [55] "Yugoslavia"                       "Poland"                           "Albania"                         
 [58] "Indonesia"                        "Ecuador"                          "Paraguay"                        
 [61] "Uruguay"                          "Belarus"                          "Soviet Union"                    
 [64] "Azerbaijan"                       "Dominican Republic"               "Iceland"                         
 [67] "Fiji"                             "Malaysia"                         "Cuba"                            
 [70] "Bahamas"                          "Malta"                            "Slovenia"                        
 [73] "Cyprus"                           "Estonia"                          "Latvia"                          
 [76] "Lithuania"                        "Slovakia"                         "Iraq"                            
 [79] "Serbia and Montenegro"            "Peru"                             "Venezuela"                       
 [82] "Thailand"                         "Ukraine"                          "Korea"                           
 [85] "Cambodia"                         "Kazakhstan"                       "Czechoslovakia"                  
 [88] "Burkina Faso"                     "Afghanistan"                      "Aruba"                           
 [91] "Georgia"                          "Bosnia and Herzegovina"           "Jamaica"                         
 [94] "East Germany"                     "Armenia"                          "Chad"                            
 [97] "Uzbekistan"                       "Haiti"                            "Gabon"                           
[100] "Nigeria"                          "Jordan"                           "Mali"                            
[103] "Republic of Macedonia"            "United Arab Emirates"             "Lebanon"                         
[106] "Federal Republic of Yugoslavia"   "The Democratic Republic Of Congo" "Kyrgyzstan"                      
[109] "Guinea"                           "Bangladesh"                       "Tanzania"                        
[112] "Kenya"                            "Saudi Arabia"                     "Ghana"                           
[115] "Honduras"                         "Benin"                            "Namibia"                         
[118] "Netherlands Antilles"             "Turks And Caicos Islands"         "Zimbabwe"                        
[121] "Ethiopia"                         "Qatar"                            "Tajikistan"                      
[124] "Puerto Rico"                      "Vietnam"                          "Serbia"                          
[127] "Pakistan"                         "Senegal"                          "Côte d'Ivoire"                   
[130] "Montenegro"                       "Cameroon"                         "Trinidad and Tobago"             
[133] "Zaire"                            "Guinea-Bissau"                    "Mongolia"                        
[136] "Turkmenistan"                     "Liechtenstein"                    "Papua New Guinea"                
[139] "Syria"                            "Nicaragua"                        "North Korea"                     
[142] "Gibraltar"                        "Mauritania"                       "Monaco"                          
[145] "Antarctica"                       "Micronesia"                       "Solomon Islands"                 
[148] "Vanuatu"                          "Niger"                            "Angola"                          
[151] "Nepal"                            "Bermuda"                          "Isle Of Man"                     
[154] "Moldova"                          "Bolivia"                          "Guyana"                          
[157] "Libya"                           
#Combine the genre with the original dataset
column_country_names=unique(unlist(tokenize_regex(df$Country,pattern=", ")))
country=data.frame(matrix(nrow=nrow(df), ncol=length(column_country_names)))
colnames(country)=column_country_names
country[]=0
for(val in column_country_names){
  country[grep(val, df[, "Country"]), val]=1
}

Plot the relative proportions of movies having the top 10 most common genres.

# TODO: Select movies from top 10 most common genres and plot their relative proportions
country_sum=colSums(country)
#Sort the genres based on the number of occurences
country_sort=sort(country_sum, decreasing=TRUE)
print(country_sort)
                             USA                               UK                           France 
                           25063                             3635                             2291 
                         Germany                           Canada                            Italy 
                            1982                             1977                             1236 
                           Japan                            India                            Spain 
                             780                              725                              714 
                       Australia                              N/A                     West Germany 
                             676                              562                              483 
                    Soviet Union                           Sweden                        Hong Kong 
                             431                              398                              398 
                     Netherlands                          Denmark                           Poland 
                             387                              381                              361 
                          Mexico                          Belgium                           Brazil 
                             294                              270                              245 
                     Switzerland                        Argentina                          Finland 
                             244                              227                              226 
                      Yugoslavia                           Norway                           Russia 
                             216                              203                              198 
                         Ireland                          Austria                            Korea 
                             198                              192                              186 
                     South Korea                           Israel                      Philippines 
                             184                              175                              169 
                           China                           Greece                     South Africa 
                             142                              131                              130 
                     New Zealand                         Bulgaria                          Hungary 
                             124                              120                              118 
                         Romania                   Czechoslovakia                           Turkey 
                             100                               91                               89 
                  Czech Republic                           Taiwan                         Portugal 
                              89                               86                               77 
                            Iran                       Luxembourg                            Chile 
                              76                               56                               48 
                        Thailand                             Cuba                        Indonesia 
                              45                               43                               41 
                    East Germany                          Iceland                            Egypt 
                              39                               32                               30 
                         Croatia                             Peru                         Slovenia 
                              26                               24                               23 
            United Arab Emirates                        Singapore   Federal Republic of Yugoslavia 
                              23                               21                               21 
                         Morocco                         Malaysia                          Ukraine 
                              20                               19                               17 
                        Colombia                         Slovakia                          Estonia 
                              16                               16                               15 
                         Tunisia                          Senegal                          Algeria 
                              14                               14                               12 
                       Venezuela                     Burkina Faso                        Palestine 
                              11                               11                               10 
                      Kazakhstan           Bosnia and Herzegovina                          Uruguay 
                              10                               10                                9 
                         Georgia                          Jamaica                          Lebanon 
                               9                                8                                8 
                     Puerto Rico                    Liechtenstein                           Panama 
                               8                                8                                7 
              Dominican Republic                          Armenia                             Mali 
                               7                                7                                7 
                          Latvia                        Lithuania                           Guinea 
                               6                                6                                6 
                         Belarus                           Cyprus                            Ghana 
                               5                                5                                5 
                         Vietnam                           Serbia                         Pakistan 
                               5                                5                                5 
                           Niger                          Bahamas                       Uzbekistan 
                               5                                4                                4 
                           Haiti                          Nigeria            Republic of Macedonia 
                               4                                4                                4 
                           Kenya                         Zimbabwe                       Costa Rica 
                               4                                4                                3 
                         Albania                            Malta                             Iraq 
                               3                                3                                3 
                            Chad                           Jordan                       Bangladesh 
                               3                                3                                3 
                      Tajikistan                         Cameroon                    Guinea-Bissau 
                               3                                3                                3 
                     Isle Of Man                        Guatemala                          Ecuador 
                               3                                2                                2 
                      Azerbaijan                         Cambodia                      Afghanistan 
                               2                                2                                2 
                           Aruba                       Kyrgyzstan                         Tanzania 
                               2                                2                                2 
                    Saudi Arabia                          Namibia                            Qatar 
                               2                                2                                2 
                      Montenegro              Trinidad and Tobago                            Zaire 
                               2                                2                                2 
                Papua New Guinea                       Mauritania                           Monaco 
                               2                                2                                2 
                      Antarctica                           Angola                         Paraguay 
                               2                                2                                1 
                            Fiji            Serbia and Montenegro                            Gabon 
                               1                                1                                1 
The Democratic Republic Of Congo                         Honduras                            Benin 
                               1                                1                                1 
            Netherlands Antilles         Turks And Caicos Islands                         Ethiopia 
                               1                                1                                1 
                   Côte d'Ivoire                         Mongolia                     Turkmenistan 
                               1                                1                                1 
                           Syria                        Nicaragua                      North Korea 
                               1                                1                                1 
                       Gibraltar                       Micronesia                  Solomon Islands 
                               1                                1                                1 
                         Vanuatu                            Nepal                          Bermuda 
                               1                                1                                1 
                         Moldova                          Bolivia                           Guyana 
                               1                                1                                1 
                           Libya 
                               1 
#Calculate the percentage of movies by country
country_top=country_sort[1:20]
country_prop=country_top/nrow(df)
print(country_prop)
         USA           UK       France      Germany       Canada        Italy        Japan        India 
    0.626575     0.090875     0.057275     0.049550     0.049425     0.030900     0.019500     0.018125 
       Spain    Australia          N/A West Germany Soviet Union       Sweden    Hong Kong  Netherlands 
    0.017850     0.016900     0.014050     0.012075     0.010775     0.009950     0.009950     0.009675 
     Denmark       Poland       Mexico      Belgium 
    0.009525     0.009025     0.007350     0.006750 
#Plot the number by pie chart
pie(country_prop)

#Take the USA out and replot the data
pie(country_sort[2:20]/nrow(df))

Q: Expected insight #1.

A: Since USA has the biggest movie industry in the world, it is very normal to see that over half of the movies was produced by USA.

Q: Expected insight #2.

A: With few exceptions, movies all come from developed countries. Since movie industry requires a lot of investment, it is normal to see the absence of developing countries and under-developed countries.

10. Unexpected insight

Come up with one new insight (backed up by data and graphs) that is unexpected at first glance and do your best to motivate it. Same instructions apply as the previous task.

# TODO: Find and illustrate one unexpected insight
# To investigate the relationship between gross revenue and countries
country_gross=cbind(df[, c("Gross", "Domestic_Gross", "Budget")], country)
# Calculate the the percentage of domestic gross over overall gross
country_gross$gross_perc=country_gross$Domestic_Gross/country_gross$Gross
domestic_market=country_gross[!is.na(country_gross$gross_perc),]
domestic_market=domestic_market[domestic_market$gross_perc>0.5,]
international_market=country_gross[!is.na(country_gross$gross_perc),]
international_market=international_market[international_market$gross_perc<=0.5,]
domestic_market=domestic_market[, !(names(domestic_market) %in% c("Gross", "Domestic_Gross", "Budget", "gross_perc"))]
international_market=international_market[, !(names(international_market) %in% c("Gross", "Domestic_Gross", "Budget", "gross_perc"))]
pie(colSums(domestic_market)[1:10], main="domestic USA included")

pie(colSums(domestic_market)[2:10], main="domestic USA excluded")

pie(colSums(international_market)[1:10], main="international USA included")

pie(colSums(international_market)[2:10], main="international USA excluded")

Q: Unexpected insight.

A: Even though USA movie industry is very international, however, most of its movies focus on domestic market. From the first pie chart above, we can find USA account for more than 75% domestic focusing movies compared to about a half in the third pie chart.

LS0tDQp0aXRsZTogJ1Byb2plY3QgMTogRXhwbG9yZSBhbmQgUHJlcGFyZSBEYXRhJw0Kc3VidGl0bGU6IHwtDQogIENTRTYyNDIgLSBEYXRhIGFuZCBWaXN1YWwgQW5hbHl0aWNzIC0gU3ByaW5nIDIwMTcNCiAgRHVlOiBTdW5kYXksIE1hcmNoIDUsIDIwMTcgYXQgMTE6NTkgUE0gVVRDLTEyOjAwIG9uIFQtU3F1YXJlDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoNCl9Ob3RlOiBUaGlzIHByb2plY3QgaW52b2x2ZXMgZ2V0dGluZyBkYXRhIHJlYWR5IGZvciBhbmFseXNpcyBhbmQgZG9pbmcgc29tZSBwcmVsaW1pbmFyeSBpbnZlc3RpZ2F0aW9ucy4gUHJvamVjdCAyIHdpbGwgaW52b2x2ZSBtb2RlbGluZyBhbmQgcHJlZGljdGlvbnMsIGFuZCB3aWxsIGJlIHJlbGVhc2VkIGF0IGEgbGF0ZXIgZGF0ZS4gQm90aCBwcm9qZWN0cyB3aWxsIGhhdmUgZXF1YWwgd2VpZ2h0YWdlIHRvd2FyZHMgeW91ciBncmFkZS5fDQoNCiMgRGF0YQ0KDQpJbiB0aGlzIHByb2plY3QsIHlvdSB3aWxsIGV4cGxvcmUgYSBkYXRhc2V0IHRoYXQgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgbW92aWVzLCBpbmNsdWRpbmcgcmF0aW5ncywgYnVkZ2V0LCBncm9zcyByZXZlbnVlIGFuZCBvdGhlciBhdHRyaWJ1dGVzLiBJdCB3YXMgcHJlcGFyZWQgYnkgRHIuIEd1eSBMZWJhbm9uLCBhbmQgaGVyZSBpcyBoaXMgZGVzY3JpcHRpb24gb2YgdGhlIGRhdGFzZXQ6DQoNCj4gVGhlIGZpbGUgW2Btb3ZpZXNfbWVyZ2VkYF0oaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2NvbnRlbnQudWRhY2l0eS1kYXRhLmNvbS9jb3Vyc2VzL2d0LWNzNjI0Mi9wcm9qZWN0L21vdmllc19tZXJnZWQpIGNvbnRhaW5zIGEgZGF0YWZyYW1lIHdpdGggdGhlIHNhbWUgbmFtZSB0aGF0IGhhcyA0MEsgcm93cyBhbmQgMzkgY29sdW1ucy4gRWFjaCByb3cgcmVwcmVzZW50cyBhIG1vdmllIHRpdGxlIGFuZCBlYWNoIGNvbHVtbiByZXByZXNlbnRzIGEgZGVzY3JpcHRvciBzdWNoIGFzIGBUaXRsZWAsIGBBY3RvcnNgLCBhbmQgYEJ1ZGdldGAuIEkgY29sbGVjdGVkIHRoZSBkYXRhIGJ5IHF1ZXJ5aW5nIElNRGLigJlzIEFQSSAoc2VlIFt3d3cub21kYmFwaS5jb21dKGh0dHA6Ly93d3cub21kYmFwaS5jb20vKSkgYW5kIGpvaW5pbmcgaXQgd2l0aCBhIHNlcGFyYXRlIGRhdGFzZXQgb2YgbW92aWUgYnVkZ2V0cyBhbmQgZ3Jvc3MgZWFybmluZ3MgKHVua25vd24gdG8geW91KS4gVGhlIGpvaW4ga2V5IHdhcyB0aGUgbW92aWUgdGl0bGUuIFRoaXMgZGF0YSBpcyBhdmFpbGFibGUgZm9yIHBlcnNvbmFsIHVzZSwgYnV0IElNRGLigJlzIHRlcm1zIG9mIHNlcnZpY2UgZG8gbm90IGFsbG93IGl0IHRvIGJlIHVzZWQgZm9yIGNvbW1lcmNpYWwgcHVycG9zZXMgb3IgZm9yIGNyZWF0aW5nIGEgY29tcGV0aW5nIHJlcG9zaXRvcnkuDQoNCiMgT2JqZWN0aXZlDQoNCllvdXIgZ29hbCBpcyB0byBpbnZlc3RpZ2F0ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIG1vdmllIGRlc2NyaXB0b3JzIGFuZCB0aGUgYm94IG9mZmljZSBzdWNjZXNzIG9mIG1vdmllcywgYXMgcmVwcmVzZW50ZWQgYnkgdGhlIHZhcmlhYmxlIGBHcm9zc2AuIFRoaXMgdGFzayBpcyBleHRyZW1lbHkgaW1wb3J0YW50IGFzIGl0IGNhbiBoZWxwIGEgc3R1ZGlvIGRlY2lkZSB3aGljaCB0aXRsZXMgdG8gZnVuZCBmb3IgcHJvZHVjdGlvbiwgaG93IG11Y2ggdG8gYmlkIG9uIHByb2R1Y2VkIG1vdmllcywgd2hlbiB0byByZWxlYXNlIGEgdGl0bGUsIGhvdyBtdWNoIHRvIGludmVzdCBpbiBtYXJrZXRpbmcgYW5kIFBSLCBldGMuIFRoaXMgaW5mb3JtYXRpb24gaXMgbW9zdCB1c2VmdWwgYmVmb3JlIGEgdGl0bGUgaXMgcmVsZWFzZWQsIGJ1dCBpdCBpcyBzdGlsbCB2ZXJ5IHZhbHVhYmxlIGFmdGVyIHRoZSBtb3ZpZSBpcyBhbHJlYWR5IHJlbGVhc2VkIHRvIHRoZSBwdWJsaWMgKGZvciBleGFtcGxlIGl0IGNhbiBhZmZlY3QgYWRkaXRpb25hbCBtYXJrZXRpbmcgc3BlbmQgb3IgaG93IG11Y2ggYSBzdHVkaW8gc2hvdWxkIG5lZ290aWF0ZSB3aXRoIG9uLWRlbWFuZCBzdHJlYW1pbmcgY29tcGFuaWVzIGZvciDigJxzZWNvbmQgd2luZG934oCdIHN0cmVhbWluZyByaWdodHMpLg0KDQojIEluc3RydWN0aW9ucw0KVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIE9wZW4gdGhpcyBmaWxlIGluIFJTdHVkaW8gdG8gZ2V0IHN0YXJ0ZWQuDQoNCldoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkNtZCtTaGlmdCtFbnRlciouIA0KDQpgYGB7cn0NCnggPSAxOjEwDQpwcmludCh4XjIpDQpgYGANCg0KUGxvdHMgYXBwZWFyIGlubGluZSB0b286DQpgYGB7cn0NCnBsb3QoeCwgeF4yLCAnbycpDQpgYGANCg0KQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkNtZCtPcHRpb24rSSouDQoNCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ21kK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuDQoNClBsZWFzZSBjb21wbGV0ZSB0aGUgdGFza3MgYmVsb3cgYW5kIHN1Ym1pdCB0aGlzIFIgTWFya2Rvd24gZmlsZSAoYXMgKipwcjEuUm1kKiopIGFzIHdlbGwgYXMgYSBQREYgZXhwb3J0IG9mIGl0IChhcyAqKnByMS5wZGYqKikuIEJvdGggc2hvdWxkIGNvbnRhaW4gYWxsIHRoZSBjb2RlLCBvdXRwdXQsIHBsb3RzIGFuZCB3cml0dGVuIHJlc3BvbnNlcyBmb3IgZWFjaCB0YXNrLg0KDQojIFNldHVwDQoNCiMjIExvYWQgZGF0YQ0KDQpNYWtlIHN1cmUgeW91J3ZlIGRvd25sb2FkZWQgdGhlIFtgbW92aWVzX21lcmdlZGBdKGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9jb250ZW50LnVkYWNpdHktZGF0YS5jb20vY291cnNlcy9ndC1jczYyNDIvcHJvamVjdC9tb3ZpZXNfbWVyZ2VkKSBmaWxlIGFuZCBpdCBpcyBpbiB0aGUgY3VycmVudCB3b3JraW5nIGRpcmVjdG9yeS4gTm93IGxvYWQgaXQgaW50byBtZW1vcnk6DQoNCmBgYHtyfQ0KbG9hZCgnbW92aWVzX21lcmdlZCcpDQpgYGANCg0KVGhpcyBjcmVhdGVzIGFuIG9iamVjdCBvZiB0aGUgc2FtZSBuYW1lIChgbW92aWVzX21lcmdlZGApLiBGb3IgY29udmVuaWVuY2UsIHlvdSBjYW4gY29weSBpdCB0byBgZGZgIGFuZCBzdGFydCB1c2luZyBpdDoNCg0KYGBge3J9DQpkZiA9IG1vdmllc19tZXJnZWQNCmNhdCgiRGF0YXNldCBoYXMiLCBkaW0oZGYpWzFdLCAicm93cyBhbmQiLCBkaW0oZGYpWzJdLCAiY29sdW1ucyIsIGVuZD0iXG4iLCBmaWxlPSIiKQ0KY29sbmFtZXMoZGYpDQpgYGANCg0KIyMgTG9hZCBSIHBhY2thZ2VzDQoNCkxvYWQgYW55IFIgcGFja2FnZXMgdGhhdCB5b3Ugd2lsbCBuZWVkIHRvIHVzZS4gWW91IGNhbiBjb21lIGJhY2sgdG8gdGhpcyBjaHVuaywgZWRpdCBpdCBhbmQgcmUtcnVuIHRvIGxvYWQgYW55IGFkZGl0aW9uYWwgcGFja2FnZXMgbGF0ZXIuDQoNCmBgYHtyfQ0KaW5zdGFsbC5wYWNrYWdlcygidG9rZW5pemVycyIpDQpsaWJyYXJ5KCJ0b2tlbml6ZXJzIikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShzdHJpbmdyKQ0KYGBgDQoNCklmIHlvdSBhcmUgbG9hZGluZyBhbnkgbm9uLXN0YW5kYXJkIHBhY2thZ2VzIChvbmVzIHRoYXQgaGF2ZSBub3QgYmVlbiBkaXNjdXNzZWQgaW4gY2xhc3Mgb3IgZXhwbGljaXRseSBhbGxvd2VkIGZvciB0aGlzIHByb2plY3QpLCBwbGVhc2UgbWVudGlvbiB0aGVtIGJlbG93LiBJbmNsdWRlIGFueSBzcGVjaWFsIGluc3RydWN0aW9ucyBpZiB0aGV5IGNhbm5vdCBiZSBpbnN0YWxsZWQgdXNpbmcgdGhlIHJlZ3VsYXIgYGluc3RhbGwucGFja2FnZXMoJzxwa2cgbmFtZT4nKWAgY29tbWFuZC4NCg0KKipOb24tc3RhbmRhcmQgcGFja2FnZXMgdXNlZCoqOiBOb25lDQoNCiMgVGFza3MNCg0KRWFjaCB0YXNrIGJlbG93IGlzIHdvcnRoICoqMTAqKiBwb2ludHMsIGFuZCBpcyBtZWFudCB0byBiZSBwZXJmb3JtZWQgc2VxdWVudGlhbGx5LCBpLmUuIGRvIHN0ZXAgMiBhZnRlciB5b3UgaGF2ZSBwcm9jZXNzZWQgdGhlIGRhdGEgYXMgZGVzY3JpYmVkIGluIHN0ZXAgMS4gVG90YWwgcG9pbnRzOiAqKjEwMCoqDQoNCkNvbXBsZXRlIGVhY2ggdGFzayBieSBpbXBsZW1lbnRpbmcgY29kZSBjaHVua3MgYXMgZGVzY3JpYmVkIGJ5IGBUT0RPYCBjb21tZW50cywgYW5kIGJ5IHJlc3BvbmRpbmcgdG8gcXVlc3Rpb25zICgiKipRKio6Iikgd2l0aCB3cml0dGVuIGFuc3dlcnMgKCIqKkEqKjoiKS4gSWYgeW91IGFyZSB1bmFibGUgdG8gZmluZCBhIG1lYW5pbmdmdWwgb3Igc3Ryb25nIHJlbGF0aW9uc2hpcCBpbiBhbnkgb2YgdGhlIGNhc2VzIHdoZW4gcmVxdWVzdGVkLCBleHBsYWluIHdoeSBub3QgYnkgcmVmZXJyaW5nIHRvIGFwcHJvcHJpYXRlIHBsb3RzL3N0YXRpc3RpY3MuDQoNCkl0IGlzIE9LIHRvIGhhbmRsZSBtaXNzaW5nIHZhbHVlcyBiZWxvdyBieSBvbWlzc2lvbiwgYnV0IHBsZWFzZSBvbWl0IGFzIGxpdHRsZSBhcyBwb3NzaWJsZS4gSXQgaXMgd29ydGh3aGlsZSB0byBpbnZlc3QgaW4gcmV1c2FibGUgYW5kIGNsZWFyIGNvZGUgYXMgeW91IG1heSBuZWVkIHRvIHVzZSBpdCBvciBtb2RpZnkgaXQgaW4gcHJvamVjdCAyLg0KDQojIyAxLiBSZW1vdmUgbm9uLW1vdmllIHJvd3MNCg0KVGhlIHZhcmlhYmxlIGBUeXBlYCBjYXB0dXJlcyB3aGV0aGVyIHRoZSByb3cgaXMgYSBtb3ZpZSwgYSBUViBzZXJpZXMsIG9yIGEgZ2FtZS4gUmVtb3ZlIGFsbCByb3dzIGZyb20gYGRmYCB0aGF0IGRvIG5vdCBjb3JyZXNwb25kIHRvIG1vdmllcy4NCg0KYGBge3J9DQojIFRPRE86IFJlbW92ZSBhbGwgcm93cyBmcm9tIGRmIHRoYXQgZG8gbm90IGNvcnJlc3BvbmQgdG8gbW92aWVzDQpkZj1kZltkZiRUeXBlPT0ibW92aWUiLF0NCm5yb3coZGYpDQpgYGANCg0KKipRKio6IEhvdyBtYW55IHJvd3MgYXJlIGxlZnQgYWZ0ZXIgcmVtb3ZhbD8gX0VudGVyIHlvdXIgcmVzcG9uc2UgYmVsb3cuXw0KDQoqKkEqKjogNDAwMDANCg0KIyMgMi4gUHJvY2VzcyBgUnVudGltZWAgY29sdW1uDQoNClRoZSB2YXJpYWJsZSBgUnVudGltZWAgcmVwcmVzZW50cyB0aGUgbGVuZ3RoIG9mIHRoZSB0aXRsZSBhcyBhIHN0cmluZy4gV3JpdGUgUiBjb2RlIHRvIGNvbnZlcnQgaXQgdG8gYSBudW1lcmljIHZhbHVlIChpbiBtaW51dGVzKSBhbmQgcmVwbGFjZSBgZGYkUnVudGltZWAgd2l0aCB0aGUgbmV3IG51bWVyaWMgY29sdW1uLg0KDQpgYGB7cn0NCiMgVE9ETzogUmVwbGFjZSBkZiRSdW50aW1lIHdpdGggYSBudW1lcmljIGNvbHVtbiBjb250YWluaW5nIHRoZSBydW50aW1lIGluIG1pbnV0ZXMNCiMgQ2hlY2sgdGhlIHRleHQgZmVhdHVyZSBvZiB0aGUgUnVudGltZSBjb2x1bW4NCnVuaXF1ZShzdHJfZXh0cmFjdF9hbGwoZGYkUnVudGltZSwgIltBLXpdKyIpKQ0KYGBgDQpgYGB7cn0NCiNSZXBsYWNlIGRmJFJ1bnRpbWUgd2l0aCBhIG51bWVyaWMgY29sdW1uIGNvbnRhaW5pbmcgdGhlIHJ1bnRpbWUgaW4gbWludXRlcw0KZm9yKGkgaW4gYygxOm5yb3coZGYpKSl7DQogIGlmKGlzLm5hKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbQS16XSsiKSkpew0KICAgIGRmW2ksIlJ1bnRpbWUiXT1hcy5udW1lcmljKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbWzpkaWdpdDpdXSsiKSkNCiAgfWVsc2UgaWYobGVuZ3RoKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbQS16XSsiKVtbMV1dKT09Mil7DQogICAgZGZbaSwiUnVudGltZSJdPShhcy5udW1lcmljKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbWzpkaWdpdDpdXSsiKVtbMV1dWzFdKSkqNjAgKyhhcy5udW1lcmljKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbWzpkaWdpdDpdXSsiKVtbMV1dWzJdKSkNCiAgfWVsc2UgaWYoc3RyX2V4dHJhY3RfYWxsKGRmW2ksIlJ1bnRpbWUiXSwiW0Etel0rIik9PSJoIil7DQogICAgZGZbaSwiUnVudGltZSJdPShhcy5udW1lcmljKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbWzpkaWdpdDpdXSsiKSkpKjYwDQogIH1lbHNlew0KICAgIGRmW2ksIlJ1bnRpbWUiXT1hcy5udW1lcmljKHN0cl9leHRyYWN0X2FsbChkZltpLCJSdW50aW1lIl0sICJbWzpkaWdpdDpdXSsiKSkNCiAgfQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KIyBDb252ZXJ0IHRoZSBkYXRhIHR5cGUgb2YgUnVudGltZSBmcm9tIGNoYXJhY3RlciB0byBudW1lcmljDQpkZiRSdW50aW1lPWFzLm51bWVyaWMoZGYkUnVudGltZSkNCmBgYA0KDQpOb3cgaW52ZXN0aWdhdGUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBgUnVudGltZWAgdmFsdWVzIGFuZCBob3cgaXQgY2hhbmdlcyBvdmVyIHllYXJzICh2YXJpYWJsZSBgWWVhcmAsIHdoaWNoIHlvdSBjYW4gYnVja2V0IGludG8gZGVjYWRlcykgYW5kIGluIHJlbGF0aW9uIHRvIHRoZSBidWRnZXQgKHZhcmlhYmxlIGBCdWRnZXRgKS4gSW5jbHVkZSBhbnkgcGxvdHMgdGhhdCBpbGx1c3RyYXRlLg0KDQpgYGB7cn0NCiMgVE9ETzogSW52ZXN0aWdhdGUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBSdW50aW1lIHZhbHVlcyBhbmQgaG93IGl0IHZhcmllcyBieSBZZWFyIGFuZCBCdWRnZXQNCnN1bW1hcnkoZGYkUnVudGltZSkNCmBgYA0KDQpgYGB7cn0NCmhpc3QoZGYkUnVudGltZSwgeGxhYj0iUnVudGltZSIsIG1haW49Ikhpc3RvZ3JhbSBvZiBSdW50aW1lIikNCmBgYA0KDQpgYGB7cn0NCiMgU3Vic2V0IHRoZSBvcmlnaW5hbCBkYXRhc2V0IHRvIGdldCBhIG5ldyBkYXRhc2V0IGZvciB0aGUgYW5hbHlzaXMNCnJ1bnRpbWU9ZGZbLGMoIlllYXIiLCAiQnVkZ2V0IiwgIlJ1bnRpbWUiKV0NCmBgYA0KDQoNCmBgYHtyfQ0KIyBHZXQgc29tZSBzdGF0aXN0aWNhbCBmZWF0dXJlcyBvZiB0aGUgdGhyZWUgY29sdW1ucw0KbWluKHJ1bnRpbWUkWWVhcikNCm1heChydW50aW1lJFllYXIpDQptaW4ocnVudGltZVshaXMubmEocnVudGltZSRCdWRnZXQpLCAiQnVkZ2V0Il0pDQptYXgocnVudGltZVshaXMubmEocnVudGltZSRCdWRnZXQpLCAiQnVkZ2V0Il0pDQpucm93KHJ1bnRpbWVbIWlzLm5hKHJ1bnRpbWUkQnVkZ2V0KSxdKQ0KbnJvdyhydW50aW1lWyFpcy5uYShydW50aW1lJFllYXIpLF0pDQojIEZyb20gdGhlIGRhdGFzZXQsIHdlIGNhbiBkaXNjb3ZlciB0aGF0DQojIEJ1ZGdldCBvbmx5IGNvbnRhaW5zIDQ1MjAgdmFsaWQgcmVjb3Jkcw0KIyBZZWFyIGNvbnRhaW4gNDAwMDAwIHZhbGlkIHJlY29yZHMNCiMgWWVhciBzcGFuIGlzIGZyb20gMTg4OCB0byAyMDE3DQpgYGANCg0KYGBge3J9DQojIFRvIGNoZWNrIHRoZSBkaXN0cmlidXRpb24gb2YgUnVudGltZSBvdmVyIFllYXIgYW5kIEJ1ZGdldC4gV2UgbmVlZCB0byBvbWl0IHRoZSByZWNvcmQgd2l0aCBudWxsIHZhbHVlcw0KcnVudGltZT1ydW50aW1lWyFpcy5uYShydW50aW1lJFJ1bnRpbWUpLF0NCiMgVG8gZmFpcmx5IGNvbXBhcmUgdGhlIFJ1bnRpbWUgb3ZlciBZZWFyLCB3ZSBuZWVkIHRvIGFnZ3JlZ2F0ZSB0aGUgUnVudGltZSBieSBZZWFyIGFuZCBNZWFuDQpwbG90KGFnZ3JlZ2F0ZShydW50aW1lWywgIlJ1bnRpbWUiXSwgbGlzdChydW50aW1lJFllYXIpLCBtZWFuKSwgdHlwZT0ibyIpDQpgYGANCg0KYGBge3J9DQojIEZyb20gdGhlIHBsb3QgYWJvdmUsIHdlIGNhbiBkaXNjb3ZlciB0aGF0IHRoZXJlIGZsdWN0dWF0aW9ucyBvdmVyIHllYXJzIGFuZCB0aGVyZSBhcmUgc29tZSB5ZWFycyBtaXNzaW5nDQojIFRvIGdldCBhbiBpbnNpZ2h0IG92ZXIgdGhlIGdlbmVyYWwgdHJlbmQgb2YgUnVudGltZSBvdmVyIFllYXIsIHdlIGNhbiBzbW9vdGggdGhlIGRhdGEgYnkgY3V0IFllYXIgaW50byBCaW5zIG9mIGRlY2FkZXMNCnJ1bnRpbWUkWWVhcl9iaW49Y3V0KHJ1bnRpbWUkWWVhciwgYygxODgwLCBzZXEoMTg5MCwgMjAyMCwgYnk9MTApKSwgZGlnLmxhYj0wLCBsYWJlbHM9RkFMU0UsIHJpZ2h0PUZBTFNFKQ0KIyBDaGFuZ2UgdGhlIGJpbiBCaW4gbnVtYmVyIHRvIHRoZSBzdGFydCB5ZWFyIG9mIHRoYXQgZGVjYWRlDQpydW50aW1lJFllYXJfY3V0PTE4ODArKHJ1bnRpbWUkWWVhcl9iaW4tMSkqMTANCiMgVGhlbiBhZ2dyZWdhdGUgdGhlICJSdW50aW1lIiBieSBkZWNhZGUgYnkgbWVhbg0KYWdncmVnYXRlKHJ1bnRpbWVbLCAiUnVudGltZSJdLCBsaXN0KHJ1bnRpbWUkWWVhcl9jdXQpLCBtZWFuLCBuYS5hY3Rpb249bmEub21pdCkNCmBgYA0KDQoNCmBgYHtyfQ0KI0Rpc3BsYXkgdGhlIGF2ZXJhZ2UgUnVudGltZSBkYXRhIGZvciB0aGUgbW92aWVzIGluIGRpZmZlcmVudCBkZWNhZGVzDQpwbG90KGFnZ3JlZ2F0ZShydW50aW1lWywiUnVudGltZSJdLCBsaXN0KHJ1bnRpbWUkWWVhcl9jdXQpLCBtZWFuKSwgdHlwZT0ibyIpDQojRnJvbSB0aGUgcGxvdCwgd2UgY2FuIGRpc2NvdmVyIHRoYXQsIGluIGdlbmVyYWwsIFJ1bnRpbWUgaW5jcmVhc2VzIG92ZXIgeWVhci4gQmV0d2VlbiAxOTAwIHRvIDE5MjAsIHRoZXJlIGlzIGV4cGxvc2lvbiBvZiBtb3ZpZSBSdW50aW1lIA0KYGBgDQoNCmBgYHtyfQ0KI0NoZWNrIHRoZSBkaXN0cmlidXRpb24gb2YgQnVkZ2V0LCB3ZSBjYW4gZGlzY292ZXIgdGhhdCB0aGUgQnVkZ2V0IGlzIHJpZ2h0IHNrZXdlZC4gTW9zdCBtb3ZpZSBjb3N0IGxlc3MgdGhhbiAxMDAgbWlsbGlvbiBkb2xsYXJzLg0KaGlzdChydW50aW1lWyFpcy5uYShydW50aW1lJEJ1ZGdldCksICJCdWRnZXQiXSwgbWFpbj1wYXN0ZSgiSGlzdG9ncmFtIG9mIEJ1ZGdldCIpLCB4bGFiPSJCdWRnZXQiKQ0KYGBgDQpgYGB7cn0NCiMgRm9yIGFuYWx5c2lzIHB1cnBvc2UsIHdlIHVzZSBsb2cgb2YgQnVkZ2V0IGluc3RlYWQgb2YgQnVkZ2V0IGJlY2F1c2UgdGhlIGxvZyBvZiB0aGUgQnVkZ2V0IGlzIG1vcmUgY2xvc2VkIHRvIG5vcm1hbCBkaXN0cmlidXRpb24NCnJ1bnRpbWUkQnVkZ2V0X2xvZz1sb2cxMChydW50aW1lJEJ1ZGdldCkNCm1pbihydW50aW1lWyFpcy5uYShydW50aW1lJEJ1ZGdldF9sb2cpLCAiQnVkZ2V0X2xvZyJdKQ0KbWF4KHJ1bnRpbWVbIWlzLm5hKHJ1bnRpbWUkQnVkZ2V0X2xvZyksICJCdWRnZXRfbG9nIl0pDQpgYGANCg0KYGBge3J9DQojV2UgY2FuIGRpc2NvdmVyIHRoYXQgdGhlIGxvZzEwKEJ1ZGdldCkgc3BhbiBmcm9tIDMgdG8gOQ0KaGlzdChydW50aW1lWyFpcy5uYShydW50aW1lJEJ1ZGdldF9sb2cpLCAiQnVkZ2V0X2xvZyJdLCB4bGFiPSJsb2cxMChCdWRnZXQpIiwgbWFpbj0iSGlzdG9ncmFtIG9mIGxvZzEwKEJ1ZGdldCkiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBGb3IgYW5hbHlzaXMgcHVycG9zZSwgd2UgY3V0IHRoZSBsb2cgb2YgQnVkZ2V0IGludG8gQmlucw0KcnVudGltZSRCdWRnZXRfbG9nX2Jpbj1jdXQocnVudGltZSRCdWRnZXRfbG9nLCBjKDMsIHNlcSgzLjUsIDksYnk9MC41KSksIGRpZy5sYWI9MCwgbGFiZWxzPUZBTFNFLCByaWdodD1GQUxTRSkNCiMgVGhlbiBhZ2dyZWdhdGUgdGhlIFJ1bnRpbWUgYnkgQnVkZ2V0IGJ5IEJ1ZGdldCBsb2cgYnkgbWVhbnMNCmFnZ3JlZ2F0ZShydW50aW1lWywzXSwgbGlzdChydW50aW1lJEJ1ZGdldF9sb2dfYmluKjAuNSszKSwgbWVhbikNCmBgYA0KDQoNCg0KYGBge3J9DQpwbG90KGFnZ3JlZ2F0ZShydW50aW1lWywzXSwgbGlzdChydW50aW1lJEJ1ZGdldF9sb2dfYmluKSxtZWFuKSwgdHlwZT0ibyIsIHhsYWI9ImxvZzEwKEJ1ZGdldCkiLCB5bGFiPSJSdW50aW1lIikNCmBgYA0KDQpfRmVlbCBmcmVlIHRvIGluc2VydCBhZGRpdGlvbmFsIGNvZGUgY2h1bmtzIGFzIG5lY2Vzc2FyeS5fDQoNCioqUSoqOiBDb21tZW50IG9uIHRoZSBkaXN0cmlidXRpb24gYXMgd2VsbCBhcyByZWxhdGlvbnNoaXBzLiBBcmUgdGhlcmUgYW55IHBhdHRlcm5zIG9yIHRyZW5kcyB0aGF0IHlvdSBjYW4gb2JzZXJ2ZT8NCg0KKipBKio6IFRoZSBkaXN0cmlidXRpb24gb2YgUnVudGltZSBvZiBtb3ZpZXMgaXMgY2xvc2UgdG8gbm9ybWFsIGRpc3RyaWJ1dGlvbiB3aXRoIHNvbWUgb3V0bGllcnMuIFRoZSBtZWRpYW4gaXMgOTAgYW5kIG1lYW4gaXMgODEuNDQuIFRoZSBydW50aW1lIG9mIG1vdmllcyBpcyBjZW50cmVkIG9uIGFuIGhvdXIgYW5kIGEgaGFsZi4NCiAgICAgICBBZnRlciBwcm9jZXNzLCB0aGVyZSBpcyBhIG9idmlvdXMgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBCdWRnZXQgYW5kIFJ1bnRpbWUuIEFzIEJ1ZGdldCBpbmNyZWFzZXMsIHRoZSBydW50aW1lIGluY3JlYXNlcyBhcyB3ZWxsLg0KDQojIyAzLiBFbmNvZGUgYEdlbnJlYCBjb2x1bW4NCg0KVGhlIGNvbHVtbiBgR2VucmVgIHJlcHJlc2VudHMgYSBsaXN0IG9mIGdlbnJlcyBhc3NvY2lhdGVkIHdpdGggdGhlIG1vdmllIGluIGEgc3RyaW5nIGZvcm1hdC4gV3JpdGUgY29kZSB0byBwYXJzZSBlYWNoIHRleHQgc3RyaW5nIGludG8gYSBiaW5hcnkgdmVjdG9yIHdpdGggMXMgcmVwcmVzZW50aW5nIHRoZSBwcmVzZW5jZSBvZiBhIGdlbnJlIGFuZCAwcyB0aGUgYWJzZW5jZSwgYW5kIGFkZCBpdCB0byB0aGUgZGF0YWZyYW1lIGFzIGFkZGl0aW9uYWwgY29sdW1ucy4gVGhlbiByZW1vdmUgdGhlIG9yaWdpbmFsIGBHZW5yZWAgY29sdW1uLg0KDQpGb3IgZXhhbXBsZSwgaWYgdGhlcmUgYXJlIGEgdG90YWwgb2YgMyBnZW5yZXM6IERyYW1hLCBDb21lZHksIGFuZCBBY3Rpb24sIGEgbW92aWUgdGhhdCBpcyBib3RoIEFjdGlvbiBhbmQgQ29tZWR5IHNob3VsZCBiZSByZXByZXNlbnRlZCBieSBhIGJpbmFyeSB2ZWN0b3IgPDAsIDEsIDE+LiBOb3RlIHRoYXQgeW91IG5lZWQgdG8gZmlyc3QgY29tcGlsZSBhIGRpY3Rpb25hcnkgb2YgYWxsIHBvc3NpYmxlIGdlbnJlcyBhbmQgdGhlbiBmaWd1cmUgb3V0IHdoaWNoIG1vdmllIGhhcyB3aGljaCBnZW5yZXMgKHlvdSBjYW4gdXNlIHRoZSBSIGB0bWAgcGFja2FnZSB0byBjcmVhdGUgdGhlIGRpY3Rpb25hcnkpLg0KDQpgYGB7cn0NCiMgVE9ETzogUmVwbGFjZSBHZW5yZSB3aXRoIGEgY29sbGVjdGlvbiBvZiBiaW5hcnkgY29sdW1ucw0KIyBXZSBsaXN0IGFsbCBvZiB0aGUgZ2VucmVzDQp1bmlxdWUodW5saXN0KHRva2VuaXplX3JlZ2V4KGRmJEdlbnJlLHBhdHRlcm49IiwgIikpKQ0KYGBgDQoNCmBgYHtyfQ0KI0NvbWJpbmUgdGhlIGdlbnJlIHdpdGggdGhlIG9yaWdpbmFsIGRhdGFzZXQNCmNvbHVtbl9nZW5yZV9uYW1lcz11bmlxdWUodW5saXN0KHRva2VuaXplX3JlZ2V4KGRmJEdlbnJlLHBhdHRlcm49IiwgIikpKQ0KZ2VucmU9ZGF0YS5mcmFtZShtYXRyaXgobnJvdz1ucm93KGRmKSwgbmNvbD1sZW5ndGgoY29sdW1uX2dlbnJlX25hbWVzKSkpDQpjb2xuYW1lcyhnZW5yZSk9Y29sdW1uX2dlbnJlX25hbWVzDQpnZW5yZVtdPTANCmZvcih2YWwgaW4gY29sdW1uX2dlbnJlX25hbWVzKXsNCiAgZ2VucmVbZ3JlcCh2YWwsIGRmWywgIkdlbnJlIl0pLCB2YWxdPTENCn0NCmdlbnJlX3ZlY3RvcmVkPWNiaW5kKGRmLCBnZW5yZSkNCmBgYA0KDQoNClBsb3QgdGhlIHJlbGF0aXZlIHByb3BvcnRpb25zIG9mIG1vdmllcyBoYXZpbmcgdGhlIHRvcCAxMCBtb3N0IGNvbW1vbiBnZW5yZXMuDQoNCmBgYHtyfQ0KIyBUT0RPOiBTZWxlY3QgbW92aWVzIGZyb20gdG9wIDEwIG1vc3QgY29tbW9uIGdlbnJlcyBhbmQgcGxvdCB0aGVpciByZWxhdGl2ZSBwcm9wb3J0aW9ucw0KZ2VucmVfc3VtPWNvbFN1bXMoZ2VucmUpDQpwcmludChnZW5yZV9zdW0pDQpgYGANCg0KYGBge3J9DQojU29ydCB0aGUgZ2VucmVzIGJhc2VkIG9uIHRoZSBudW1iZXIgb2Ygb2NjdXJlbmNlcw0KZ2VucmVfc29ydD1zb3J0KGdlbnJlX3N1bSwgZGVjcmVhc2luZz1UUlVFKQ0KcHJpbnQoZ2VucmVfc29ydCkNCmBgYA0KDQpgYGB7cn0NCiNUYWtlIHRoZSB0b3AgMTAgZ2VucmVzDQpnZW5yZV9zb3J0X25hbWVzPW5hbWVzKGdlbnJlX3NvcnQpDQpnZW5yZV90b3A9Z2VucmVfc29ydFsxOjEwXQ0KZ2VucmVfcHJvcD1nZW5yZV90b3AvbnJvdyhkZikNCnByaW50KGdlbnJlX3Byb3ApDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpwaWUoZ2VucmVfcHJvcCkNCmBgYA0KDQpFeGFtaW5lIGhvdyB0aGUgZGlzdHJpYnV0aW9uIG9mIGBSdW50aW1lYCBjaGFuZ2VzIGFjcm9zcyBnZW5yZXMgZm9yIHRoZSB0b3AgMTAgbW9zdCBjb21tb24gZ2VucmVzLg0KDQpgYGB7cn0NCiMgVE9ETzogUGxvdCBSdW50aW1lIGRpc3RyaWJ1dGlvbiBmb3IgdG9wIDEwIG1vc3QgY29tbW9uIGdlbnJlcw0KIyBDcmVhdGUgaW5kaXZpZHVhbCBkYXRhIGZyYW1lIGZvciBlYWNoIGdlbnJlDQpmb3IoZ2VuIGluIGdlbnJlX3NvcnRfbmFtZXNbMToxMF0pew0KICBhc3NpZ24oZ2VuLCBnZW5yZV92ZWN0b3JlZFtnZW5yZV92ZWN0b3JlZFssIGdlbl09PTEsXSkNCn0NCg0KIyBDcmVhdGUgaGlzdG9ncmFtIGZvciBlYWNoIGdlbnJlIGFuZCBkaXNwbGF5IHRoZWlyIHJ1bnRpbWUgZGlzdHJpYnV0aW9uDQpmb3IoIGdlbiBpbiBnZW5yZV9zb3J0X25hbWVzWzE6MTBdKXsNCiAgaGlzdChnZXQoZ2VuKVshaXMubmEoZ2V0KGdlbikkUnVudGltZSksICJSdW50aW1lIl0sIG1haW49cGFzdGUoIkhpc3RvZ3JhbSBvZiIsIGdlbiwgIlJ1bnRpbWUiKSwgeGxhYj0iUnVudGltZSIpDQogICAgDQoNCn0NCmBgYA0KDQoqKlEqKjogRGVzY3JpYmUgdGhlIGludGVyZXN0aW5nIHJlbGF0aW9uc2hpcChzKSB5b3Ugb2JzZXJ2ZS4gQXJlIHRoZXJlIGFueSBleHBlY3RlZCBvciB1bmV4cGVjdGVkIHRyZW5kcyB0aGF0IGFyZSBldmlkZW50Pw0KDQoqKkEqKjogRm9yIG1vc3Qgb2YgZ2VucmUsIHJ1bnRpbWUgaGFzIGEgbm9ybWFsLWxpa2UgZGlzdHJpYnV0aW9uIA0KICAgICAgIEV4cGVjdGVkOiBNb3N0IG9mIGdlbnJlIGhhdmUgYSBkaXN0cmlidXRpb24gY2xvc2UgdG8gbm9ybWFsIGRpc3RyaWJ1dGlvbi4gDQogICAgICAgVW5leHBlY3RlZDogQ29tZWR5IGFuZCBBbmltYXRpb24gaGFzIGEgZGlzdHJpYnV0aW9uIHNob3dpbmcgYSBzdHJvbmcgc2tld25lc3MuIA0KDQojIyA0LiBFbGltaW5hdGUgbWlzbWF0Y2hlZCByb3dzDQoNClRoZSBkYXRhZnJhbWUgd2FzIHB1dCB0b2dldGhlciBieSBtZXJnaW5nIHR3byBkaWZmZXJlbnQgc291cmNlcyBvZiBkYXRhIGFuZCBpdCBpcyBwb3NzaWJsZSB0aGF0IHRoZSBtZXJnaW5nIHByb2Nlc3Mgd2FzIGluYWNjdXJhdGUgaW4gc29tZSBjYXNlcyAodGhlIG1lcmdlIHdhcyBkb25lIGJhc2VkIG9uIG1vdmllIHRpdGxlLCBidXQgdGhlcmUgYXJlIGNhc2VzIG9mIGRpZmZlcmVudCBtb3ZpZXMgd2l0aCB0aGUgc2FtZSB0aXRsZSkuIFRoZSBmaXJzdCBzb3VyY2XigJlzIHJlbGVhc2UgdGltZSB3YXMgcmVwcmVzZW50ZWQgYnkgdGhlIGNvbHVtbiBgWWVhcmAgKG51bWVyaWMgcmVwcmVzZW50YXRpb24gb2YgdGhlIHllYXIpIGFuZCB0aGUgc2Vjb25kIGJ5IHRoZSBjb2x1bW4gYFJlbGVhc2VkYCAoc3RyaW5nIHJlcHJlc2VudGF0aW9uIG9mIHJlbGVhc2UgZGF0ZSkuDQoNCkZpbmQgYW5kIHJlbW92ZSBhbGwgcm93cyB3aGVyZSB5b3Ugc3VzcGVjdCBhIG1lcmdlIGVycm9yIG9jY3VycmVkIGJhc2VkIG9uIGEgbWlzbWF0Y2ggYmV0d2VlbiB0aGVzZSB0d28gdmFyaWFibGVzLiBUbyBtYWtlIHN1cmUgc3Vic2VxdWVudCBhbmFseXNpcyBhbmQgbW9kZWxpbmcgd29yayB3ZWxsLCBhdm9pZCByZW1vdmluZyBtb3JlIHRoYW4gMTAlIG9mIHRoZSByb3dzIHRoYXQgaGF2ZSBhIGBHcm9zc2AgdmFsdWUgcHJlc2VudC4NCg0KYGBge3J9DQojIFRPRE86IFJlbW92ZSByb3dzIHdpdGggUmVsZWFzZWQtWWVhciBtaXNtYXRjaA0KIyBvbWl0IE5BIHJvd3MNCnRlc3Q9ZGZbc2V0ZGlmZihyb3duYW1lcyhkZiksIHJvd25hbWVzKGRmW2lzLm5hKGRmJFJlbGVhc2VkKSxdKSksXQ0KdGVzdF9tYXRjaD10ZXN0W2FicygodGVzdCRZZWFyLWFzLm51bWVyaWMoc3Vic3RyKHRlc3QkUmVsZWFzZWQsIDEsIDQpKSkpPD0xLF0NCnRlc3RfZGlzbWF0Y2g9ZGZbc2V0ZGlmZihyb3duYW1lcyhkZiksIHJvd25hbWVzKGRmW3Jvd25hbWVzKHRlc3RfbWF0Y2gpLF0pKSxdDQoNCmBgYA0KDQpgYGB7cn0NCiNDb3VudCB0aGUgcmVjb3JkcyB3aXRoIEdyb3NzIHZhbHVlIGluIG1hdGNoZWQgcmVjb3JkcyBhbmQgZGlzbWF0Y2hlZCByZWNvcmRzIHRvIGVuc3VyZSB0aGUgImxlc3MgdGhhbiAxMCUgcmVtb3ZlbWVudCIgcmVxdWlyZW1lbnQgaXMgbWV0DQpucm93KHRlc3RfbWF0Y2hbIWlzLm5hKHRlc3RfbWF0Y2gkR3Jvc3MpLCBdKQ0KbnJvdyh0ZXN0X2Rpc21hdGNoWyFpcy5uYSh0ZXN0X2Rpc21hdGNoJEdyb3NzKSxdKQ0KbnJvdyh0ZXN0X2Rpc21hdGNoWyFpcy5uYSh0ZXN0X2Rpc21hdGNoJEdyb3NzKSxdKS9ucm93KHRlc3RfbWF0Y2hbIWlzLm5hKHRlc3RfbWF0Y2gkR3Jvc3MpLCBdKQ0KDQpgYGANCg0KKipRKio6IFdoYXQgaXMgeW91ciBwcmVjaXNlIHJlbW92YWwgbG9naWMgYW5kIGhvdyBtYW55IHJvd3MgZGlkIHlvdSBlbmQgdXAgcmVtb3Zpbmc/DQoNCioqQSoqOiBBdCBmaXJzdCwgd2UgZXhjbHVkZSB0aGUgcmVjb3JkcyB3aXRoIG1pc3NpbmcgUmVsZWFzZWQgY29sdW1uLCB0aGVuIHdlIGRvIHRoZSBjYWxjdWxhdGlvbiBvZiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuICJZZWFyIiBjb2x1bW4gYW5kICJSZWxlYXNlZCIgY29sdW1uLiBUaGVuIHdlIGV4Y2x1ZGUgdGhlIHJlY29yZHMgd2hpY2ggdGhlIGRpZmZlcmVuY2Ugb2YgIlllYXIiIGNvbHVtbiBhbmQgdGhlICJSZWxlYXNlZCIgY29sdW1uIGlzIGJpZ2dlciB0aGFuIDEuDQoNCiMjIDUuIEV4cGxvcmUgYEdyb3NzYCByZXZlbnVlDQoNCkZvciB0aGUgY29tbWVyY2lhbCBzdWNjZXNzIG9mIGEgbW92aWUsIHByb2R1Y3Rpb24gaG91c2VzIHdhbnQgdG8gbWF4aW1pemUgR3Jvc3MgcmV2ZW51ZS4gSW52ZXN0aWdhdGUgaWYgR3Jvc3MgcmV2ZW51ZSBpcyByZWxhdGVkIHRvIEJ1ZGdldCwgUnVudGltZSBvciBHZW5yZSBpbiBhbnkgd2F5Lg0KDQpOb3RlOiBUbyBnZXQgYSBtZWFuaW5nZnVsIHJlbGF0aW9uc2hpcCwgeW91IG1heSBoYXZlIHRvIHBhcnRpdGlvbiB0aGUgbW92aWVzIGludG8gc3Vic2V0cyBzdWNoIGFzIHNob3J0IHZzLiBsb25nIGR1cmF0aW9uLCBvciBieSBnZW5yZSwgZXRjLg0KDQpgYGB7cn0NCiMgVE9ETzogSW52ZXN0aWdhdGUgaWYgR3Jvc3MgUmV2ZW51ZSBpcyByZWxhdGVkIHRvIEJ1ZGdldCwgUnVudGltZSBvciBHZW5yZQ0KZ19yZXZlbnVlPWRmWyFpcy5uYShkZiRHcm9zcyksXQ0KZ19yZXZlbnVlPWdfcmV2ZW51ZVshaXMubmEoZ19yZXZlbnVlJEJ1ZGdldCksXQ0KDQpgYGANCg0KYGBge3J9DQpnX3JldmVudWUkQnVkZ2V0X2xvZz1sb2cxMChnX3JldmVudWUkQnVkZ2V0KQ0KZ19yZXZlbnVlJEJ1ZGdldF9sb2dfYmluPWN1dChnX3JldmVudWUkQnVkZ2V0X2xvZywgYygzLCBzZXEoMy41LCA5LCBieT0wLjUpKSwgZGlnLmxhYj0wLCBsYWJlbHM9RkFMU0UscmlnaHQ9RkFMU0UpDQpnX3JldmVudWVfYWdnPWFnZ3JlZ2F0ZShnX3JldmVudWVbLCJHcm9zcyJdLCBsaXN0KGdfcmV2ZW51ZSRCdWRnZXRfbG9nX2JpbiowLjUrMyksIG1lYW4pDQpnX3JldmVudWVfYWdnJEdyb3NzX2xvZz1sb2cyKGdfcmV2ZW51ZV9hZ2dbLDJdKQ0KcHJpbnQoZ19yZXZlbnVlX2FnZykNCmBgYA0KDQpgYGB7cn0NCnBsb3QoZ19yZXZlbnVlX2FnZ1ssYygxLDMpXSwgdHlwZT0ibyIsIHhsYWI9ImxvZyhCdWRnZXQpIiwgeWxhYj0ibG9nKEdyb3NzKSIpDQpgYGANCg0KKipRKio6IERpZCB5b3UgZmluZCBhbnkgb2JzZXJ2YWJsZSByZWxhdGlvbnNoaXBzIG9yIGNvbWJpbmF0aW9ucyBvZiBCdWRnZXQvUnVudGltZS9HZW5yZSB0aGF0IHJlc3VsdCBpbiBoaWdoIEdyb3NzIHJldmVudWU/IElmIHlvdSBkaXZpZGVkIHRoZSBtb3ZpZXMgaW50byBkaWZmZXJlbnQgc3Vic2V0cywgeW91IG1heSBnZXQgZGlmZmVyZW50IGFuc3dlcnMgZm9yIHRoZW0gLSBwb2ludCBvdXQgaW50ZXJlc3Rpbmcgb25lcy4NCg0KKipBKio6IFdlIGNhbiBkaXNjb3ZlciB0aGVyZSBpcyBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIEJ1ZGdldCBhbmQgR3Jvc3MgdmFsdWUNCg0KYGBge3J9DQojIFRPRE86IEludmVzdGlnYXRlIGlmIEdyb3NzIFJldmVudWUgaXMgcmVsYXRlZCB0byBSZWxlYXNlIE1vbnRoDQojIFJldHJpZXZlIHRoZSBtb250aCBmcm9tIHRoZSAiUmVsZWFzZWQiIGNvbHVtbg0KZ19yZXZlbnVlJG1vbnRoPXN1YnN0cihnX3JldmVudWUkUmVsZWFzZWQsIDYsIDcpDQojIEFnZ3JlZ2F0ZSB0aGUgR3Jvc3MgYnkgbW9udGgsIHdlIGNhbiBkaXNjb3ZlciB0aGUgbW92aWVzIGFyZSBtdWNoIG1vcmUgc3VjY2Vzc2Z1bCBpbiBzdW1tZXIgYW5kIFllYXIgRW5kDQpwbG90KGFnZ3JlZ2F0ZShnX3JldmVudWUkR3Jvc3MsIGxpc3QoZ19yZXZlbnVlJG1vbnRoKSwgbWVhbiksIHR5cGU9Im8iLCB4bGFiPSJNb250aCIsIHlsYWI9Ikdyb3NzIikNCmBgYA0KDQojIyA2LiBQcm9jZXNzIGBBd2FyZHNgIGNvbHVtbg0KDQpUaGUgdmFyaWFibGUgYEF3YXJkc2AgZGVzY3JpYmVzIG5vbWluYXRpb25zIGFuZCBhd2FyZHMgaW4gdGV4dCBmb3JtYXQuIENvbnZlcnQgaXQgdG8gMiBudW1lcmljIGNvbHVtbnMsIHRoZSBmaXJzdCBjYXB0dXJpbmcgdGhlIG51bWJlciBvZiB3aW5zLCBhbmQgdGhlIHNlY29uZCBjYXB0dXJpbmcgbm9taW5hdGlvbnMuIFJlcGxhY2UgdGhlIGBBd2FyZHNgIGNvbHVtbiB3aXRoIHRoZXNlIG5ldyBjb2x1bW5zLCBhbmQgdGhlbiBzdHVkeSB0aGUgcmVsYXRpb25zaGlwIG9mIGBHcm9zc2AgcmV2ZW51ZSB3aXRoIHJlc3BlY3QgdG8gdGhlbS4NCg0KTm90ZSB0aGF0IHRoZSBmb3JtYXQgb2YgdGhlIGBBd2FyZHNgIGNvbHVtbiBpcyBub3Qgc3RhbmRhcmQ7IHlvdSBtYXkgaGF2ZSB0byB1c2UgcmVndWxhciBleHByZXNzaW9ucyB0byBmaW5kIHRoZSByZWxldmFudCB2YWx1ZXMuIFRyeSB5b3VyIGJlc3QgdG8gcHJvY2VzcyB0aGVtLCBhbmQgeW91IG1heSBsZWF2ZSB0aGUgb25lcyB0aGF0IGRvbid0IGhhdmUgZW5vdWdoIGluZm9ybWF0aW9uIGFzIE5BcyBvciBzZXQgdGhlbSB0byAwcy4NCg0KYGBge3J9DQojIFRPRE86IENvbnZlcnQgQXdhcmRzIHRvIDIgbnVtZXJpYyBjb2x1bW5zOiB3aW5zIGFuZCBub21pbmF0aW9ucw0KIyBQcm9jZXNzIHRoZSAiQXdhcmRzIiBjb2x1bW4gYW5kIGZpbGwgaW4gdGhlICJ3aW5zIiBhbmQgIm5vbWluYXRpb25zIiBjb2x1bW5zDQpmb3IgKGkgaW4gYygxOm5yb3coZGYpKSl7DQogICAgd2luPTANCiAgICBub21pbmF0aW9uPTANCiAgaWYoZ3JlcGwoIldvbiIsZGZbaSwgIkF3YXJkcyJdKSl7DQogICAgd2luPWFzLm51bWVyaWMoc3RyX2V4dHJhY3Qoc3RyX2V4dHJhY3QoZGZbaSwiQXdhcmRzIl0sICJXb24gKFtcXGRdKykiKSwgIltbOmRpZ2l0Ol1dKyIpKQ0KICAgIHdpbj13aW4rYXMubnVtZXJpYyhzdHJfZXh0cmFjdChzdHJfZXh0cmFjdChkZltpLCJBd2FyZHMiXSwgIihbXFxkXSspIHdpbiIpLCAiW1s6ZGlnaXQ6XV0rIikpDQogICAgbm9taW5hdGlvbj1ub21pbmF0aW9uK2FzLm51bWVyaWMoc3RyX2V4dHJhY3Qoc3RyX2V4dHJhY3QoZGZbaSwiQXdhcmRzIl0sICIoW1xcZF0rKSBub21pbmF0aW9uIiksICJbWzpkaWdpdDpdXSsiKSkNCiAgfWVsc2UgaWYoZ3JlcGwoIk5vbWluYXRlZCIsZGZbaSwgIkF3YXJkcyJdKSl7DQogICAgbm9taW5hdGlvbj1hcy5udW1lcmljKHN0cl9leHRyYWN0KHN0cl9leHRyYWN0KGRmW2ksIkF3YXJkcyJdLCAiTm9taW5hdGVkIGZvciAoW1xcZF0rKSIpLCAiW1s6ZGlnaXQ6XV0rIikpDQogICAgd2luPXdpbithcy5udW1lcmljKHN0cl9leHRyYWN0KHN0cl9leHRyYWN0KGRmW2ksIkF3YXJkcyJdLCAiKFtcXGRdKykgd2luIiksICJbWzpkaWdpdDpdXSsiKSkNCiAgICBub21pbmF0aW9uPW5vbWluYXRpb24rYXMubnVtZXJpYyhzdHJfZXh0cmFjdChzdHJfZXh0cmFjdChkZltpLCJBd2FyZHMiXSwgIihbXFxkXSspIG5vbWluYXRpb24iKSwgIltbOmRpZ2l0Ol1dKyIpKQ0KICB9ZWxzZXsNCiAgICB3aW49d2luK2FzLm51bWVyaWMoc3RyX2V4dHJhY3Qoc3RyX2V4dHJhY3QoZGZbaSwiQXdhcmRzIl0sICIoW1xcZF0rKSB3aW4iKSwgIltbOmRpZ2l0Ol1dKyIpKQ0KICAgIG5vbWluYXRpb249bm9taW5hdGlvbithcy5udW1lcmljKHN0cl9leHRyYWN0KHN0cl9leHRyYWN0KGRmW2ksIkF3YXJkcyJdLCAiKFtcXGRdKykgbm9taW5hdGlvbiIpLCAiW1s6ZGlnaXQ6XV0rIikpDQogIH0NCiAgICBkZltpLCJ3aW5zIl09d2luDQogICAgZGZbaSwgIm5vbWluYXRpb25zIl09bm9taW5hdGlvbg0KICAgIGlmKGlzLm5hKGRmW2ksIndpbnMiXSkpe2RmW2ksIndpbnMiXT0wfQ0KICAgIGlmKGlzLm5hKGRmW2ksIm5vbWluYXRpb25zIl0pKXtkZltpLCJub21pbmF0aW9ucyJdPTB9DQp9DQoNCg0KYGBgDQoNCg0KDQoqKlEqKjogSG93IGRpZCB5b3UgY29uc3RydWN0IHlvdXIgY29udmVyc2lvbiBtZWNoYW5pc20/IEhvdyBtYW55IHJvd3MgaGFkIHZhbGlkL25vbi16ZXJvIHdpbnMgb3Igbm9taW5hdGlvbnM/DQoNCioqQSoqOiAxLiBJZiB0aGUgcmVjb3JkcyBjb250YWlucyB0aGUgd29yZCAiV29uIiwgdGhlbiB0YWtlIHRoZSBudW1iZXIgYW5kIGFkZCBpdCB0byB0aGUgdmFyaWFibGUgIndpbiIuIFRoZW4gY2hlY2sgdGhlIG51bWJlciBhZnRlciB0aGUgd29yZCAid2luIiwgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAid2luIi4gVGhlbiBjaGVjayB0aGUgbnVtYmVyIGFmdGVyIHRoZSB3b3JkICJub21pbmF0aW9uIiwgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAibm9taW5hdGlvbiIuDQogICAgICAgMi4gRWxzZSBpZiB0aGUgcmVjb3JkIGNvbnRhaW5zIHRoZSB3b3JkICJOb21pbmF0ZWQiLCB0aGVuIHRha2UgdGhlIG51bWJlciBhbmQgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAibm9taW5hdGlvbiIuIFRoZW4gY2hlY2sgdGhlIG51bWJlciBhZnRlciB0aGUgd29yZCAid2luIiwgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAid2luIi4gVGhlbiBjaGVjayB0aGUgbnVtYmVyIGFmdGVyIHRoZSB3b3JkICJub21pbmF0aW9uIiwgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAibm9taW5hdGlvbiINCiAgICAgICAzLiBFbHNlIFRoZW4gY2hlY2sgdGhlIG51bWJlciBhZnRlciB0aGUgd29yZCAid2luIiwgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAid2luIi4gVGhlbiBjaGVjayB0aGUgbnVtYmVyIGFmdGVyIHRoZSB3b3JkICJub21pbmF0aW9uIiwgYWRkIGl0IHRvIHRoZSB2YXJpYWJsZSAibm9taW5hdGlvbiINCiAgICAgICA0LiBTZXQgcmVzdCBhcyAwDQogICAgICANCg0KYGBge3J9DQojIFRPRE86IFBsb3QgR3Jvc3MgcmV2ZW51ZSBhZ2FpbnN0IHdpbnMgYW5kIG5vbWluYXRpb25zDQpncm9zc19hd2FyZHM9ZGZbIWlzLm5hKGRmJEdyb3NzKSxdDQphZ2dyZWdhdGUoZ3Jvc3NfYXdhcmRzWywiR3Jvc3MiXSwgbGlzdChncm9zc19hd2FyZHNbLCJ3aW5zIl0pLCBtZWFuKQ0KDQpgYGANCg0KYGBge3J9DQpwbG90KGFnZ3JlZ2F0ZShncm9zc19hd2FyZHNbLCJHcm9zcyJdLCBsaXN0KGdyb3NzX2F3YXJkc1ssIndpbnMiXSksIG1lYW4pLCB0eXBlPSJvIiwgeGxhYj0iV2lucyIsIHlsYWI9Ikdyb3NzIikNCmBgYA0KDQpgYGB7cn0NCmFnZ3JlZ2F0ZShncm9zc19hd2FyZHNbLCJHcm9zcyJdLCBsaXN0KGdyb3NzX2F3YXJkc1ssIm5vbWluYXRpb25zIl0pLCBtZWFuKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChhZ2dyZWdhdGUoZ3Jvc3NfYXdhcmRzWywiR3Jvc3MiXSwgbGlzdChncm9zc19hd2FyZHNbLCJub21pbmF0aW9ucyJdKSwgbWVhbiksIHR5cGU9Im8iLCB4bGFiPSJOb21pbmF0aW9ucyIsIHlsYWI9Ikdyb3NzIikNCmBgYA0KDQoqKlEqKjogSG93IGRvZXMgdGhlIGdyb3NzIHJldmVudWUgdmFyeSBieSBudW1iZXIgb2YgYXdhcmRzIHdvbiBhbmQgbm9taW5hdGlvbnMgcmVjZWl2ZWQ/DQoNCioqQSoqOiBXZSBjYW4gZGlzY292ZXIgdGhhdCBtb3ZpZXMgd2l0aCBub21pbmF0aW9uIGFuZCB3aW5zIGFyb3VuZCA3MCB0byAxNTAgaGF2ZSBoaWdoZXN0IEdyb3NzIHJldmVudWUuIEFzIG5vbWluYXRpb24gYW5kIHdpbnMgZGVjcmVhc2VzIG9yIGluY3JlYXNlcyBmcm9tIHRoaXMgcmFuZ2UsIHRoZSBHcm9zcyByZXZlbnVlIGRlY3JlYXNlcy4NCg0KIyMgNy4gTW92aWUgcmF0aW5ncyBmcm9tIElNRGIgYW5kIFJvdHRlbiBUb21hdG9lcw0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB2YXJpYWJsZXMgdGhhdCBkZXNjcmliZSByYXRpbmdzLCBpbmNsdWRpbmcgSU1EYiByYXRpbmdzIChgaW1kYlJhdGluZ2AgcmVwcmVzZW50cyBhdmVyYWdlIHVzZXIgcmF0aW5ncyBhbmQgYGltZGJWb3Rlc2AgcmVwcmVzZW50cyB0aGUgbnVtYmVyIG9mIHVzZXIgcmF0aW5ncyksIGFuZCBtdWx0aXBsZSBSb3R0ZW4gVG9tYXRvZXMgcmF0aW5ncyAocmVwcmVzZW50ZWQgYnkgc2V2ZXJhbCB2YXJpYWJsZXMgcHJlLWZpeGVkIGJ5IGB0b21hdG9gKS4gUmVhZCB1cCBvbiBzdWNoIHJhdGluZ3Mgb24gdGhlIHdlYiAoZm9yIGV4YW1wbGUgW3JvdHRlbnRvbWF0b2VzLmNvbS9hYm91dF0oaHR0cHM6Ly93d3cucm90dGVudG9tYXRvZXMuY29tL2Fib3V0KSBhbmQgWyB3d3cuaW1kYi5jb20vaGVscC9zaG93X2xlYWY/dm90ZXN0b3BmYXFdKGh0dHA6Ly8gd3d3LmltZGIuY29tL2hlbHAvc2hvd19sZWFmP3ZvdGVzdG9wZmFxKSkuDQoNCkludmVzdGlnYXRlIHRoZSBwYWlyd2lzZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlc2UgZGlmZmVyZW50IGRlc2NyaXB0b3JzIHVzaW5nIGdyYXBocy4NCg0KYGBge3J9DQojIFRPRE86IElsbHVzdHJhdGUgaG93IHJhdGluZ3MgZnJvbSBJTURiIGFuZCBSb3R0ZW4gVG9tYXRvZXMgYXJlIHJlbGF0ZWQNCnBhaXJzKGRmWyxjKCJpbWRiUmF0aW5nIiwgImltZGJWb3RlcyIsICJ0b21hdG9NZXRlciIsICJ0b21hdG9SYXRpbmciLCJ0b21hdG9SZXZpZXdzIiAsICJ0b21hdG9GcmVzaCIgICAgICAgLCJ0b21hdG9Sb3R0ZW4iICwidG9tYXRvVXNlck1ldGVyIiAsICJ0b21hdG9Vc2VyUmF0aW5nIiwidG9tYXRvVXNlclJldmlld3MiKV0pDQpgYGANCg0KDQoNCioqUSoqOiBDb21tZW50IG9uIHRoZSBzaW1pbGFyaXRpZXMgYW5kIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHVzZXIgcmF0aW5ncyBvZiBJTURiIGFuZCB0aGUgY3JpdGljcyByYXRpbmdzIG9mIFJvdHRlbiBUb21hdG9lcy4NCg0KKipBKio6IElNREIgcmF0aW5ncyBkb2VzIG5vdCBoYXZlIGEgc3Ryb25nIHNlbGYgY29ycmVsYXRpb24sIGhvd2V2ZXIsIHRvbWF0byByYXRpbmcgc2hvd3MgYSB2ZXJ5IHN0cm9uZyBzZWxmIGNvcnJlbGF0aW9ucy4gRm9yIGV4YW1wbGUsIFRvbWF0b1Jldmlld3MgYWxtb3N0IGZvcm0gYSBsaW5lYXIgcmVsYXRpb25zaGlwIHdpdGggVG9tYXRvRnJlc2guDQogICAgICAgU2ltaWxhcml0aWVzOiBJTURCIGFuZCBSb3R0ZW4gVG9tYXRvZXMgYWxsIGNlbnRyYWxpemVkIG92ZXIgYSByYW5nZSBvZiByYXRpbmcgc2NvcmVzLiANCiAgICAgICBEaWZmaXJlbmNlczogVGhlcmUgYXJlIHNvbWUgUm90dGVuIFRvbWF0b2VzIHJhdGluZ3Mgd2hpY2ggZGlzdHJpYnV0ZWQgb3ZlciB0d28gcG9sYXIsIGxpa2UgVG9tb3RvVXNlclJldmlld3MuDQoNCiMjIDguIFJhdGluZ3MgYW5kIGF3YXJkcw0KDQpUaGVzZSByYXRpbmdzIHR5cGljYWxseSByZWZsZWN0IHRoZSBnZW5lcmFsIGFwcGVhbCBvZiB0aGUgbW92aWUgdG8gdGhlIHB1YmxpYyBvciBnYXRoZXIgb3BpbmlvbnMgZnJvbSBhIGxhcmdlciBib2R5IG9mIGNyaXRpY3MuIFdoZXJlYXMgYXdhcmRzIGFyZSBnaXZlbiBieSBwcm9mZXNzaW9uYWwgc29jaWV0aWVzIHRoYXQgbWF5IGV2YWx1YXRlIGEgbW92aWUgb24gc3BlY2lmaWMgYXR0cmlidXRlcywgc3VjaCBhcyBhcnRpc3RpYyBwZXJmb3JtYW5jZSwgc2NyZWVucGxheSwgc291bmQgZGVzaWduLCBldGMuDQoNClN0dWR5IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiByYXRpbmdzIGFuZCBhd2FyZHMgdXNpbmcgZ3JhcGhzIChhd2FyZHMgaGVyZSByZWZlcnMgdG8gd2lucyBhbmQvb3Igbm9taW5hdGlvbnMpLiANCg0KYGBge3J9DQojIFRPRE86IFNob3cgaG93IHJhdGluZ3MgYW5kIGF3YXJkcyBhcmUgcmVsYXRlZA0KIyBDcmVhdGUgaW5kaXZpZHVhbCBkYXRhIGZyYW1lIGZvciBlYWNoIHJhdGluZw0KcmF0aW5ncz1jKCJpbWRiUmF0aW5nIiwgImltZGJWb3RlcyIsICJ0b21hdG9NZXRlciIsICJ0b21hdG9SYXRpbmciLCJ0b21hdG9SZXZpZXdzIiAsICJ0b21hdG9GcmVzaCIgICAgICAgLCJ0b21hdG9Sb3R0ZW4iICwidG9tYXRvVXNlck1ldGVyIiAsICJ0b21hdG9Vc2VyUmF0aW5nIiwidG9tYXRvVXNlclJldmlld3MiKQ0KZm9yKHJhdGluZyBpbiByYXRpbmdzKXsNCiAgYXNzaWduKHJhdGluZywgZGZbIWlzLm5hKGRmWyxyYXRpbmddKSxjKHJhdGluZywgIndpbnMiLCJub21pbmF0aW9ucyIpXSkNCn0NCg0KYGBgDQoNCmBgYHtyfQ0KDQojIEZvciBlYWNoIHJhdGluZywgZGlzcGxheSB0aGVpciBkaXN0cmlidXRpb24gb3ZlciB3aW5zIGFuZCBub21pbmF0aW9ucw0KZm9yKHJhdGluZyBpbiByYXRpbmdzKXsNCiAgcGxvdChhZ2dyZWdhdGUoZ2V0KHJhdGluZylbLHJhdGluZ10sIGxpc3QoZ2V0KHJhdGluZylbLCJ3aW5zIl0pLCBtZWFuKSwgdHlwZT0ibyIsIHhsYWI9IndpbnMiLCB5bGFiPXJhdGluZykNCiAgcGxvdChhZ2dyZWdhdGUoZ2V0KHJhdGluZylbLHJhdGluZ10sIGxpc3QoZ2V0KHJhdGluZylbLCJub21pbmF0aW9ucyJdKSwgbWVhbiksIHR5cGU9Im8iLCB4bGFiPSJub21pbmF0aW9ucyIsIHlsYWI9cmF0aW5nKSAgDQoNCn0NCmBgYA0KDQoqKlEqKjogSG93IGdvb2QgYXJlIHRoZXNlIHJhdGluZ3MgaW4gdGVybXMgb2YgcHJlZGljdGluZyB0aGUgc3VjY2VzcyBvZiBhIG1vdmllIGluIHdpbm5pbmcgYXdhcmRzIG9yIG5vbWluYXRpb25zPyBJcyB0aGVyZSBhIGhpZ2ggY29ycmVsYXRpb24gYmV0d2VlbiB0d28gdmFyaWFibGVzPw0KDQoqKkEqKjogV2UgY2FuIGZpbmQgdGhhdCwgZXhjZXB0ICJ0b21hdG9Vc2VyUmV2aWV3cyIgYW5kICJ0b21hdG9Sb3R0ZW4iLCBhbGwgb2Ygb3RoZXIgcmV2aWV3cyBoYXMgYSBzdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB3aW5zL25vbWluYXRpb25zIGFuZCByZXZpZXcgc2NvcmVzLg0KDQojIyA5LiBFeHBlY3RlZCBpbnNpZ2h0cw0KDQpDb21lIHVwIHdpdGggdHdvIG5ldyBpbnNpZ2h0cyAoYmFja2VkIHVwIGJ5IGRhdGEgYW5kIGdyYXBocykgdGhhdCBpcyBleHBlY3RlZC4gSGVyZSDigJxuZXfigJ0gbWVhbnMgaW5zaWdodHMgdGhhdCBhcmUgbm90IGFuIGltbWVkaWF0ZSBjb25zZXF1ZW5jZSBvZiBvbmUgb2YgdGhlIGFib3ZlIHRhc2tzLiBZb3UgbWF5IHVzZSBhbnkgb2YgdGhlIGNvbHVtbnMgYWxyZWFkeSBleHBsb3JlZCBhYm92ZSBvciBhIGRpZmZlcmVudCBvbmUgaW4gdGhlIGRhdGFzZXQsIHN1Y2ggYXMgYFRpdGxlYCwgYEFjdG9yc2AsIGV0Yy4NCg0KYGBge3J9DQojIFRPRE86IEZpbmQgYW5kIGlsbHVzdHJhdGUgdHdvIGV4cGVjdGVkIGluc2lnaHRzDQoNCg0KdW5pcXVlKHVubGlzdCh0b2tlbml6ZV9yZWdleChkZiRDb3VudHJ5LHBhdHRlcm49IiwgIikpKQ0KYGBgDQoNCmBgYHtyfQ0KI0NvbWJpbmUgdGhlIGdlbnJlIHdpdGggdGhlIG9yaWdpbmFsIGRhdGFzZXQNCmNvbHVtbl9jb3VudHJ5X25hbWVzPXVuaXF1ZSh1bmxpc3QodG9rZW5pemVfcmVnZXgoZGYkQ291bnRyeSxwYXR0ZXJuPSIsICIpKSkNCmNvdW50cnk9ZGF0YS5mcmFtZShtYXRyaXgobnJvdz1ucm93KGRmKSwgbmNvbD1sZW5ndGgoY29sdW1uX2NvdW50cnlfbmFtZXMpKSkNCmNvbG5hbWVzKGNvdW50cnkpPWNvbHVtbl9jb3VudHJ5X25hbWVzDQpjb3VudHJ5W109MA0KZm9yKHZhbCBpbiBjb2x1bW5fY291bnRyeV9uYW1lcyl7DQogIGNvdW50cnlbZ3JlcCh2YWwsIGRmWywgIkNvdW50cnkiXSksIHZhbF09MQ0KfQ0KYGBgDQoNCg0KUGxvdCB0aGUgcmVsYXRpdmUgcHJvcG9ydGlvbnMgb2YgbW92aWVzIGhhdmluZyB0aGUgdG9wIDEwIG1vc3QgY29tbW9uIGdlbnJlcy4NCg0KYGBge3J9DQojIFRPRE86IFNlbGVjdCBtb3ZpZXMgZnJvbSB0b3AgMTAgbW9zdCBjb21tb24gZ2VucmVzIGFuZCBwbG90IHRoZWlyIHJlbGF0aXZlIHByb3BvcnRpb25zDQpjb3VudHJ5X3N1bT1jb2xTdW1zKGNvdW50cnkpDQojU29ydCB0aGUgZ2VucmVzIGJhc2VkIG9uIHRoZSBudW1iZXIgb2Ygb2NjdXJlbmNlcw0KY291bnRyeV9zb3J0PXNvcnQoY291bnRyeV9zdW0sIGRlY3JlYXNpbmc9VFJVRSkNCnByaW50KGNvdW50cnlfc29ydCkNCmBgYA0KDQoNCmBgYHtyfQ0KI0NhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiBtb3ZpZXMgYnkgY291bnRyeQ0KY291bnRyeV90b3A9Y291bnRyeV9zb3J0WzE6MjBdDQpjb3VudHJ5X3Byb3A9Y291bnRyeV90b3AvbnJvdyhkZikNCnByaW50KGNvdW50cnlfcHJvcCkNCmBgYA0KDQpgYGB7cn0NCiNQbG90IHRoZSBudW1iZXIgYnkgcGllIGNoYXJ0DQpwaWUoY291bnRyeV9wcm9wKQ0KYGBgDQoNCmBgYHtyfQ0KI1Rha2UgdGhlIFVTQSBvdXQgYW5kIHJlcGxvdCB0aGUgZGF0YQ0KcGllKGNvdW50cnlfc29ydFsyOjIwXS9ucm93KGRmKSkNCmBgYA0KDQoqKlEqKjogRXhwZWN0ZWQgaW5zaWdodCAjMS4NCg0KKipBKio6IFNpbmNlIFVTQSBoYXMgdGhlIGJpZ2dlc3QgbW92aWUgaW5kdXN0cnkgaW4gdGhlIHdvcmxkLCBpdCBpcyB2ZXJ5IG5vcm1hbCB0byBzZWUgdGhhdCBvdmVyIGhhbGYgb2YgdGhlIG1vdmllcyB3YXMgcHJvZHVjZWQgYnkgVVNBLg0KDQoNCioqUSoqOiBFeHBlY3RlZCBpbnNpZ2h0ICMyLg0KDQoqKkEqKjogV2l0aCBmZXcgZXhjZXB0aW9ucywgbW92aWVzIGFsbCBjb21lIGZyb20gZGV2ZWxvcGVkIGNvdW50cmllcy4gU2luY2UgbW92aWUgaW5kdXN0cnkgcmVxdWlyZXMgYSBsb3Qgb2YgaW52ZXN0bWVudCwgaXQgaXMgbm9ybWFsIHRvIHNlZSB0aGUgYWJzZW5jZSBvZiBkZXZlbG9waW5nIGNvdW50cmllcyBhbmQgdW5kZXItZGV2ZWxvcGVkIGNvdW50cmllcy4NCg0KDQojIyAxMC4gVW5leHBlY3RlZCBpbnNpZ2h0DQoNCkNvbWUgdXAgd2l0aCBvbmUgbmV3IGluc2lnaHQgKGJhY2tlZCB1cCBieSBkYXRhIGFuZCBncmFwaHMpIHRoYXQgaXMgdW5leHBlY3RlZCBhdCBmaXJzdCBnbGFuY2UgYW5kIGRvIHlvdXIgYmVzdCB0byBtb3RpdmF0ZSBpdC4gU2FtZSBpbnN0cnVjdGlvbnMgYXBwbHkgYXMgdGhlIHByZXZpb3VzIHRhc2suDQoNCmBgYHtyfQ0KIyBUT0RPOiBGaW5kIGFuZCBpbGx1c3RyYXRlIG9uZSB1bmV4cGVjdGVkIGluc2lnaHQNCiMgVG8gaW52ZXN0aWdhdGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdyb3NzIHJldmVudWUgYW5kIGNvdW50cmllcw0KY291bnRyeV9ncm9zcz1jYmluZChkZlssIGMoIkdyb3NzIiwgIkRvbWVzdGljX0dyb3NzIiwgIkJ1ZGdldCIpXSwgY291bnRyeSkNCiMgQ2FsY3VsYXRlIHRoZSB0aGUgcGVyY2VudGFnZSBvZiBkb21lc3RpYyBncm9zcyBvdmVyIG92ZXJhbGwgZ3Jvc3MNCmNvdW50cnlfZ3Jvc3MkZ3Jvc3NfcGVyYz1jb3VudHJ5X2dyb3NzJERvbWVzdGljX0dyb3NzL2NvdW50cnlfZ3Jvc3MkR3Jvc3MNCg0KYGBgDQoNCmBgYHtyfQ0KIyBTcGxpdCB0aGUgZGF0YXNldCBpbnRvIGRvbWVzdGljIG1hcmtldCBhbmQgaW50ZXJuYXRpb25hbCBtYXJrZXQgYmFzZWQgb24gZ3Jvc3MgcGVyY2VudGFnZQ0KZG9tZXN0aWNfbWFya2V0PWNvdW50cnlfZ3Jvc3NbIWlzLm5hKGNvdW50cnlfZ3Jvc3MkZ3Jvc3NfcGVyYyksXQ0KZG9tZXN0aWNfbWFya2V0PWRvbWVzdGljX21hcmtldFtkb21lc3RpY19tYXJrZXQkZ3Jvc3NfcGVyYz4wLjUsXQ0KaW50ZXJuYXRpb25hbF9tYXJrZXQ9Y291bnRyeV9ncm9zc1shaXMubmEoY291bnRyeV9ncm9zcyRncm9zc19wZXJjKSxdDQppbnRlcm5hdGlvbmFsX21hcmtldD1pbnRlcm5hdGlvbmFsX21hcmtldFtpbnRlcm5hdGlvbmFsX21hcmtldCRncm9zc19wZXJjPD0wLjUsXQ0KYGBgDQoNCmBgYHtyfQ0KZG9tZXN0aWNfbWFya2V0PWRvbWVzdGljX21hcmtldFssICEobmFtZXMoZG9tZXN0aWNfbWFya2V0KSAlaW4lIGMoIkdyb3NzIiwgIkRvbWVzdGljX0dyb3NzIiwgIkJ1ZGdldCIsICJncm9zc19wZXJjIikpXQ0KaW50ZXJuYXRpb25hbF9tYXJrZXQ9aW50ZXJuYXRpb25hbF9tYXJrZXRbLCAhKG5hbWVzKGludGVybmF0aW9uYWxfbWFya2V0KSAlaW4lIGMoIkdyb3NzIiwgIkRvbWVzdGljX0dyb3NzIiwgIkJ1ZGdldCIsICJncm9zc19wZXJjIikpXQ0KYGBgDQoNCmBgYHtyfQ0KcGllKGNvbFN1bXMoZG9tZXN0aWNfbWFya2V0KVsxOjEwXSwgbWFpbj0iRG9tZXN0aWMgVVNBIGluY2x1ZGVkIikNCnBpZShjb2xTdW1zKGRvbWVzdGljX21hcmtldClbMjoxMF0sIG1haW49IkRvbWVzdGljIFVTQSBleGNsdWRlZCIpDQpwaWUoY29sU3VtcyhpbnRlcm5hdGlvbmFsX21hcmtldClbMToxMF0sIG1haW49IkludGVybmF0aW9uYWwgVVNBIGluY2x1ZGVkIikNCnBpZShjb2xTdW1zKGludGVybmF0aW9uYWxfbWFya2V0KVsyOjEwXSwgbWFpbj0iSW50ZXJuYXRpb25hbCBVU0EgZXhjbHVkZWQiKQ0KYGBgDQoNCg0KKipRKio6IFVuZXhwZWN0ZWQgaW5zaWdodC4NCg0KKipBKio6IEV2ZW4gdGhvdWdoIFVTQSBtb3ZpZSBpbmR1c3RyeSBpcyB2ZXJ5IGludGVybmF0aW9uYWwsIGhvd2V2ZXIsIG1vc3Qgb2YgaXRzIG1vdmllcyBmb2N1cyBvbiBkb21lc3RpYyBtYXJrZXQuIEZyb20gdGhlIGZpcnN0IHBpZSBjaGFydCBhYm92ZSwgd2UgY2FuIGZpbmQgVVNBIGFjY291bnQgZm9yIG1vcmUgdGhhbiA3NSUgZG9tZXN0aWMgZm9jdXNpbmcgbW92aWVzIGNvbXBhcmVkIHRvIGFib3V0IGEgaGFsZiBpbiB0aGUgdGhpcmQgcGllIGNoYXJ0Lg0KDQo=