Practicum: Functions & Loops

Exersize week~5

library(htmltools)

HTML('
<div class="profile-card">
  <div>
    <img src="Almetcokkk.JPG">
  </div>
  
  <div class="profile-name">Veronica Maria Lucia F Xavier</div>
  
  <div class="divider"></div>
  
  <div class="profile-nim">NIM: 52250021</div>
</div>
')
Veronica Maria Lucia F Xavier
NIM: 52250021

1 Task: Dynamic Multi-Formula Function

In this task, we use Functions and Loops in R to make calculations faster and easier. Instead of writing the same math formula many times, we create a function that can do it for us automatically. We also use loops so the computer can process many numbers at once without us having to do it one by one

# Main function: compute various formulas based on input
compute_formula <- function(x_vals, formula) {
  
# Validate formula input
  valid_formulas <- c("linear", "quadratic", "cubic", "exponential")
  if (!formula %in% valid_formulas) {
    stop(paste("Invalid formula! Choose one of:", 
               paste(valid_formulas, collapse = ", ")))
  }
  
# Calculate results based on formula type
  results <- numeric(length(x_vals))
  for (i in seq_along(x_vals)) {
    x <- x_vals[i]
    if (formula == "linear")      results[i] <- 2 * x + 1
    if (formula == "quadratic")   results[i] <- x^2 - 3 * x + 2
    if (formula == "cubic")       results[i] <- x^3 - 2 * x^2 + x
    if (formula == "exponential") results[i] <- exp(0.3 * x)
  }
  return(results)
}

# Calculate all formulas for x = 1:20
x_vals <- 1:20
formulas <- c("linear", "quadratic", "cubic", "exponential")

# Use a nested loop: each formula is calculated and stored in a data frame
all_results <- data.frame()
for (f in formulas) {
  y_vals <- compute_formula(x_vals, f)
  temp_df <- data.frame(x = x_vals, y = y_vals, formula = f)
  all_results <- rbind(all_results, temp_df)
}

# Plot all formulas in one graph
ggplot(all_results, aes(x = x, y = y, color = formula)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 1.5) +
  labs(title = "Task 1: Mathematical Formula Comparison",
       x = "X Value", y = "Y Value", color = "Formula") +
  theme_minimal() +
  scale_color_brewer(palette = "Set1")

2 Task: Nested Simulation - Multi-Sales & Discounts

This task aims to simulate sales activities using R by applying nested functions. The model represents multiple salespersons over a certain number of days, where each sale is influenced by different discount rates based on the sales amount. By running this simulation, we can better understand how sales performance, discounts, and cumulative results change over time.

# 1. Define the Discount Logic Function
apply_discount <- function(sales_amount) {
  if (sales_amount > 10000)      discount <- 0.20  # 20% off
  else if (sales_amount > 7000)  discount <- 0.15  # 15% off
  else if (sales_amount > 4000)  discount <- 0.10  # 10% off
  else                           discount <- 0.05  # 5% off
  return(discount)
}

# 2. Define the Simulation Engine
simulate_sales <- function(n_salesperson, days) {
  set.seed(42) # Ensures the same results every time
  sales_data <- data.frame()
  
  for (s in 1:n_salesperson) {
    cumulative <- 0
    for (d in 1:days) {
      # Generate random sales between 1,000 and 15,000
      amount <- round(runif(1, min = 1000, max = 15000), 2)
      discount <- apply_discount(amount)
      cumulative <- cumulative + amount
      
      # Create a single row for this day
      row <- data.frame(
        sales_id         = s,
        day              = d,
        sales_amount     = amount,
        discount_rate    = discount,
        net_sales        = amount * (1 - discount),
        cumulative_sales = cumulative
      )
      sales_data <- rbind(sales_data, row)
    }
  }
  return(sales_data)
}

# 3. Run the Simulation
df_sales <- simulate_sales(n_salesperson = 5, days = 10)

# 4. Create a Summary Table
summary_performance <- df_sales %>%
  group_by(sales_id) %>%
  summarise(
    Total_Revenue    = sum(sales_amount),
    Average_Sales    = mean(sales_amount),
    Avg_Discount_Pct = mean(discount_rate) * 100,
    Final_Cumulative = max(cumulative_sales),
    .groups = "drop"
  )

print(summary_performance)
## # A tibble: 5 × 5
##   sales_id Total_Revenue Average_Sales Avg_Discount_Pct Final_Cumulative
##      <int>         <dbl>         <dbl>            <dbl>            <dbl>
## 1        1        99077.         9908.             16.5           99077.
## 2        2        92604.         9260.             15.5           92604.
## 3        3        96154.         9615.             15             96154.
## 4        4        82690.         8269.             14             82690.
## 5        5        98664.         9866.             15.5           98664.
ggplot(df_sales, aes(x = day, 
                     y = cumulative_sales, 
                     color = factor(sales_id), 
                     group = sales_id)) +
  geom_line(linewidth = 1.2) + 
  geom_point(size = 2) +
  scale_color_brewer(palette = "Dark2") +
  labs(
    title = "Cumulative Sales Performance",
    subtitle = "Tracking 10-day revenue growth per salesperson",
    x = "Simulation Day",
    y = "Total Sales (Currency)",
    color = "Staff ID"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

3 Task: Multi-Level Performance Categorization

This task focuses on classifying sales performance into different levels based on sales volume. By using a function, each sales value is grouped into categories such as Excellent, Very Good, Good, Average, and Poor. This helps to clearly understand the distribution of performance and makes it easier to analyze overall sales effectiveness.

library(tidyr)
df_sales <- df_sales %>%
  mutate(performance = case_when(
    sales_amount >= 12000 ~ "Excellent",
    sales_amount >= 9000  ~ "Very Good",
    sales_amount >= 6000  ~ "Good",
    sales_amount >= 3000  ~ "Average",
    TRUE                  ~ "Poor" 
  )) %>%

  mutate(performance = factor(performance, 
                              levels = c("Excellent", "Very Good", "Good", "Average", "Poor")))

perf_summary <- df_sales %>%
  count(performance) %>%
  mutate(percentage = n / sum(n),
         label = paste0(performance, "\n", scales::percent(percentage, accuracy = 0.1)))


ggplot(perf_summary, aes(x = "", y = percentage, fill = performance)) +
  geom_bar(stat = "identity", width = 1, color = "white", linewidth = 0.8) + 
  coord_polar("y", start = 0) +
  geom_text(aes(label = label),
            position = position_stack(vjust = 0.5), 
            size = 4, 
            fontface = "bold",
            color = "gray10") +
  scale_fill_brewer(palette = "RdYlGn", direction = -1) +
  labs(title = "Performance Category Distribution",
       subtitle = "Analysis based on total sales volume",
       caption = "Source: Internal Sales Data") +
  theme_void() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
    plot.subtitle = element_text(hjust = 0.5, size = 12, color = "gray30"),
    legend.position = "none"
  )

4 Task: Simulate Dataset Multi-Company

This task simulates a multi-company employee dataset using nested loops and conditional logic. A total of 4 companies are generated, each with 20 employees. Each employee has attributes including salary, department, performance score, and KPI score. Employees with a KPI score above 90 are flagged as top performers. The output includes a summary table per company and visualizations of KPI and salary distributions.

library(dplyr)
library(ggplot2)

# Function to calculate KPI score based on performance score
calculate_kpi <- function(performance_score) {
  
  # High performers get higher KPI bonus
  if (performance_score >= 85) {
    kpi <- performance_score + runif(1, 5, 15)
  } else if (performance_score >= 70) {
    kpi <- performance_score + runif(1, 0, 10)
  } else {
    kpi <- performance_score - runif(1, 0, 5)
  }
  
  # Ensure KPI does not exceed 100
  return(min(round(kpi, 1), 100))
}

# Main function: generate employee dataset from multiple companies
generate_company_data <- function(num_companies, num_employees) {
  
  set.seed(123)
  
  departments <- c("Finance", "Marketing", "Operations", "HR", "Technology")
  all_data <- data.frame()
  
  # Outer loop: companies
  for (p in 1:num_companies) {
    
    # Inner loop: employees
    for (k in 1:num_employees) {
      
      performance_score <- round(runif(1, min = 50, max = 100), 1)
      kpi_score         <- calculate_kpi(performance_score)
      salary            <- round(runif(1, min = 4000000, max = 20000000), 0)
      department        <- sample(departments, 1)
      
      # Mark top performers
      top_performer <- ifelse(kpi_score > 90, "Yes", "No")
      
      row <- data.frame(
        company_id       = paste0("Company-", p),
        employee_id      = paste0("E", p, "-", k),
        salary           = salary,
        department       = department,
        performance_score= performance_score,
        kpi_score        = kpi_score,
        top_performer    = top_performer
      )
      
      all_data <- rbind(all_data, row)
    }
  }
  
  return(all_data)
}

# Generate dataset: 4 companies, 20 employees each
company_data <- generate_company_data(num_companies = 4, num_employees = 20)

# Summary statistics per company
company_summary <- company_data %>%
  group_by(company_id) %>%
  summarise(
    avg_salary        = round(mean(salary), 0),
    avg_performance   = round(mean(performance_score), 1),
    avg_kpi           = round(mean(kpi_score), 1),
    highest_kpi       = max(kpi_score),
    total_top         = sum(top_performer == "Yes"),
    .groups = "drop"
  )

print(company_summary)
## # A tibble: 4 × 6
##   company_id avg_salary avg_performance avg_kpi highest_kpi total_top
##   <chr>           <dbl>           <dbl>   <dbl>       <dbl>     <int>
## 1 Company-1    11838980            69.8    70.7         100         4
## 2 Company-2    11915675            73.3    75.6         100         4
## 3 Company-3    11504080            70.6    72.3         100         3
## 4 Company-4    10394888            77.4    80.1         100        10
ggplot(company_summary, 
       aes(x = factor(company_id, levels = paste0("Company-", 1:4)),
           y = avg_kpi, 
           fill = company_id)) +
  
  # Create the bar chart
  geom_col(width = 0.6) +
  
  # Add value labels on top of bars (rounded to 1 decimal place)
  geom_text(aes(label = round(avg_kpi, 1)),
            vjust = -0.5, size = 4.5, fontface = "bold") +
  
  # Titles and Axis Labels
  labs(
    title    = "Average KPI Score per Company",
    subtitle = "Performance comparison across companies",
    x        = "Company",
    y        = "Average KPI Score"
  ) +
  
  # Professional theme and styling
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "none",
    plot.title      = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle   = element_text(hjust = 0.5, color = "gray40"),
    axis.text.x     = element_text(size = 11),
    axis.text.y     = element_text(size = 11),
    panel.grid.major.x = element_blank() # Clean up vertical grid lines
  ) +
  
  # Color palette
  scale_fill_brewer(palette = "Set2")

company_data$company_id <- factor(
  company_data$company_id,
  levels = c("Company-1", "Company-2", "Company-3", "Company-4")
)

# ================================
# Visualization 2: Salary Distribution
# ================================
plot2 <- ggplot(company_data, 
                aes(x = company_id, 
                    y = salary, 
                    fill = company_id)) +
  
  geom_boxplot(width = 0.5, alpha = 0.7, outlier.color = "red") +
  
  geom_jitter(width = 0.15, alpha = 0.3, size = 1.5) +
  
  stat_summary(fun = median, geom = "point", size = 3) +
  
  labs(
    title    = "Task 4: Salary Distribution per Company",
    subtitle = "Ordered by Company ID (Company-1 to Company-4)",
    x        = "Company",
    y        = "Salary (IDR)"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5, size = 11)
  ) +
  
  scale_fill_brewer(palette = "Set2")

plot2

5 Task: Monte Carlo simulation

This task implements a Monte Carlo simulation to estimate the value of Pi using randomly generated points inside a unit square. A total of 2,000 random points are plotted, and the ratio of points falling inside the circle is used to approximate Pi. Additionally, the probability of a random point landing inside a defined sub-square region is calculated and visualized.

library(ggplot2)

# Monte Carlo simulation function to estimate the value of Pi
monte_carlo_pi <- function(num_points) {
  set.seed(99)
  
  # Generate random points in range [-1, 1]
  x_coord <- runif(num_points, -1, 1)
  y_coord <- runif(num_points, -1, 1)
  
  # Compute distance from origin (0,0)
  distance <- sqrt(x_coord^2 + y_coord^2)
  
  # Classify points
  position <- ifelse(distance <= 1, "Inside", "Outside")
  
  # Pi estimation
  points_inside <- sum(position == "Inside")
  pi_estimate   <- (points_inside / num_points) * 4
  
  # Sub-square probability (0 ≤ x ≤ 0.5 and 0 ≤ y ≤ 0.5)
  in_subsquare <- sum(x_coord >= 0 & x_coord <= 0.5 &
                      y_coord >= 0 & y_coord <= 0.5)
  prob_subsquare <- round(in_subsquare / num_points, 4)
  
  # Clear console output
  cat("========== Monte Carlo Simulation ==========\n")
  cat("Total Points       :", num_points, "\n")
  cat("Points Inside      :", points_inside, "\n")
  cat("Estimated Pi       :", round(pi_estimate, 5), "\n")
  cat("Actual Pi          :", round(pi, 5), "\n")
  cat("Sub-square Prob.   :", prob_subsquare, "\n")
  cat("===========================================\n")
  
  # Return structured data
  return(list(
    data = data.frame(x = x_coord, y = y_coord, position = position),
    pi_estimate = pi_estimate,
    prob_subsquare = prob_subsquare
  ))
}

# Run simulation
result <- monte_carlo_pi(num_points = 2000)
## ========== Monte Carlo Simulation ==========
## Total Points       : 2000 
## Points Inside      : 1571 
## Estimated Pi       : 3.142 
## Actual Pi          : 3.14159 
## Sub-square Prob.   : 0.055 
## ===========================================
monte_carlo_data <- result$data

