knitr::opts_chunk$set(echo = TRUE)
library(tidyverse)
library(shiny)
library(shinydashboard)
library(DT)
library(ggplot2)
save(mlws_clean, file = "mlws_clea.RData")
load("mlws.RData")

Checking name of each variables

ls(mlws)
 [1] "Band Width (mm)"     "Brand"               "Case Diameter (mm)"  "Case Material"       "Case Thickness (mm)" "Complications"      
 [7] "Crystal Material"    "Dial Color"          "Model"               "Movement Type"       "Power Reserve"       "Price (USD)"        
[13] "Strap Material"      "Water Resistance"   

Checking name of each variables

variable_names <- colnames(mlws)
print(variable_names)
 [1] "Brand"               "Model"               "Case Material"       "Strap Material"      "Movement Type"       "Water Resistance"   
 [7] "Case Diameter (mm)"  "Case Thickness (mm)" "Band Width (mm)"     "Dial Color"          "Crystal Material"    "Complications"      
[13] "Power Reserve"       "Price (USD)"        

Checking type of each variables

str(mlws)
spc_tbl_ [507 × 14] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Brand              : chr [1:507] "Rolex" "Omega" "Tag Heuer" "Breitling" ...
 $ Model              : chr [1:507] "Submariner" "Seamaster" "Carrera" "Navitimer" ...
 $ Case Material      : chr [1:507] "Stainless Steel" "Titanium" "Stainless Steel" "Stainless Steel" ...
 $ Strap Material     : chr [1:507] "Stainless Steel" "Rubber" "Leather" "Stainless Steel" ...
 $ Movement Type      : chr [1:507] "Automatic" "Automatic" "Automatic" "Automatic" ...
 $ Water Resistance   : chr [1:507] "300 meters" "600 meters" "100 meters" "30 meters" ...
 $ Case Diameter (mm) : num [1:507] 40 43.5 41 43 31 42.9 44.3 42 39.3 38 ...
 $ Case Thickness (mm): num [1:507] 13 14.47 13 14.25 6.05 ...
 $ Band Width (mm)    : num [1:507] 20 21 20 22 20 20 20 22 19 20 ...
 $ Dial Color         : chr [1:507] "Black" "Blue" "White" "Black" ...
 $ Crystal Material   : chr [1:507] "Sapphire" "Sapphire" "Sapphire" "Sapphire" ...
 $ Complications      : chr [1:507] "Date" "Date" "Chronograph" "Chronograph" ...
 $ Power Reserve      : chr [1:507] "48 hours" "60 hours" "42 hours" "70 hours" ...
 $ Price (USD)        : num [1:507] 9500 5800 4200 7900 2800 5500 1400 1200 650 495 ...
 - attr(*, "spec")=
  .. cols(
  ..   Brand = col_character(),
  ..   Model = col_character(),
  ..   `Case Material` = col_character(),
  ..   `Strap Material` = col_character(),
  ..   `Movement Type` = col_character(),
  ..   `Water Resistance` = col_character(),
  ..   `Case Diameter (mm)` = col_double(),
  ..   `Case Thickness (mm)` = col_double(),
  ..   `Band Width (mm)` = col_double(),
  ..   `Dial Color` = col_character(),
  ..   `Crystal Material` = col_character(),
  ..   Complications = col_character(),
  ..   `Power Reserve` = col_character(),
  ..   `Price (USD)` = col_number()
  .. )
 - attr(*, "problems")=<externalptr> 

removing non significant variables

mlws_clean <- mlws %>%
  select(-`Case Thickness (mm)`, -`Band Width (mm)`, -`Crystal Material`, -Complications, -`Power Reserve`)
str(mlws_clean)
tibble [507 × 9] (S3: tbl_df/tbl/data.frame)
 $ Brand             : chr [1:507] "Rolex" "Omega" "Tag Heuer" "Breitling" ...
 $ Model             : chr [1:507] "Submariner" "Seamaster" "Carrera" "Navitimer" ...
 $ Case Material     : chr [1:507] "Stainless Steel" "Titanium" "Stainless Steel" "Stainless Steel" ...
 $ Strap Material    : chr [1:507] "Stainless Steel" "Rubber" "Leather" "Stainless Steel" ...
 $ Movement Type     : chr [1:507] "Automatic" "Automatic" "Automatic" "Automatic" ...
 $ Water Resistance  : chr [1:507] "300 meters" "600 meters" "100 meters" "30 meters" ...
 $ Case Diameter (mm): num [1:507] 40 43.5 41 43 31 42.9 44.3 42 39.3 38 ...
 $ Dial Color        : chr [1:507] "Black" "Blue" "White" "Black" ...
 $ Price (USD)       : num [1:507] 9500 5800 4200 7900 2800 5500 1400 1200 650 495 ...

identifying how many categories per variable

categories_per_column <- sapply(mlws_clean, n_distinct)
print(categories_per_column)
             Brand              Model      Case Material     Strap Material      Movement Type   Water Resistance Case Diameter (mm) 
                39                 95                 17                 14                  4                 12                 37 
        Dial Color        Price (USD) 
                 7                183 

