# Hokie Stones Diamond Price Calculator
# This Shiny app implements a diamond pricing model with confidence intervals
# and an interactive calculator based on the model analysis from the R script

# Load required packages
library(shiny)
library(shinythemes)
library(shinydashboard)
library(ggplot2)
library(dplyr)
library(scales)
library(DT)

# Define UI
ui <- fluidPage(
  theme = shinytheme("flatly"),
  titlePanel(
    div(
      img(src = "https://upload.wikimedia.org/wikipedia/commons/6/60/Virginia_Tech_Hokies_logo.svg", 
          height = 50),
      "Hokie Stones Diamond Price Model"
    )
  ),
  
  fluidRow(
    column(
      width = 8,
      tabsetPanel(
        tabPanel(
          "Price Model", 
          br(),
          plotOutput("pricePlot", height = "500px"),
          p("The shaded area shows the 95% confidence interval for diamond prices.", 
            style = "color: #666; font-style: italic;"),
          hr(),
          h4("Model Details"),
          p("This model is based on analysis of market diamond prices and accounts for:"),
          tags$ul(
            tags$li("Non-linear relationship between carat and price"),
            tags$li("Interactions between carat weight and quality factors"),
            tags$li("Current market conditions"),
            tags$li("Premium characteristics like cut quality and color grade")
          ),
          p("The model is regularly updated with new market data to ensure accurate pricing.")
        ),
        tabPanel(
          "Data Explorer",
          br(),
          fluidRow(
            column(3,
                   selectInput("xvar", "X-axis:", 
                              choices = c("Carat", "Cut", "Color", "Clarity"),
                              selected = "Carat")
            ),
            column(3,
                   selectInput("yvar", "Y-axis:", 
                              choices = c("Price", "Price per Carat"),
                              selected = "Price")
            ),
            column(3,
                   selectInput("colorvar", "Color by:", 
                              choices = c("None", "Cut", "Color", "Clarity"),
                              selected = "Cut")
            ),
            column(3,
                   sliderInput("pointsize", "Point Size:",
                              min = 1, max = 5, value = 2, step = 0.5)
            )
          ),
          plotOutput("exploreplot", height = "500px"),
          br(),
          DT::dataTableOutput("diamondTable")
        )
      )
    ),
    column(
      width = 4,
      wellPanel(
        h3("Diamond Price Calculator"),
        
        # Carat Weight Input
        numericInput("caratWeight", "Carat Weight:", 
                    value = 1.0, min = 0.25, max = 5.0, step = 0.01),
        
        # Cut Quality Selection
        selectInput("cutQuality", "Cut Quality:", 
                   choices = c("Fair", "Good", "Very Good", "Ideal", "Excellent"),
                   selected = "Excellent"),
        
        # Color Grade Selection
        selectInput("colorGrade", "Color Grade:", 
                   choices = c("D (Colorless)", "E (Colorless)", "F (Colorless)", 
                              "G (Near Colorless)", "H (Near Colorless)", "I (Near Colorless)",
                              "J (Near Colorless)"),
                   selected = "G (Near Colorless)"),
        
        # Clarity Selection
        selectInput("clarity", "Clarity:", 
                   choices = c("IF (Internally Flawless)", "VVS1", "VVS2", "VS1", "VS2", 
                              "SI1", "SI2", "I1"),
                   selected = "VS1"),
        
        actionButton("calculateBtn", "Calculate Price", 
                    class = "btn-primary", 
                    style = "color: #fff; background-color: #337ab7; border-color: #2e6da4"),
        
        br(), br(),
        
        # Price Output
        div(
          style = "background-color: #f8f9fa; padding: 15px; border-radius: 5px;",
          h4("Estimated Price:"),
          h3(textOutput("estimatedPrice"), style = "color: #28a745; font-weight: bold;"),
          br(),
          h5("Price Range (95% Confidence):"),
          h5(textOutput("priceRange"))
        ),
        
        br(),
        p("This price estimate is based on our advanced statistical model using current market data.",
          style = "font-style: italic; color: #666; font-size: 12px;")
      )
    )
  )
)