# Create circle boundary
circle_angle <- seq(0, 2 * pi, length.out = 300)
circle_path <- data.frame(
  cx = cos(circle_angle),
  cy = sin(circle_angle)
)
ggplot(monte_carlo_data, aes(x = x, y = y, color = position)) +
  # Individual points
  geom_point(size = 1, alpha = 0.6) +
  # Circle boundary
  geom_path(data = circle_path, aes(x = cx, y = cy), 
            color = "black", linewidth = 1, inherit.aes = FALSE) +
  # Sub-square analysis box
  annotate("rect", xmin = 0, xmax = 0.5, ymin = 0, ymax = 0.5, 
           fill = NA, color = "purple", linewidth = 1, linetype = "dashed") +
  # Labels and titles
  labs(
    title    = "Task 5: Monte Carlo Simulation - Pi Estimation",
    subtitle = "Purple dashed box = Sub-square for probability analysis",
    x        = "X Coordinate",
    y        = "Y Coordinate",
    color    = "Point Position",
    caption  = paste("Estimated Pi =", round((sum(monte_carlo_data$position == 
                                                    "Inside")/2000)*4, 4))
  ) +
  # Custom colors (Blue for Inside, Red for Outside)
  scale_color_manual(values = c("Inside" = "#2196F3", "Outside" = "#F44336")) +
  coord_fixed() + 
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", size = 14))

6 Task: Data Transformation

This task demonstrates data transformation techniques using loop-based normalization. Two methods are applied: Min-Max normalization, which rescales values to a range of 0 to 1, and Z-Score standardization, which centers values around a mean of 0 with a standard deviation of 1. New features such as salary bracket and performance category are also engineered from the existing dataset. Distributions before and after transformation are compared using histograms and boxplots.

library(dplyr)
library(ggplot2)

# Min-Max Normalization (scale 0–1)
minmax_normalization <- function(df) {
  result <- df
  
  for (col in names(df)) {
    if (is.numeric(df[[col]])) {
      min_val <- min(df[[col]], na.rm = TRUE)
      max_val <- max(df[[col]], na.rm = TRUE)
      
      result[[col]] <- round(
        (df[[col]] - min_val) / (max_val - min_val), 4
      )
    }
  }
  return(result)
}

# Z-Score Standardization (mean = 0, sd = 1)
zscore_normalization <- function(df) {
  result <- df
  
  for (col in names(df)) {
    if (is.numeric(df[[col]])) {
      mean_val <- mean(df[[col]], na.rm = TRUE)
      sd_val   <- sd(df[[col]], na.rm = TRUE)
      
      result[[col]] <- round(
        (df[[col]] - mean_val) / sd_val, 4
      )
    }
  }
  return(result)
}

# 3. DATA PREPARATION
# Select numeric columns
numeric_data <- company_data[, c("salary", "performance_score", "kpi_score")]

# Apply normalization
data_minmax <- minmax_normalization(numeric_data)
data_zscore <- zscore_normalization(numeric_data)


# 4. FEATURE ENGINEERING

company_data <- company_data %>%
  mutate(
    salary_category = case_when(
      salary >= 15000000 ~ "High",
      salary >= 9000000  ~ "Medium",
      TRUE             ~ "Low"
    ),
    performance_category = case_when(
      performance_score >= 85 ~ "Excellent",
      performance_score >= 70 ~ "Good",
      TRUE                ~ "Needs Improvement"
    )
  )

# 5. DATA COMBINATION FOR VISUALIZATION

# Salary comparison (before vs after normalization)
before_data <- numeric_data %>% mutate(status = "Before Normalization")
after_data  <- data_minmax %>% mutate(status = "After Min-Max")

combined_data <- rbind(before_data, after_data)
# 6. VISUALIZATION 1: SALARY DISTRIBUTION

ggplot(combined_data, aes(x = salary, fill = status)) +
  
  geom_histogram(
    bins = 12,
    alpha = 0.5,          
    position = "identity",
    color = "white"
  ) +
  
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
  
  labs(
    title    = "Salary Distribution Before and After Normalization",
    subtitle = "Comparison using Min-Max Normalization",
    x        = "Salary Value",
    y        = "Frequency",
    fill     = "Data Status"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5),
    
    axis.title = element_text(face = "bold"),
    
    panel.grid.major.x = element_blank()
  ) +
  
  scale_fill_manual(values = c(
    "Before Normalization" = "#EF9A9A",
    "After Min-Max"        = "#90CAF9"
  ))

# 7. VISUALIZATION 2: KPI BOXPLOT
# Combine KPI data (before vs after normalization)
# Combine KPI data
kpi_combined <- data.frame(
  value  = c(numeric_data$kpi_score, data_minmax$kpi_score),
  status = c(
    rep("Before Normalization", nrow(numeric_data)),
    rep("After Min-Max", nrow(data_minmax))
  )
)

# Plot
ggplot(kpi_combined, aes(x = status, y = value, fill = status)) +
  
  geom_boxplot(width = 0.5, alpha = 0.7, outlier.color = "red") +
  
  geom_jitter(width = 0.15, alpha = 0.3, size = 1.5) +
  
  labs(
    title    = "KPI Score Distribution Before and After Normalization",
    subtitle = "Min-Max normalization rescales data to 0–1 range",
    x        = "Data Status",
    y        = "KPI Score"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    legend.position = "none",
    
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5),
    
    axis.title = element_text(face = "bold"),
    axis.text  = element_text(size = 11)
  ) +
  
  scale_fill_manual(values = c(
    "Before Normalization" = "#FFCC80",
    "After Min-Max"        = "#A5D6A7"
  ))

7 Task: Mini Project - Dashboard

This mini project generates a large-scale dataset consisting of 6 companies with 50 employees each. The dataset includes employee ID, company ID, salary, performance score, KPI score, and department. Employees are categorized into KPI tiers: Platinum, Gold, Silver, and Bronze. The analysis includes per-company summaries, department-level comparisons, and advanced visualizations such as grouped bar charts, scatter plots with regression lines, and salary distribution histograms.

library(dplyr)
library(ggplot2)

generate_company_data <- function(num_companies, num_employees) {
  
  set.seed(123)
  departments <- c("Finance", "Marketing", "Operations", "HR", "Technology")
  all_data <- data.frame()
  
  for (c in 1:num_companies) {
    for (e in 1:num_employees) {
      
      performance_score <- round(runif(1, 50, 100), 1)
      
      # KPI calculation
      if (performance_score >= 85) {
        kpi_score <- performance_score + runif(1, 5, 15)
      } else if (performance_score >= 70) {
        kpi_score <- performance_score + runif(1, 0, 10)
      } else {
        kpi_score <- performance_score - runif(1, 0, 5)
      }
      
      kpi_score <- min(round(kpi_score, 1), 100)
      
      salary <- round(runif(1, 4000000, 20000000), 0)
      department <- sample(departments, 1)
      
      top_performer <- ifelse(kpi_score > 90, "Yes", "No")
      
      row <- data.frame(
        company_id        = paste0("Company-", c),
        employee_id       = paste0("E", c, "-", e),
        salary            = salary,
        department        = department,
        performance_score = performance_score,
        kpi_score         = kpi_score,
        top_performer     = top_performer
      )
      
      all_data <- rbind(all_data, row)
    }
  }
  
  return(all_data)
}

data_dashboard <- generate_company_data(6, 50)

# Ensure correct order
data_dashboard$company_id <- factor(
  data_dashboard$company_id,
  levels = paste0("Company-", 1:6)
)

# Add categories
data_dashboard <- data_dashboard %>%
  mutate(
    salary_category = case_when(
      salary >= 15000000 ~ "High",
      salary >= 9000000  ~ "Medium",
      TRUE               ~ "Low"
    ),
    kpi_level = case_when(
      kpi_score >= 90 ~ "Platinum",
      kpi_score >= 80 ~ "Gold",
      kpi_score >= 70 ~ "Silver",
      TRUE            ~ "Bronze"
    )
  )


dashboard_summary <- data_dashboard %>%
  group_by(company_id) %>%
  summarise(
    avg_salary      = round(mean(salary), 0),
    avg_kpi         = round(mean(kpi_score), 1),
    avg_performance = round(mean(performance_score), 1),
    total_top       = sum(top_performer == "Yes"),
    .groups = "drop"
  )

print(dashboard_summary)
## # A tibble: 6 × 5
##   company_id avg_salary avg_kpi avg_performance total_top
##   <fct>           <dbl>   <dbl>           <dbl>     <int>
## 1 Company-1    11985826    72.4            71.1         9
## 2 Company-2    11127014    76.9            74.8        18
## 3 Company-3    12609185    78.2            76          17
## 4 Company-4    11493841    73.7            71.4        12
## 5 Company-5    12365574    79.5            76.7        19
## 6 Company-6    11371336    73.3            71.7        12

7.1 Chart 1 : Top Performers per Company

The number of top performers varies across companies, indicating differences in employee performance quality. Some companies have significantly more high-performingemployees, which suggests stronger performance management or talent quality.

ggplot(dashboard_summary, 
       aes(x = factor(company_id, levels = paste0("Company-", 1:6)), 
           y = total_top, 
           fill = company_id)) +
  
  # Bar
  geom_col(width = 0.6) +
  
  # Label di atas bar
  geom_text(aes(label = total_top), 
            vjust = -0.4, size = 4, fontface = "bold") +
  
  # Skala Y biar tidak kepotong
  scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
  
  labs(
    title    = "Top Performers per Company",
    subtitle = "Employees with KPI Score Above 90",
    x        = "Company",
    y        = "Number of Top Performers"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    legend.position = "none",
    
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5, size = 11),
    
    axis.text.x = element_text(size = 11),
    axis.text.y = element_text(size = 11),
    axis.title  = element_text(face = "bold"),
    
    panel.grid.major.x = element_blank()
  ) +
  
  scale_fill_brewer(palette = "Set2")

7.2 Chart 2 : Average KPI by Department & Company

KPI scores differ not only between companies but also across departments. Certain departments consistently show higher KPI averages, suggesting that performance may depend on the nature of the department and its operational focus.

# Summary
department_summary <- data_dashboard %>%
  group_by(company_id, department) %>%
  summarise(avg_kpi = round(mean(kpi_score), 1), .groups = "drop")

# Plot
ggplot(department_summary,
       aes(x = factor(department,
                      levels = c("Finance","Marketing","Operations","HR","Technology")),
           y = avg_kpi,
           fill = company_id)) +
  
  # Bar chart
  geom_col(position = position_dodge(width = 0.7), width = 0.6) +
  
  scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
  
  labs(
    title    = "Average KPI by Department and Company",
    subtitle = "Comparison across departments",
    x        = "Department",
    y        = "Average KPI",
    fill     = "Company"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5),
    
    axis.text.x = element_text(angle = 25, hjust = 1),
    axis.title  = element_text(face = "bold"),
    
    panel.grid.major.x = element_blank()
  ) +
  
  scale_fill_brewer(palette = "Set2")

7.3 Chart 3 : Performance vs KPI Relationship

There is a clear positive relationship between performance scores and KPI scores. Employees with higher performance scores tend to achieve higher KPI values, confirming that KPI evaluation aligns well with performance metrics.

ggplot(data_dashboard, 
       aes(x = performance_score, 
           y = kpi_score, 
           color = company_id)) +
  
  # Titik data
  geom_point(alpha = 0.6, size = 2) +
  
  # Garis regresi
    geom_smooth(
    method = "lm",
    formula = y ~ x,
    se = FALSE,
    linewidth = 1
  ) +
  
  geom_hline(yintercept = 80, linetype = "dashed", color = "gray60") +
  geom_vline(xintercept = 75, linetype = "dashed", color = "gray60") +
  
  labs(
    title    = "Performance Score vs KPI Score",
    subtitle = "Relationship between performance and KPI across companies",
    x        = "Performance Score",
    y        = "KPI Score",
    color    = "Company"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5),
    
    axis.title = element_text(face = "bold"),
    axis.text  = element_text(size = 11)
  ) +
  
  scale_color_brewer(palette = "Dark2")

7.4 Chart 4 : Salary Distribution per Company

Salary distributions vary across companies, but most follow a relatively simila r spread. This indicates that while salary ranges are comparable, there may still be slight differences in compensation strategies between companies.

ggplot(data_dashboard, aes(x = salary, fill = company_id)) +
  
  geom_histogram(
    bins = 10,              
    color = "white",
    alpha = 0.8
  ) +
  
  facet_wrap(~company_id, ncol = 3) +
  
  labs(
    title    = "Salary Distribution per Company",
    subtitle = "Histogram of employee salaries across companies",
    x        = "Salary (IDR)",
    y        = "Frequency"
  ) +
  
  theme_minimal() +
  
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5)
  ) +
  
  scale_fill_brewer(palette = "Set2")

7.5 Chart 5 : KPI Level Distribution

Most employees fall into the Gold and Silver categories, while fewer reach the Platinum level. This suggests that achieving top-tier performance is relatively rare, and most employees perform at a moderate to high level.

kpi_level_summary <- data_dashboard %>%
  count(company_id, kpi_level) %>%
  mutate(
    kpi_level = factor(kpi_level,
      levels = c("Platinum", "Gold", "Silver", "Bronze")
    ),
    company_id = factor(company_id, levels = paste0("Company-", 1:6))
  )

# Plot
ggplot(kpi_level_summary,
       aes(x = company_id, y = n, fill = kpi_level)) +
  
  # Bar
  geom_col(width = 0.6) +
  
  # Label 
  geom_text(aes(label = n),
            position = position_stack(vjust = 0.5),
            size = 3.5, color = "white") +
  
  labs(
    title    = "KPI Level Distribution per Company",
    subtitle = "Employee classification based on KPI score",
    x        = "Company",
    y        = "Number of Employees",
    fill     = "KPI Level"
  ) +
  
  theme_minimal(base_size = 12) +
  
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    plot.subtitle = element_text(hjust = 0.5),
    
    axis.title = element_text(face = "bold"),
    axis.text  = element_text(size = 11)
  ) +
  
  scale_fill_manual(values = c(
    "Platinum" = "#9C27B0",
    "Gold"     = "#FFC107",
    "Silver"   = "#9E9E9E",
    "Bronze"   = "#795548"
  ))

8 Automated Report Generation

This task focuses on building an automated reporting system using R. The goal is to generate reports for each company that include summary statistics and visualizations. By using functions and loops, the process becomes faster, more consistent, and reduces manual errors.

library(dplyr)
library(ggplot2)