Checking the different values within variables

unique(mlws_clean$`Movement Type`)
[1] "Automatic" "Quartz"    "Manual"    "Eco-Drive"
unique(mlws_clean$`Case Material`)
 [1] "Stainless Steel"        "Titanium"               "18k White Gold"         "Ceramic"                "18K Rose Gold"         
 [6] "Rose Gold"              "White Gold"             "18K White Gold"         "18k Rose Gold"          "High-Tech Ceramic"     
[11] "German Submarine Steel" "Bronze"                 "Carbon Fiber"           "Yellow Gold"            "18k King Gold"         
[16] "18k Yellow Gold"        "18K Yellow Gold"       
unique(mlws_clean$`Strap Material`)
 [1] "Stainless Steel"  "Rubber"           "Leather"          "Textile"          "Jubilee"          "Alligator"       
 [7] "Canvas"           "Fabric"           "Silicone"         "Titanium"         "Rose Gold"        "NATO Strap"      
[13] "Jubilee Bracelet" "NATO strap"      
unique(mlws_clean$`Water Resistance`)
 [1] "300 meters"  "600 meters"  "100 meters"  "30 meters"   "200 meters"  "50 meters"   "150 meters"  "500 meters"  "60 meters"  
[10] "120 meters"  "1000 meters" "2000 meters"

Identifying missing values

mlws_clean$RowIndex <- seq_len(nrow(mlws_clean))
mlws_clean_filtered <- na.omit(mlws_clean)
mlws_clean_filtered <- mlws_clean %>% 
  filter(!if_any(everything(), is.na))
removed_rows <- setdiff(mlws_clean$RowIndex, mlws_clean_filtered$RowIndex)
print(removed_rows)
[1] 25

Checking the dataset with the removed rows (1 row deleted from process)

mlws_clean <- mlws_clean %>%
  filter(!if_any(everything(), is.na))
str(mlws_clean)
tibble [506 × 10] (S3: tbl_df/tbl/data.frame)
 $ Brand             : chr [1:506] "Rolex" "Omega" "Tag Heuer" "Breitling" ...
 $ Model             : chr [1:506] "Submariner" "Seamaster" "Carrera" "Navitimer" ...
 $ Case Material     : chr [1:506] "Stainless Steel" "Titanium" "Stainless Steel" "Stainless Steel" ...
 $ Strap Material    : chr [1:506] "Stainless Steel" "Rubber" "Leather" "Stainless Steel" ...
 $ Movement Type     : chr [1:506] "Automatic" "Automatic" "Automatic" "Automatic" ...
 $ Water Resistance  : chr [1:506] "300 meters" "600 meters" "100 meters" "30 meters" ...
 $ Case Diameter (mm): num [1:506] 40 43.5 41 43 31 42.9 44.3 42 39.3 38 ...
 $ Dial Color        : chr [1:506] "Black" "Blue" "White" "Black" ...
 $ Price (USD)       : num [1:506] 9500 5800 4200 7900 2800 5500 1400 1200 650 495 ...
 $ RowIndex          : int [1:506] 1 2 3 4 5 6 7 8 9 10 ...

deleting the index row and renaming the dataset mlws_clean instead of mlws_clean_filtered

mlws_clean_filtered$RowIndex <- NULL
mlws_clean <- mlws_clean_filtered
rm(mlws_clean_filtered)
str(mlws_clean)
tibble [506 × 9] (S3: tbl_df/tbl/data.frame)
 $ Brand             : chr [1:506] "Rolex" "Omega" "Tag Heuer" "Breitling" ...
 $ Model             : chr [1:506] "Submariner" "Seamaster" "Carrera" "Navitimer" ...
 $ Case Material     : chr [1:506] "Stainless Steel" "Titanium" "Stainless Steel" "Stainless Steel" ...
 $ Strap Material    : chr [1:506] "Stainless Steel" "Rubber" "Leather" "Stainless Steel" ...
 $ Movement Type     : chr [1:506] "Automatic" "Automatic" "Automatic" "Automatic" ...
 $ Water Resistance  : chr [1:506] "300 meters" "600 meters" "100 meters" "30 meters" ...
 $ Case Diameter (mm): num [1:506] 40 43.5 41 43 31 42.9 44.3 42 39.3 38 ...
 $ Dial Color        : chr [1:506] "Black" "Blue" "White" "Black" ...
 $ Price (USD)       : num [1:506] 9500 5800 4200 7900 2800 5500 1400 1200 650 495 ...

Replacing the incorrect speeling of A. Lange & Sohne to A. Lange & Söhne

mlws_clean$Brand <- gsub("A. Lange & Sohne", "A. Lange & Söhne", mlws_clean$Brand)
table(mlws_clean$Brand)

   A. Lange & Söhne     Audemars Piguet     Baume & Mercier         Bell & Ross           Blancpain             Breguet 
                  3                  38                   1                   3                  29                  26 
          Breitling             Bulgari              Bulova             Cartier             Chopard             Citizen 
                 16                   3                   1                  19                  14                   2 