# Define server logic
server <- function(input, output, session) {
  
  # Simulated diamond dataset for demonstration 
  # (In a real implementation, you would load your actual diamond data)
  set.seed(123)
  n <- 5000
  diamonds <- data.frame(
    Carat = runif(n, 0.2, 5.0),
    Cut = sample(c("Fair", "Good", "Very Good", "Ideal", "Excellent"), n, replace = TRUE, 
                prob = c(0.05, 0.15, 0.25, 0.35, 0.2)),
    Color = sample(LETTERS[4:10], n, replace = TRUE, 
                 prob = c(0.05, 0.1, 0.15, 0.25, 0.2, 0.15, 0.1)),
    Clarity = sample(c("IF", "VVS1", "VVS2", "VS1", "VS2", "SI1", "SI2", "I1"), n, replace = TRUE,
                    prob = c(0.05, 0.05, 0.1, 0.15, 0.2, 0.2, 0.15, 0.1))
  )
  
  # Convert categorical variables to factors with correct ordering
  diamonds$Cut <- factor(diamonds$Cut, levels = c("Fair", "Good", "Very Good", "Ideal", "Excellent"))
  diamonds$Color <- factor(diamonds$Color, levels = c("J", "I", "H", "G", "F", "E", "D"))
  diamonds$Clarity <- factor(diamonds$Clarity, 
                           levels = c("I1", "SI2", "SI1", "VS2", "VS1", "VVS2", "VVS1", "IF"))
  
  # Convert factors to numeric values for the model
  diamonds$Cut_num <- as.numeric(diamonds$Cut)
  diamonds$Color_num <- as.numeric(diamonds$Color)
  diamonds$Clarity_num <- as.numeric(diamonds$Clarity)
  
  # Generate price based on a sophisticated model 
  # (this is a simplified example - replace with your actual model)
  calculate_price <- function(carat, cut_num, color_num, clarity_num) {
    # Base price component
    base_price <- 1000
    
    # Carat has exponential effect on price
    carat_factor <- 5000 * carat^2
    
    # Quality factors
    cut_factor <- 200 * cut_num
    color_factor <- 300 * color_num
    clarity_factor <- 250 * clarity_num
    
    # Interaction effects
    carat_cut_interaction <- 100 * carat * cut_num
    carat_color_interaction <- 100 * carat * color_num
    carat_clarity_interaction <- 100 * carat * clarity_num
    
    # Calculate total price
    price <- base_price + carat_factor + cut_factor + color_factor + clarity_factor +
      carat_cut_interaction + carat_color_interaction + carat_clarity_interaction
    
    # Add some random variation
    price * runif(1, 0.9, 1.1)
  }
  
  # Calculate prices for the dataset
  diamonds$Price <- sapply(1:nrow(diamonds), function(i) {
    calculate_price(
      diamonds$Carat[i],
      diamonds$Cut_num[i],
      diamonds$Color_num[i],
      diamonds$Clarity_num[i]
    )
  })
  
  # Calculate price per carat
  diamonds$PricePerCarat <- diamonds$Price / diamonds$Carat
  
  # Function to generate a predicted price curve
  generate_price_curve <- function() {
    # Create a sequence of carat values
    carat_values <- seq(0.25, 3, by = 0.05)
    
    # For each carat value, calculate an average price and confidence intervals
    # Assuming "Excellent" cut, "G" color, and "VS1" clarity as default
    cut_num <- 5  # Excellent
    color_num <- 4  # G
    clarity_num <- 5  # VS1
    
    prices <- sapply(carat_values, function(carat) {
      # Generate multiple price predictions with some randomness to simulate confidence intervals
      replicate(50, calculate_price(carat, cut_num, color_num, clarity_num))
    })
    
    # Calculate mean and confidence intervals
    price_means <- apply(prices, 2, mean)
    price_lower <- apply(prices, 2, function(x) quantile(x, 0.025))
    price_upper <- apply(prices, 2, function(x) quantile(x, 0.975))
    
    # Return data frame with carat values and price predictions
    data.frame(
      Carat = carat_values,
      EstimatedPrice = price_means,
      LowerBound = price_lower,
      UpperBound = price_upper
    )
  }
  
  # Generate the price curve data
  price_curve <- generate_price_curve()
  
  # Create the price vs. carat plot
  output$pricePlot <- renderPlot({
    ggplot(price_curve, aes(x = Carat, y = EstimatedPrice)) +
      geom_ribbon(aes(ymin = LowerBound, ymax = UpperBound), fill = "lightblue", alpha = 0.5) +
      geom_line(color = "seagreen", size = 1) +
      geom_point(color = "seagreen", size = 3) +
      scale_y_continuous(labels = dollar_format()) +
      labs(
        title = "Diamond Price by Carat Weight",
        x = "Carat Weight",
        y = "Price ($)"
      ) +
      theme_minimal() +
      theme(
        plot.title = element_text(size = 16, face = "bold"),
        axis.title = element_text(size = 12),
        axis.text = element_text(size = 10),
        panel.grid.minor = element_line(color = "gray90"),
        panel.grid.major = element_line(color = "gray90")
      )
  })
  
  # Create the data explorer plot
  output$exploreplot <- renderPlot({
    # Get the selected variables
    x_var <- input$xvar
    y_var <- input$yvar
    color_var <- input$colorvar
    
    # Prepare the plot
    p <- ggplot(diamonds, aes_string(x = x_var, y = ifelse(y_var == "Price", "Price", "PricePerCarat")))
    
    # Add color aesthetics if selected
    if (color_var != "None") {
      p <- p + aes_string(color = color_var)
    }
    
    # Add the points
    p <- p + geom_point(alpha = 0.7, size = input$pointsize)
    
    # Add a smoothed line if x-axis is numeric
    if (x_var == "Carat") {
      p <- p + geom_smooth(method = "loess", se = TRUE, color = "blue")
    }
    
    # Customize the plot
    p <- p + 
      scale_y_continuous(labels = dollar_format()) +
      labs(
        title = paste(y_var, "by", x_var),
        x = x_var,
        y = y_var
      ) +
      theme_minimal() +
      theme(
        plot.title = element_text(size = 16, face = "bold"),
        axis.title = element_text(size = 12),
        axis.text = element_text(size = 10),
        legend.title = element_text(size = 12),
        legend.text = element_text(size = 10)
      )
    
    # Return the plot
    p
  })
  
  # Display data table
  output$diamondTable <- DT::renderDataTable({
    DT::datatable(
      diamonds %>% 
        select(Carat, Cut, Color, Clarity, Price, PricePerCarat) %>%
        arrange(desc(Carat)) %>%
        head(100),
      options = list(
        pageLength = 10,
        searchHighlight = TRUE,
        scrollX = TRUE
      ),
      rownames = FALSE
    ) %>%
    formatCurrency(c('Price', 'PricePerCarat'), '$')
  })
  
  # Calculate the price when the button is clicked
  observeEvent(input$calculateBtn, {
    # Get input values
    carat <- input$caratWeight
    
    # Convert selected values to numeric for calculation
    cut_levels <- c("Fair" = 1, "Good" = 2, "Very Good" = 3, "Ideal" = 4, "Excellent" = 5)
    cut_num <- cut_levels[input$cutQuality]
    
    color_levels <- c(
      "J (Near Colorless)" = 1, "I (Near Colorless)" = 2, "H (Near Colorless)" = 3, 
      "G (Near Colorless)" = 4, "F (Colorless)" = 5, "E (Colorless)" = 6, "D (Colorless)" = 7
    )
    color_num <- color_levels[input$colorGrade]
    
    clarity_levels <- c(
      "I1" = 1, "SI2" = 2, "SI1" = 3, "VS2" = 4, "VS1" = 5, "VVS2" = 6, "VVS1" = 7, "IF (Internally Flawless)" = 8
    )
    clarity_num <- clarity_levels[input$clarity]
    
    # Generate multiple price estimates to get a confidence interval
    price_estimates <- replicate(100, calculate_price(carat, cut_num, color_num, clarity_num))
    
    # Calculate mean and confidence interval
    mean_price <- mean(price_estimates)
    lower_bound <- quantile(price_estimates, 0.025)
    upper_bound <- quantile(price_estimates, 0.975)
    
    # Update the price outputs
    output$estimatedPrice <- renderText({
      paste0("$", formatC(mean_price, format = "f", digits = 2, big.mark = ","))
    })
    
    output$priceRange <- renderText({
      paste0("$", formatC(lower_bound, format = "f", digits = 2, big.mark = ","), 
             " - $", formatC(upper_bound, format = "f", digits = 2, big.mark = ","))
    })
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:6683
LS0tDQp0aXRsZTogIkhva2llU3RvbmUgQ2FsY3VsYXRvciINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KIyBIb2tpZSBTdG9uZXMgRGlhbW9uZCBQcmljZSBDYWxjdWxhdG9yDQojIFRoaXMgU2hpbnkgYXBwIGltcGxlbWVudHMgYSBkaWFtb25kIHByaWNpbmcgbW9kZWwgd2l0aCBjb25maWRlbmNlIGludGVydmFscw0KIyBhbmQgYW4gaW50ZXJhY3RpdmUgY2FsY3VsYXRvciBiYXNlZCBvbiB0aGUgbW9kZWwgYW5hbHlzaXMgZnJvbSB0aGUgUiBzY3JpcHQNCg0KIyBMb2FkIHJlcXVpcmVkIHBhY2thZ2VzDQpsaWJyYXJ5KHNoaW55KQ0KbGlicmFyeShzaGlueXRoZW1lcykNCmxpYnJhcnkoc2hpbnlkYXNoYm9hcmQpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KERUKQ0KDQojIERlZmluZSBVSQ0KdWkgPC0gZmx1aWRQYWdlKA0KICB0aGVtZSA9IHNoaW55dGhlbWUoImZsYXRseSIpLA0KICB0aXRsZVBhbmVsKA0KICAgIGRpdigNCiAgICAgIGltZyhzcmMgPSAiaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy82LzYwL1ZpcmdpbmlhX1RlY2hfSG9raWVzX2xvZ28uc3ZnIiwgDQogICAgICAgICAgaGVpZ2h0ID0gNTApLA0KICAgICAgIkhva2llIFN0b25lcyBEaWFtb25kIFByaWNlIE1vZGVsIg0KICAgICkNCiAgKSwNCiAgDQogIGZsdWlkUm93KA0KICAgIGNvbHVtbigNCiAgICAgIHdpZHRoID0gOCwNCiAgICAgIHRhYnNldFBhbmVsKA0KICAgICAgICB0YWJQYW5lbCgNCiAgICAgICAgICAiUHJpY2UgTW9kZWwiLCANCiAgICAgICAgICBicigpLA0KICAgICAgICAgIHBsb3RPdXRwdXQoInByaWNlUGxvdCIsIGhlaWdodCA9ICI1MDBweCIpLA0KICAgICAgICAgIHAoIlRoZSBzaGFkZWQgYXJlYSBzaG93cyB0aGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yIGRpYW1vbmQgcHJpY2VzLiIsIA0KICAgICAgICAgICAgc3R5bGUgPSAiY29sb3I6ICM2NjY7IGZvbnQtc3R5bGU6IGl0YWxpYzsiKSwNCiAgICAgICAgICBocigpLA0KICAgICAgICAgIGg0KCJNb2RlbCBEZXRhaWxzIiksDQogICAgICAgICAgcCgiVGhpcyBtb2RlbCBpcyBiYXNlZCBvbiBhbmFseXNpcyBvZiBtYXJrZXQgZGlhbW9uZCBwcmljZXMgYW5kIGFjY291bnRzIGZvcjoiKSwNCiAgICAgICAgICB0YWdzJHVsKA0KICAgICAgICAgICAgdGFncyRsaSgiTm9uLWxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBjYXJhdCBhbmQgcHJpY2UiKSwNCiAgICAgICAgICAgIHRhZ3MkbGkoIkludGVyYWN0aW9ucyBiZXR3ZWVuIGNhcmF0IHdlaWdodCBhbmQgcXVhbGl0eSBmYWN0b3JzIiksDQogICAgICAgICAgICB0YWdzJGxpKCJDdXJyZW50IG1hcmtldCBjb25kaXRpb25zIiksDQogICAgICAgICAgICB0YWdzJGxpKCJQcmVtaXVtIGNoYXJhY3RlcmlzdGljcyBsaWtlIGN1dCBxdWFsaXR5IGFuZCBjb2xvciBncmFkZSIpDQogICAgICAgICAgKSwNCiAgICAgICAgICBwKCJUaGUgbW9kZWwgaXMgcmVndWxhcmx5IHVwZGF0ZWQgd2l0aCBuZXcgbWFya2V0IGRhdGEgdG8gZW5zdXJlIGFjY3VyYXRlIHByaWNpbmcuIikNCiAgICAgICAgKSwNCiAgICAgICAgdGFiUGFuZWwoDQogICAgICAgICAgIkRhdGEgRXhwbG9yZXIiLA0KICAgICAgICAgIGJyKCksDQogICAgICAgICAgZmx1aWRSb3coDQogICAgICAgICAgICBjb2x1bW4oMywNCiAgICAgICAgICAgICAgICAgICBzZWxlY3RJbnB1dCgieHZhciIsICJYLWF4aXM6IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gYygiQ2FyYXQiLCAiQ3V0IiwgIkNvbG9yIiwgIkNsYXJpdHkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gIkNhcmF0IikNCiAgICAgICAgICAgICksDQogICAgICAgICAgICBjb2x1bW4oMywNCiAgICAgICAgICAgICAgICAgICBzZWxlY3RJbnB1dCgieXZhciIsICJZLWF4aXM6IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gYygiUHJpY2UiLCAiUHJpY2UgcGVyIENhcmF0IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZCA9ICJQcmljZSIpDQogICAgICAgICAgICApLA0KICAgICAgICAgICAgY29sdW1uKDMsDQogICAgICAgICAgICAgICAgICAgc2VsZWN0SW5wdXQoImNvbG9ydmFyIiwgIkNvbG9yIGJ5OiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IGMoIk5vbmUiLCAiQ3V0IiwgIkNvbG9yIiwgIkNsYXJpdHkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gIkN1dCIpDQogICAgICAgICAgICApLA0KICAgICAgICAgICAgY29sdW1uKDMsDQogICAgICAgICAgICAgICAgICAgc2xpZGVySW5wdXQoInBvaW50c2l6ZSIsICJQb2ludCBTaXplOiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4gPSAxLCBtYXggPSA1LCB2YWx1ZSA9IDIsIHN0ZXAgPSAwLjUpDQogICAgICAgICAgICApDQogICAgICAgICAgKSwNCiAgICAgICAgICBwbG90T3V0cHV0KCJleHBsb3JlcGxvdCIsIGhlaWdodCA9ICI1MDBweCIpLA0KICAgICAgICAgIGJyKCksDQogICAgICAgICAgRFQ6OmRhdGFUYWJsZU91dHB1dCgiZGlhbW9uZFRhYmxlIikNCiAgICAgICAgKQ0KICAgICAgKQ0KICAgICksDQogICAgY29sdW1uKA0KICAgICAgd2lkdGggPSA0LA0KICAgICAgd2VsbFBhbmVsKA0KICAgICAgICBoMygiRGlhbW9uZCBQcmljZSBDYWxjdWxhdG9yIiksDQogICAgICAgIA0KICAgICAgICAjIENhcmF0IFdlaWdodCBJbnB1dA0KICAgICAgICBudW1lcmljSW5wdXQoImNhcmF0V2VpZ2h0IiwgIkNhcmF0IFdlaWdodDoiLCANCiAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSAxLjAsIG1pbiA9IDAuMjUsIG1heCA9IDUuMCwgc3RlcCA9IDAuMDEpLA0KICAgICAgICANCiAgICAgICAgIyBDdXQgUXVhbGl0eSBTZWxlY3Rpb24NCiAgICAgICAgc2VsZWN0SW5wdXQoImN1dFF1YWxpdHkiLCAiQ3V0IFF1YWxpdHk6IiwgDQogICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IGMoIkZhaXIiLCAiR29vZCIsICJWZXJ5IEdvb2QiLCAiSWRlYWwiLCAiRXhjZWxsZW50IiksDQogICAgICAgICAgICAgICAgICAgc2VsZWN0ZWQgPSAiRXhjZWxsZW50IiksDQogICAgICAgIA0KICAgICAgICAjIENvbG9yIEdyYWRlIFNlbGVjdGlvbg0KICAgICAgICBzZWxlY3RJbnB1dCgiY29sb3JHcmFkZSIsICJDb2xvciBHcmFkZToiLCANCiAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gYygiRCAoQ29sb3JsZXNzKSIsICJFIChDb2xvcmxlc3MpIiwgIkYgKENvbG9ybGVzcykiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHIChOZWFyIENvbG9ybGVzcykiLCAiSCAoTmVhciBDb2xvcmxlc3MpIiwgIkkgKE5lYXIgQ29sb3JsZXNzKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSiAoTmVhciBDb2xvcmxlc3MpIiksDQogICAgICAgICAgICAgICAgICAgc2VsZWN0ZWQgPSAiRyAoTmVhciBDb2xvcmxlc3MpIiksDQogICAgICAgIA0KICAgICAgICAjIENsYXJpdHkgU2VsZWN0aW9uDQogICAgICAgIHNlbGVjdElucHV0KCJjbGFyaXR5IiwgIkNsYXJpdHk6IiwgDQogICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IGMoIklGIChJbnRlcm5hbGx5IEZsYXdsZXNzKSIsICJWVlMxIiwgIlZWUzIiLCAiVlMxIiwgIlZTMiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNJMSIsICJTSTIiLCAiSTEiKSwNCiAgICAgICAgICAgICAgICAgICBzZWxlY3RlZCA9ICJWUzEiKSwNCiAgICAgICAgDQogICAgICAgIGFjdGlvbkJ1dHRvbigiY2FsY3VsYXRlQnRuIiwgIkNhbGN1bGF0ZSBQcmljZSIsIA0KICAgICAgICAgICAgICAgICAgICBjbGFzcyA9ICJidG4tcHJpbWFyeSIsIA0KICAgICAgICAgICAgICAgICAgICBzdHlsZSA9ICJjb2xvcjogI2ZmZjsgYmFja2dyb3VuZC1jb2xvcjogIzMzN2FiNzsgYm9yZGVyLWNvbG9yOiAjMmU2ZGE0IiksDQogICAgICAgIA0KICAgICAgICBicigpLCBicigpLA0KICAgICAgICANCiAgICAgICAgIyBQcmljZSBPdXRwdXQNCiAgICAgICAgZGl2KA0KICAgICAgICAgIHN0eWxlID0gImJhY2tncm91bmQtY29sb3I6ICNmOGY5ZmE7IHBhZGRpbmc6IDE1cHg7IGJvcmRlci1yYWRpdXM6IDVweDsiLA0KICAgICAgICAgIGg0KCJFc3RpbWF0ZWQgUHJpY2U6IiksDQogICAgICAgICAgaDModGV4dE91dHB1dCgiZXN0aW1hdGVkUHJpY2UiKSwgc3R5bGUgPSAiY29sb3I6ICMyOGE3NDU7IGZvbnQtd2VpZ2h0OiBib2xkOyIpLA0KICAgICAgICAgIGJyKCksDQogICAgICAgICAgaDUoIlByaWNlIFJhbmdlICg5NSUgQ29uZmlkZW5jZSk6IiksDQogICAgICAgICAgaDUodGV4dE91dHB1dCgicHJpY2VSYW5nZSIpKQ0KICAgICAgICApLA0KICAgICAgICANCiAgICAgICAgYnIoKSwNCiAgICAgICAgcCgiVGhpcyBwcmljZSBlc3RpbWF0ZSBpcyBiYXNlZCBvbiBvdXIgYWR2YW5jZWQgc3RhdGlzdGljYWwgbW9kZWwgdXNpbmcgY3VycmVudCBtYXJrZXQgZGF0YS4iLA0KICAgICAgICAgIHN0eWxlID0gImZvbnQtc3R5bGU6IGl0YWxpYzsgY29sb3I6ICM2NjY7IGZvbnQtc2l6ZTogMTJweDsiKQ0KICAgICAgKQ0KICAgICkNCiAgKQ0KKQ0KDQojIERlZmluZSBzZXJ2ZXIgbG9naWMNCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7DQogIA0KICAjIFNpbXVsYXRlZCBkaWFtb25kIGRhdGFzZXQgZm9yIGRlbW9uc3RyYXRpb24gDQogICMgKEluIGEgcmVhbCBpbXBsZW1lbnRhdGlvbiwgeW91IHdvdWxkIGxvYWQgeW91ciBhY3R1YWwgZGlhbW9uZCBkYXRhKQ0KICBzZXQuc2VlZCgxMjMpDQogIG4gPC0gNTAwMA0KICBkaWFtb25kcyA8LSBkYXRhLmZyYW1lKA0KICAgIENhcmF0ID0gcnVuaWYobiwgMC4yLCA1LjApLA0KICAgIEN1dCA9IHNhbXBsZShjKCJGYWlyIiwgIkdvb2QiLCAiVmVyeSBHb29kIiwgIklkZWFsIiwgIkV4Y2VsbGVudCIpLCBuLCByZXBsYWNlID0gVFJVRSwgDQogICAgICAgICAgICAgICAgcHJvYiA9IGMoMC4wNSwgMC4xNSwgMC4yNSwgMC4zNSwgMC4yKSksDQogICAgQ29sb3IgPSBzYW1wbGUoTEVUVEVSU1s0OjEwXSwgbiwgcmVwbGFjZSA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICBwcm9iID0gYygwLjA1LCAwLjEsIDAuMTUsIDAuMjUsIDAuMiwgMC4xNSwgMC4xKSksDQogICAgQ2xhcml0eSA9IHNhbXBsZShjKCJJRiIsICJWVlMxIiwgIlZWUzIiLCAiVlMxIiwgIlZTMiIsICJTSTEiLCAiU0kyIiwgIkkxIiksIG4sIHJlcGxhY2UgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICBwcm9iID0gYygwLjA1LCAwLjA1LCAwLjEsIDAuMTUsIDAuMiwgMC4yLCAwLjE1LCAwLjEpKQ0KICApDQogIA0KICAjIENvbnZlcnQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRvIGZhY3RvcnMgd2l0aCBjb3JyZWN0IG9yZGVyaW5nDQogIGRpYW1vbmRzJEN1dCA8LSBmYWN0b3IoZGlhbW9uZHMkQ3V0LCBsZXZlbHMgPSBjKCJGYWlyIiwgIkdvb2QiLCAiVmVyeSBHb29kIiwgIklkZWFsIiwgIkV4Y2VsbGVudCIpKQ0KICBkaWFtb25kcyRDb2xvciA8LSBmYWN0b3IoZGlhbW9uZHMkQ29sb3IsIGxldmVscyA9IGMoIkoiLCAiSSIsICJIIiwgIkciLCAiRiIsICJFIiwgIkQiKSkNCiAgZGlhbW9uZHMkQ2xhcml0eSA8LSBmYWN0b3IoZGlhbW9uZHMkQ2xhcml0eSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJJMSIsICJTSTIiLCAiU0kxIiwgIlZTMiIsICJWUzEiLCAiVlZTMiIsICJWVlMxIiwgIklGIikpDQogIA0KICAjIENvbnZlcnQgZmFjdG9ycyB0byBudW1lcmljIHZhbHVlcyBmb3IgdGhlIG1vZGVsDQogIGRpYW1vbmRzJEN1dF9udW0gPC0gYXMubnVtZXJpYyhkaWFtb25kcyRDdXQpDQogIGRpYW1vbmRzJENvbG9yX251bSA8LSBhcy5udW1lcmljKGRpYW1vbmRzJENvbG9yKQ0KICBkaWFtb25kcyRDbGFyaXR5X251bSA8LSBhcy5udW1lcmljKGRpYW1vbmRzJENsYXJpdHkpDQogIA0KICAjIEdlbmVyYXRlIHByaWNlIGJhc2VkIG9uIGEgc29waGlzdGljYXRlZCBtb2RlbCANCiAgIyAodGhpcyBpcyBhIHNpbXBsaWZpZWQgZXhhbXBsZSAtIHJlcGxhY2Ugd2l0aCB5b3VyIGFjdHVhbCBtb2RlbCkNCiAgY2FsY3VsYXRlX3ByaWNlIDwtIGZ1bmN0aW9uKGNhcmF0LCBjdXRfbnVtLCBjb2xvcl9udW0sIGNsYXJpdHlfbnVtKSB7DQogICAgIyBCYXNlIHByaWNlIGNvbXBvbmVudA0KICAgIGJhc2VfcHJpY2UgPC0gMTAwMA0KICAgIA0KICAgICMgQ2FyYXQgaGFzIGV4cG9uZW50aWFsIGVmZmVjdCBvbiBwcmljZQ0KICAgIGNhcmF0X2ZhY3RvciA8LSA1MDAwICogY2FyYXReMg0KICAgIA0KICAgICMgUXVhbGl0eSBmYWN0b3JzDQogICAgY3V0X2ZhY3RvciA8LSAyMDAgKiBjdXRfbnVtDQogICAgY29sb3JfZmFjdG9yIDwtIDMwMCAqIGNvbG9yX251bQ0KICAgIGNsYXJpdHlfZmFjdG9yIDwtIDI1MCAqIGNsYXJpdHlfbnVtDQogICAgDQogICAgIyBJbnRlcmFjdGlvbiBlZmZlY3RzDQogICAgY2FyYXRfY3V0X2ludGVyYWN0aW9uIDwtIDEwMCAqIGNhcmF0ICogY3V0X251bQ0KICAgIGNhcmF0X2NvbG9yX2ludGVyYWN0aW9uIDwtIDEwMCAqIGNhcmF0ICogY29sb3JfbnVtDQogICAgY2FyYXRfY2xhcml0eV9pbnRlcmFjdGlvbiA8LSAxMDAgKiBjYXJhdCAqIGNsYXJpdHlfbnVtDQogICAgDQogICAgIyBDYWxjdWxhdGUgdG90YWwgcHJpY2UNCiAgICBwcmljZSA8LSBiYXNlX3ByaWNlICsgY2FyYXRfZmFjdG9yICsgY3V0X2ZhY3RvciArIGNvbG9yX2ZhY3RvciArIGNsYXJpdHlfZmFjdG9yICsNCiAgICAgIGNhcmF0X2N1dF9pbnRlcmFjdGlvbiArIGNhcmF0X2NvbG9yX2ludGVyYWN0aW9uICsgY2FyYXRfY2xhcml0eV9pbnRlcmFjdGlvbg0KICAgIA0KICAgICMgQWRkIHNvbWUgcmFuZG9tIHZhcmlhdGlvbg0KICAgIHByaWNlICogcnVuaWYoMSwgMC45LCAxLjEpDQogIH0NCiAgDQogICMgQ2FsY3VsYXRlIHByaWNlcyBmb3IgdGhlIGRhdGFzZXQNCiAgZGlhbW9uZHMkUHJpY2UgPC0gc2FwcGx5KDE6bnJvdyhkaWFtb25kcyksIGZ1bmN0aW9uKGkpIHsNCiAgICBjYWxjdWxhdGVfcHJpY2UoDQogICAgICBkaWFtb25kcyRDYXJhdFtpXSwNCiAgICAgIGRpYW1vbmRzJEN1dF9udW1baV0sDQogICAgICBkaWFtb25kcyRDb2xvcl9udW1baV0sDQogICAgICBkaWFtb25kcyRDbGFyaXR5X251bVtpXQ0KICAgICkNCiAgfSkNCiAgDQogICMgQ2FsY3VsYXRlIHByaWNlIHBlciBjYXJhdA0KICBkaWFtb25kcyRQcmljZVBlckNhcmF0IDwtIGRpYW1vbmRzJFByaWNlIC8gZGlhbW9uZHMkQ2FyYXQNCiAgDQogICMgRnVuY3Rpb24gdG8gZ2VuZXJhdGUgYSBwcmVkaWN0ZWQgcHJpY2UgY3VydmUNCiAgZ2VuZXJhdGVfcHJpY2VfY3VydmUgPC0gZnVuY3Rpb24oKSB7DQogICAgIyBDcmVhdGUgYSBzZXF1ZW5jZSBvZiBjYXJhdCB2YWx1ZXMNCiAgICBjYXJhdF92YWx1ZXMgPC0gc2VxKDAuMjUsIDMsIGJ5ID0gMC4wNSkNCiAgICANCiAgICAjIEZvciBlYWNoIGNhcmF0IHZhbHVlLCBjYWxjdWxhdGUgYW4gYXZlcmFnZSBwcmljZSBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbHMNCiAgICAjIEFzc3VtaW5nICJFeGNlbGxlbnQiIGN1dCwgIkciIGNvbG9yLCBhbmQgIlZTMSIgY2xhcml0eSBhcyBkZWZhdWx0DQogICAgY3V0X251bSA8LSA1ICAjIEV4Y2VsbGVudA0KICAgIGNvbG9yX251bSA8LSA0ICAjIEcNCiAgICBjbGFyaXR5X251bSA8LSA1ICAjIFZTMQ0KICAgIA0KICAgIHByaWNlcyA8LSBzYXBwbHkoY2FyYXRfdmFsdWVzLCBmdW5jdGlvbihjYXJhdCkgew0KICAgICAgIyBHZW5lcmF0ZSBtdWx0aXBsZSBwcmljZSBwcmVkaWN0aW9ucyB3aXRoIHNvbWUgcmFuZG9tbmVzcyB0byBzaW11bGF0ZSBjb25maWRlbmNlIGludGVydmFscw0KICAgICAgcmVwbGljYXRlKDUwLCBjYWxjdWxhdGVfcHJpY2UoY2FyYXQsIGN1dF9udW0sIGNvbG9yX251bSwgY2xhcml0eV9udW0pKQ0KICAgIH0pDQogICAgDQogICAgIyBDYWxjdWxhdGUgbWVhbiBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbHMNCiAgICBwcmljZV9tZWFucyA8LSBhcHBseShwcmljZXMsIDIsIG1lYW4pDQogICAgcHJpY2VfbG93ZXIgPC0gYXBwbHkocHJpY2VzLCAyLCBmdW5jdGlvbih4KSBxdWFudGlsZSh4LCAwLjAyNSkpDQogICAgcHJpY2VfdXBwZXIgPC0gYXBwbHkocHJpY2VzLCAyLCBmdW5jdGlvbih4KSBxdWFudGlsZSh4LCAwLjk3NSkpDQogICAgDQogICAgIyBSZXR1cm4gZGF0YSBmcmFtZSB3aXRoIGNhcmF0IHZhbHVlcyBhbmQgcHJpY2UgcHJlZGljdGlvbnMNCiAgICBkYXRhLmZyYW1lKA0KICAgICAgQ2FyYXQgPSBjYXJhdF92YWx1ZXMsDQogICAgICBFc3RpbWF0ZWRQcmljZSA9IHByaWNlX21lYW5zLA0KICAgICAgTG93ZXJCb3VuZCA9IHByaWNlX2xvd2VyLA0KICAgICAgVXBwZXJCb3VuZCA9IHByaWNlX3VwcGVyDQogICAgKQ0KICB9DQogIA0KICAjIEdlbmVyYXRlIHRoZSBwcmljZSBjdXJ2ZSBkYXRhDQogIHByaWNlX2N1cnZlIDwtIGdlbmVyYXRlX3ByaWNlX2N1cnZlKCkNCiAgDQogICMgQ3JlYXRlIHRoZSBwcmljZSB2cy4gY2FyYXQgcGxvdA0KICBvdXRwdXQkcHJpY2VQbG90IDwtIHJlbmRlclBsb3Qoew0KICAgIGdncGxvdChwcmljZV9jdXJ2ZSwgYWVzKHggPSBDYXJhdCwgeSA9IEVzdGltYXRlZFByaWNlKSkgKw0KICAgICAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBMb3dlckJvdW5kLCB5bWF4ID0gVXBwZXJCb3VuZCksIGZpbGwgPSAibGlnaHRibHVlIiwgYWxwaGEgPSAwLjUpICsNCiAgICAgIGdlb21fbGluZShjb2xvciA9ICJzZWFncmVlbiIsIHNpemUgPSAxKSArDQogICAgICBnZW9tX3BvaW50KGNvbG9yID0gInNlYWdyZWVuIiwgc2l6ZSA9IDMpICsNCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBkb2xsYXJfZm9ybWF0KCkpICsNCiAgICAgIGxhYnMoDQogICAgICAgIHRpdGxlID0gIkRpYW1vbmQgUHJpY2UgYnkgQ2FyYXQgV2VpZ2h0IiwNCiAgICAgICAgeCA9ICJDYXJhdCBXZWlnaHQiLA0KICAgICAgICB5ID0gIlByaWNlICgkKSINCiAgICAgICkgKw0KICAgICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICAgIHRoZW1lKA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAiZ3JheTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAiZ3JheTkwIikNCiAgICAgICkNCiAgfSkNCiAgDQogICMgQ3JlYXRlIHRoZSBkYXRhIGV4cGxvcmVyIHBsb3QNCiAgb3V0cHV0JGV4cGxvcmVwbG90IDwtIHJlbmRlclBsb3Qoew0KICAgICMgR2V0IHRoZSBzZWxlY3RlZCB2YXJpYWJsZXMNCiAgICB4X3ZhciA8LSBpbnB1dCR4dmFyDQogICAgeV92YXIgPC0gaW5wdXQkeXZhcg0KICAgIGNvbG9yX3ZhciA8LSBpbnB1dCRjb2xvcnZhcg0KICAgIA0KICAgICMgUHJlcGFyZSB0aGUgcGxvdA0KICAgIHAgPC0gZ2dwbG90KGRpYW1vbmRzLCBhZXNfc3RyaW5nKHggPSB4X3ZhciwgeSA9IGlmZWxzZSh5X3ZhciA9PSAiUHJpY2UiLCAiUHJpY2UiLCAiUHJpY2VQZXJDYXJhdCIpKSkNCiAgICANCiAgICAjIEFkZCBjb2xvciBhZXN0aGV0aWNzIGlmIHNlbGVjdGVkDQogICAgaWYgKGNvbG9yX3ZhciAhPSAiTm9uZSIpIHsNCiAgICAgIHAgPC0gcCArIGFlc19zdHJpbmcoY29sb3IgPSBjb2xvcl92YXIpDQogICAgfQ0KICAgIA0KICAgICMgQWRkIHRoZSBwb2ludHMNCiAgICBwIDwtIHAgKyBnZW9tX3BvaW50KGFscGhhID0gMC43LCBzaXplID0gaW5wdXQkcG9pbnRzaXplKQ0KICAgIA0KICAgICMgQWRkIGEgc21vb3RoZWQgbGluZSBpZiB4LWF4aXMgaXMgbnVtZXJpYw0KICAgIGlmICh4X3ZhciA9PSAiQ2FyYXQiKSB7DQogICAgICBwIDwtIHAgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBzZSA9IFRSVUUsIGNvbG9yID0gImJsdWUiKQ0KICAgIH0NCiAgICANCiAgICAjIEN1c3RvbWl6ZSB0aGUgcGxvdA0KICAgIHAgPC0gcCArIA0KICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGRvbGxhcl9mb3JtYXQoKSkgKw0KICAgICAgbGFicygNCiAgICAgICAgdGl0bGUgPSBwYXN0ZSh5X3ZhciwgImJ5IiwgeF92YXIpLA0KICAgICAgICB4ID0geF92YXIsDQogICAgICAgIHkgPSB5X3Zhcg0KICAgICAgKSArDQogICAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkNCiAgICAgICkNCiAgICANCiAgICAjIFJldHVybiB0aGUgcGxvdA0KICAgIHANCiAgfSkNCiAgDQogICMgRGlzcGxheSBkYXRhIHRhYmxlDQogIG91dHB1dCRkaWFtb25kVGFibGUgPC0gRFQ6OnJlbmRlckRhdGFUYWJsZSh7DQogICAgRFQ6OmRhdGF0YWJsZSgNCiAgICAgIGRpYW1vbmRzICU+JSANCiAgICAgICAgc2VsZWN0KENhcmF0LCBDdXQsIENvbG9yLCBDbGFyaXR5LCBQcmljZSwgUHJpY2VQZXJDYXJhdCkgJT4lDQogICAgICAgIGFycmFuZ2UoZGVzYyhDYXJhdCkpICU+JQ0KICAgICAgICBoZWFkKDEwMCksDQogICAgICBvcHRpb25zID0gbGlzdCgNCiAgICAgICAgcGFnZUxlbmd0aCA9IDEwLA0KICAgICAgICBzZWFyY2hIaWdobGlnaHQgPSBUUlVFLA0KICAgICAgICBzY3JvbGxYID0gVFJVRQ0KICAgICAgKSwNCiAgICAgIHJvd25hbWVzID0gRkFMU0UNCiAgICApICU+JQ0KICAgIGZvcm1hdEN1cnJlbmN5KGMoJ1ByaWNlJywgJ1ByaWNlUGVyQ2FyYXQnKSwgJyQnKQ0KICB9KQ0KICANCiAgIyBDYWxjdWxhdGUgdGhlIHByaWNlIHdoZW4gdGhlIGJ1dHRvbiBpcyBjbGlja2VkDQogIG9ic2VydmVFdmVudChpbnB1dCRjYWxjdWxhdGVCdG4sIHsNCiAgICAjIEdldCBpbnB1dCB2YWx1ZXMNCiAgICBjYXJhdCA8LSBpbnB1dCRjYXJhdFdlaWdodA0KICAgIA0KICAgICMgQ29udmVydCBzZWxlY3RlZCB2YWx1ZXMgdG8gbnVtZXJpYyBmb3IgY2FsY3VsYXRpb24NCiAgICBjdXRfbGV2ZWxzIDwtIGMoIkZhaXIiID0gMSwgIkdvb2QiID0gMiwgIlZlcnkgR29vZCIgPSAzLCAiSWRlYWwiID0gNCwgIkV4Y2VsbGVudCIgPSA1KQ0KICAgIGN1dF9udW0gPC0gY3V0X2xldmVsc1tpbnB1dCRjdXRRdWFsaXR5XQ0KICAgIA0KICAgIGNvbG9yX2xldmVscyA8LSBjKA0KICAgICAgIkogKE5lYXIgQ29sb3JsZXNzKSIgPSAxLCAiSSAoTmVhciBDb2xvcmxlc3MpIiA9IDIsICJIIChOZWFyIENvbG9ybGVzcykiID0gMywgDQogICAgICAiRyAoTmVhciBDb2xvcmxlc3MpIiA9IDQsICJGIChDb2xvcmxlc3MpIiA9IDUsICJFIChDb2xvcmxlc3MpIiA9IDYsICJEIChDb2xvcmxlc3MpIiA9IDcNCiAgICApDQogICAgY29sb3JfbnVtIDwtIGNvbG9yX2xldmVsc1tpbnB1dCRjb2xvckdyYWRlXQ0KICAgIA0KICAgIGNsYXJpdHlfbGV2ZWxzIDwtIGMoDQogICAgICAiSTEiID0gMSwgIlNJMiIgPSAyLCAiU0kxIiA9IDMsICJWUzIiID0gNCwgIlZTMSIgPSA1LCAiVlZTMiIgPSA2LCAiVlZTMSIgPSA3LCAiSUYgKEludGVybmFsbHkgRmxhd2xlc3MpIiA9IDgNCiAgICApDQogICAgY2xhcml0eV9udW0gPC0gY2xhcml0eV9sZXZlbHNbaW5wdXQkY2xhcml0eV0NCiAgICANCiAgICAjIEdlbmVyYXRlIG11bHRpcGxlIHByaWNlIGVzdGltYXRlcyB0byBnZXQgYSBjb25maWRlbmNlIGludGVydmFsDQogICAgcHJpY2VfZXN0aW1hdGVzIDwtIHJlcGxpY2F0ZSgxMDAsIGNhbGN1bGF0ZV9wcmljZShjYXJhdCwgY3V0X251bSwgY29sb3JfbnVtLCBjbGFyaXR5X251bSkpDQogICAgDQogICAgIyBDYWxjdWxhdGUgbWVhbiBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbA0KICAgIG1lYW5fcHJpY2UgPC0gbWVhbihwcmljZV9lc3RpbWF0ZXMpDQogICAgbG93ZXJfYm91bmQgPC0gcXVhbnRpbGUocHJpY2VfZXN0aW1hdGVzLCAwLjAyNSkNCiAgICB1cHBlcl9ib3VuZCA8LSBxdWFudGlsZShwcmljZV9lc3RpbWF0ZXMsIDAuOTc1KQ0KICAgIA0KICAgICMgVXBkYXRlIHRoZSBwcmljZSBvdXRwdXRzDQogICAgb3V0cHV0JGVzdGltYXRlZFByaWNlIDwtIHJlbmRlclRleHQoew0KICAgICAgcGFzdGUwKCIkIiwgZm9ybWF0QyhtZWFuX3ByaWNlLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDIsIGJpZy5tYXJrID0gIiwiKSkNCiAgICB9KQ0KICAgIA0KICAgIG91dHB1dCRwcmljZVJhbmdlIDwtIHJlbmRlclRleHQoew0KICAgICAgcGFzdGUwKCIkIiwgZm9ybWF0Qyhsb3dlcl9ib3VuZCwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSAyLCBiaWcubWFyayA9ICIsIiksIA0KICAgICAgICAgICAgICIgLSAkIiwgZm9ybWF0Qyh1cHBlcl9ib3VuZCwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSAyLCBiaWcubWFyayA9ICIsIikpDQogICAgfSkNCiAgfSkNCn0NCg0KIyBSdW4gdGhlIGFwcGxpY2F0aW9uIA0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQ0KYGBgDQoNCg==