# =====================================================
# 1. FUNCTION FIX (Ensure argument is: 'comp')
# =====================================================
generate_company_report <- function(data, comp) {
  
  # Filter data based on company ID
  # Use 'comp' according to the function argument
  company_data <- data %>%
    filter(company_id == comp)
  
  # Validation: if no data found, stop the function
  if (nrow(company_data) == 0) return(NULL)
  
  cat("\n\n## REPORT FOR:", comp, "\n")
  cat("-------------------------------------\n")
  
  # ------------------------------
  # SUMMARY STATISTICS
  # ------------------------------
  # Use na.rm = TRUE to avoid errors from missing values
  summary_table <- company_data %>%
    summarise(
      avg_salary      = round(mean(salary, na.rm = TRUE), 0),
      avg_performance = round(mean(performance_score, na.rm = TRUE), 2),
      avg_kpi         = round(mean(kpi_score, na.rm = TRUE), 2),
      total_employees = n()
    )
  
  print(knitr::kable(summary_table))
  
  # ------------------------------
  # PLOT: SALARY DISTRIBUTION
  # ------------------------------
  plot_salary <- ggplot(company_data, aes(x = salary)) +
    
    geom_histogram(
      bins = 10,
      fill = "#A5D6A7",
      color = "white"
    ) +
    
    labs(
      title = paste("Salary Distribution -", comp),
      x = "Salary",
      y = "Frequency"
    ) +
    
    theme_minimal() +
    
    theme(
      plot.title = element_text(hjust = 0.5)  # Center title
    )
  
  print(plot_salary)
  
  cat("\n")
}

# =====================================================
# 2. LOOP EXECUTION (Make sure data_dashboard exists)
# =====================================================

# Get unique company IDs
company_list <- unique(data_dashboard$company_id)

# Run loop
for (item in company_list) {
  generate_company_report(data_dashboard, item)
}
## 
## 
## ## REPORT FOR: Company-1 
## -------------------------------------
## 
## 
## | avg_salary| avg_performance| avg_kpi| total_employees|
## |----------:|---------------:|-------:|---------------:|
## |   11985826|           71.08|   72.35|              50|

## 
## 
## 
## ## REPORT FOR: Company-2 
## -------------------------------------
## 
## 
## | avg_salary| avg_performance| avg_kpi| total_employees|
## |----------:|---------------:|-------:|---------------:|
## |   11127014|           74.77|   76.87|              50|

## 
## 
## 
## ## REPORT FOR: Company-3 
## -------------------------------------
## 
## 
## | avg_salary| avg_performance| avg_kpi| total_employees|
## |----------:|---------------:|-------:|---------------:|
## |   12609185|              76|   78.23|              50|

## 
## 
## 
## ## REPORT FOR: Company-4 
## -------------------------------------
## 
## 
## | avg_salary| avg_performance| avg_kpi| total_employees|
## |----------:|---------------:|-------:|---------------:|
## |   11493841|           71.44|   73.71|              50|

## 
## 
## 
## ## REPORT FOR: Company-5 
## -------------------------------------
## 
## 
## | avg_salary| avg_performance| avg_kpi| total_employees|
## |----------:|---------------:|-------:|---------------:|
## |   12365574|           76.66|   79.47|              50|

## 
## 
## 
## ## REPORT FOR: Company-6 
## -------------------------------------
## 
## 
## | avg_salary| avg_performance| avg_kpi| total_employees|
## |----------:|---------------:|-------:|---------------:|
## |   11371336|           71.67|   73.35|              50|