Frederique Constant    Girard-Perregaux  Glashütte Original            Hamilton              Hublot                 IWC 
                  2                  20                   1                   4                  26                  39 
   Jaeger-LeCoultre         Jaquet Droz            Longines           Montblanc               Omega                Oris 
                 16                   5                  10                   9                  23                   2 
            Panerai      Patek Philippe              Piaget                Rado               Rolex               Seiko 
                 25                  33                   6                   1                  27                   5 
               Sinn           Tag Heuer           TAG Heuer              Tissot               Tudor       Ulysse Nardin 
                  3                  11                   2                   2                  14                  10 
Vacheron Constantin              Zenith 
                 25                  30 
library(ggplot2)

ggplot(mlws_clean, aes(x = Brand, y = `Price (USD)`, fill = "Brand")) +
  geom_boxplot() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "Price Distribution by Brand", x = "Brand", y = "Price (USD)")

NA
NA
ggplot(mlws_clean, aes(x = `Case Material`, fill = `Case Material`)) +
  geom_bar() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "none") + 
  labs(title = "Frequency of Watches by Case Material", x = "Case Material", y = "Count")
ggplot(mlws_clean, aes(x = `Case Diameter (mm)`)) +
  geom_histogram(binwidth = 1, fill = "black", color = "white") +
  labs(title = "Distribution of Case Diameters", x = "Case Diameter (mm)", y = "Frequency") +
  theme_dark()

average_price_per_brand <- mlws_clean %>%
  group_by(Brand) %>%
  summarise(Average_Price = mean(`Price (USD)`, na.rm = TRUE)) %>%
  arrange(Average_Price) 

ggplot(average_price_per_brand, aes(x = reorder(Brand, Average_Price), y = Average_Price, fill = Brand)) +
  geom_col() + 
  coord_flip() + 
  theme(
    axis.text.x = element_text(angle = 0, hjust = 1), 
    legend.position = "none"  
  ) +
  labs(title = "Average Watch Price per Brand in USD", x = "Average Price (USD)", y = "Brand")

# Library imports
library(tidyverse)
library(shiny)
library(shinydashboard)
library(DT)
library(ggplot2)
library(rsconnect)

# Load data
load("mlws.RData")

# Preprocessing data
mlws_clean <- mlws %>%
  select(-`Case Thickness (mm)`, -`Band Width (mm)`, -`Crystal Material`, -Complications, -`Power Reserve`) %>%
  mutate(Brand = gsub("A. Lange & Sohne", "A. Lange & Söhne", Brand)) %>%
  filter(!if_any(everything(), is.na)) %>%
  mutate(RowIndex = row_number()) %>%
  filter(!is.na(`Price (USD)`)) %>%
  arrange(desc(`Price (USD)`))

# UI definition
ui <- fluidPage(
  titlePanel("My Luxury Watch Shopper"),
  sidebarLayout(
    sidebarPanel(
      selectInput("brand", "Brand:", choices = c("All", unique(mlws_clean$Brand))),
      sliderInput("priceRange", "Price Range:", min = min(mlws_clean$`Price (USD)`, na.rm = TRUE), 
                  max = max(mlws_clean$`Price (USD)`, na.rm = TRUE), 
                  value = c(min(mlws_clean$`Price (USD)`, na.rm = TRUE), max(mlws_clean$`Price (USD)`, na.rm = TRUE))),
      selectInput("caseMaterial", "Case Material:", choices = c("All", unique(mlws_clean$`Case Material`))),
      selectInput("strapMaterial", "Strap Material:", choices = c("All", unique(mlws_clean$`Strap Material`))),
      actionButton("goButton", "Find Watches")
    ),
    mainPanel(
      tabsetPanel(
        tabPanel("Recommendations",
                 DTOutput("table"),
                 h2("Top 5 Best Watches for You", align = "center"),
                 DTOutput("topTierTable")  # Changed to DTOutput for DataTables integration
        ),
        tabPanel("Price Distribution", 
                 plotOutput("pricePlot"),
                 plotOutput("avgPricePlot")
        ),
        tabPanel("Case Material Distribution", plotOutput("caseMaterialPlot")),
        tabPanel("Recommendation Details", textOutput("recText"), plotOutput("radarChart"))
      )
    )
  )
)


server <- function(input, output) {
  # Reactive expression for filtered data
  dataFiltered <- reactive({
    req(input$goButton)
    mlws_clean %>%
      filter((Brand == input$brand | input$brand == "All") &
               `Price (USD)` >= input$priceRange[1] &
               `Price (USD)` <= input$priceRange[2] &
               (`Case Material` == input$caseMaterial | input$caseMaterial == "All") &
               (`Strap Material` == input$strapMaterial | input$strapMaterial == "All"))
  })
  
  # Rendering the DataTable
  output$table <- renderDT({
    dataFiltered()
  }, options = list(pageLength = 5))
  
  # Rendering the price distribution plot
  output$pricePlot <- renderPlot({
    ggplot(dataFiltered(), aes(x = `Price (USD)`)) +
      geom_histogram(binwidth = 500, fill = "blue") +
      labs(title = "Price Distribution", x = "Price (USD)", y = "Count") +
      theme_minimal(base_size = 14) + 
      theme(panel.background = element_blank(), 
            panel.grid.major = element_blank(), 
            panel.grid.minor = element_blank())
  })
  
  # Rendering the average price per case material plot
  output$avgPricePlot <- renderPlot({
    avg_price_data <- mlws_clean %>%
      group_by(`Case Material`) %>%
      summarize(AvgPrice = mean(`Price (USD)`, na.rm = TRUE)) %>%
      ungroup() %>%
      arrange(desc(AvgPrice))
    
    ggplot(avg_price_data, aes(x = reorder(`Case Material`, AvgPrice), y = AvgPrice, fill = `Case Material`)) +
      geom_col() +
      theme_minimal(base_size = 14) + 
      theme(panel.background = element_blank(),
            panel.grid.major = element_blank(),
            panel.grid.minor = element_blank(),
            legend.position = "none") +
      labs(title = "Average Price per Case Material", x = "Case Material", y = "Average Price (USD)") +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  # Rendering the case material distribution plot
  output$caseMaterialPlot <- renderPlot({
    ggplot(dataFiltered(), aes(x = `Case Material`, fill = `Case Material`)) +
      geom_bar(stat = "count") +
      theme_minimal(base_size = 14) + 
      theme(panel.background = element_blank(),
            panel.grid.major = element_blank(),
            panel.grid.minor = element_blank()) +
      labs(title = "Case Material Distribution", x = "Case Material", y = "Count") +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  # This is the function to get top tier watches
  getTopTierWatches <- function(data, n = 5) {
    data %>%
      arrange(desc(`Price (USD)`)) %>%
      slice_head(n = n)
  }
  
  # Reactivity expression for top 5 watches
  topTierWatches <- reactive({
    req(input$goButton) 
    getTopTierWatches(dataFiltered())
  })
  
  output$topTierTable <- renderDT({
    topTierWatches() %>%
      select(Brand, Model, `Price (USD)`)
  }, options = list(pageLength = 5, searching = FALSE, paging = FALSE, info = FALSE))
  
  # Placeholder for recommendation text
  output$recText <- renderText({
    rec <- recommendedWatch()
    if(nrow(rec) > 0) {
      paste("We recommend the", rec$Brand, "watch with a price of", rec$`Price (USD)`, "USD.")
    } else {
      "No recommendation available based on the current criteria."
    }
  })
  
  # Generating a recommendation based on filtered data
  recommendedWatch <- reactive({
    data <- dataFiltered()
    if(nrow(data) > 0) {
      data[which.max(data$`Price (USD)`), ]
    } else {
      data.frame()
    }
  })
}

# Run the application
shinyApp(ui, server)

Listening on http://127.0.0.1:7806
NA
LS0tCnRpdGxlOiAiTXkgTHV4dXJ5IFdhdGNoIFNob3BwZXIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCgpgYGB7cn0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzaGlueSkKbGlicmFyeShzaGlueWRhc2hib2FyZCkKbGlicmFyeShEVCkKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCmBgYHtyfQpzYXZlKG1sd3NfY2xlYW4sIGZpbGUgPSAibWx3c19jbGVhLlJEYXRhIikKYGBgCgoKYGBge3J9CmxvYWQoIm1sd3MuUkRhdGEiKQpgYGAKCkNoZWNraW5nIG5hbWUgb2YgZWFjaCB2YXJpYWJsZXMKYGBge3J9CmxzKG1sd3MpCmBgYAoKQ2hlY2tpbmcgbmFtZSBvZiBlYWNoIHZhcmlhYmxlcwpgYGB7cn0KdmFyaWFibGVfbmFtZXMgPC0gY29sbmFtZXMobWx3cykKcHJpbnQodmFyaWFibGVfbmFtZXMpCgpgYGAKQ2hlY2tpbmcgdHlwZSBvZiBlYWNoIHZhcmlhYmxlcwpgYGB7cn0Kc3RyKG1sd3MpCmBgYAoKcmVtb3Zpbmcgbm9uIHNpZ25pZmljYW50IHZhcmlhYmxlcwpgYGB7cn0KbWx3c19jbGVhbiA8LSBtbHdzICU+JQogIHNlbGVjdCgtYENhc2UgVGhpY2tuZXNzIChtbSlgLCAtYEJhbmQgV2lkdGggKG1tKWAsIC1gQ3J5c3RhbCBNYXRlcmlhbGAsIC1Db21wbGljYXRpb25zLCAtYFBvd2VyIFJlc2VydmVgKQpzdHIobWx3c19jbGVhbikKYGBgCgppZGVudGlmeWluZyBob3cgbWFueSBjYXRlZ29yaWVzIHBlciB2YXJpYWJsZQpgYGB7cn0KY2F0ZWdvcmllc19wZXJfY29sdW1uIDwtIHNhcHBseShtbHdzX2NsZWFuLCBuX2Rpc3RpbmN0KQpwcmludChjYXRlZ29yaWVzX3Blcl9jb2x1bW4pCmBgYAoKQ2hlY2tpbmcgdGhlIGRpZmZlcmVudCB2YWx1ZXMgd2l0aGluIHZhcmlhYmxlcwpgYGB7cn0KdW5pcXVlKG1sd3NfY2xlYW4kYE1vdmVtZW50IFR5cGVgKQpgYGAKYGBge3J9CnVuaXF1ZShtbHdzX2NsZWFuJGBDYXNlIE1hdGVyaWFsYCkKYGBgCmBgYHtyfQp1bmlxdWUobWx3c19jbGVhbiRgU3RyYXAgTWF0ZXJpYWxgKQpgYGAKYGBge3J9CnVuaXF1ZShtbHdzX2NsZWFuJGBXYXRlciBSZXNpc3RhbmNlYCkKYGBgCgoKCgoKSWRlbnRpZnlpbmcgbWlzc2luZyB2YWx1ZXMKYGBge3J9Cm1sd3NfY2xlYW4kUm93SW5kZXggPC0gc2VxX2xlbihucm93KG1sd3NfY2xlYW4pKQpgYGAKYGBge3J9Cm1sd3NfY2xlYW5fZmlsdGVyZWQgPC0gbmEub21pdChtbHdzX2NsZWFuKQptbHdzX2NsZWFuX2ZpbHRlcmVkIDwtIG1sd3NfY2xlYW4gJT4lIAogIGZpbHRlcighaWZfYW55KGV2ZXJ5dGhpbmcoKSwgaXMubmEpKQpgYGAKYGBge3J9CnJlbW92ZWRfcm93cyA8LSBzZXRkaWZmKG1sd3NfY2xlYW4kUm93SW5kZXgsIG1sd3NfY2xlYW5fZmlsdGVyZWQkUm93SW5kZXgpCnByaW50KHJlbW92ZWRfcm93cykKYGBgCgpDaGVja2luZyB0aGUgZGF0YXNldCB3aXRoIHRoZSByZW1vdmVkIHJvd3MgKDEgcm93IGRlbGV0ZWQgZnJvbSBwcm9jZXNzKQpgYGB7cn0KbWx3c19jbGVhbiA8LSBtbHdzX2NsZWFuICU+JQogIGZpbHRlcighaWZfYW55KGV2ZXJ5dGhpbmcoKSwgaXMubmEpKQpzdHIobWx3c19jbGVhbikKYGBgCgpkZWxldGluZyB0aGUgaW5kZXggcm93IGFuZCByZW5hbWluZyB0aGUgZGF0YXNldCBtbHdzX2NsZWFuIGluc3RlYWQgb2YgbWx3c19jbGVhbl9maWx0ZXJlZApgYGB7cn0KbWx3c19jbGVhbl9maWx0ZXJlZCRSb3dJbmRleCA8LSBOVUxMCm1sd3NfY2xlYW4gPC0gbWx3c19jbGVhbl9maWx0ZXJlZApybShtbHdzX2NsZWFuX2ZpbHRlcmVkKQpzdHIobWx3c19jbGVhbikKYGBgCgpSZXBsYWNpbmcgdGhlIGluY29ycmVjdCBzcGVlbGluZyBvZiBBLiBMYW5nZSAmIFNvaG5lIHRvIEEuIExhbmdlICYgU8O2aG5lIApgYGB7cn0KbWx3c19jbGVhbiRCcmFuZCA8LSBnc3ViKCJBLiBMYW5nZSAmIFNvaG5lIiwgIkEuIExhbmdlICYgU8O2aG5lIiwgbWx3c19jbGVhbiRCcmFuZCkKdGFibGUobWx3c19jbGVhbiRCcmFuZCkKYGBgCgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKCmdncGxvdChtbHdzX2NsZWFuLCBhZXMoeCA9IEJyYW5kLCB5ID0gYFByaWNlIChVU0QpYCwgZmlsbCA9ICJCcmFuZCIpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKICBsYWJzKHRpdGxlID0gIlByaWNlIERpc3RyaWJ1dGlvbiBieSBCcmFuZCIsIHggPSAiQnJhbmQiLCB5ID0gIlByaWNlIChVU0QpIikKCgpgYGAKCgpgYGB7cn0KZ2dwbG90KG1sd3NfY2xlYW4sIGFlcyh4ID0gYENhc2UgTWF0ZXJpYWxgLCBmaWxsID0gYENhc2UgTWF0ZXJpYWxgKSkgKwogIGdlb21fYmFyKCkgKwpgYGAKCgpgYGB7cn0KYGBgCgoKYGBge3J9CnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIAogIGxhYnModGl0bGUgPSAiRnJlcXVlbmN5IG9mIFdhdGNoZXMgYnkgQ2FzZSBNYXRlcmlhbCIsIHggPSAiQ2FzZSBNYXRlcmlhbCIsIHkgPSAiQ291bnQiKQoKYGBgCgoKCgpgYGB7cn0KZ2dwbG90KG1sd3NfY2xlYW4sIGFlcyh4ID0gYENhc2UgRGlhbWV0ZXIgKG1tKWApKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gImJsYWNrIiwgY29sb3IgPSAid2hpdGUiKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgQ2FzZSBEaWFtZXRlcnMiLCB4ID0gIkNhc2UgRGlhbWV0ZXIgKG1tKSIsIHkgPSAiRnJlcXVlbmN5IikgKwogIHRoZW1lX2RhcmsoKQoKYGBgCgpgYGB7cn0KYXZlcmFnZV9wcmljZV9wZXJfYnJhbmQgPC0gbWx3c19jbGVhbiAlPiUKICBncm91cF9ieShCcmFuZCkgJT4lCiAgc3VtbWFyaXNlKEF2ZXJhZ2VfUHJpY2UgPSBtZWFuKGBQcmljZSAoVVNEKWAsIG5hLnJtID0gVFJVRSkpICU+JQogIGFycmFuZ2UoQXZlcmFnZV9QcmljZSkgCgpnZ3Bsb3QoYXZlcmFnZV9wcmljZV9wZXJfYnJhbmQsIGFlcyh4ID0gcmVvcmRlcihCcmFuZCwgQXZlcmFnZV9QcmljZSksIHkgPSBBdmVyYWdlX1ByaWNlLCBmaWxsID0gQnJhbmQpKSArCiAgZ2VvbV9jb2woKSArIAogIGNvb3JkX2ZsaXAoKSArIAogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpLCAKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiAgCiAgKSArCiAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIFdhdGNoIFByaWNlIHBlciBCcmFuZCBpbiBVU0QiLCB4ID0gIkF2ZXJhZ2UgUHJpY2UgKFVTRCkiLCB5ID0gIkJyYW5kIikKCmBgYAoKYGBge3J9CiMgTGlicmFyeSBpbXBvcnRzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KHNoaW55ZGFzaGJvYXJkKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocnNjb25uZWN0KQoKIyBMb2FkIGRhdGEKbG9hZCgibWx3cy5SRGF0YSIpCgojIFByZXByb2Nlc3NpbmcgZGF0YQptbHdzX2NsZWFuIDwtIG1sd3MgJT4lCiAgc2VsZWN0KC1gQ2FzZSBUaGlja25lc3MgKG1tKWAsIC1gQmFuZCBXaWR0aCAobW0pYCwgLWBDcnlzdGFsIE1hdGVyaWFsYCwgLUNvbXBsaWNhdGlvbnMsIC1gUG93ZXIgUmVzZXJ2ZWApICU+JQogIG11dGF0ZShCcmFuZCA9IGdzdWIoIkEuIExhbmdlICYgU29obmUiLCAiQS4gTGFuZ2UgJiBTw7ZobmUiLCBCcmFuZCkpICU+JQogIGZpbHRlcighaWZfYW55KGV2ZXJ5dGhpbmcoKSwgaXMubmEpKSAlPiUKICBtdXRhdGUoUm93SW5kZXggPSByb3dfbnVtYmVyKCkpICU+JQogIGZpbHRlcighaXMubmEoYFByaWNlIChVU0QpYCkpICU+JQogIGFycmFuZ2UoZGVzYyhgUHJpY2UgKFVTRClgKSkKCiMgVUkgZGVmaW5pdGlvbgp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiTXkgTHV4dXJ5IFdhdGNoIFNob3BwZXIiKSwKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogICAgICBzZWxlY3RJbnB1dCgiYnJhbmQiLCAiQnJhbmQ6IiwgY2hvaWNlcyA9IGMoIkFsbCIsIHVuaXF1ZShtbHdzX2NsZWFuJEJyYW5kKSkpLAogICAgICBzbGlkZXJJbnB1dCgicHJpY2VSYW5nZSIsICJQcmljZSBSYW5nZToiLCBtaW4gPSBtaW4obWx3c19jbGVhbiRgUHJpY2UgKFVTRClgLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KG1sd3NfY2xlYW4kYFByaWNlIChVU0QpYCwgbmEucm0gPSBUUlVFKSwgCiAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyhtaW4obWx3c19jbGVhbiRgUHJpY2UgKFVTRClgLCBuYS5ybSA9IFRSVUUpLCBtYXgobWx3c19jbGVhbiRgUHJpY2UgKFVTRClgLCBuYS5ybSA9IFRSVUUpKSksCiAgICAgIHNlbGVjdElucHV0KCJjYXNlTWF0ZXJpYWwiLCAiQ2FzZSBNYXRlcmlhbDoiLCBjaG9pY2VzID0gYygiQWxsIiwgdW5pcXVlKG1sd3NfY2xlYW4kYENhc2UgTWF0ZXJpYWxgKSkpLAogICAgICBzZWxlY3RJbnB1dCgic3RyYXBNYXRlcmlhbCIsICJTdHJhcCBNYXRlcmlhbDoiLCBjaG9pY2VzID0gYygiQWxsIiwgdW5pcXVlKG1sd3NfY2xlYW4kYFN0cmFwIE1hdGVyaWFsYCkpKSwKICAgICAgYWN0aW9uQnV0dG9uKCJnb0J1dHRvbiIsICJGaW5kIFdhdGNoZXMiKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgdGFic2V0UGFuZWwoCiAgICAgICAgdGFiUGFuZWwoIlJlY29tbWVuZGF0aW9ucyIsCiAgICAgICAgICAgICAgICAgRFRPdXRwdXQoInRhYmxlIiksCiAgICAgICAgICAgICAgICAgaDIoIlRvcCA1IEJlc3QgV2F0Y2hlcyBmb3IgWW91IiwgYWxpZ24gPSAiY2VudGVyIiksCiAgICAgICAgICAgICAgICAgRFRPdXRwdXQoInRvcFRpZXJUYWJsZSIpICAjIENoYW5nZWQgdG8gRFRPdXRwdXQgZm9yIERhdGFUYWJsZXMgaW50ZWdyYXRpb24KICAgICAgICApLAogICAgICAgIHRhYlBhbmVsKCJQcmljZSBEaXN0cmlidXRpb24iLCAKICAgICAgICAgICAgICAgICBwbG90T3V0cHV0KCJwcmljZVBsb3QiKSwKICAgICAgICAgICAgICAgICBwbG90T3V0cHV0KCJhdmdQcmljZVBsb3QiKQogICAgICAgICksCiAgICAgICAgdGFiUGFuZWwoIkNhc2UgTWF0ZXJpYWwgRGlzdHJpYnV0aW9uIiwgcGxvdE91dHB1dCgiY2FzZU1hdGVyaWFsUGxvdCIpKSwKICAgICAgICB0YWJQYW5lbCgiUmVjb21tZW5kYXRpb24gRGV0YWlscyIsIHRleHRPdXRwdXQoInJlY1RleHQiKSwgcGxvdE91dHB1dCgicmFkYXJDaGFydCIpKQogICAgICApCiAgICApCiAgKQopCgoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICAjIFJlYWN0aXZlIGV4cHJlc3Npb24gZm9yIGZpbHRlcmVkIGRhdGEKICBkYXRhRmlsdGVyZWQgPC0gcmVhY3RpdmUoewogICAgcmVxKGlucHV0JGdvQnV0dG9uKQogICAgbWx3c19jbGVhbiAlPiUKICAgICAgZmlsdGVyKChCcmFuZCA9PSBpbnB1dCRicmFuZCB8IGlucHV0JGJyYW5kID09ICJBbGwiKSAmCiAgICAgICAgICAgICAgIGBQcmljZSAoVVNEKWAgPj0gaW5wdXQkcHJpY2VSYW5nZVsxXSAmCiAgICAgICAgICAgICAgIGBQcmljZSAoVVNEKWAgPD0gaW5wdXQkcHJpY2VSYW5nZVsyXSAmCiAgICAgICAgICAgICAgIChgQ2FzZSBNYXRlcmlhbGAgPT0gaW5wdXQkY2FzZU1hdGVyaWFsIHwgaW5wdXQkY2FzZU1hdGVyaWFsID09ICJBbGwiKSAmCiAgICAgICAgICAgICAgIChgU3RyYXAgTWF0ZXJpYWxgID09IGlucHV0JHN0cmFwTWF0ZXJpYWwgfCBpbnB1dCRzdHJhcE1hdGVyaWFsID09ICJBbGwiKSkKICB9KQogIAogICMgUmVuZGVyaW5nIHRoZSBEYXRhVGFibGUKICBvdXRwdXQkdGFibGUgPC0gcmVuZGVyRFQoewogICAgZGF0YUZpbHRlcmVkKCkKICB9LCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSkpCiAgCiAgIyBSZW5kZXJpbmcgdGhlIHByaWNlIGRpc3RyaWJ1dGlvbiBwbG90CiAgb3V0cHV0JHByaWNlUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIGdncGxvdChkYXRhRmlsdGVyZWQoKSwgYWVzKHggPSBgUHJpY2UgKFVTRClgKSkgKwogICAgICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUwMCwgZmlsbCA9ICJibHVlIikgKwogICAgICBsYWJzKHRpdGxlID0gIlByaWNlIERpc3RyaWJ1dGlvbiIsIHggPSAiUHJpY2UgKFVTRCkiLCB5ID0gIkNvdW50IikgKwogICAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArIAogICAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpCiAgfSkKICAKICAjIFJlbmRlcmluZyB0aGUgYXZlcmFnZSBwcmljZSBwZXIgY2FzZSBtYXRlcmlhbCBwbG90CiAgb3V0cHV0JGF2Z1ByaWNlUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIGF2Z19wcmljZV9kYXRhIDwtIG1sd3NfY2xlYW4gJT4lCiAgICAgIGdyb3VwX2J5KGBDYXNlIE1hdGVyaWFsYCkgJT4lCiAgICAgIHN1bW1hcml6ZShBdmdQcmljZSA9IG1lYW4oYFByaWNlIChVU0QpYCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgYXJyYW5nZShkZXNjKEF2Z1ByaWNlKSkKICAgIAogICAgZ2dwbG90KGF2Z19wcmljZV9kYXRhLCBhZXMoeCA9IHJlb3JkZXIoYENhc2UgTWF0ZXJpYWxgLCBBdmdQcmljZSksIHkgPSBBdmdQcmljZSwgZmlsbCA9IGBDYXNlIE1hdGVyaWFsYCkpICsKICAgICAgZ2VvbV9jb2woKSArCiAgICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsgCiAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogICAgICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgUHJpY2UgcGVyIENhc2UgTWF0ZXJpYWwiLCB4ID0gIkNhc2UgTWF0ZXJpYWwiLCB5ID0gIkF2ZXJhZ2UgUHJpY2UgKFVTRCkiKSArCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCiAgfSkKICAKICAjIFJlbmRlcmluZyB0aGUgY2FzZSBtYXRlcmlhbCBkaXN0cmlidXRpb24gcGxvdAogIG91dHB1dCRjYXNlTWF0ZXJpYWxQbG90IDwtIHJlbmRlclBsb3QoewogICAgZ2dwbG90KGRhdGFGaWx0ZXJlZCgpLCBhZXMoeCA9IGBDYXNlIE1hdGVyaWFsYCwgZmlsbCA9IGBDYXNlIE1hdGVyaWFsYCkpICsKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIpICsKICAgICAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgICBsYWJzKHRpdGxlID0gIkNhc2UgTWF0ZXJpYWwgRGlzdHJpYnV0aW9uIiwgeCA9ICJDYXNlIE1hdGVyaWFsIiwgeSA9ICJDb3VudCIpICsKICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKICB9KQogIAogICMgVGhpcyBpcyB0aGUgZnVuY3Rpb24gdG8gZ2V0IHRvcCB0aWVyIHdhdGNoZXMKICBnZXRUb3BUaWVyV2F0Y2hlcyA8LSBmdW5jdGlvbihkYXRhLCBuID0gNSkgewogICAgZGF0YSAlPiUKICAgICAgYXJyYW5nZShkZXNjKGBQcmljZSAoVVNEKWApKSAlPiUKICAgICAgc2xpY2VfaGVhZChuID0gbikKICB9CiAgCiAgIyBSZWFjdGl2aXR5IGV4cHJlc3Npb24gZm9yIHRvcCA1IHdhdGNoZXMKICB0b3BUaWVyV2F0Y2hlcyA8LSByZWFjdGl2ZSh7CiAgICByZXEoaW5wdXQkZ29CdXR0b24pIAogICAgZ2V0VG9wVGllcldhdGNoZXMoZGF0YUZpbHRlcmVkKCkpCiAgfSkKICAKICBvdXRwdXQkdG9wVGllclRhYmxlIDwtIHJlbmRlckRUKHsKICAgIHRvcFRpZXJXYXRjaGVzKCkgJT4lCiAgICAgIHNlbGVjdChCcmFuZCwgTW9kZWwsIGBQcmljZSAoVVNEKWApCiAgfSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDUsIHNlYXJjaGluZyA9IEZBTFNFLCBwYWdpbmcgPSBGQUxTRSwgaW5mbyA9IEZBTFNFKSkKICAKICAjIFBsYWNlaG9sZGVyIGZvciByZWNvbW1lbmRhdGlvbiB0ZXh0CiAgb3V0cHV0JHJlY1RleHQgPC0gcmVuZGVyVGV4dCh7CiAgICByZWMgPC0gcmVjb21tZW5kZWRXYXRjaCgpCiAgICBpZihucm93KHJlYykgPiAwKSB7CiAgICAgIHBhc3RlKCJXZSByZWNvbW1lbmQgdGhlIiwgcmVjJEJyYW5kLCAid2F0Y2ggd2l0aCBhIHByaWNlIG9mIiwgcmVjJGBQcmljZSAoVVNEKWAsICJVU0QuIikKICAgIH0gZWxzZSB7CiAgICAgICJObyByZWNvbW1lbmRhdGlvbiBhdmFpbGFibGUgYmFzZWQgb24gdGhlIGN1cnJlbnQgY3JpdGVyaWEuIgogICAgfQogIH0pCiAgCiAgIyBHZW5lcmF0aW5nIGEgcmVjb21tZW5kYXRpb24gYmFzZWQgb24gZmlsdGVyZWQgZGF0YQogIHJlY29tbWVuZGVkV2F0Y2ggPC0gcmVhY3RpdmUoewogICAgZGF0YSA8LSBkYXRhRmlsdGVyZWQoKQogICAgaWYobnJvdyhkYXRhKSA+IDApIHsKICAgICAgZGF0YVt3aGljaC5tYXgoZGF0YSRgUHJpY2UgKFVTRClgKSwgXQogICAgfSBlbHNlIHsKICAgICAgZGF0YS5mcmFtZSgpCiAgICB9CiAgfSkKfQoKIyBSdW4gdGhlIGFwcGxpY2F0aW9uCnNoaW55QXBwKHVpLCBzZXJ2ZXIpCgpgYGAKCg==