LS0tDQp0aXRsZTogIlByYWN0aWN1bTogRnVuY3Rpb25zICYgTG9vcHMiICAgICAgICMgTWFpbiB0aXRsZSBvZiB0aGUgZG9jdW1lbnQNCnN1YnRpdGxlOiAiRXhlcnNpemUgd2Vla341IiAgIyBTdWJ0aXRsZSBvciB0b3BpYyBmb3Igd2VlayA0DQphdXRob3I6IA0KLSAiVmVyb25pY2EgTSBMIEYgWGF2aWVyIiAgICAgICAjIFJlcGxhY2Ugd2l0aCB5b3VyIGZ1bGwgbmFtZQ0KZGF0ZTogICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCIgIyBBdXRvIGRpc3BsYXlzIHRoZSBjdXJyZW50IGRhdGUNCm91dHB1dDogICAgICAgICAgICAgICAgICAgICAgICAgIyBPdXRwdXQgc2VjdGlvbiBkZWZpbmVzIHRoZSBmb3JtYXQgYW5kIGxheW91dCANCiAgcm1kZm9ybWF0czo6cmVhZHRoZWRvd246ICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20vanViYS9ybWRmb3JtYXRzDQogICAgc2VsZl9jb250YWluZWQ6IHRydWUgICAgICAgICMgRW1iZWRzIGFsbCByZXNvdXJjZXMgKENTUywgSlMsIGltYWdlcykgDQogICAgdGh1bWJuYWlsczogdHJ1ZSAgICAgICAgICAgICMgRGlzcGxheXMgaW1hZ2UgdGh1bWJuYWlscyBpbiB0aGUgZG9jDQogICAgbGlnaHRib3g6IHRydWUgICAgICAgICAgICAgICMgRW5hYmxlcyBjbGljayB0byBlbmxhcmdlIGltYWdlcw0KICAgIGdhbGxlcnk6IHRydWUgICAgICAgICAgICAgICAjIEdyb3VwcyBpbWFnZXMgaW50byBhbiBpbnRlcmFjdGl2ZSBnYWxsZXJ5DQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlICAgICAgICMgQXV0b21hdGljYWxseSBudW1iZXJzIGFsbCBzZWN0aW9ucw0KICAgIGxpYl9kaXI6IGxpYnMgICAgICAgICAgICAgICAjIERpcmVjdG9yeSB3aGVyZSBKYXZhU2NyaXB0L0NTUyBsaWJyYXJpZXMNCiAgICBkZl9wcmludDogInBhZ2VkIiAgICAgICAgICAgIyBEaXNwbGF5cyBkYXRhIGZyYW1lcyBhcyBpbnRlcmFjdGl2ZSBwYWdlZCANCiAgICBjb2RlX2ZvbGRpbmc6ICJzaG93IiAgICAgICAgIyBBbGxvd3MgZm9sZGluZy91bmZvbGRpbmcgUiBjb2RlIGJsb2NrcyANCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMgICAgICAgICAgIyBBZGRzIGEgYnV0dG9uIHRvIGRvd25sb2FkIGFsbCBSIGNvZGUNCiAgICBjc3M6OlN0eWxlLmNzczoNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpgYGANCg0KPGJvZHk+DQo8c3R5bGU+DQoNCi5wcm9maWxlLWNhcmQgew0KICAgIGJhY2tncm91bmQ6ICNmZmZmZmY7DQogICAgYm9yZGVyLXJhZGl1czogMTVweDsNCiAgICBib3gtc2hhZG93OiAwIDEwcHggMzBweCByZ2JhKDAsIDAsIDAsIDAuMSk7DQogICAgcGFkZGluZzogMzBweDsNCiAgICBtYXgtd2lkdGg6IDUwMHB4Ow0KICAgIG1hcmdpbjogNDBweCBhdXRvOw0KICAgIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgxMzVkZWcsICNmZmU2ZWYsICNmYWQ3ZTgsICNmZmVlZjcpOw0KICAgIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgICBib3JkZXI6IDFweCBzb2xpZCAjZjBmMGYwOw0KfQ0KDQoucHJvZmlsZS1jYXJkIGltZyB7DQogIHdpZHRoOiAxODBweDsNCiAgYm9yZGVyLXJhZGl1czogNTAlOw0KICBib3JkZXI6IDRweCBzb2xpZCB3aGl0ZTsNCiAgYm94LXNoYWRvdzogMCAwIDEycHggcmdiYSgwLDAsMCwwLjI1KTsNCn0NCg0KLnByb2ZpbGUtaW1nOmhvdmVyIHsNCiAgICB0cmFuc2Zvcm06IHNjYWxlKDEuMDUpOyAvKiBFZmVrIHpvb20gc2FhdCBrdXJzb3IgZGkgYXRhcyBmb3RvICovDQp9DQoNCi5wcm9maWxlLW5hbWUgew0KICAgIGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLCBzYW5zLXNlcmlmOw0KICAgIGZvbnQtc2l6ZTogMjRweDsNCiAgICBmb250LXdlaWdodDogNzAwOw0KICAgIGNvbG9yOiAjMmMzZTUwOw0KICAgIG1hcmdpbjogMTBweCAwIDVweCAwOw0KfQ0KDQoucHJvZmlsZS1uaW0gew0KICAgIGZvbnQtZmFtaWx5OiAnQ291cmllciBOZXcnLCBtb25vc3BhY2U7DQogICAgZm9udC1zaXplOiAxNnB4Ow0KICAgIGNvbG9yOiAjN2Y4YzhkOw0KICAgIGxldHRlci1zcGFjaW5nOiAxcHg7DQp9DQoNCi5kaXZpZGVyIHsNCiAgICBoZWlnaHQ6IDJweDsNCiAgICB3aWR0aDogNTBweDsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMzQ5OGRiOw0KICAgIG1hcmdpbjogMTVweCBhdXRvOw0KfQ0KDQo8L3N0eWxlPg0KDQpgYGB7cn0NCmxpYnJhcnkoaHRtbHRvb2xzKQ0KDQpIVE1MKCcNCjxkaXYgY2xhc3M9InByb2ZpbGUtY2FyZCI+DQogIDxkaXY+DQogICAgPGltZyBzcmM9IkFsbWV0Y29ra2suSlBHIj4NCiAgPC9kaXY+DQogIA0KICA8ZGl2IGNsYXNzPSJwcm9maWxlLW5hbWUiPlZlcm9uaWNhIE1hcmlhIEx1Y2lhIEYgWGF2aWVyPC9kaXY+DQogIA0KICA8ZGl2IGNsYXNzPSJkaXZpZGVyIj48L2Rpdj4NCiAgDQogIDxkaXYgY2xhc3M9InByb2ZpbGUtbmltIj5OSU06IDUyMjUwMDIxPC9kaXY+DQo8L2Rpdj4NCicpDQpgYGANCg0KIyMgVGFzazogRHluYW1pYyBNdWx0aS1Gb3JtdWxhIEZ1bmN0aW9uDQpJbiB0aGlzIHRhc2ssIHdlIHVzZSBGdW5jdGlvbnMgYW5kIExvb3BzIGluIFIgdG8gbWFrZSBjYWxjdWxhdGlvbnMgZmFzdGVyIGFuZCBlYXNpZXIuIA0KSW5zdGVhZCBvZiB3cml0aW5nIHRoZSBzYW1lIG1hdGggZm9ybXVsYSBtYW55IHRpbWVzLCB3ZSBjcmVhdGUgYSAqKmZ1bmN0aW9uKiogdGhhdA0KY2FuIGRvIGl0IGZvciB1cyBhdXRvbWF0aWNhbGx5LiBXZSBhbHNvIHVzZSAqKmxvb3BzKiogc28gdGhlIGNvbXB1dGVyIGNhbiBwcm9jZXNzIA0KbWFueSBudW1iZXJzIGF0IG9uY2Ugd2l0aG91dCB1cyBoYXZpbmcgdG8gZG8gaXQgb25lIGJ5IG9uZQ0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBNYWluIGZ1bmN0aW9uOiBjb21wdXRlIHZhcmlvdXMgZm9ybXVsYXMgYmFzZWQgb24gaW5wdXQNCmNvbXB1dGVfZm9ybXVsYSA8LSBmdW5jdGlvbih4X3ZhbHMsIGZvcm11bGEpIHsNCiAgDQojIFZhbGlkYXRlIGZvcm11bGEgaW5wdXQNCiAgdmFsaWRfZm9ybXVsYXMgPC0gYygibGluZWFyIiwgInF1YWRyYXRpYyIsICJjdWJpYyIsICJleHBvbmVudGlhbCIpDQogIGlmICghZm9ybXVsYSAlaW4lIHZhbGlkX2Zvcm11bGFzKSB7DQogICAgc3RvcChwYXN0ZSgiSW52YWxpZCBmb3JtdWxhISBDaG9vc2Ugb25lIG9mOiIsIA0KICAgICAgICAgICAgICAgcGFzdGUodmFsaWRfZm9ybXVsYXMsIGNvbGxhcHNlID0gIiwgIikpKQ0KICB9DQogIA0KIyBDYWxjdWxhdGUgcmVzdWx0cyBiYXNlZCBvbiBmb3JtdWxhIHR5cGUNCiAgcmVzdWx0cyA8LSBudW1lcmljKGxlbmd0aCh4X3ZhbHMpKQ0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHhfdmFscykpIHsNCiAgICB4IDwtIHhfdmFsc1tpXQ0KICAgIGlmIChmb3JtdWxhID09ICJsaW5lYXIiKSAgICAgIHJlc3VsdHNbaV0gPC0gMiAqIHggKyAxDQogICAgaWYgKGZvcm11bGEgPT0gInF1YWRyYXRpYyIpICAgcmVzdWx0c1tpXSA8LSB4XjIgLSAzICogeCArIDINCiAgICBpZiAoZm9ybXVsYSA9PSAiY3ViaWMiKSAgICAgICByZXN1bHRzW2ldIDwtIHheMyAtIDIgKiB4XjIgKyB4DQogICAgaWYgKGZvcm11bGEgPT0gImV4cG9uZW50aWFsIikgcmVzdWx0c1tpXSA8LSBleHAoMC4zICogeCkNCiAgfQ0KICByZXR1cm4ocmVzdWx0cykNCn0NCg0KIyBDYWxjdWxhdGUgYWxsIGZvcm11bGFzIGZvciB4ID0gMToyMA0KeF92YWxzIDwtIDE6MjANCmZvcm11bGFzIDwtIGMoImxpbmVhciIsICJxdWFkcmF0aWMiLCAiY3ViaWMiLCAiZXhwb25lbnRpYWwiKQ0KDQojIFVzZSBhIG5lc3RlZCBsb29wOiBlYWNoIGZvcm11bGEgaXMgY2FsY3VsYXRlZCBhbmQgc3RvcmVkIGluIGEgZGF0YSBmcmFtZQ0KYWxsX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgpDQpmb3IgKGYgaW4gZm9ybXVsYXMpIHsNCiAgeV92YWxzIDwtIGNvbXB1dGVfZm9ybXVsYSh4X3ZhbHMsIGYpDQogIHRlbXBfZGYgPC0gZGF0YS5mcmFtZSh4ID0geF92YWxzLCB5ID0geV92YWxzLCBmb3JtdWxhID0gZikNCiAgYWxsX3Jlc3VsdHMgPC0gcmJpbmQoYWxsX3Jlc3VsdHMsIHRlbXBfZGYpDQp9DQoNCiMgUGxvdCBhbGwgZm9ybXVsYXMgaW4gb25lIGdyYXBoDQpnZ3Bsb3QoYWxsX3Jlc3VsdHMsIGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gZm9ybXVsYSkpICsNCiAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDEuMikgKw0KICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsNCiAgbGFicyh0aXRsZSA9ICJUYXNrIDE6IE1hdGhlbWF0aWNhbCBGb3JtdWxhIENvbXBhcmlzb24iLA0KICAgICAgIHggPSAiWCBWYWx1ZSIsIHkgPSAiWSBWYWx1ZSIsIGNvbG9yID0gIkZvcm11bGEiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKQ0KYGBgDQoNCiMjIFRhc2s6IE5lc3RlZCBTaW11bGF0aW9uIC0gTXVsdGktU2FsZXMgJiBEaXNjb3VudHMNClRoaXMgdGFzayBhaW1zIHRvIHNpbXVsYXRlIHNhbGVzIGFjdGl2aXRpZXMgdXNpbmcgUiBieSBhcHBseWluZyBuZXN0ZWQgZnVuY3Rpb25zLg0KVGhlIG1vZGVsIHJlcHJlc2VudHMgbXVsdGlwbGUgc2FsZXNwZXJzb25zIG92ZXIgYSBjZXJ0YWluIG51bWJlciBvZiBkYXlzLCB3aGVyZSBlYWNoIA0Kc2FsZSBpcyBpbmZsdWVuY2VkIGJ5IGRpZmZlcmVudCBkaXNjb3VudCByYXRlcyBiYXNlZCBvbiB0aGUgc2FsZXMgYW1vdW50LiBCeSBydW5uaW5nIHRoaXMgc2ltdWxhdGlvbiwgd2UgY2FuIGJldHRlciB1bmRlcnN0YW5kIGhvdyBzYWxlcyBwZXJmb3JtYW5jZSwgZGlzY291bnRzLCBhbmQgY3VtdWxhdGl2ZSByZXN1bHRzIGNoYW5nZSBvdmVyIHRpbWUuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQojIDEuIERlZmluZSB0aGUgRGlzY291bnQgTG9naWMgRnVuY3Rpb24NCmFwcGx5X2Rpc2NvdW50IDwtIGZ1bmN0aW9uKHNhbGVzX2Ftb3VudCkgew0KICBpZiAoc2FsZXNfYW1vdW50ID4gMTAwMDApICAgICAgZGlzY291bnQgPC0gMC4yMCAgIyAyMCUgb2ZmDQogIGVsc2UgaWYgKHNhbGVzX2Ftb3VudCA+IDcwMDApICBkaXNjb3VudCA8LSAwLjE1ICAjIDE1JSBvZmYNCiAgZWxzZSBpZiAoc2FsZXNfYW1vdW50ID4gNDAwMCkgIGRpc2NvdW50IDwtIDAuMTAgICMgMTAlIG9mZg0KICBlbHNlICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzY291bnQgPC0gMC4wNSAgIyA1JSBvZmYNCiAgcmV0dXJuKGRpc2NvdW50KQ0KfQ0KDQojIDIuIERlZmluZSB0aGUgU2ltdWxhdGlvbiBFbmdpbmUNCnNpbXVsYXRlX3NhbGVzIDwtIGZ1bmN0aW9uKG5fc2FsZXNwZXJzb24sIGRheXMpIHsNCiAgc2V0LnNlZWQoNDIpICMgRW5zdXJlcyB0aGUgc2FtZSByZXN1bHRzIGV2ZXJ5IHRpbWUNCiAgc2FsZXNfZGF0YSA8LSBkYXRhLmZyYW1lKCkNCiAgDQogIGZvciAocyBpbiAxOm5fc2FsZXNwZXJzb24pIHsNCiAgICBjdW11bGF0aXZlIDwtIDANCiAgICBmb3IgKGQgaW4gMTpkYXlzKSB7DQogICAgICAjIEdlbmVyYXRlIHJhbmRvbSBzYWxlcyBiZXR3ZWVuIDEsMDAwIGFuZCAxNSwwMDANCiAgICAgIGFtb3VudCA8LSByb3VuZChydW5pZigxLCBtaW4gPSAxMDAwLCBtYXggPSAxNTAwMCksIDIpDQogICAgICBkaXNjb3VudCA8LSBhcHBseV9kaXNjb3VudChhbW91bnQpDQogICAgICBjdW11bGF0aXZlIDwtIGN1bXVsYXRpdmUgKyBhbW91bnQNCiAgICAgIA0KICAgICAgIyBDcmVhdGUgYSBzaW5nbGUgcm93IGZvciB0aGlzIGRheQ0KICAgICAgcm93IDwtIGRhdGEuZnJhbWUoDQogICAgICAgIHNhbGVzX2lkICAgICAgICAgPSBzLA0KICAgICAgICBkYXkgICAgICAgICAgICAgID0gZCwNCiAgICAgICAgc2FsZXNfYW1vdW50ICAgICA9IGFtb3VudCwNCiAgICAgICAgZGlzY291bnRfcmF0ZSAgICA9IGRpc2NvdW50LA0KICAgICAgICBuZXRfc2FsZXMgICAgICAgID0gYW1vdW50ICogKDEgLSBkaXNjb3VudCksDQogICAgICAgIGN1bXVsYXRpdmVfc2FsZXMgPSBjdW11bGF0aXZlDQogICAgICApDQogICAgICBzYWxlc19kYXRhIDwtIHJiaW5kKHNhbGVzX2RhdGEsIHJvdykNCiAgICB9DQogIH0NCiAgcmV0dXJuKHNhbGVzX2RhdGEpDQp9DQoNCiMgMy4gUnVuIHRoZSBTaW11bGF0aW9uDQpkZl9zYWxlcyA8LSBzaW11bGF0ZV9zYWxlcyhuX3NhbGVzcGVyc29uID0gNSwgZGF5cyA9IDEwKQ0KDQojIDQuIENyZWF0ZSBhIFN1bW1hcnkgVGFibGUNCnN1bW1hcnlfcGVyZm9ybWFuY2UgPC0gZGZfc2FsZXMgJT4lDQogIGdyb3VwX2J5KHNhbGVzX2lkKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIFRvdGFsX1JldmVudWUgICAgPSBzdW0oc2FsZXNfYW1vdW50KSwNCiAgICBBdmVyYWdlX1NhbGVzICAgID0gbWVhbihzYWxlc19hbW91bnQpLA0KICAgIEF2Z19EaXNjb3VudF9QY3QgPSBtZWFuKGRpc2NvdW50X3JhdGUpICogMTAwLA0KICAgIEZpbmFsX0N1bXVsYXRpdmUgPSBtYXgoY3VtdWxhdGl2ZV9zYWxlcyksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApDQoNCnByaW50KHN1bW1hcnlfcGVyZm9ybWFuY2UpDQoNCmdncGxvdChkZl9zYWxlcywgYWVzKHggPSBkYXksIA0KICAgICAgICAgICAgICAgICAgICAgeSA9IGN1bXVsYXRpdmVfc2FsZXMsIA0KICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBmYWN0b3Ioc2FsZXNfaWQpLCANCiAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gc2FsZXNfaWQpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsgDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ3VtdWxhdGl2ZSBTYWxlcyBQZXJmb3JtYW5jZSIsDQogICAgc3VidGl0bGUgPSAiVHJhY2tpbmcgMTAtZGF5IHJldmVudWUgZ3Jvd3RoIHBlciBzYWxlc3BlcnNvbiIsDQogICAgeCA9ICJTaW11bGF0aW9uIERheSIsDQogICAgeSA9ICJUb3RhbCBTYWxlcyAoQ3VycmVuY3kpIiwNCiAgICBjb2xvciA9ICJTdGFmZiBJRCINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KYGBgDQoNCiMjIFRhc2s6IE11bHRpLUxldmVsIFBlcmZvcm1hbmNlIENhdGVnb3JpemF0aW9uDQpUaGlzIHRhc2sgZm9jdXNlcyBvbiBjbGFzc2lmeWluZyBzYWxlcyBwZXJmb3JtYW5jZSBpbnRvIGRpZmZlcmVudCBsZXZlbHMgYmFzZWQNCm9uIHNhbGVzIHZvbHVtZS4gQnkgdXNpbmcgYSBmdW5jdGlvbiwgZWFjaCBzYWxlcyB2YWx1ZSBpcyBncm91cGVkIGludG8gY2F0ZWdvcmllcyBzdWNoIGFzICoqRXhjZWxsZW50LCBWZXJ5IEdvb2QsIEdvb2QsIEF2ZXJhZ2UsIGFuZCBQb29yKiouDQpUaGlzIGhlbHBzIHRvIGNsZWFybHkgdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHBlcmZvcm1hbmNlIGFuZCBtYWtlcyBpdCBlYXNpZXINCnRvIGFuYWx5emUgb3ZlcmFsbCBzYWxlcyBlZmZlY3RpdmVuZXNzLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KbGlicmFyeSh0aWR5cikNCmRmX3NhbGVzIDwtIGRmX3NhbGVzICU+JQ0KICBtdXRhdGUocGVyZm9ybWFuY2UgPSBjYXNlX3doZW4oDQogICAgc2FsZXNfYW1vdW50ID49IDEyMDAwIH4gIkV4Y2VsbGVudCIsDQogICAgc2FsZXNfYW1vdW50ID49IDkwMDAgIH4gIlZlcnkgR29vZCIsDQogICAgc2FsZXNfYW1vdW50ID49IDYwMDAgIH4gIkdvb2QiLA0KICAgIHNhbGVzX2Ftb3VudCA+PSAzMDAwICB+ICJBdmVyYWdlIiwNCiAgICBUUlVFICAgICAgICAgICAgICAgICAgfiAiUG9vciIgDQogICkpICU+JQ0KDQogIG11dGF0ZShwZXJmb3JtYW5jZSA9IGZhY3RvcihwZXJmb3JtYW5jZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJFeGNlbGxlbnQiLCAiVmVyeSBHb29kIiwgIkdvb2QiLCAiQXZlcmFnZSIsICJQb29yIikpKQ0KDQpwZXJmX3N1bW1hcnkgPC0gZGZfc2FsZXMgJT4lDQogIGNvdW50KHBlcmZvcm1hbmNlKSAlPiUNCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSBuIC8gc3VtKG4pLA0KICAgICAgICAgbGFiZWwgPSBwYXN0ZTAocGVyZm9ybWFuY2UsICJcbiIsIHNjYWxlczo6cGVyY2VudChwZXJjZW50YWdlLCBhY2N1cmFjeSA9IDAuMSkpKQ0KDQoNCmdncGxvdChwZXJmX3N1bW1hcnksIGFlcyh4ID0gIiIsIHkgPSBwZXJjZW50YWdlLCBmaWxsID0gcGVyZm9ybWFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDEsIGNvbG9yID0gIndoaXRlIiwgbGluZXdpZHRoID0gMC44KSArIA0KICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbGFiZWwpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIA0KICAgICAgICAgICAgc2l6ZSA9IDQsIA0KICAgICAgICAgICAgZm9udGZhY2UgPSAiYm9sZCIsDQogICAgICAgICAgICBjb2xvciA9ICJncmF5MTAiKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUmRZbEduIiwgZGlyZWN0aW9uID0gLTEpICsNCiAgbGFicyh0aXRsZSA9ICJQZXJmb3JtYW5jZSBDYXRlZ29yeSBEaXN0cmlidXRpb24iLA0KICAgICAgIHN1YnRpdGxlID0gIkFuYWx5c2lzIGJhc2VkIG9uIHRvdGFsIHNhbGVzIHZvbHVtZSIsDQogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IEludGVybmFsIFNhbGVzIERhdGEiKSArDQogIHRoZW1lX3ZvaWQoKSArDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDEyLCBjb2xvciA9ICJncmF5MzAiKSwNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSINCiAgKQ0KYGBgDQoNCiMjIFRhc2s6IFNpbXVsYXRlIERhdGFzZXQgTXVsdGktQ29tcGFueQ0KVGhpcyB0YXNrIHNpbXVsYXRlcyBhIG11bHRpLWNvbXBhbnkgZW1wbG95ZWUgZGF0YXNldCB1c2luZyBuZXN0ZWQgbG9vcHMgDQphbmQgY29uZGl0aW9uYWwgbG9naWMuIEEgdG90YWwgb2YgNCBjb21wYW5pZXMgYXJlIGdlbmVyYXRlZCwgZWFjaCB3aXRoIA0KMjAgZW1wbG95ZWVzLiBFYWNoIGVtcGxveWVlIGhhcyBhdHRyaWJ1dGVzIGluY2x1ZGluZyBzYWxhcnksIGRlcGFydG1lbnQsIA0KcGVyZm9ybWFuY2Ugc2NvcmUsIGFuZCBLUEkgc2NvcmUuIEVtcGxveWVlcyB3aXRoIGEgS1BJIHNjb3JlIGFib3ZlIDkwIA0KYXJlIGZsYWdnZWQgYXMgdG9wIHBlcmZvcm1lcnMuIFRoZSBvdXRwdXQgaW5jbHVkZXMgYSBzdW1tYXJ5IHRhYmxlIHBlciANCmNvbXBhbnkgYW5kIHZpc3VhbGl6YXRpb25zIG9mIEtQSSBhbmQgc2FsYXJ5IGRpc3RyaWJ1dGlvbnMuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIEZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBLUEkgc2NvcmUgYmFzZWQgb24gcGVyZm9ybWFuY2Ugc2NvcmUNCmNhbGN1bGF0ZV9rcGkgPC0gZnVuY3Rpb24ocGVyZm9ybWFuY2Vfc2NvcmUpIHsNCiAgDQogICMgSGlnaCBwZXJmb3JtZXJzIGdldCBoaWdoZXIgS1BJIGJvbnVzDQogIGlmIChwZXJmb3JtYW5jZV9zY29yZSA+PSA4NSkgew0KICAgIGtwaSA8LSBwZXJmb3JtYW5jZV9zY29yZSArIHJ1bmlmKDEsIDUsIDE1KQ0KICB9IGVsc2UgaWYgKHBlcmZvcm1hbmNlX3Njb3JlID49IDcwKSB7DQogICAga3BpIDwtIHBlcmZvcm1hbmNlX3Njb3JlICsgcnVuaWYoMSwgMCwgMTApDQogIH0gZWxzZSB7DQogICAga3BpIDwtIHBlcmZvcm1hbmNlX3Njb3JlIC0gcnVuaWYoMSwgMCwgNSkNCiAgfQ0KICANCiAgIyBFbnN1cmUgS1BJIGRvZXMgbm90IGV4Y2VlZCAxMDANCiAgcmV0dXJuKG1pbihyb3VuZChrcGksIDEpLCAxMDApKQ0KfQ0KDQojIE1haW4gZnVuY3Rpb246IGdlbmVyYXRlIGVtcGxveWVlIGRhdGFzZXQgZnJvbSBtdWx0aXBsZSBjb21wYW5pZXMNCmdlbmVyYXRlX2NvbXBhbnlfZGF0YSA8LSBmdW5jdGlvbihudW1fY29tcGFuaWVzLCBudW1fZW1wbG95ZWVzKSB7DQogIA0KICBzZXQuc2VlZCgxMjMpDQogIA0KICBkZXBhcnRtZW50cyA8LSBjKCJGaW5hbmNlIiwgIk1hcmtldGluZyIsICJPcGVyYXRpb25zIiwgIkhSIiwgIlRlY2hub2xvZ3kiKQ0KICBhbGxfZGF0YSA8LSBkYXRhLmZyYW1lKCkNCiAgDQogICMgT3V0ZXIgbG9vcDogY29tcGFuaWVzDQogIGZvciAocCBpbiAxOm51bV9jb21wYW5pZXMpIHsNCiAgICANCiAgICAjIElubmVyIGxvb3A6IGVtcGxveWVlcw0KICAgIGZvciAoayBpbiAxOm51bV9lbXBsb3llZXMpIHsNCiAgICAgIA0KICAgICAgcGVyZm9ybWFuY2Vfc2NvcmUgPC0gcm91bmQocnVuaWYoMSwgbWluID0gNTAsIG1heCA9IDEwMCksIDEpDQogICAgICBrcGlfc2NvcmUgICAgICAgICA8LSBjYWxjdWxhdGVfa3BpKHBlcmZvcm1hbmNlX3Njb3JlKQ0KICAgICAgc2FsYXJ5ICAgICAgICAgICAgPC0gcm91bmQocnVuaWYoMSwgbWluID0gNDAwMDAwMCwgbWF4ID0gMjAwMDAwMDApLCAwKQ0KICAgICAgZGVwYXJ0bWVudCAgICAgICAgPC0gc2FtcGxlKGRlcGFydG1lbnRzLCAxKQ0KICAgICAgDQogICAgICAjIE1hcmsgdG9wIHBlcmZvcm1lcnMNCiAgICAgIHRvcF9wZXJmb3JtZXIgPC0gaWZlbHNlKGtwaV9zY29yZSA+IDkwLCAiWWVzIiwgIk5vIikNCiAgICAgIA0KICAgICAgcm93IDwtIGRhdGEuZnJhbWUoDQogICAgICAgIGNvbXBhbnlfaWQgICAgICAgPSBwYXN0ZTAoIkNvbXBhbnktIiwgcCksDQogICAgICAgIGVtcGxveWVlX2lkICAgICAgPSBwYXN0ZTAoIkUiLCBwLCAiLSIsIGspLA0KICAgICAgICBzYWxhcnkgICAgICAgICAgID0gc2FsYXJ5LA0KICAgICAgICBkZXBhcnRtZW50ICAgICAgID0gZGVwYXJ0bWVudCwNCiAgICAgICAgcGVyZm9ybWFuY2Vfc2NvcmU9IHBlcmZvcm1hbmNlX3Njb3JlLA0KICAgICAgICBrcGlfc2NvcmUgICAgICAgID0ga3BpX3Njb3JlLA0KICAgICAgICB0b3BfcGVyZm9ybWVyICAgID0gdG9wX3BlcmZvcm1lcg0KICAgICAgKQ0KICAgICAgDQogICAgICBhbGxfZGF0YSA8LSByYmluZChhbGxfZGF0YSwgcm93KQ0KICAgIH0NCiAgfQ0KICANCiAgcmV0dXJuKGFsbF9kYXRhKQ0KfQ0KDQojIEdlbmVyYXRlIGRhdGFzZXQ6IDQgY29tcGFuaWVzLCAyMCBlbXBsb3llZXMgZWFjaA0KY29tcGFueV9kYXRhIDwtIGdlbmVyYXRlX2NvbXBhbnlfZGF0YShudW1fY29tcGFuaWVzID0gNCwgbnVtX2VtcGxveWVlcyA9IDIwKQ0KDQojIFN1bW1hcnkgc3RhdGlzdGljcyBwZXIgY29tcGFueQ0KY29tcGFueV9zdW1tYXJ5IDwtIGNvbXBhbnlfZGF0YSAlPiUNCiAgZ3JvdXBfYnkoY29tcGFueV9pZCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhdmdfc2FsYXJ5ICAgICAgICA9IHJvdW5kKG1lYW4oc2FsYXJ5KSwgMCksDQogICAgYXZnX3BlcmZvcm1hbmNlICAgPSByb3VuZChtZWFuKHBlcmZvcm1hbmNlX3Njb3JlKSwgMSksDQogICAgYXZnX2twaSAgICAgICAgICAgPSByb3VuZChtZWFuKGtwaV9zY29yZSksIDEpLA0KICAgIGhpZ2hlc3Rfa3BpICAgICAgID0gbWF4KGtwaV9zY29yZSksDQogICAgdG90YWxfdG9wICAgICAgICAgPSBzdW0odG9wX3BlcmZvcm1lciA9PSAiWWVzIiksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApDQoNCnByaW50KGNvbXBhbnlfc3VtbWFyeSkNCmBgYA0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KZ2dwbG90KGNvbXBhbnlfc3VtbWFyeSwgDQogICAgICAgYWVzKHggPSBmYWN0b3IoY29tcGFueV9pZCwgbGV2ZWxzID0gcGFzdGUwKCJDb21wYW55LSIsIDE6NCkpLA0KICAgICAgICAgICB5ID0gYXZnX2twaSwgDQogICAgICAgICAgIGZpbGwgPSBjb21wYW55X2lkKSkgKw0KICANCiAgIyBDcmVhdGUgdGhlIGJhciBjaGFydA0KICBnZW9tX2NvbCh3aWR0aCA9IDAuNikgKw0KICANCiAgIyBBZGQgdmFsdWUgbGFiZWxzIG9uIHRvcCBvZiBiYXJzIChyb3VuZGVkIHRvIDEgZGVjaW1hbCBwbGFjZSkNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKGF2Z19rcGksIDEpKSwNCiAgICAgICAgICAgIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDQuNSwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgDQogICMgVGl0bGVzIGFuZCBBeGlzIExhYmVscw0KICBsYWJzKA0KICAgIHRpdGxlICAgID0gIkF2ZXJhZ2UgS1BJIFNjb3JlIHBlciBDb21wYW55IiwNCiAgICBzdWJ0aXRsZSA9ICJQZXJmb3JtYW5jZSBjb21wYXJpc29uIGFjcm9zcyBjb21wYW5pZXMiLA0KICAgIHggICAgICAgID0gIkNvbXBhbnkiLA0KICAgIHkgICAgICAgID0gIkF2ZXJhZ2UgS1BJIFNjb3JlIg0KICApICsNCiAgDQogICMgUHJvZmVzc2lvbmFsIHRoZW1lIGFuZCBzdHlsaW5nDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgICAgICA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE0KSwNCiAgICBwbG90LnN1YnRpdGxlICAgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGNvbG9yID0gImdyYXk0MCIpLA0KICAgIGF4aXMudGV4dC54ICAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLA0KICAgIGF4aXMudGV4dC55ICAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSAjIENsZWFuIHVwIHZlcnRpY2FsIGdyaWQgbGluZXMNCiAgKSArDQogIA0KICAjIENvbG9yIHBhbGV0dGUNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikNCmBgYA0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KY29tcGFueV9kYXRhJGNvbXBhbnlfaWQgPC0gZmFjdG9yKA0KICBjb21wYW55X2RhdGEkY29tcGFueV9pZCwNCiAgbGV2ZWxzID0gYygiQ29tcGFueS0xIiwgIkNvbXBhbnktMiIsICJDb21wYW55LTMiLCAiQ29tcGFueS00IikNCikNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyBWaXN1YWxpemF0aW9uIDI6IFNhbGFyeSBEaXN0cmlidXRpb24NCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCnBsb3QyIDwtIGdncGxvdChjb21wYW55X2RhdGEsIA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gY29tcGFueV9pZCwgDQogICAgICAgICAgICAgICAgICAgIHkgPSBzYWxhcnksIA0KICAgICAgICAgICAgICAgICAgICBmaWxsID0gY29tcGFueV9pZCkpICsNCiAgDQogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuNSwgYWxwaGEgPSAwLjcsIG91dGxpZXIuY29sb3IgPSAicmVkIikgKw0KICANCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjE1LCBhbHBoYSA9IDAuMywgc2l6ZSA9IDEuNSkgKw0KICANCiAgc3RhdF9zdW1tYXJ5KGZ1biA9IG1lZGlhbiwgZ2VvbSA9ICJwb2ludCIsIHNpemUgPSAzKSArDQogIA0KICBsYWJzKA0KICAgIHRpdGxlICAgID0gIlRhc2sgNDogU2FsYXJ5IERpc3RyaWJ1dGlvbiBwZXIgQ29tcGFueSIsDQogICAgc3VidGl0bGUgPSAiT3JkZXJlZCBieSBDb21wYW55IElEIChDb21wYW55LTEgdG8gQ29tcGFueS00KSIsDQogICAgeCAgICAgICAgPSAiQ29tcGFueSIsDQogICAgeSAgICAgICAgPSAiU2FsYXJ5IChJRFIpIg0KICApICsNCiAgDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsNCiAgDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTQpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxMSkNCiAgKSArDQogIA0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKQ0KDQpwbG90Mg0KYGBgDQoNCg0KIyMgVGFzazogTW9udGUgQ2FybG8gc2ltdWxhdGlvbg0KVGhpcyB0YXNrIGltcGxlbWVudHMgYSBNb250ZSBDYXJsbyBzaW11bGF0aW9uIHRvIGVzdGltYXRlIHRoZSB2YWx1ZSBvZiANClBpIHVzaW5nIHJhbmRvbWx5IGdlbmVyYXRlZCBwb2ludHMgaW5zaWRlIGEgdW5pdCBzcXVhcmUuIEEgdG90YWwgb2YgMiwwMDAgDQpyYW5kb20gcG9pbnRzIGFyZSBwbG90dGVkLCBhbmQgdGhlIHJhdGlvIG9mIHBvaW50cyBmYWxsaW5nIGluc2lkZSB0aGUgDQpjaXJjbGUgaXMgdXNlZCB0byBhcHByb3hpbWF0ZSBQaS4gQWRkaXRpb25hbGx5LCB0aGUgcHJvYmFiaWxpdHkgb2YgYSANCnJhbmRvbSBwb2ludCBsYW5kaW5nIGluc2lkZSBhIGRlZmluZWQgc3ViLXNxdWFyZSByZWdpb24gaXMgY2FsY3VsYXRlZCANCmFuZCB2aXN1YWxpemVkLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIE1vbnRlIENhcmxvIHNpbXVsYXRpb24gZnVuY3Rpb24gdG8gZXN0aW1hdGUgdGhlIHZhbHVlIG9mIFBpDQptb250ZV9jYXJsb19waSA8LSBmdW5jdGlvbihudW1fcG9pbnRzKSB7DQogIHNldC5zZWVkKDk5KQ0KICANCiAgIyBHZW5lcmF0ZSByYW5kb20gcG9pbnRzIGluIHJhbmdlIFstMSwgMV0NCiAgeF9jb29yZCA8LSBydW5pZihudW1fcG9pbnRzLCAtMSwgMSkNCiAgeV9jb29yZCA8LSBydW5pZihudW1fcG9pbnRzLCAtMSwgMSkNCiAgDQogICMgQ29tcHV0ZSBkaXN0YW5jZSBmcm9tIG9yaWdpbiAoMCwwKQ0KICBkaXN0YW5jZSA8LSBzcXJ0KHhfY29vcmReMiArIHlfY29vcmReMikNCiAgDQogICMgQ2xhc3NpZnkgcG9pbnRzDQogIHBvc2l0aW9uIDwtIGlmZWxzZShkaXN0YW5jZSA8PSAxLCAiSW5zaWRlIiwgIk91dHNpZGUiKQ0KICANCiAgIyBQaSBlc3RpbWF0aW9uDQogIHBvaW50c19pbnNpZGUgPC0gc3VtKHBvc2l0aW9uID09ICJJbnNpZGUiKQ0KICBwaV9lc3RpbWF0ZSAgIDwtIChwb2ludHNfaW5zaWRlIC8gbnVtX3BvaW50cykgKiA0DQogIA0KICAjIFN1Yi1zcXVhcmUgcHJvYmFiaWxpdHkgKDAg4omkIHgg4omkIDAuNSBhbmQgMCDiiaQgeSDiiaQgMC41KQ0KICBpbl9zdWJzcXVhcmUgPC0gc3VtKHhfY29vcmQgPj0gMCAmIHhfY29vcmQgPD0gMC41ICYNCiAgICAgICAgICAgICAgICAgICAgICB5X2Nvb3JkID49IDAgJiB5X2Nvb3JkIDw9IDAuNSkNCiAgcHJvYl9zdWJzcXVhcmUgPC0gcm91bmQoaW5fc3Vic3F1YXJlIC8gbnVtX3BvaW50cywgNCkNCiAgDQogICMgQ2xlYXIgY29uc29sZSBvdXRwdXQNCiAgY2F0KCI9PT09PT09PT09IE1vbnRlIENhcmxvIFNpbXVsYXRpb24gPT09PT09PT09PVxuIikNCiAgY2F0KCJUb3RhbCBQb2ludHMgICAgICAgOiIsIG51bV9wb2ludHMsICJcbiIpDQogIGNhdCgiUG9pbnRzIEluc2lkZSAgICAgIDoiLCBwb2ludHNfaW5zaWRlLCAiXG4iKQ0KICBjYXQoIkVzdGltYXRlZCBQaSAgICAgICA6Iiwgcm91bmQocGlfZXN0aW1hdGUsIDUpLCAiXG4iKQ0KICBjYXQoIkFjdHVhbCBQaSAgICAgICAgICA6Iiwgcm91bmQocGksIDUpLCAiXG4iKQ0KICBjYXQoIlN1Yi1zcXVhcmUgUHJvYi4gICA6IiwgcHJvYl9zdWJzcXVhcmUsICJcbiIpDQogIGNhdCgiPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuIikNCiAgDQogICMgUmV0dXJuIHN0cnVjdHVyZWQgZGF0YQ0KICByZXR1cm4obGlzdCgNCiAgICBkYXRhID0gZGF0YS5mcmFtZSh4ID0geF9jb29yZCwgeSA9IHlfY29vcmQsIHBvc2l0aW9uID0gcG9zaXRpb24pLA0KICAgIHBpX2VzdGltYXRlID0gcGlfZXN0aW1hdGUsDQogICAgcHJvYl9zdWJzcXVhcmUgPSBwcm9iX3N1YnNxdWFyZQ0KICApKQ0KfQ0KDQojIFJ1biBzaW11bGF0aW9uDQpyZXN1bHQgPC0gbW9udGVfY2FybG9fcGkobnVtX3BvaW50cyA9IDIwMDApDQptb250ZV9jYXJsb19kYXRhIDwtIHJlc3VsdCRkYXRhDQoNCiMgQ3JlYXRlIGNpcmNsZSBib3VuZGFyeQ0KY2lyY2xlX2FuZ2xlIDwtIHNlcSgwLCAyICogcGksIGxlbmd0aC5vdXQgPSAzMDApDQpjaXJjbGVfcGF0aCA8LSBkYXRhLmZyYW1lKA0KICBjeCA9IGNvcyhjaXJjbGVfYW5nbGUpLA0KICBjeSA9IHNpbihjaXJjbGVfYW5nbGUpDQopDQpgYGANCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmdncGxvdChtb250ZV9jYXJsb19kYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvciA9IHBvc2l0aW9uKSkgKw0KICAjIEluZGl2aWR1YWwgcG9pbnRzDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGFscGhhID0gMC42KSArDQogICMgQ2lyY2xlIGJvdW5kYXJ5DQogIGdlb21fcGF0aChkYXRhID0gY2lyY2xlX3BhdGgsIGFlcyh4ID0gY3gsIHkgPSBjeSksIA0KICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBsaW5ld2lkdGggPSAxLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArDQogICMgU3ViLXNxdWFyZSBhbmFseXNpcyBib3gNCiAgYW5ub3RhdGUoInJlY3QiLCB4bWluID0gMCwgeG1heCA9IDAuNSwgeW1pbiA9IDAsIHltYXggPSAwLjUsIA0KICAgICAgICAgICBmaWxsID0gTkEsIGNvbG9yID0gInB1cnBsZSIsIGxpbmV3aWR0aCA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgIyBMYWJlbHMgYW5kIHRpdGxlcw0KICBsYWJzKA0KICAgIHRpdGxlICAgID0gIlRhc2sgNTogTW9udGUgQ2FybG8gU2ltdWxhdGlvbiAtIFBpIEVzdGltYXRpb24iLA0KICAgIHN1YnRpdGxlID0gIlB1cnBsZSBkYXNoZWQgYm94ID0gU3ViLXNxdWFyZSBmb3IgcHJvYmFiaWxpdHkgYW5hbHlzaXMiLA0KICAgIHggICAgICAgID0gIlggQ29vcmRpbmF0ZSIsDQogICAgeSAgICAgICAgPSAiWSBDb29yZGluYXRlIiwNCiAgICBjb2xvciAgICA9ICJQb2ludCBQb3NpdGlvbiIsDQogICAgY2FwdGlvbiAgPSBwYXN0ZSgiRXN0aW1hdGVkIFBpID0iLCByb3VuZCgoc3VtKG1vbnRlX2NhcmxvX2RhdGEkcG9zaXRpb24gPT0gDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkluc2lkZSIpLzIwMDApKjQsIDQpKQ0KICApICsNCiAgIyBDdXN0b20gY29sb3JzIChCbHVlIGZvciBJbnNpZGUsIFJlZCBmb3IgT3V0c2lkZSkNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIkluc2lkZSIgPSAiIzIxOTZGMyIsICJPdXRzaWRlIiA9ICIjRjQ0MzM2IikpICsNCiAgY29vcmRfZml4ZWQoKSArIA0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCkpDQpgYGANCg0KIyMgVGFzazogRGF0YSBUcmFuc2Zvcm1hdGlvbg0KVGhpcyB0YXNrIGRlbW9uc3RyYXRlcyBkYXRhIHRyYW5zZm9ybWF0aW9uIHRlY2huaXF1ZXMgdXNpbmcgbG9vcC1iYXNlZCANCm5vcm1hbGl6YXRpb24uIFR3byBtZXRob2RzIGFyZSBhcHBsaWVkOiBNaW4tTWF4IG5vcm1hbGl6YXRpb24sIHdoaWNoIA0KcmVzY2FsZXMgdmFsdWVzIHRvIGEgcmFuZ2Ugb2YgMCB0byAxLCBhbmQgWi1TY29yZSBzdGFuZGFyZGl6YXRpb24sIA0Kd2hpY2ggY2VudGVycyB2YWx1ZXMgYXJvdW5kIGEgbWVhbiBvZiAwIHdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgMS4gDQpOZXcgZmVhdHVyZXMgc3VjaCBhcyBzYWxhcnkgYnJhY2tldCBhbmQgcGVyZm9ybWFuY2UgY2F0ZWdvcnkgYXJlIGFsc28gDQplbmdpbmVlcmVkIGZyb20gdGhlIGV4aXN0aW5nIGRhdGFzZXQuIERpc3RyaWJ1dGlvbnMgYmVmb3JlIGFuZCBhZnRlciANCnRyYW5zZm9ybWF0aW9uIGFyZSBjb21wYXJlZCB1c2luZyBoaXN0b2dyYW1zIGFuZCBib3hwbG90cy4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCiMgTWluLU1heCBOb3JtYWxpemF0aW9uIChzY2FsZSAw4oCTMSkNCm1pbm1heF9ub3JtYWxpemF0aW9uIDwtIGZ1bmN0aW9uKGRmKSB7DQogIHJlc3VsdCA8LSBkZg0KICANCiAgZm9yIChjb2wgaW4gbmFtZXMoZGYpKSB7DQogICAgaWYgKGlzLm51bWVyaWMoZGZbW2NvbF1dKSkgew0KICAgICAgbWluX3ZhbCA8LSBtaW4oZGZbW2NvbF1dLCBuYS5ybSA9IFRSVUUpDQogICAgICBtYXhfdmFsIDwtIG1heChkZltbY29sXV0sIG5hLnJtID0gVFJVRSkNCiAgICAgIA0KICAgICAgcmVzdWx0W1tjb2xdXSA8LSByb3VuZCgNCiAgICAgICAgKGRmW1tjb2xdXSAtIG1pbl92YWwpIC8gKG1heF92YWwgLSBtaW5fdmFsKSwgNA0KICAgICAgKQ0KICAgIH0NCiAgfQ0KICByZXR1cm4ocmVzdWx0KQ0KfQ0KDQojIFotU2NvcmUgU3RhbmRhcmRpemF0aW9uIChtZWFuID0gMCwgc2QgPSAxKQ0KenNjb3JlX25vcm1hbGl6YXRpb24gPC0gZnVuY3Rpb24oZGYpIHsNCiAgcmVzdWx0IDwtIGRmDQogIA0KICBmb3IgKGNvbCBpbiBuYW1lcyhkZikpIHsNCiAgICBpZiAoaXMubnVtZXJpYyhkZltbY29sXV0pKSB7DQogICAgICBtZWFuX3ZhbCA8LSBtZWFuKGRmW1tjb2xdXSwgbmEucm0gPSBUUlVFKQ0KICAgICAgc2RfdmFsICAgPC0gc2QoZGZbW2NvbF1dLCBuYS5ybSA9IFRSVUUpDQogICAgICANCiAgICAgIHJlc3VsdFtbY29sXV0gPC0gcm91bmQoDQogICAgICAgIChkZltbY29sXV0gLSBtZWFuX3ZhbCkgLyBzZF92YWwsIDQNCiAgICAgICkNCiAgICB9DQogIH0NCiAgcmV0dXJuKHJlc3VsdCkNCn0NCg0KIyAzLiBEQVRBIFBSRVBBUkFUSU9ODQojIFNlbGVjdCBudW1lcmljIGNvbHVtbnMNCm51bWVyaWNfZGF0YSA8LSBjb21wYW55X2RhdGFbLCBjKCJzYWxhcnkiLCAicGVyZm9ybWFuY2Vfc2NvcmUiLCAia3BpX3Njb3JlIildDQoNCiMgQXBwbHkgbm9ybWFsaXphdGlvbg0KZGF0YV9taW5tYXggPC0gbWlubWF4X25vcm1hbGl6YXRpb24obnVtZXJpY19kYXRhKQ0KZGF0YV96c2NvcmUgPC0genNjb3JlX25vcm1hbGl6YXRpb24obnVtZXJpY19kYXRhKQ0KDQoNCiMgNC4gRkVBVFVSRSBFTkdJTkVFUklORw0KDQpjb21wYW55X2RhdGEgPC0gY29tcGFueV9kYXRhICU+JQ0KICBtdXRhdGUoDQogICAgc2FsYXJ5X2NhdGVnb3J5ID0gY2FzZV93aGVuKA0KICAgICAgc2FsYXJ5ID49IDE1MDAwMDAwIH4gIkhpZ2giLA0KICAgICAgc2FsYXJ5ID49IDkwMDAwMDAgIH4gIk1lZGl1bSIsDQogICAgICBUUlVFICAgICAgICAgICAgIH4gIkxvdyINCiAgICApLA0KICAgIHBlcmZvcm1hbmNlX2NhdGVnb3J5ID0gY2FzZV93aGVuKA0KICAgICAgcGVyZm9ybWFuY2Vfc2NvcmUgPj0gODUgfiAiRXhjZWxsZW50IiwNCiAgICAgIHBlcmZvcm1hbmNlX3Njb3JlID49IDcwIH4gIkdvb2QiLA0KICAgICAgVFJVRSAgICAgICAgICAgICAgICB+ICJOZWVkcyBJbXByb3ZlbWVudCINCiAgICApDQogICkNCg0KIyA1LiBEQVRBIENPTUJJTkFUSU9OIEZPUiBWSVNVQUxJWkFUSU9ODQoNCiMgU2FsYXJ5IGNvbXBhcmlzb24gKGJlZm9yZSB2cyBhZnRlciBub3JtYWxpemF0aW9uKQ0KYmVmb3JlX2RhdGEgPC0gbnVtZXJpY19kYXRhICU+JSBtdXRhdGUoc3RhdHVzID0gIkJlZm9yZSBOb3JtYWxpemF0aW9uIikNCmFmdGVyX2RhdGEgIDwtIGRhdGFfbWlubWF4ICU+JSBtdXRhdGUoc3RhdHVzID0gIkFmdGVyIE1pbi1NYXgiKQ0KDQpjb21iaW5lZF9kYXRhIDwtIHJiaW5kKGJlZm9yZV9kYXRhLCBhZnRlcl9kYXRhKQ0KYGBgDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQojIDYuIFZJU1VBTElaQVRJT04gMTogU0FMQVJZIERJU1RSSUJVVElPTg0KDQpnZ3Bsb3QoY29tYmluZWRfZGF0YSwgYWVzKHggPSBzYWxhcnksIGZpbGwgPSBzdGF0dXMpKSArDQogIA0KICBnZW9tX2hpc3RvZ3JhbSgNCiAgICBiaW5zID0gMTIsDQogICAgYWxwaGEgPSAwLjUsICAgICAgICAgIA0KICAgIHBvc2l0aW9uID0gImlkZW50aXR5IiwNCiAgICBjb2xvciA9ICJ3aGl0ZSINCiAgKSArDQogIA0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAsIDAuMDUpKSkgKw0KICANCiAgbGFicygNCiAgICB0aXRsZSAgICA9ICJTYWxhcnkgRGlzdHJpYnV0aW9uIEJlZm9yZSBhbmQgQWZ0ZXIgTm9ybWFsaXphdGlvbiIsDQogICAgc3VidGl0bGUgPSAiQ29tcGFyaXNvbiB1c2luZyBNaW4tTWF4IE5vcm1hbGl6YXRpb24iLA0KICAgIHggICAgICAgID0gIlNhbGFyeSBWYWx1ZSIsDQogICAgeSAgICAgICAgPSAiRnJlcXVlbmN5IiwNCiAgICBmaWxsICAgICA9ICJEYXRhIFN0YXR1cyINCiAgKSArDQogIA0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArDQogIA0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTQpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLA0KICAgIA0KICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgDQogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpDQogICkgKw0KICANCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygNCiAgICAiQmVmb3JlIE5vcm1hbGl6YXRpb24iID0gIiNFRjlBOUEiLA0KICAgICJBZnRlciBNaW4tTWF4IiAgICAgICAgPSAiIzkwQ0FGOSINCiAgKSkNCmBgYA0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyA3LiBWSVNVQUxJWkFUSU9OIDI6IEtQSSBCT1hQTE9UDQojIENvbWJpbmUgS1BJIGRhdGEgKGJlZm9yZSB2cyBhZnRlciBub3JtYWxpemF0aW9uKQ0KIyBDb21iaW5lIEtQSSBkYXRhDQprcGlfY29tYmluZWQgPC0gZGF0YS5mcmFtZSgNCiAgdmFsdWUgID0gYyhudW1lcmljX2RhdGEka3BpX3Njb3JlLCBkYXRhX21pbm1heCRrcGlfc2NvcmUpLA0KICBzdGF0dXMgPSBjKA0KICAgIHJlcCgiQmVmb3JlIE5vcm1hbGl6YXRpb24iLCBucm93KG51bWVyaWNfZGF0YSkpLA0KICAgIHJlcCgiQWZ0ZXIgTWluLU1heCIsIG5yb3coZGF0YV9taW5tYXgpKQ0KICApDQopDQoNCiMgUGxvdA0KZ2dwbG90KGtwaV9jb21iaW5lZCwgYWVzKHggPSBzdGF0dXMsIHkgPSB2YWx1ZSwgZmlsbCA9IHN0YXR1cykpICsNCiAgDQogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuNSwgYWxwaGEgPSAwLjcsIG91dGxpZXIuY29sb3IgPSAicmVkIikgKw0KICANCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjE1LCBhbHBoYSA9IDAuMywgc2l6ZSA9IDEuNSkgKw0KICANCiAgbGFicygNCiAgICB0aXRsZSAgICA9ICJLUEkgU2NvcmUgRGlzdHJpYnV0aW9uIEJlZm9yZSBhbmQgQWZ0ZXIgTm9ybWFsaXphdGlvbiIsDQogICAgc3VidGl0bGUgPSAiTWluLU1heCBub3JtYWxpemF0aW9uIHJlc2NhbGVzIGRhdGEgdG8gMOKAkzEgcmFuZ2UiLA0KICAgIHggICAgICAgID0gIkRhdGEgU3RhdHVzIiwNCiAgICB5ICAgICAgICA9ICJLUEkgU2NvcmUiDQogICkgKw0KICANCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKw0KICANCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgDQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICBheGlzLnRleHQgID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkNCiAgKSArDQogIA0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKA0KICAgICJCZWZvcmUgTm9ybWFsaXphdGlvbiIgPSAiI0ZGQ0M4MCIsDQogICAgIkFmdGVyIE1pbi1NYXgiICAgICAgICA9ICIjQTVENkE3Ig0KICApKQ0KYGBgDQoNCiMjIFRhc2s6IE1pbmkgUHJvamVjdCAtIERhc2hib2FyZA0KVGhpcyBtaW5pIHByb2plY3QgZ2VuZXJhdGVzIGEgbGFyZ2Utc2NhbGUgZGF0YXNldCBjb25zaXN0aW5nIG9mIDYgDQpjb21wYW5pZXMgd2l0aCA1MCBlbXBsb3llZXMgZWFjaC4gVGhlIGRhdGFzZXQgaW5jbHVkZXMgZW1wbG95ZWUgSUQsIA0KY29tcGFueSBJRCwgc2FsYXJ5LCBwZXJmb3JtYW5jZSBzY29yZSwgS1BJIHNjb3JlLCBhbmQgZGVwYXJ0bWVudC4gDQpFbXBsb3llZXMgYXJlIGNhdGVnb3JpemVkIGludG8gS1BJIHRpZXJzOiBQbGF0aW51bSwgR29sZCwgU2lsdmVyLCBhbmQgDQpCcm9uemUuIFRoZSBhbmFseXNpcyBpbmNsdWRlcyBwZXItY29tcGFueSBzdW1tYXJpZXMsIGRlcGFydG1lbnQtbGV2ZWwgDQpjb21wYXJpc29ucywgYW5kIGFkdmFuY2VkIHZpc3VhbGl6YXRpb25zIHN1Y2ggYXMgZ3JvdXBlZCBiYXIgY2hhcnRzLCANCnNjYXR0ZXIgcGxvdHMgd2l0aCByZWdyZXNzaW9uIGxpbmVzLCBhbmQgc2FsYXJ5IGRpc3RyaWJ1dGlvbiBoaXN0b2dyYW1zLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCg0KZ2VuZXJhdGVfY29tcGFueV9kYXRhIDwtIGZ1bmN0aW9uKG51bV9jb21wYW5pZXMsIG51bV9lbXBsb3llZXMpIHsNCiAgDQogIHNldC5zZWVkKDEyMykNCiAgZGVwYXJ0bWVudHMgPC0gYygiRmluYW5jZSIsICJNYXJrZXRpbmciLCAiT3BlcmF0aW9ucyIsICJIUiIsICJUZWNobm9sb2d5IikNCiAgYWxsX2RhdGEgPC0gZGF0YS5mcmFtZSgpDQogIA0KICBmb3IgKGMgaW4gMTpudW1fY29tcGFuaWVzKSB7DQogICAgZm9yIChlIGluIDE6bnVtX2VtcGxveWVlcykgew0KICAgICAgDQogICAgICBwZXJmb3JtYW5jZV9zY29yZSA8LSByb3VuZChydW5pZigxLCA1MCwgMTAwKSwgMSkNCiAgICAgIA0KICAgICAgIyBLUEkgY2FsY3VsYXRpb24NCiAgICAgIGlmIChwZXJmb3JtYW5jZV9zY29yZSA+PSA4NSkgew0KICAgICAgICBrcGlfc2NvcmUgPC0gcGVyZm9ybWFuY2Vfc2NvcmUgKyBydW5pZigxLCA1LCAxNSkNCiAgICAgIH0gZWxzZSBpZiAocGVyZm9ybWFuY2Vfc2NvcmUgPj0gNzApIHsNCiAgICAgICAga3BpX3Njb3JlIDwtIHBlcmZvcm1hbmNlX3Njb3JlICsgcnVuaWYoMSwgMCwgMTApDQogICAgICB9IGVsc2Ugew0KICAgICAgICBrcGlfc2NvcmUgPC0gcGVyZm9ybWFuY2Vfc2NvcmUgLSBydW5pZigxLCAwLCA1KQ0KICAgICAgfQ0KICAgICAgDQogICAgICBrcGlfc2NvcmUgPC0gbWluKHJvdW5kKGtwaV9zY29yZSwgMSksIDEwMCkNCiAgICAgIA0KICAgICAgc2FsYXJ5IDwtIHJvdW5kKHJ1bmlmKDEsIDQwMDAwMDAsIDIwMDAwMDAwKSwgMCkNCiAgICAgIGRlcGFydG1lbnQgPC0gc2FtcGxlKGRlcGFydG1lbnRzLCAxKQ0KICAgICAgDQogICAgICB0b3BfcGVyZm9ybWVyIDwtIGlmZWxzZShrcGlfc2NvcmUgPiA5MCwgIlllcyIsICJObyIpDQogICAgICANCiAgICAgIHJvdyA8LSBkYXRhLmZyYW1lKA0KICAgICAgICBjb21wYW55X2lkICAgICAgICA9IHBhc3RlMCgiQ29tcGFueS0iLCBjKSwNCiAgICAgICAgZW1wbG95ZWVfaWQgICAgICAgPSBwYXN0ZTAoIkUiLCBjLCAiLSIsIGUpLA0KICAgICAgICBzYWxhcnkgICAgICAgICAgICA9IHNhbGFyeSwNCiAgICAgICAgZGVwYXJ0bWVudCAgICAgICAgPSBkZXBhcnRtZW50LA0KICAgICAgICBwZXJmb3JtYW5jZV9zY29yZSA9IHBlcmZvcm1hbmNlX3Njb3JlLA0KICAgICAgICBrcGlfc2NvcmUgICAgICAgICA9IGtwaV9zY29yZSwNCiAgICAgICAgdG9wX3BlcmZvcm1lciAgICAgPSB0b3BfcGVyZm9ybWVyDQogICAgICApDQogICAgICANCiAgICAgIGFsbF9kYXRhIDwtIHJiaW5kKGFsbF9kYXRhLCByb3cpDQogICAgfQ0KICB9DQogIA0KICByZXR1cm4oYWxsX2RhdGEpDQp9DQoNCmRhdGFfZGFzaGJvYXJkIDwtIGdlbmVyYXRlX2NvbXBhbnlfZGF0YSg2LCA1MCkNCg0KIyBFbnN1cmUgY29ycmVjdCBvcmRlcg0KZGF0YV9kYXNoYm9hcmQkY29tcGFueV9pZCA8LSBmYWN0b3IoDQogIGRhdGFfZGFzaGJvYXJkJGNvbXBhbnlfaWQsDQogIGxldmVscyA9IHBhc3RlMCgiQ29tcGFueS0iLCAxOjYpDQopDQoNCiMgQWRkIGNhdGVnb3JpZXMNCmRhdGFfZGFzaGJvYXJkIDwtIGRhdGFfZGFzaGJvYXJkICU+JQ0KICBtdXRhdGUoDQogICAgc2FsYXJ5X2NhdGVnb3J5ID0gY2FzZV93aGVuKA0KICAgICAgc2FsYXJ5ID49IDE1MDAwMDAwIH4gIkhpZ2giLA0KICAgICAgc2FsYXJ5ID49IDkwMDAwMDAgIH4gIk1lZGl1bSIsDQogICAgICBUUlVFICAgICAgICAgICAgICAgfiAiTG93Ig0KICAgICksDQogICAga3BpX2xldmVsID0gY2FzZV93aGVuKA0KICAgICAga3BpX3Njb3JlID49IDkwIH4gIlBsYXRpbnVtIiwNCiAgICAgIGtwaV9zY29yZSA+PSA4MCB+ICJHb2xkIiwNCiAgICAgIGtwaV9zY29yZSA+PSA3MCB+ICJTaWx2ZXIiLA0KICAgICAgVFJVRSAgICAgICAgICAgIH4gIkJyb256ZSINCiAgICApDQogICkNCg0KDQpkYXNoYm9hcmRfc3VtbWFyeSA8LSBkYXRhX2Rhc2hib2FyZCAlPiUNCiAgZ3JvdXBfYnkoY29tcGFueV9pZCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhdmdfc2FsYXJ5ICAgICAgPSByb3VuZChtZWFuKHNhbGFyeSksIDApLA0KICAgIGF2Z19rcGkgICAgICAgICA9IHJvdW5kKG1lYW4oa3BpX3Njb3JlKSwgMSksDQogICAgYXZnX3BlcmZvcm1hbmNlID0gcm91bmQobWVhbihwZXJmb3JtYW5jZV9zY29yZSksIDEpLA0KICAgIHRvdGFsX3RvcCAgICAgICA9IHN1bSh0b3BfcGVyZm9ybWVyID09ICJZZXMiKSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkNCg0KcHJpbnQoZGFzaGJvYXJkX3N1bW1hcnkpDQpgYGANCg0KIyMjIENoYXJ0IDEgOiAqKlRvcCBQZXJmb3JtZXJzIHBlciBDb21wYW55KioNClRoZSBudW1iZXIgb2YgdG9wIHBlcmZvcm1lcnMgdmFyaWVzIGFjcm9zcyBjb21wYW5pZXMsIGluZGljYXRpbmcgZGlmZmVyZW5jZXMgDQppbiBlbXBsb3llZSBwZXJmb3JtYW5jZSBxdWFsaXR5LiBTb21lIGNvbXBhbmllcyBoYXZlIHNpZ25pZmljYW50bHkgbW9yZSANCmhpZ2gtcGVyZm9ybWluZ2VtcGxveWVlcywgd2hpY2ggc3VnZ2VzdHMgc3Ryb25nZXIgcGVyZm9ybWFuY2UgbWFuYWdlbWVudCBvciANCnRhbGVudCBxdWFsaXR5Lg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KZ2dwbG90KGRhc2hib2FyZF9zdW1tYXJ5LCANCiAgICAgICBhZXMoeCA9IGZhY3Rvcihjb21wYW55X2lkLCBsZXZlbHMgPSBwYXN0ZTAoIkNvbXBhbnktIiwgMTo2KSksIA0KICAgICAgICAgICB5ID0gdG90YWxfdG9wLCANCiAgICAgICAgICAgZmlsbCA9IGNvbXBhbnlfaWQpKSArDQogIA0KICAjIEJhcg0KICBnZW9tX2NvbCh3aWR0aCA9IDAuNikgKw0KICANCiAgIyBMYWJlbCBkaSBhdGFzIGJhcg0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gdG90YWxfdG9wKSwgDQogICAgICAgICAgICB2anVzdCA9IC0wLjQsIHNpemUgPSA0LCBmb250ZmFjZSA9ICJib2xkIikgKw0KICANCiAgIyBTa2FsYSBZIGJpYXIgdGlkYWsga2Vwb3RvbmcNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjEpKSkgKw0KICANCiAgbGFicygNCiAgICB0aXRsZSAgICA9ICJUb3AgUGVyZm9ybWVycyBwZXIgQ29tcGFueSIsDQogICAgc3VidGl0bGUgPSAiRW1wbG95ZWVzIHdpdGggS1BJIFNjb3JlIEFib3ZlIDkwIiwNCiAgICB4ICAgICAgICA9ICJDb21wYW55IiwNCiAgICB5ICAgICAgICA9ICJOdW1iZXIgb2YgVG9wIFBlcmZvcm1lcnMiDQogICkgKw0KICANCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKw0KICANCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDExKSwNCiAgICANCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSksDQogICAgYXhpcy50aXRsZSAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgDQogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpDQogICkgKw0KICANCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikNCmBgYA0KDQojIyMgQ2hhcnQgMiA6ICoqQXZlcmFnZSBLUEkgYnkgRGVwYXJ0bWVudCAmIENvbXBhbnkqKg0KS1BJIHNjb3JlcyBkaWZmZXIgbm90IG9ubHkgYmV0d2VlbiBjb21wYW5pZXMgYnV0IGFsc28gYWNyb3NzIGRlcGFydG1lbnRzLg0KQ2VydGFpbiBkZXBhcnRtZW50cyBjb25zaXN0ZW50bHkgc2hvdyBoaWdoZXIgS1BJIGF2ZXJhZ2VzLCBzdWdnZXN0aW5nIHRoYXQNCnBlcmZvcm1hbmNlIG1heSBkZXBlbmQgb24gdGhlIG5hdHVyZSBvZiB0aGUgZGVwYXJ0bWVudCBhbmQgaXRzIG9wZXJhdGlvbmFsIGZvY3VzLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBTdW1tYXJ5DQpkZXBhcnRtZW50X3N1bW1hcnkgPC0gZGF0YV9kYXNoYm9hcmQgJT4lDQogIGdyb3VwX2J5KGNvbXBhbnlfaWQsIGRlcGFydG1lbnQpICU+JQ0KICBzdW1tYXJpc2UoYXZnX2twaSA9IHJvdW5kKG1lYW4oa3BpX3Njb3JlKSwgMSksIC5ncm91cHMgPSAiZHJvcCIpDQoNCiMgUGxvdA0KZ2dwbG90KGRlcGFydG1lbnRfc3VtbWFyeSwNCiAgICAgICBhZXMoeCA9IGZhY3RvcihkZXBhcnRtZW50LA0KICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkZpbmFuY2UiLCJNYXJrZXRpbmciLCJPcGVyYXRpb25zIiwiSFIiLCJUZWNobm9sb2d5IikpLA0KICAgICAgICAgICB5ID0gYXZnX2twaSwNCiAgICAgICAgICAgZmlsbCA9IGNvbXBhbnlfaWQpKSArDQogIA0KICAjIEJhciBjaGFydA0KICBnZW9tX2NvbChwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC43KSwgd2lkdGggPSAwLjYpICsNCiAgDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwgMC4xKSkpICsNCiAgDQogIGxhYnMoDQogICAgdGl0bGUgICAgPSAiQXZlcmFnZSBLUEkgYnkgRGVwYXJ0bWVudCBhbmQgQ29tcGFueSIsDQogICAgc3VidGl0bGUgPSAiQ29tcGFyaXNvbiBhY3Jvc3MgZGVwYXJ0bWVudHMiLA0KICAgIHggICAgICAgID0gIkRlcGFydG1lbnQiLA0KICAgIHkgICAgICAgID0gIkF2ZXJhZ2UgS1BJIiwNCiAgICBmaWxsICAgICA9ICJDb21wYW55Ig0KICApICsNCiAgDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsNCiAgDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAyNSwgaGp1c3QgPSAxKSwNCiAgICBheGlzLnRpdGxlICA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICANCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkNCiAgKSArDQogIA0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKQ0KYGBgDQoNCiMjIyBDaGFydCAzIDogKipQZXJmb3JtYW5jZSB2cyBLUEkgUmVsYXRpb25zaGlwKioNClRoZXJlIGlzIGEgY2xlYXIgcG9zaXRpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gcGVyZm9ybWFuY2Ugc2NvcmVzIGFuZCBLUEkgc2NvcmVzLg0KRW1wbG95ZWVzIHdpdGggaGlnaGVyIHBlcmZvcm1hbmNlIHNjb3JlcyB0ZW5kIHRvIGFjaGlldmUgaGlnaGVyIEtQSSB2YWx1ZXMsIA0KY29uZmlybWluZyB0aGF0IEtQSSBldmFsdWF0aW9uIGFsaWducyB3ZWxsIHdpdGggcGVyZm9ybWFuY2UgbWV0cmljcy4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmdncGxvdChkYXRhX2Rhc2hib2FyZCwgDQogICAgICAgYWVzKHggPSBwZXJmb3JtYW5jZV9zY29yZSwgDQogICAgICAgICAgIHkgPSBrcGlfc2NvcmUsIA0KICAgICAgICAgICBjb2xvciA9IGNvbXBhbnlfaWQpKSArDQogIA0KICAjIFRpdGlrIGRhdGENCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgc2l6ZSA9IDIpICsNCiAgDQogICMgR2FyaXMgcmVncmVzaQ0KICAgIGdlb21fc21vb3RoKA0KICAgIG1ldGhvZCA9ICJsbSIsDQogICAgZm9ybXVsYSA9IHkgfiB4LA0KICAgIHNlID0gRkFMU0UsDQogICAgbGluZXdpZHRoID0gMQ0KICApICsNCiAgDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDgwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5NjAiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDc1LCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5NjAiKSArDQogIA0KICBsYWJzKA0KICAgIHRpdGxlICAgID0gIlBlcmZvcm1hbmNlIFNjb3JlIHZzIEtQSSBTY29yZSIsDQogICAgc3VidGl0bGUgPSAiUmVsYXRpb25zaGlwIGJldHdlZW4gcGVyZm9ybWFuY2UgYW5kIEtQSSBhY3Jvc3MgY29tcGFuaWVzIiwNCiAgICB4ICAgICAgICA9ICJQZXJmb3JtYW5jZSBTY29yZSIsDQogICAgeSAgICAgICAgPSAiS1BJIFNjb3JlIiwNCiAgICBjb2xvciAgICA9ICJDb21wYW55Ig0KICApICsNCiAgDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsNCiAgDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgDQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICBheGlzLnRleHQgID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkNCiAgKSArDQogIA0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpDQpgYGANCg0KIyMjIENoYXJ0IDQgOiAqKlNhbGFyeSBEaXN0cmlidXRpb24gcGVyIENvbXBhbnkqKg0KU2FsYXJ5IGRpc3RyaWJ1dGlvbnMgdmFyeSBhY3Jvc3MgY29tcGFuaWVzLCBidXQgbW9zdCBmb2xsb3cgYSByZWxhdGl2ZWx5IHNpbWlsYQ0KciBzcHJlYWQuIFRoaXMgaW5kaWNhdGVzIHRoYXQgd2hpbGUgc2FsYXJ5IHJhbmdlcyBhcmUgY29tcGFyYWJsZSwgdGhlcmUgbWF5IHN0aWxsDQpiZSBzbGlnaHQgZGlmZmVyZW5jZXMgaW4gY29tcGVuc2F0aW9uIHN0cmF0ZWdpZXMgYmV0d2VlbiBjb21wYW5pZXMuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQpnZ3Bsb3QoZGF0YV9kYXNoYm9hcmQsIGFlcyh4ID0gc2FsYXJ5LCBmaWxsID0gY29tcGFueV9pZCkpICsNCiAgDQogIGdlb21faGlzdG9ncmFtKA0KICAgIGJpbnMgPSAxMCwgICAgICAgICAgICAgIA0KICAgIGNvbG9yID0gIndoaXRlIiwNCiAgICBhbHBoYSA9IDAuOA0KICApICsNCiAgDQogIGZhY2V0X3dyYXAofmNvbXBhbnlfaWQsIG5jb2wgPSAzKSArDQogIA0KICBsYWJzKA0KICAgIHRpdGxlICAgID0gIlNhbGFyeSBEaXN0cmlidXRpb24gcGVyIENvbXBhbnkiLA0KICAgIHN1YnRpdGxlID0gIkhpc3RvZ3JhbSBvZiBlbXBsb3llZSBzYWxhcmllcyBhY3Jvc3MgY29tcGFuaWVzIiwNCiAgICB4ICAgICAgICA9ICJTYWxhcnkgKElEUikiLA0KICAgIHkgICAgICAgID0gIkZyZXF1ZW5jeSINCiAgKSArDQogIA0KICB0aGVtZV9taW5pbWFsKCkgKw0KICANCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpDQogICkgKw0KICANCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikNCmBgYA0KDQojIyMgQ2hhcnQgNSA6ICoqS1BJIExldmVsIERpc3RyaWJ1dGlvbioqDQpNb3N0IGVtcGxveWVlcyBmYWxsIGludG8gdGhlIEdvbGQgYW5kIFNpbHZlciBjYXRlZ29yaWVzLCB3aGlsZSBmZXdlciByZWFjaCB0aGUgDQpQbGF0aW51bSBsZXZlbC4gVGhpcyBzdWdnZXN0cyB0aGF0IGFjaGlldmluZyB0b3AtdGllciBwZXJmb3JtYW5jZSBpcyByZWxhdGl2ZWx5IHJhcmUsIA0KYW5kIG1vc3QgZW1wbG95ZWVzIHBlcmZvcm0gYXQgYSBtb2RlcmF0ZSB0byBoaWdoIGxldmVsLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KDQprcGlfbGV2ZWxfc3VtbWFyeSA8LSBkYXRhX2Rhc2hib2FyZCAlPiUNCiAgY291bnQoY29tcGFueV9pZCwga3BpX2xldmVsKSAlPiUNCiAgbXV0YXRlKA0KICAgIGtwaV9sZXZlbCA9IGZhY3RvcihrcGlfbGV2ZWwsDQogICAgICBsZXZlbHMgPSBjKCJQbGF0aW51bSIsICJHb2xkIiwgIlNpbHZlciIsICJCcm9uemUiKQ0KICAgICksDQogICAgY29tcGFueV9pZCA9IGZhY3Rvcihjb21wYW55X2lkLCBsZXZlbHMgPSBwYXN0ZTAoIkNvbXBhbnktIiwgMTo2KSkNCiAgKQ0KDQojIFBsb3QNCmdncGxvdChrcGlfbGV2ZWxfc3VtbWFyeSwNCiAgICAgICBhZXMoeCA9IGNvbXBhbnlfaWQsIHkgPSBuLCBmaWxsID0ga3BpX2xldmVsKSkgKw0KICANCiAgIyBCYXINCiAgZ2VvbV9jb2wod2lkdGggPSAwLjYpICsNCiAgDQogICMgTGFiZWwgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwNCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLA0KICAgICAgICAgICAgc2l6ZSA9IDMuNSwgY29sb3IgPSAid2hpdGUiKSArDQogIA0KICBsYWJzKA0KICAgIHRpdGxlICAgID0gIktQSSBMZXZlbCBEaXN0cmlidXRpb24gcGVyIENvbXBhbnkiLA0KICAgIHN1YnRpdGxlID0gIkVtcGxveWVlIGNsYXNzaWZpY2F0aW9uIGJhc2VkIG9uIEtQSSBzY29yZSIsDQogICAgeCAgICAgICAgPSAiQ29tcGFueSIsDQogICAgeSAgICAgICAgPSAiTnVtYmVyIG9mIEVtcGxveWVlcyIsDQogICAgZmlsbCAgICAgPSAiS1BJIExldmVsIg0KICApICsNCiAgDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsNCiAgDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgDQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICBheGlzLnRleHQgID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkNCiAgKSArDQogIA0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKA0KICAgICJQbGF0aW51bSIgPSAiIzlDMjdCMCIsDQogICAgIkdvbGQiICAgICA9ICIjRkZDMTA3IiwNCiAgICAiU2lsdmVyIiAgID0gIiM5RTlFOUUiLA0KICAgICJCcm9uemUiICAgPSAiIzc5NTU0OCINCiAgKSkNCmBgYA0KDQojIyBBdXRvbWF0ZWQgUmVwb3J0IEdlbmVyYXRpb24NClRoaXMgdGFzayBmb2N1c2VzIG9uIGJ1aWxkaW5nIGFuIGF1dG9tYXRlZCByZXBvcnRpbmcgc3lzdGVtIHVzaW5nIFIuIFRoZSBnb2FsIGlzDQp0byBnZW5lcmF0ZSByZXBvcnRzIGZvciBlYWNoIGNvbXBhbnkgdGhhdCBpbmNsdWRlIHN1bW1hcnkgc3RhdGlzdGljcyBhbmQgdmlzdWFsaXphdGlvbnMuDQpCeSB1c2luZyBmdW5jdGlvbnMgYW5kIGxvb3BzLCB0aGUgcHJvY2VzcyBiZWNvbWVzIGZhc3RlciwgbW9yZSBjb25zaXN0ZW50LCBhbmQNCnJlZHVjZXMgbWFudWFsIGVycm9ycy4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgMS4gRlVOQ1RJT04gRklYIChFbnN1cmUgYXJndW1lbnQgaXM6ICdjb21wJykNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmdlbmVyYXRlX2NvbXBhbnlfcmVwb3J0IDwtIGZ1bmN0aW9uKGRhdGEsIGNvbXApIHsNCiAgDQogICMgRmlsdGVyIGRhdGEgYmFzZWQgb24gY29tcGFueSBJRA0KICAjIFVzZSAnY29tcCcgYWNjb3JkaW5nIHRvIHRoZSBmdW5jdGlvbiBhcmd1bWVudA0KICBjb21wYW55X2RhdGEgPC0gZGF0YSAlPiUNCiAgICBmaWx0ZXIoY29tcGFueV9pZCA9PSBjb21wKQ0KICANCiAgIyBWYWxpZGF0aW9uOiBpZiBubyBkYXRhIGZvdW5kLCBzdG9wIHRoZSBmdW5jdGlvbg0KICBpZiAobnJvdyhjb21wYW55X2RhdGEpID09IDApIHJldHVybihOVUxMKQ0KICANCiAgY2F0KCJcblxuIyMgUkVQT1JUIEZPUjoiLCBjb21wLCAiXG4iKQ0KICBjYXQoIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiIpDQogIA0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIFNVTU1BUlkgU1RBVElTVElDUw0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIFVzZSBuYS5ybSA9IFRSVUUgdG8gYXZvaWQgZXJyb3JzIGZyb20gbWlzc2luZyB2YWx1ZXMNCiAgc3VtbWFyeV90YWJsZSA8LSBjb21wYW55X2RhdGEgJT4lDQogICAgc3VtbWFyaXNlKA0KICAgICAgYXZnX3NhbGFyeSAgICAgID0gcm91bmQobWVhbihzYWxhcnksIG5hLnJtID0gVFJVRSksIDApLA0KICAgICAgYXZnX3BlcmZvcm1hbmNlID0gcm91bmQobWVhbihwZXJmb3JtYW5jZV9zY29yZSwgbmEucm0gPSBUUlVFKSwgMiksDQogICAgICBhdmdfa3BpICAgICAgICAgPSByb3VuZChtZWFuKGtwaV9zY29yZSwgbmEucm0gPSBUUlVFKSwgMiksDQogICAgICB0b3RhbF9lbXBsb3llZXMgPSBuKCkNCiAgICApDQogIA0KICBwcmludChrbml0cjo6a2FibGUoc3VtbWFyeV90YWJsZSkpDQogIA0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIFBMT1Q6IFNBTEFSWSBESVNUUklCVVRJT04NCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgcGxvdF9zYWxhcnkgPC0gZ2dwbG90KGNvbXBhbnlfZGF0YSwgYWVzKHggPSBzYWxhcnkpKSArDQogICAgDQogICAgZ2VvbV9oaXN0b2dyYW0oDQogICAgICBiaW5zID0gMTAsDQogICAgICBmaWxsID0gIiNBNUQ2QTciLA0KICAgICAgY29sb3IgPSAid2hpdGUiDQogICAgKSArDQogICAgDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gcGFzdGUoIlNhbGFyeSBEaXN0cmlidXRpb24gLSIsIGNvbXApLA0KICAgICAgeCA9ICJTYWxhcnkiLA0KICAgICAgeSA9ICJGcmVxdWVuY3kiDQogICAgKSArDQogICAgDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICANCiAgICB0aGVtZSgNCiAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpICAjIENlbnRlciB0aXRsZQ0KICAgICkNCiAgDQogIHByaW50KHBsb3Rfc2FsYXJ5KQ0KICANCiAgY2F0KCJcbiIpDQp9DQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgMi4gTE9PUCBFWEVDVVRJT04gKE1ha2Ugc3VyZSBkYXRhX2Rhc2hib2FyZCBleGlzdHMpDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMgR2V0IHVuaXF1ZSBjb21wYW55IElEcw0KY29tcGFueV9saXN0IDwtIHVuaXF1ZShkYXRhX2Rhc2hib2FyZCRjb21wYW55X2lkKQ0KDQojIFJ1biBsb29wDQpmb3IgKGl0ZW0gaW4gY29tcGFueV9saXN0KSB7DQogIGdlbmVyYXRlX2NvbXBhbnlfcmVwb3J0KGRhdGFfZGFzaGJvYXJkLCBpdGVtKQ0KfQ0KYGBg