1 Librerias

# Librerías necesarias
library(tidyverse)
## Warning: package 'lubridate' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readxl)
library(purrr)
library(knitr)
#install.packages("kableExtra")
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'kableExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     group_rows
library(ggplot2)
#install.packages("igraph")
library(igraph)
## Warning: package 'igraph' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'igraph'
## 
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## 
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## 
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## 
## The following object is masked from 'package:tidyr':
## 
##     crossing
## 
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## 
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## 
## The following object is masked from 'package:base':
## 
##     union
#install.packages("forecast")
#install.packages("lubridate")
library(forecast)
## Warning: package 'forecast' was built under R version 4.4.3
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(lubridate)
library(corrplot)
## corrplot 0.95 loaded
library(RColorBrewer)
#install.packages("ggcorrplot")
library(ggcorrplot)
## Warning: package 'ggcorrplot' was built under R version 4.4.3
library(caret)
## Cargando paquete requerido: lattice
## 
## Adjuntando el paquete: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(car)
## Cargando paquete requerido: carData
## 
## Adjuntando el paquete: 'car'
## 
## The following object is masked from 'package:dplyr':
## 
##     recode
## 
## The following object is masked from 'package:purrr':
## 
##     some
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Adjuntando el paquete: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
#install.packages("xgboost")
library(xgboost)
## Warning: package 'xgboost' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'xgboost'
## 
## The following object is masked from 'package:dplyr':
## 
##     slice
#install.packages("patchwork")
library(patchwork)
## Warning: package 'patchwork' was built under R version 4.4.3

2 Carga de datos

# Cargar archivo Excel desde ruta local
ruta <- "C:\\Users\\almai\\Downloads\\filtered_data.xlsx"

datos <- read_excel(ruta)

# Vista genera
head(datos)
## # A tibble: 6 × 25
##   Trx_Num Trx_Fecha           Origen    Sucursal Num_Cliente ID_Inventario  Cant
##     <dbl> <dttm>              <chr>     <chr>          <dbl>         <dbl> <dbl>
## 1   46453 2023-02-09 00:00:00 FACTURAS… ACA             1049        155001     2
## 2   46455 2023-02-09 00:00:00 FACTURAS… ACA             7455        155001    40
## 3   46476 2023-02-16 00:00:00 FACTURAS… ACA             4981        155001    30
## 4   46480 2023-02-16 00:00:00 FACTURAS… ACA             7455        155001    60
## 5   46501 2023-02-20 00:00:00 FACTURAS… ACA             2925        155001     5
## 6   46528 2023-02-25 00:00:00 FACTURAS… ACA             1049        155001     2
## # ℹ 18 more variables: Linea <chr>, Especifico <chr>, Canal_Venta <chr>,
## #   Tipo_Precio <dbl>, Tipo_Modificador <chr>, Venta <dbl>, Costo_Venta <dbl>,
## #   Costo_Devolucion <dbl>, Ajus_Sistema <dbl>, Precio_Lista_Unitario <dbl>,
## #   Precio_Final_Unitario <dbl>, Semana <dbl>, Mes <dbl>, Ciudad <chr>,
## #   Zona <chr>, Devoluciones <dbl>, Diferencia_Precio <dbl>,
## #   Descuento_Porcentaje <dbl>
str(datos)
## tibble [32,244 × 25] (S3: tbl_df/tbl/data.frame)
##  $ Trx_Num              : num [1:32244] 46453 46455 46476 46480 46501 ...
##  $ Trx_Fecha            : POSIXct[1:32244], format: "2023-02-09" "2023-02-09" ...
##  $ Origen               : chr [1:32244] "FACTURAS IMPORTADAS" "FACTURAS IMPORTADAS" "FACTURAS IMPORTADAS" "FACTURAS IMPORTADAS" ...
##  $ Sucursal             : chr [1:32244] "ACA" "ACA" "ACA" "ACA" ...
##  $ Num_Cliente          : num [1:32244] 1049 7455 4981 7455 2925 ...
##  $ ID_Inventario        : num [1:32244] 155001 155001 155001 155001 155001 ...
##  $ Cant                 : num [1:32244] 2 40 30 60 5 2 1 4 1 1 ...
##  $ Linea                : chr [1:32244] "PQU" "PQU" "PQU" "PQU" ...
##  $ Especifico           : chr [1:32244] "TCG" "TCG" "TCG" "TCG" ...
##  $ Canal_Venta          : chr [1:32244] "Sucursal" "Sucursal" "Sucursal" "Sucursal" ...
##  $ Tipo_Precio          : num [1:32244] 1 1 1 1 1 1 1 1 1 1 ...
##  $ Tipo_Modificador     : chr [1:32244] "CARTA DESCUENTO" "CARTA DESCUENTO" "CARTA DESCUENTO" "CARTA DESCUENTO" ...
##  $ Venta                : num [1:32244] 1187 21280 15960 31920 2968 ...
##  $ Costo_Venta          : num [1:32244] 1194 23874 17906 35811 2570 ...
##  $ Costo_Devolucion     : num [1:32244] 0 0 0 0 0 0 0 0 0 0 ...
##  $ Ajus_Sistema         : num [1:32244] -2515 -2515 -2515 -2515 -2515 ...
##  $ Precio_Lista_Unitario: num [1:32244] 4192 4192 4192 4192 4192 ...
##  $ Precio_Final_Unitario: num [1:32244] 594 532 532 532 594 ...
##  $ Semana               : num [1:32244] 6 6 7 7 8 8 5 5 5 5 ...
##  $ Mes                  : num [1:32244] 2 2 2 2 2 2 2 2 2 2 ...
##  $ Ciudad               : chr [1:32244] "Acapulco" "Acapulco" "Acapulco" "Acapulco" ...
##  $ Zona                 : chr [1:32244] "Sur" "Sur" "Sur" "Sur" ...
##  $ Devoluciones         : num [1:32244] 0 0 0 0 0 0 0 0 0 0 ...
##  $ Diferencia_Precio    : num [1:32244] 3598 3660 3660 3660 3598 ...
##  $ Descuento_Porcentaje : num [1:32244] 85.8 87.3 87.3 87.3 85.8 ...
# Obtener los 5 productos más vendidos (por valor)
top_ids <- datos %>%
  group_by(ID_Inventario) %>%
  summarise(Ventas_Totales = sum(Venta, na.rm = TRUE)) %>%
  arrange(desc(Ventas_Totales)) %>%
  slice_head(n = 5) %>%
  pull(ID_Inventario)

print("Top 5 productos más vendidos (ID_Inventario):")
## [1] "Top 5 productos más vendidos (ID_Inventario):"
print(top_ids)
## [1]  155001 3929788 3904152  155002 3678055
# Filtrar datos válidos
datos_filtrados <- datos %>%
  filter(ID_Inventario %in% top_ids) %>%
  filter(!is.na(Precio_Final_Unitario))

# Contar observaciones por producto
conteo <- datos_filtrados %>%
  count(ID_Inventario, sort = TRUE)

print("Número de registros por producto en datos_filtrados:")
## [1] "Número de registros por producto en datos_filtrados:"
print(conteo)
## # A tibble: 5 × 2
##   ID_Inventario     n
##           <dbl> <int>
## 1       3929788 13717
## 2        155001  8559
## 3        155002  5752
## 4       3904152  2556
## 5       3678055  1660
# Verifica si hay suficientes datos
if (nrow(datos_filtrados) == 0) {
  stop("No hay datos suficientes luego de filtrar por top_ids y precios válidos.")
}
# Combinaciones de pares
productos <- unique(datos_filtrados$ID_Inventario)
pares_productos <- combn(productos, 2, simplify = FALSE)

# Inicializar resultados
resultados_ks <- map_df(pares_productos, function(par) {
  prod1 <- par[1]
  prod2 <- par[2]
  
  precios1 <- datos_filtrados %>%
    filter(ID_Inventario == prod1) %>%
    pull(Precio_Final_Unitario)
  
  precios2 <- datos_filtrados %>%
    filter(ID_Inventario == prod2) %>%
    pull(Precio_Final_Unitario)
  
  print(paste("Comparando productos", prod1, "vs", prod2))
  print(paste("Cantidad de precios:", length(precios1), "y", length(precios2)))
  
  if (length(precios1) >= 5 & length(precios2) >= 5) {
    prueba <- suppressWarnings(ks.test(precios1, precios2))
    data.frame(
      Producto_1 = prod1,
      Producto_2 = prod2,
      D = round(prueba$statistic, 4),
      p_value = round(prueba$p.value, 4),
      Conclusion = ifelse(prueba$p.value > 0.05, "Distribuciones similares", "Distribuciones diferentes")
    )
  } else {
    data.frame(
      Producto_1 = prod1,
      Producto_2 = prod2,
      D = NA,
      p_value = NA,
      Conclusion = "Datos insuficientes"
    )
  }
})
## [1] "Comparando productos 155001 vs 3929788"
## [1] "Cantidad de precios: 8559 y 13717"
## [1] "Comparando productos 155001 vs 155002"
## [1] "Cantidad de precios: 8559 y 5752"
## [1] "Comparando productos 155001 vs 3904152"
## [1] "Cantidad de precios: 8559 y 2556"
## [1] "Comparando productos 155001 vs 3678055"
## [1] "Cantidad de precios: 8559 y 1660"
## [1] "Comparando productos 3929788 vs 155002"
## [1] "Cantidad de precios: 13717 y 5752"
## [1] "Comparando productos 3929788 vs 3904152"
## [1] "Cantidad de precios: 13717 y 2556"
## [1] "Comparando productos 3929788 vs 3678055"
## [1] "Cantidad de precios: 13717 y 1660"
## [1] "Comparando productos 155002 vs 3904152"
## [1] "Cantidad de precios: 5752 y 2556"
## [1] "Comparando productos 155002 vs 3678055"
## [1] "Cantidad de precios: 5752 y 1660"
## [1] "Comparando productos 3904152 vs 3678055"
## [1] "Cantidad de precios: 2556 y 1660"
print("Resultados de la prueba KS:")
## [1] "Resultados de la prueba KS:"
print(resultados_ks)
##        Producto_1 Producto_2     D p_value                Conclusion
## D...1      155001    3929788 1.000       0 Distribuciones diferentes
## D...2      155001     155002 0.043       0 Distribuciones diferentes
## D...3      155001    3904152 1.000       0 Distribuciones diferentes
## D...4      155001    3678055 1.000       0 Distribuciones diferentes
## D...5     3929788     155002 1.000       0 Distribuciones diferentes
## D...6     3929788    3904152 1.000       0 Distribuciones diferentes
## D...7     3929788    3678055 1.000       0 Distribuciones diferentes
## D...8      155002    3904152 1.000       0 Distribuciones diferentes
## D...9      155002    3678055 1.000       0 Distribuciones diferentes
## D...10    3904152    3678055 1.000       0 Distribuciones diferentes
try(dev.off(), silent = TRUE)
## null device 
##           1
# Filtrar los productos
df_155001 <- datos_filtrados %>%
  filter(ID_Inventario == 155001) %>%
  select(Precio_Final_Unitario) %>%
  mutate(Producto = "155001")

df_155002 <- datos_filtrados %>%
  filter(ID_Inventario == 155002) %>%
  select(Precio_Final_Unitario) %>%
  mutate(Producto = "155002")

# Unir en un solo dataframe
df_ecdf <- bind_rows(df_155001, df_155002)

# Graficar ECDF
ggplot(df_ecdf, aes(x = Precio_Final_Unitario, color = Producto)) +
  stat_ecdf(geom = "step", size = 1) +
  labs(title = "ECDF de Precio Final Unitario: Productos 155001 vs 155002",
       x = "Precio Final Unitario",
       y = "Función de Distribución Acumulada (ECDF)",
       color = "Producto") +
  theme_minimal(base_size = 14)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

3 ARMA

4 PREDICCIONES DE VENTAS

4.1 PRODUCTO 155001

# Producto 155001
id_prod <- 155001

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))


# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 155001
Producto Modelo MAPE RMSE
155001 ARMA 17.68164 224023.2

4.2 PRODUCTO 3929788

# Producto 3929788

id_prod <- 3929788

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
mse <- mean((serie_ts - fitted_values)^2)

# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE  = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 3929788
Producto Modelo MAPE RMSE
2 3929788 ARMA 12.83705 224023.2

4.3 PRODUCTO 3904152

# Producto 3904152
id_prod <- 3904152

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))


# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 3904152
Producto Modelo MAPE RMSE
3 3904152 ARMA 15.35679 156981.2

4.4 PRODUCTO 155002

# Producto 155002
id_prod <- 155002

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))


# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 155002
Producto Modelo MAPE RMSE
4 155002 ARMA 25.83303 192121.8

4.5 PRODUCTO 3678055

# Producto 3678055
id_prod <- 3678055

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))

# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 3678055
Producto Modelo MAPE RMSE
5 3678055 ARMA 22.73775 177040.5

5 REGRESION LINEAL

5.1 MAPA DE CALOR

# Variables numéricas relevantes
vars_numericas <- c("Cant", "Venta", "Costo_Venta",
                    "Precio_Final_Unitario", "Descuento_Porcentaje")

# Preparación de los datos
datos_cor <- datos_filtrados %>%
  select(all_of(vars_numericas)) %>%
  na.omit()

# Generar la matriz de correlación
matriz_cor <- cor(datos_cor)

# Ajuste del gráfico sin mar
ggcorrplot(matriz_cor,
           method = "square",
           type = "upper",
           lab = TRUE, 
           lab_size = 2,                   # Mejor tamaño de los coeficientes
           tl.cex = 10,                    # Tamaño de etiquetas más grande
           tl.srt = 45,                    # Rotación de 45° de etiquetas
           colors = c("#6D9EC1", "white", "#E46726"),
           title = "Mapa de Correlación - Variables Numéricas",
           ggtheme = theme_minimal(base_size = 14) +
             theme(
               axis.text.x = element_text(angle = 45, hjust = 1),
               axis.text.y = element_text(angle = 0, hjust = 1))
)

5.2 PRODUCTO 155001

# Filtrar solo los datos para el producto 155001
datos_155001 <- datos_filtrados %>%
  filter(ID_Inventario == 155001) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_155001 <- datos_155001 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_155001)
## # A tibble: 6 × 8
##    Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##    <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1  1187.     2       1194.                  594.                 85.8
## 2 21280     40      23874.                  532                  87.3
## 3 15960     30      17906.                  532                  87.3
## 4 31920     60      35811.                  532                  87.3
## 5  2968      5       2570.                  594.                 85.8
## 6  1187.     2        958.                  594.                 85.8
## # ℹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_155001)

# Ver resumen del modelo
summary(modelo_regresion_155001)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_155001)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -12228.9   -128.0     35.3    100.1  24517.4 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           -3.319e+03  1.165e+03  -2.849   0.0044 ** 
## Cant                   1.578e+02  3.300e+00  47.839   <2e-16 ***
## Costo_Venta            6.802e-01  8.789e-03  77.388   <2e-16 ***
## Precio_Final_Unitario  2.859e+00  2.544e-01  11.241   <2e-16 ***
## Descuento_Porcentaje   2.326e+01  1.199e+01   1.939   0.0525 .  
## Tiempo                 8.990e+04  1.940e+05   0.463   0.6431    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 742.9 on 8553 degrees of freedom
## Multiple R-squared:  0.9879, Adjusted R-squared:  0.9879 
## F-statistic: 1.393e+05 on 5 and 8553 DF,  p-value: < 2.2e-16
# Ajuste del modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_155001)

# Predicciones usando el modelo ajustado
predicciones_155001 <- predict(modelo_regresion_155001, newdata = datos_155001)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_155001 <- mean(abs((datos_155001$Venta - predicciones_155001) / datos_155001$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_155001 <- sqrt(mean((datos_155001$Venta - predicciones_155001)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 155001: ", mape_155001, "\n")
## MAPE del modelo de regresión lineal para 155001:  14.49668
cat("RMSE del modelo de regresión lineal para 155001: ", rmse_155001, "\n")
## RMSE del modelo de regresión lineal para 155001:  742.6409
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155001)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_155001,
  RMSE = rmse_155001

))

5.3 PRODUCTO 3929788

# Filtrar solo los datos para el producto 3929788
datos_3929788 <- datos_filtrados %>%
  filter(ID_Inventario == 3929788) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_3929788 <- datos_3929788 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3929788)
## # A tibble: 6 × 8
##    Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##    <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1   364     10        254.                  36.4                 60  
## 2   242.     6        167.                  40.3                 60  
## 3   697.    15        506.                  46.5                 48.9
## 4 13020    300      10110.                  43.4                 52.3
## 5  2170     50       1685.                  43.4                 52.3
## 6   434     10        337.                  43.4                 52.3
## # ℹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3929788 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                              data = datos_3929788)

# Ver resumen del modelo
summary(modelo_regresion_3929788)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_3929788)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4348.4   -81.8   -45.4    27.9  3441.5 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            5.154e+02  1.690e+02   3.049   0.0023 ** 
## Cant                   2.552e+00  2.503e-01  10.193  < 2e-16 ***
## Costo_Venta            1.102e+00  8.312e-03 132.603  < 2e-16 ***
## Precio_Final_Unitario  8.877e-01  1.674e+00   0.530   0.5960    
## Descuento_Porcentaje  -7.636e+00  1.750e+00  -4.364 1.29e-05 ***
## Tiempo                 2.082e+05  4.365e+04   4.771 1.85e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 218.9 on 13711 degrees of freedom
## Multiple R-squared:  0.9949, Adjusted R-squared:  0.9949 
## F-statistic: 5.393e+05 on 5 and 13711 DF,  p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_3929788 <- predict(modelo_regresion_3929788, newdata = datos_3929788)

# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3929788 <- mean(abs((datos_3929788$Venta - predicciones_3929788) / pmax(datos_3929788$Venta, 0.01))) * 100

# Calcular MSE (Mean Squared Error)
rmse_3929788 <- sqrt(mean((datos_3929788$Venta - predicciones_3929788)^2))


# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3929788: ", mape_3929788, "\n")
## MAPE del modelo de regresión lineal para 3929788:  22.37678
cat("RMSE del modelo de regresión lineal para 3929788: ", rmse_3929788, "\n")
## RMSE del modelo de regresión lineal para 3929788:  218.8655
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3929788)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_3929788,
  RMSE = rmse_3929788
))

5.4 PRODUCTO 3904152

# Filtrar solo los datos para el producto 3904152
datos_3904152 <- datos_filtrados %>%
  filter(ID_Inventario == 3904152) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_3904152 <- datos_3904152 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3904152)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1  3402     1       2462.                  3402                 62.4
## 2  9240     3       7382.                  3080                 66.0
## 3  3402     1       2461.                  3402                 62.4
## 4  3402     1       2462.                  3402                 62.4
## 5  3402     1       2462.                  3402                 62.4
## 6 30800    10      24563.                  3080                 66.0
## # ℹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3904152 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_3904152)

# Ver resumen del modelo
summary(modelo_regresion_3904152)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_3904152)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -7591.2  -149.6   -11.9   114.8  3142.7 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            1.279e+04  1.005e+03  12.720  < 2e-16 ***
## Cant                   2.651e+02  5.929e+01   4.470 8.16e-06 ***
## Costo_Venta            1.172e+00  2.513e-02  46.650  < 2e-16 ***
## Precio_Final_Unitario -1.734e-01  9.940e-02  -1.744   0.0812 .  
## Descuento_Porcentaje  -1.888e+02  1.108e+01 -17.049  < 2e-16 ***
## Tiempo                 2.313e+06  1.487e+05  15.561  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 376.9 on 2550 degrees of freedom
## Multiple R-squared:  0.9987, Adjusted R-squared:  0.9987 
## F-statistic: 4.059e+05 on 5 and 2550 DF,  p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_3904152 <- predict(modelo_regresion_3904152, newdata = datos_3904152)

# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3904152 <- mean(abs((datos_3904152$Venta - predicciones_3904152) / pmax(datos_3904152$Venta, 0.01))) * 100

# Calcular MSE (Mean Squared Error)
rmse_3904152 <- sqrt(mean((datos_3904152$Venta - predicciones_3904152)^2))


# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3904152: ", mape_3904152, "\n")
## MAPE del modelo de regresión lineal para 3904152:  3.343441
cat("RMSE del modelo de regresión lineal para 3904152: ", rmse_3904152, "\n")
## RMSE del modelo de regresión lineal para 3904152:  376.4854
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3904152)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_3904152,
  RMSE = rmse_3904152
))

5.5 PRODUCTO 155002

# Filtrar solo los datos para el producto 155002
datos_155002 <- datos_filtrados %>%
  filter(ID_Inventario == 155002) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_155002 <- datos_155002 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_155002)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1 5320     10       8247.                  532                  87.3
## 2 5320     10       8247.                  532                  87.3
## 3 2660      5       4124.                  532                  87.3
## 4  630      1        519.                  630                  85.0
## 5 1120      2       1037.                  560                  86.6
## 6 1537.     3       1556.                  512.                 87.8
## # ℹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155002 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_155002)

# Ver resumen del modelo
summary(modelo_regresion_155002)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_155002)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -11980   -167     55    154  46301 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            8.521e+02  1.894e+03   0.450 0.652854    
## Cant                   2.992e+02  3.945e+00  75.856  < 2e-16 ***
## Costo_Venta            3.025e-01  9.834e-03  30.758  < 2e-16 ***
## Precio_Final_Unitario  3.681e+00  3.980e-01   9.250  < 2e-16 ***
## Descuento_Porcentaje  -2.875e+01  1.957e+01  -1.469 0.141855    
## Tiempo                 9.982e+05  3.004e+05   3.323 0.000896 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 890.7 on 5746 degrees of freedom
## Multiple R-squared:  0.9723, Adjusted R-squared:  0.9723 
## F-statistic: 4.039e+04 on 5 and 5746 DF,  p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_155002 <- predict(modelo_regresion_155002, newdata = datos_155002)

# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_155002 <- mean(abs((datos_155002$Venta - predicciones_155002) / pmax(datos_155002$Venta, 0.01))) * 100

# Calcular RMSE (Root Mean Squared Error)
rmse_155002 <- sqrt(mean((datos_155002$Venta - predicciones_155002)^2))


# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 155002: ", mape_155002, "\n")
## MAPE del modelo de regresión lineal para 155002:  19.918
cat("RMSE del modelo de regresión lineal para 155002: ", rmse_155002, "\n")
## RMSE del modelo de regresión lineal para 155002:  890.2337
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155002)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_155002,
  RMSE = rmse_155002
  ))

5.6 PRODUCTO 3678055

# Filtrar solo los datos para el producto 3678055
datos_3678055 <- datos_filtrados %>%
  filter(ID_Inventario == 3678055) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_3678055 <- datos_3678055 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3678055)
## # A tibble: 6 × 8
##    Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##    <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1 36358      7      28807.                 5194                  65.9
## 2  5670      1       4213.                 5670                  62.8
## 3 10773      2       8232.                 5386.                 64.7
## 4  5670      1       4116.                 5670                  62.8
## 5  5386.     1       4156.                 5386.                 64.7
## 6  5386.     1       4213.                 5386.                 64.7
## # ℹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3678055 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_3678055)

# Ver resumen del modelo
summary(modelo_regresion_3678055)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_3678055)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3921.4  -165.9    -7.8   170.0  3631.6 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            1.260e+04  1.396e+03   9.024  < 2e-16 ***
## Cant                   1.570e+03  1.017e+02  15.439  < 2e-16 ***
## Costo_Venta            9.057e-01  2.541e-02  35.648  < 2e-16 ***
## Precio_Final_Unitario  2.424e-01  8.578e-02   2.826  0.00477 ** 
## Descuento_Porcentaje  -2.127e+02  1.512e+01 -14.061  < 2e-16 ***
## Tiempo                 2.034e+06  2.032e+05  10.009  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 461.5 on 1654 degrees of freedom
## Multiple R-squared:  0.9976, Adjusted R-squared:  0.9976 
## F-statistic: 1.376e+05 on 5 and 1654 DF,  p-value: < 2.2e-16
#Predicciones usando el modelo ajustado
predicciones_3678055 <- predict(modelo_regresion_3678055, newdata = datos_3678055)
# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3678055 <- mean(abs((datos_3678055$Venta - predicciones_3678055) / pmax(datos_3678055$Venta, 0.01))) * 100

# Calcular RMSE (Root Mean Squared Error)

rmse_3678055 <- mean((datos_3678055$Venta - predicciones_3678055)^2)

# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3678055: ", mape_3678055, "\n")
## MAPE del modelo de regresión lineal para 3678055:  2.900802
cat("RMSE del modelo de regresión lineal para 3678055: ", rmse_3678055, "\n")
## RMSE del modelo de regresión lineal para 3678055:  212205.3
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3678055)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_3678055,
  RMSE = rmse_3678055
))

5.7 ANALISIS DE VARIABLES IMPORTANTES

# Función simplificada para analizar coeficientes
analizar_coeficientes <- function(modelo, nombre_producto) {
  resumen <- summary(modelo)
  coef_df <- as.data.frame(resumen$coefficients)
  colnames(coef_df) <- c("Estimate", "Std.Error", "t.value", "p.value")
  coef_df$Variable <- rownames(coef_df)
  coef_df$Producto <- nombre_producto
  coef_df$Significativo <- ifelse(coef_df$p.value < 0.05, "Sí", "No")
  
  return(coef_df %>%
           select(Producto, Variable, Estimate, p.value, Significativo) %>%
           arrange(desc(abs(Estimate))))
}

# Aplicar a cada modelo
coef_155001 <- analizar_coeficientes(modelo_regresion_155001, "155001")
coef_155002 <- analizar_coeficientes(modelo_regresion_155002, "155002")
coef_3678055 <- analizar_coeficientes(modelo_regresion_3678055, "3678055")
coef_3904152 <- analizar_coeficientes(modelo_regresion_3904152, "3904152")
coef_3929788 <- analizar_coeficientes(modelo_regresion_3929788, "3929788")

# Combinar todos los coeficientes
todos_coeficientes <- bind_rows(coef_155001, coef_155002, coef_3678055, coef_3904152, coef_3929788)

# Tabla con variables importantes incluyendo significancia
variables_importantes <- todos_coeficientes %>%
  filter(Variable != "(Intercept)") %>%
  group_by(Producto) %>%
  arrange(Producto, desc(abs(Estimate))) %>%
  mutate(Impacto = ifelse(Estimate > 0, "Positivo", "Negativo"))

# Tabla completa con todas las variables importantes
kable(variables_importantes %>% 
        select(Producto, Variable, Estimate, p.value, Significativo, Impacto),
      caption = "Variables importantes por producto",
      col.names = c("Producto", "Variable", "Coeficiente", "p-value", "Significativo", "Impacto"),
      digits = c(0, 0, 4, 4, 0, 0)) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Variables importantes por producto
Producto Variable Coeficiente p-value Significativo Impacto
155001 Tiempo 89900.3830 0.6431 No Positivo
155001 Cant 157.8442 0.0000 Positivo
155001 Descuento_Porcentaje 23.2560 0.0525 No Positivo
155001 Precio_Final_Unitario 2.8592 0.0000 Positivo
155001 Costo_Venta 0.6802 0.0000 Positivo
155002 Tiempo 998207.9076 0.0009 Positivo
155002 Cant 299.2286 0.0000 Positivo
155002 Descuento_Porcentaje -28.7504 0.1419 No Negativo
155002 Precio_Final_Unitario 3.6812 0.0000 Positivo
155002 Costo_Venta 0.3025 0.0000 Positivo
3678055 Tiempo 2034282.2796 0.0000 Positivo
3678055 Cant 1570.0692 0.0000 Positivo
3678055 Descuento_Porcentaje -212.6746 0.0000 Negativo
3678055 Costo_Venta 0.9057 0.0000 Positivo
3678055 Precio_Final_Unitario 0.2425 0.0048 Positivo
3904152 Tiempo 2313419.2297 0.0000 Positivo
3904152 Cant 265.0523 0.0000 Positivo
3904152 Descuento_Porcentaje -188.8303 0.0000 Negativo
3904152 Costo_Venta 1.1722 0.0000 Positivo
3904152 Precio_Final_Unitario -0.1734 0.0812 No Negativo
3929788 Tiempo 208247.8173 0.0000 Positivo
3929788 Descuento_Porcentaje -7.6365 0.0000 Negativo
3929788 Cant 2.5517 0.0000 Positivo
3929788 Costo_Venta 1.1022 0.0000 Positivo
3929788 Precio_Final_Unitario 0.8877 0.5960 No Positivo
# Tabla resumen con top 3 por producto
top_por_producto <- variables_importantes %>%
  group_by(Producto) %>%
  slice_head(n = 3) %>%
  select(Producto, Variable, Estimate, p.value, Significativo, Impacto)

kable(top_por_producto,
      caption = "Top 3 variables más importantes por producto",
      col.names = c("Producto", "Variable", "Coeficiente", "p-value", "Significativo", "Impacto"),
      digits = c(0, 0, 4, 4, 0, 0)) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Top 3 variables más importantes por producto
Producto Variable Coeficiente p-value Significativo Impacto
155001 Tiempo 89900.3830 0.6431 No Positivo
155001 Cant 157.8442 0.0000 Positivo
155001 Descuento_Porcentaje 23.2560 0.0525 No Positivo
155002 Tiempo 998207.9076 0.0009 Positivo
155002 Cant 299.2286 0.0000 Positivo
155002 Descuento_Porcentaje -28.7504 0.1419 No Negativo
3678055 Tiempo 2034282.2796 0.0000 Positivo
3678055 Cant 1570.0692 0.0000 Positivo
3678055 Descuento_Porcentaje -212.6746 0.0000 Negativo
3904152 Tiempo 2313419.2297 0.0000 Positivo
3904152 Cant 265.0523 0.0000 Positivo
3904152 Descuento_Porcentaje -188.8303 0.0000 Negativo
3929788 Tiempo 208247.8173 0.0000 Positivo
3929788 Descuento_Porcentaje -7.6365 0.0000 Negativo
3929788 Cant 2.5517 0.0000 Positivo

6 RANDOM FOREST

6.1 PRODUCTO 155001

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
  select(-Trx_Fecha, -Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_155001 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_155001)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 1637507
##                     % Var explained: 96.4
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155001, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE
rmse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto 155001\n")
## Modelo Random Forest para producto 155001
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.436948
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 434537.4
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155001)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  30.698487  164401541874
## Costo_Venta           35.945409  201329490141
## Precio_Final_Unitario  2.777111    5275172729
## Descuento_Porcentaje   3.271347    9916142883
## Tiempo                 3.694441    5432163340
# Graficar importancia de variables
varImpPlot(modelo_rf_155001, main = "Importancia de Variables - Producto 155001")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155001",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 155001",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores:\n")
## Estadísticas descriptivas de los errores:
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 9.281218
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 659.1677
cat("Mínimo:", min(errores), "\n")
## Mínimo: -5614.309
cat("Máximo:", max(errores), "\n")
## Máximo: 49375.13
cat("Mediana:", median(errores), "\n")
## Mediana: -0.06176243
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155001",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf
))

6.2 PRODUCTO 3929788

# Crear una variable de tiempo continua basada en la fecha
datos_3929788 <- datos_3929788 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), 
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses

# Mostrar un resumen de los datos
summary(datos_3929788)
##      Venta               Cant          Costo_Venta       Precio_Final_Unitario
##  Min.   :   30.52   Min.   :   1.00   Min.   :   25.36   Min.   : 28.00       
##  1st Qu.:  403.20   1st Qu.:  10.00   1st Qu.:  280.17   1st Qu.: 36.40       
##  Median :  873.60   Median :  20.00   Median :  623.38   Median : 40.32       
##  Mean   : 1660.11   Mean   :  42.99   Mean   : 1301.82   Mean   : 40.83       
##  3rd Qu.: 2016.00   3rd Qu.:  50.00   3rd Qu.: 1513.40   3rd Qu.: 43.57       
##  Max.   :53096.40   Max.   :1540.00   Max.   :46610.46   Max.   :108.92       
##  Descuento_Porcentaje   Trx_Fecha                          Fecha           
##  Min.   : 0.00        Min.   :2023-01-02 00:00:00.00   Min.   :2023-01-01  
##  1st Qu.:60.00        1st Qu.:2023-07-05 00:00:00.00   1st Qu.:2023-07-01  
##  Median :60.19        Median :2024-01-15 00:00:00.00   Median :2024-01-01  
##  Mean   :60.84        Mean   :2024-01-06 20:09:02.74   Mean   :2023-12-21  
##  3rd Qu.:65.28        3rd Qu.:2024-07-09 00:00:00.00   3rd Qu.:2024-07-01  
##  Max.   :71.72        Max.   :2024-12-31 00:00:00.00   Max.   :2024-12-01  
##      Tiempo         
##  Min.   :0.000e+00  
##  1st Qu.:6.983e-05  
##  Median :1.408e-04  
##  Mean   :1.370e-04  
##  3rd Qu.:2.110e-04  
##  Max.   :2.701e-04
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3929788 %>%
  select(-Trx_Fecha, -Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_3929788 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_3929788)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 20761.86
##                     % Var explained: 99.78
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3929788, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE

rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 3929788\n")
## Modelo Random Forest para producto 3929788
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.5359169
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 66.13281
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3929788)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  31.762370   57074339573
## Costo_Venta           36.919426   65771486623
## Precio_Final_Unitario  2.159695    1150762357
## Descuento_Porcentaje   8.032237    4227679643
## Tiempo                 1.838465     467935533
# Graficar importancia de variables
varImpPlot(modelo_rf_3929788, main = "Importancia de Variables - Producto 3929788")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3929788",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 3929788",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3929788",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),

    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf

))

6.3 PRODUCTO 3904152

# Crear una variable de tiempo continua basada en la fecha
datos_3904152 <- datos_3904152 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), 
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses

# Mostrar un resumen de los datos
summary(datos_3904152)
##      Venta             Cant         Costo_Venta     Precio_Final_Unitario
##  Min.   :  2677   Min.   : 1.000   Min.   :  2250   Min.   :2677         
##  1st Qu.:  3108   1st Qu.: 1.000   1st Qu.:  2331   1st Qu.:3051         
##  Median :  3402   Median : 1.000   Median :  2472   Median :3108         
##  Mean   :  7495   Mean   : 2.413   Mean   :  5724   Mean   :3158         
##  3rd Qu.:  7140   3rd Qu.: 2.000   3rd Qu.:  5194   3rd Qu.:3318         
##  Max.   :169344   Max.   :56.000   Max.   :131032   Max.   :3639         
##  Descuento_Porcentaje   Trx_Fecha                          Fecha           
##  Min.   :60.00        Min.   :2023-01-02 00:00:00.00   Min.   :2023-01-01  
##  1st Qu.:64.43        1st Qu.:2023-07-12 00:00:00.00   1st Qu.:2023-07-01  
##  Median :65.90        Median :2024-01-16 00:00:00.00   Median :2024-01-01  
##  Mean   :65.72        Mean   :2024-01-06 20:44:30.42   Mean   :2023-12-21  
##  3rd Qu.:66.60        3rd Qu.:2024-06-28 00:00:00.00   3rd Qu.:2024-06-01  
##  Max.   :70.43        Max.   :2024-12-31 00:00:00.00   Max.   :2024-12-01  
##      Tiempo         
##  Min.   :0.000e+00  
##  1st Qu.:6.983e-05  
##  Median :1.408e-04  
##  Mean   :1.366e-04  
##  3rd Qu.:1.995e-04  
##  Max.   :2.701e-04
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3904152 %>%
  select(-Trx_Fecha, -Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_3904152 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_3904152)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 912601.7
##                     % Var explained: 99.19
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3904152, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 3904152\n")
## Modelo Random Forest para producto 3904152
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.2195905
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 483.3678
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3904152)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  27.857528  127827976697
## Costo_Venta           33.273998  150734822975
## Precio_Final_Unitario  3.859049    3595343640
## Descuento_Porcentaje   4.653136    5848187484
## Tiempo                 2.155004    1653911972
# Graficar importancia de variables
varImpPlot(modelo_rf_3904152, main = "Importancia de Variables - Producto 3904152")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3904152",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 3904152",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3904152",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),

    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf

))

6.4 PRODUCTO 155002

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155002 %>%
  select(-Trx_Fecha, -Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_155002 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_155002)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 974009.6
##                     % Var explained: 96.6
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155002, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# MSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 155002\n")
## Modelo Random Forest para producto 155002
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.5501504
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 424.4274
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155002)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  36.613264   83857376373
## Costo_Venta           28.749389   69607996778
## Precio_Final_Unitario  6.394198    3516723253
## Descuento_Porcentaje   5.570749    5564986313
## Tiempo                 7.237416    1576644713
# Graficar importancia de variables
varImpPlot(modelo_rf_155002, main = "Importancia de Variables - Producto 155002")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155002",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 155002",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155002",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 3678055
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf

))

6.5 PRODUCTO 3678055

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3678055 %>%
  select(-Trx_Fecha, -Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_3678055 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_3678055)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 824301.4
##                     % Var explained: 99.07
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3678055, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 3678055\n")
## Modelo Random Forest para producto 3678055
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.2187988
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 454.4609
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3678055)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  28.895897   64016596017
## Costo_Venta           34.510616   77510803343
## Precio_Final_Unitario  4.162788    1376111301
## Descuento_Porcentaje   4.373664    1850346083
## Tiempo                 2.942522    1167999995
# Graficar importancia de variables
varImpPlot(modelo_rf_3678055, main = "Importancia de Variables - Producto 3678055")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3678055",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 3678055",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3678055",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf
))

7 XGBOOST

7.1 PRODUCTO 155001

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
  select(-Trx_Fecha, -Fecha)

# Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
indices_train <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
datos_train <- datos_modelo[indices_train, ]
datos_test <- datos_modelo[-indices_train, ]

# Separar variables predictoras y variable objetivo
X_train <- as.matrix(datos_train[, colnames(datos_train) != "Venta"])
y_train <- datos_train$Venta

X_test <- as.matrix(datos_test[, colnames(datos_test) != "Venta"])
y_test <- datos_test$Venta

# Crear matrices DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = X_train, label = y_train)
dtest <- xgb.DMatrix(data = X_test, label = y_test)

# Definir una rejilla completa de hiperparámetros para búsqueda
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),         # Learning rate
  max_depth = c(3, 5, 7, 9),             # Profundidad máxima
  subsample = c(0.6, 0.8, 1.0),          # Submuestra de observaciones
  colsample_bytree = c(0.6, 0.8, 1.0),   # Submuestra de variables
  min_child_weight = c(1, 3, 5),         # Peso mínimo en nodos hijos
  gamma = c(0, 0.1, 0.3)                 # Regularización gamma
)

# Mostrar cuántas combinaciones tenemos
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n")
## Número total de combinaciones de hiperparámetros: 1296
# Para este ejemplo, vamos a limitar el número de combinaciones
# Seleccionando un subconjunto aleatorio de combinaciones (20 combinaciones)
set.seed(123)
if (nrow(param_grid) > 20) {
  muestra_indices <- sample(1:nrow(param_grid), 20)
  param_grid_reducida <- param_grid[muestra_indices, ]
} else {
  param_grid_reducida <- param_grid
}

cat("Número de combinaciones a evaluar:", nrow(param_grid_reducida), "\n")
## Número de combinaciones a evaluar: 20
# Función para evaluar un conjunto de hiperparámetros con validación cruzada
evaluate_params <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  # Realizar validación cruzada
  cv_results <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 100,
    nfold = 5,  # 5-fold validación cruzada
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  # Extraer el mejor RMSE y el número óptimo de rondas
  best_rmse <- min(cv_results$evaluation_log$test_rmse_mean)
  best_nrounds <- which.min(cv_results$evaluation_log$test_rmse_mean)
  
  return(list(rmse = best_rmse, nrounds = best_nrounds, params = params))
}

# Inicializar tabla para almacenar resultados
resultados_grid <- data.frame(
  eta = numeric(nrow(param_grid_reducida)),
  max_depth = numeric(nrow(param_grid_reducida)),
  subsample = numeric(nrow(param_grid_reducida)),
  colsample_bytree = numeric(nrow(param_grid_reducida)),
  min_child_weight = numeric(nrow(param_grid_reducida)),
  gamma = numeric(nrow(param_grid_reducida)),
  nrounds = numeric(nrow(param_grid_reducida)),
  rmse = numeric(nrow(param_grid_reducida))
)





# Realizar la búsqueda en cuadrícula (esto puede tardar varios minutos)
cat("Iniciando búsqueda en cuadrícula...\n")
## Iniciando búsqueda en cuadrícula...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  # Obtener fila de parámetros actual
  params_row <- param_grid_reducida[i, ]
  
  # Evaluar combinación actual
  result <- evaluate_params(params_row)
  
  # Guardar resultados
  resultados_grid$eta[i] <- params_row$eta
  resultados_grid$max_depth[i] <- params_row$max_depth
  resultados_grid$subsample[i] <- params_row$subsample
  resultados_grid$colsample_bytree[i] <- params_row$colsample_bytree
  resultados_grid$min_child_weight[i] <- params_row$min_child_weight
  resultados_grid$gamma[i] <- params_row$gamma
  resultados_grid$nrounds[i] <- result$nrounds
  resultados_grid$rmse[i] <- result$rmse
}
## Evaluando combinación 1 de 20
## Evaluando combinación 2 de 20
## Evaluando combinación 3 de 20
## Evaluando combinación 4 de 20
## Evaluando combinación 5 de 20
## Evaluando combinación 6 de 20
## Evaluando combinación 7 de 20
## Evaluando combinación 8 de 20
## Evaluando combinación 9 de 20
## Evaluando combinación 10 de 20
## Evaluando combinación 11 de 20
## Evaluando combinación 12 de 20
## Evaluando combinación 13 de 20
## Evaluando combinación 14 de 20
## Evaluando combinación 15 de 20
## Evaluando combinación 16 de 20
## Evaluando combinación 17 de 20
## Evaluando combinación 18 de 20
## Evaluando combinación 19 de 20
## Evaluando combinación 20 de 20
# Ordenar resultados por RMSE (de menor a mayor)
resultados_grid <- resultados_grid[order(resultados_grid$rmse), ]

# Mostrar los 5 mejores conjuntos de hiperparámetros
cat("\nLos 5 mejores conjuntos de hiperparámetros:\n")
## 
## Los 5 mejores conjuntos de hiperparámetros:
print(head(resultados_grid, 5))
##     eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 2  0.10         9       0.8              0.6                1   0.1     100
## 4  0.05         9       1.0              0.8                1   0.1     100
## 10 0.05         9       0.8              0.6                3   0.3     100
## 14 0.10         3       0.6              0.6                3   0.3      92
## 7  0.05         5       1.0              1.0                3   0.3     100
##         rmse
## 2   848.7171
## 4   950.4798
## 10 1014.0598
## 14 1024.6858
## 7  1025.5960
# Obtener los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid$eta[1],
  max_depth = resultados_grid$max_depth[1],
  subsample = resultados_grid$subsample[1],
  colsample_bytree = resultados_grid$colsample_bytree[1],
  min_child_weight = resultados_grid$min_child_weight[1],
  gamma = resultados_grid$gamma[1]
)

mejor_nrounds <- resultados_grid$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.1
## 
## $max_depth
## [1] 9
## 
## $subsample
## [1] 0.8
## 
## $colsample_bytree
## [1] 0.6
## 
## $min_child_weight
## [1] 1
## 
## $gamma
## [1] 0.1
cat("Número óptimo de rondas:", mejor_nrounds, "\n")
## Número óptimo de rondas: 100
cat("RMSE en validación cruzada:", resultados_grid$rmse[1], "\n\n")
## RMSE en validación cruzada: 848.7171
# Entrenar el modelo final con los mejores hiperparámetros
modelo_xgb_155001 <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

# Hacer predicciones en el conjunto de prueba
predicciones_test <- predict(modelo_xgb_155001, dtest)

# Calcular métricas en el conjunto de prueba
# MAPE
mape_test <- mean(abs((y_test - predicciones_test) / pmax(y_test, 0.01))) * 100

# RMSE
rmse_test <- sqrt(mean((y_test - predicciones_test)^2))


# Mostrar las métricas en el conjunto de prueba
cat("Métricas en el conjunto de prueba:\n")
## Métricas en el conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 5.169695
cat("RMSE del modelo XGBoost:", rmse_test, "\n\n")
## RMSE del modelo XGBoost: 524.871
# Ahora hacer predicciones en el conjunto completo para comparabilidad con otros modelos
X_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completas <- predict(modelo_xgb_155001, X_completo)







# Calcular métricas en el conjunto completo
# MAPE
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completas) / pmax(datos_modelo$Venta, 0.01))) * 100

# MSE
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completas)^2))



# Mostrar las métricas en el conjunto completo
cat("Métricas en el conjunto completo:\n")
## Métricas en el conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 3.700244
cat("RMSE del modelo XGBoost:", rmse_completo, "\n\n")
## RMSE del modelo XGBoost: 252.753
# Importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_xgb_155001
)
print(importancia)
##                  Feature       Gain     Cover Frequency
##                   <char>      <num>     <num>     <num>
## 1:           Costo_Venta 0.51898958 0.3205537 0.2603270
## 2:                  Cant 0.41669639 0.2572725 0.1700986
## 3:                Tiempo 0.02556506 0.1063427 0.1522526
## 4:  Descuento_Porcentaje 0.01944186 0.1279960 0.1556221
## 5: Precio_Final_Unitario 0.01930711 0.1878350 0.2616997
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia, 
                   main = "Importancia de Variables - Producto 155001 (XGBoost)")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_completas
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155001 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 155001 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_completas, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155001 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo

))

7.2 PRODUCTO 3929788

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3929788 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),          # Learning rate
  max_depth = c(3, 6, 9),                 # Profundidad máxima
  min_child_weight = c(1, 3, 5),          # Peso mínimo de nodo hijo
  subsample = c(0.7, 0.9),                # Proporción de observaciones
  colsample_bytree = c(0.7, 0.9),         # Proporción de variables
  gamma = c(0, 0.1, 0.3)                  # Regularización gamma
)

# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 3929788\n")
## Grid Search para XGBoost - Producto 3929788
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
  selected_indices <- sample(1:nrow(param_grid), 12)
  param_grid <- param_grid[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  # Extraer parámetros de la combinación actual
  params <- list(
    objective = "reg:squarederror",      # Objetivo de regresión
    eval_metric = "rmse",               # Métrica de evaluación
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
  cat("  eta =", params$eta, 
      ", max_depth =", params$max_depth, 
      ", min_child_weight =", params$min_child_weight, 
      ", subsample =", params$subsample, 
      ", colsample_bytree =", params$colsample_bytree,
      ", gamma =", params$gamma, "\n")
  
  # Validación cruzada para encontrar el número óptimo de iteraciones
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,                    # Máximo número de iteraciones
    nfold = 5,                        # 5-fold validación cruzada
    early_stopping_rounds = 20,       # Detener si no hay mejora en 20 rondas
    verbose = 0                       # Suprimir mensajes
  )
  
  # Extraer mejor iteración y su RMSE
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  # Guardar resultados
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
##   eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 581.031 
## 
## Evaluando combinación 2 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 126.5033 
## 
## Evaluando combinación 3 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 157.9687 
## 
## Evaluando combinación 4 de 12 :
##   eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 124.4965 
## 
## Evaluando combinación 5 de 12 :
##   eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 198 
##   RMSE en validación cruzada: 118.6207 
## 
## Evaluando combinación 6 de 12 :
##   eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 566.3366 
## 
## Evaluando combinación 7 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 144.8686 
## 
## Evaluando combinación 8 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 125.288 
## 
## Evaluando combinación 9 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 113.7886 
## 
## Evaluando combinación 10 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 163.1105 
## 
## Evaluando combinación 11 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 115.0487 
## 
## Evaluando combinación 12 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 142.9083
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 9  0.05         6                3       0.7              0.9   0.3     200
## 11 0.05         6                3       0.7              0.9   0.0     200
## 5  0.10         6                5       0.9              0.9   0.1     198
## 4  0.30         9                5       0.7              0.9   0.1     200
## 8  0.10         3                3       0.7              0.7   0.1     200
## 2  0.10         9                3       0.9              0.9   0.3     200
## 12 0.10         3                3       0.9              0.7   0.3     200
## 7  0.05         3                5       0.9              0.7   0.1     200
## 3  0.05         3                1       0.9              0.7   0.0     200
## 10 0.10         9                1       0.9              0.7   0.3     200
## 6  0.01         6                5       0.9              0.9   0.1     200
## 1  0.01         9                3       0.7              0.9   0.1     200
##     rmse_cv
## 9  113.7886
## 11 115.0487
## 5  118.6207
## 4  124.4965
## 8  125.2880
## 2  126.5033
## 12 142.9083
## 7  144.8686
## 3  157.9687
## 10 163.1105
## 6  566.3366
## 1  581.0310
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 3929788",
    x = "Combinación de Hiperparámetros",
    y = "RMSE en Validación Cruzada"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 6
## 
## $min_child_weight
## [1] 3
## 
## $subsample
## [1] 0.7
## 
## $colsample_bytree
## [1] 0.9
## 
## $gamma
## [1] 0.3
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 200
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)
# Generar predicciones sobre TODO el conjunto de datos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
dcompleto <- xgb.DMatrix(data = x_completo)
predicciones_completo <- predict(modelo_final, newdata = dcompleto)


ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3929788 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo

# Histograma de errores
hist(errores, 
     main = "Distribución de Errores - Producto 3929788 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3929788 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.3 PRODUCTO 3904152

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3904152 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),          # Learning rate
  max_depth = c(3, 6, 9),                 # Profundidad máxima
  min_child_weight = c(1, 3, 5),          # Peso mínimo de nodo hijo
  subsample = c(0.7, 0.9),                # Proporción de observaciones
  colsample_bytree = c(0.7, 0.9),         # Proporción de variables
  gamma = c(0, 0.1, 0.3)                  # Regularización gamma
)

# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 3904152\n")
## Grid Search para XGBoost - Producto 3904152
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
  selected_indices <- sample(1:nrow(param_grid), 12)
  param_grid <- param_grid[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  # Extraer parámetros de la combinación actual
  params <- list(
    objective = "reg:squarederror",      # Objetivo de regresión
    eval_metric = "rmse",               # Métrica de evaluación
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
  cat("  eta =", params$eta, 
      ", max_depth =", params$max_depth, 
      ", min_child_weight =", params$min_child_weight, 
      ", subsample =", params$subsample, 
      ", colsample_bytree =", params$colsample_bytree,
      ", gamma =", params$gamma, "\n")
  
  # Validación cruzada para encontrar el número óptimo de iteraciones
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,                    # Máximo número de iteraciones
    nfold = 5,                        # 5-fold validación cruzada
    early_stopping_rounds = 20,       # Detener si no hay mejora en 20 rondas
    verbose = 0                       # Suprimir mensajes
  )
  
  # Extraer mejor iteración y su RMSE
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  # Guardar resultados
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
##   eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 2598.527 
## 
## Evaluando combinación 2 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 81 
##   RMSE en validación cruzada: 855.4766 
## 
## Evaluando combinación 3 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 632.2493 
## 
## Evaluando combinación 4 de 12 :
##   eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 111 
##   RMSE en validación cruzada: 1094.701 
## 
## Evaluando combinación 5 de 12 :
##   eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 35 
##   RMSE en validación cruzada: 1725.775 
## 
## Evaluando combinación 6 de 12 :
##   eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 3008.817 
## 
## Evaluando combinación 7 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 120 
##   RMSE en validación cruzada: 1765.138 
## 
## Evaluando combinación 8 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 153 
##   RMSE en validación cruzada: 932.4027 
## 
## Evaluando combinación 9 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 198 
##   RMSE en validación cruzada: 764.2976 
## 
## Evaluando combinación 10 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 801.357 
## 
## Evaluando combinación 11 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 743.391 
## 
## Evaluando combinación 12 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 128 
##   RMSE en validación cruzada: 863.9891
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3  0.05         3                1       0.9              0.7   0.0     200
## 11 0.05         6                3       0.7              0.9   0.0     200
## 9  0.05         6                3       0.7              0.9   0.3     198
## 10 0.10         9                1       0.9              0.7   0.3     200
## 2  0.10         9                3       0.9              0.9   0.3      81
## 12 0.10         3                3       0.9              0.7   0.3     128
## 8  0.10         3                3       0.7              0.7   0.1     153
## 4  0.30         9                5       0.7              0.9   0.1     111
## 5  0.10         6                5       0.9              0.9   0.1      35
## 7  0.05         3                5       0.9              0.7   0.1     120
## 1  0.01         9                3       0.7              0.9   0.1     200
## 6  0.01         6                5       0.9              0.9   0.1     200
##      rmse_cv
## 3   632.2493
## 11  743.3910
## 9   764.2976
## 10  801.3570
## 2   855.4766
## 12  863.9891
## 8   932.4027
## 4  1094.7013
## 5  1725.7754
## 7  1765.1378
## 1  2598.5266
## 6  3008.8172
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 3904152",
    x = "Combinación de Hiperparámetros",
    y = "RMSE en Validación Cruzada"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 3
## 
## $min_child_weight
## [1] 1
## 
## $subsample
## [1] 0.9
## 
## $colsample_bytree
## [1] 0.7
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 200
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

modelo_xgb_3904152 <- modelo_final


# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)

# Calcular métricas
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100

# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))


# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 1.802047
cat("RMSE del modelo XGBoost:", rmse_test, "\n\n")
## RMSE del modelo XGBoost: 525.6254
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)

# Calcular métricas en conjunto completo
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / 
                        pmax(datos_modelo$Venta, 0.01))) * 100

mse_completo <- mean((datos_modelo$Venta - predicciones_completo)^2)

# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 1.599453
cat("RMSE del modelo XGBoost:", rmse_completo, "\n\n")
## RMSE del modelo XGBoost: 252.753
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_final
)

# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
##                  Feature         Gain      Cover  Frequency
##                   <char>        <num>      <num>      <num>
## 1:           Costo_Venta 0.5477063005 0.34762755 0.32088123
## 2:                  Cant 0.4429396314 0.35591065 0.32662835
## 3: Precio_Final_Unitario 0.0049318239 0.09313008 0.11781609
## 4:  Descuento_Porcentaje 0.0038652717 0.13943792 0.15804598
## 5:                Tiempo 0.0005569725 0.06389380 0.07662835
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia, 
                   main = "Importancia de Variables - Producto 3904152 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_completo
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3904152 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo

# Histograma de errores
hist(errores, 
     main = "Distribución de Errores - Producto 3904152 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3904152 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.4 PRODUCTO 155002

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155002 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),          # Learning rate
  max_depth = c(3, 6, 9),                 # Profundidad máxima
  min_child_weight = c(1, 3, 5),          # Peso mínimo de nodo hijo
  subsample = c(0.7, 0.9),                # Proporción de observaciones
  colsample_bytree = c(0.7, 0.9),         # Proporción de variables
  gamma = c(0, 0.1, 0.3)                  # Regularización gamma
)

# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 155002\n")
## Grid Search para XGBoost - Producto 155002
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
  selected_indices <- sample(1:nrow(param_grid), 12)
  param_grid <- param_grid[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  # Extraer parámetros de la combinación actual
  params <- list(
    objective = "reg:squarederror",      # Objetivo de regresión
    eval_metric = "rmse",               # Métrica de evaluación
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
  cat("  eta =", params$eta, 
      ", max_depth =", params$max_depth, 
      ", min_child_weight =", params$min_child_weight, 
      ", subsample =", params$subsample, 
      ", colsample_bytree =", params$colsample_bytree,
      ", gamma =", params$gamma, "\n")
  
  # Validación cruzada para encontrar el número óptimo de iteraciones
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,                    # Máximo número de iteraciones
    nfold = 5,                        # 5-fold validación cruzada
    early_stopping_rounds = 20,       # Detener si no hay mejora en 20 rondas
    verbose = 0                       # Suprimir mensajes
  )
  
  # Extraer mejor iteración y su RMSE
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  # Guardar resultados
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
##   eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 1373.535 
## 
## Evaluando combinación 2 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 91 
##   RMSE en validación cruzada: 915.5984 
## 
## Evaluando combinación 3 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 840.4357 
## 
## Evaluando combinación 4 de 12 :
##   eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 65 
##   RMSE en validación cruzada: 981.4425 
## 
## Evaluando combinación 5 de 12 :
##   eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 60 
##   RMSE en validación cruzada: 938.5359 
## 
## Evaluando combinación 6 de 12 :
##   eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 1270.417 
## 
## Evaluando combinación 7 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 958.5418 
## 
## Evaluando combinación 8 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 107 
##   RMSE en validación cruzada: 923.5338 
## 
## Evaluando combinación 9 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 114 
##   RMSE en validación cruzada: 897.544 
## 
## Evaluando combinación 10 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 930.1425 
## 
## Evaluando combinación 11 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0 
##   Mejor iteración: 78 
##   RMSE en validación cruzada: 937.0212 
## 
## Evaluando combinación 12 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 199 
##   RMSE en validación cruzada: 890.997
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3  0.05         3                1       0.9              0.7   0.0     200
## 12 0.10         3                3       0.9              0.7   0.3     199
## 9  0.05         6                3       0.7              0.9   0.3     114
## 2  0.10         9                3       0.9              0.9   0.3      91
## 8  0.10         3                3       0.7              0.7   0.1     107
## 10 0.10         9                1       0.9              0.7   0.3     200
## 11 0.05         6                3       0.7              0.9   0.0      78
## 5  0.10         6                5       0.9              0.9   0.1      60
## 7  0.05         3                5       0.9              0.7   0.1     200
## 4  0.30         9                5       0.7              0.9   0.1      65
## 6  0.01         6                5       0.9              0.9   0.1     200
## 1  0.01         9                3       0.7              0.9   0.1     200
##      rmse_cv
## 3   840.4357
## 12  890.9970
## 9   897.5440
## 2   915.5984
## 8   923.5338
## 10  930.1425
## 11  937.0212
## 5   938.5359
## 7   958.5418
## 4   981.4425
## 6  1270.4174
## 1  1373.5351
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 155002",
    x = "Combinación de Hiperparámetros",
    y = "RMSE en Validación Cruzada"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 3
## 
## $min_child_weight
## [1] 1
## 
## $subsample
## [1] 0.9
## 
## $colsample_bytree
## [1] 0.7
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 200
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

modelo_xgb_155002 <- modelo_final

# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)

# Calcular métricas
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100

# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))



# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 7.13874
cat("RMSE del modelo XGBoost:", rmse_test, "\n\n")
## RMSE del modelo XGBoost: 751.2073
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)

# Calcular métricas en conjunto completo
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / 
                        pmax(datos_modelo$Venta, 0.01))) * 100

rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))


# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 6.835722
cat("RMSE del modelo XGBoost:", rmse_completo, "\n\n")
## RMSE del modelo XGBoost: 417.9467
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_final
)

# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
##                  Feature        Gain      Cover  Frequency
##                   <char>       <num>      <num>      <num>
## 1:           Costo_Venta 0.533136299 0.26895646 0.27553648
## 2:                  Cant 0.429482575 0.42288315 0.35622318
## 3:  Descuento_Porcentaje 0.017905273 0.08692447 0.11673820
## 4: Precio_Final_Unitario 0.016551713 0.17326591 0.18798283
## 5:                Tiempo 0.002924141 0.04797001 0.06351931
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia, 
                   main = "Importancia de Variables - Producto 155002 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_completo
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155002 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo

# Histograma de errores
hist(errores, 
     main = "Distribución de Errores - Producto 155002 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155002 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.5 PRODUCTO 3678055

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3678055 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),
  max_depth = c(3, 6, 9),
  min_child_weight = c(1, 3, 5),
  subsample = c(0.7, 0.9),
  colsample_bytree = c(0.7, 0.9),
  gamma = c(0, 0.1, 0.3)
)

cat("Grid Search para XGBoost - Producto 3678055\n")
## Grid Search para XGBoost - Producto 3678055
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Selección aleatoria de 12 combinaciones (si se desea limitar)
set.seed(456)
if (nrow(param_grid) > 12) {
  param_grid <- param_grid[sample(1:nrow(param_grid), 12), ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()
cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), "...\n")
  
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,
    nfold = 5,
    early_stopping_rounds = 20,
    verbose = 0
  )
  
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 ...
## Evaluando combinación 2 de 12 ...
## Evaluando combinación 3 de 12 ...
## Evaluando combinación 4 de 12 ...
## Evaluando combinación 5 de 12 ...
## Evaluando combinación 6 de 12 ...
## Evaluando combinación 7 de 12 ...
## Evaluando combinación 8 de 12 ...
## Evaluando combinación 9 de 12 ...
## Evaluando combinación 10 de 12 ...
## Evaluando combinación 11 de 12 ...
## Evaluando combinación 12 de 12 ...
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3  0.05         3                1       0.9              0.7   0.0     200
## 5  0.10         6                5       0.9              0.9   0.1     116
## 11 0.05         6                3       0.7              0.9   0.0     185
## 9  0.05         6                3       0.7              0.9   0.3     112
## 12 0.10         3                3       0.9              0.7   0.3     105
## 2  0.10         9                3       0.9              0.9   0.3     111
## 4  0.30         9                5       0.7              0.9   0.1      31
## 10 0.10         9                1       0.9              0.7   0.3     200
## 7  0.05         3                5       0.9              0.7   0.1     200
## 8  0.10         3                3       0.7              0.7   0.1     200
## 6  0.01         6                5       0.9              0.9   0.1     200
## 1  0.01         9                3       0.7              0.9   0.1     200
##      rmse_cv
## 3   679.8778
## 5   692.5683
## 11  699.5892
## 9   731.9505
## 12  746.3491
## 2   805.6540
## 4   818.3057
## 10  834.9787
## 7   947.5520
## 8   973.0962
## 6  2407.9225
## 1  2456.9115
# Visualización
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 3678055",
    x = "Combinación de Hiperparámetros",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

# Paso 7: Entrenar modelo final
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

modelo_xgb_3678055 <- modelo_final


# Paso 8: Evaluar modelo
predicciones_test <- predict(modelo_final, dtest)
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))



# Paso 9: Predicción en todo el conjunto
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)

mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / pmax(datos_modelo$Venta, 0.01))) * 100
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))




# Paso 10: Importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_final
)
xgb.plot.importance(importancia, 
                    main = "Importancia de Variables - Producto 3678055 (XGBoost)")

# Paso 11: Gráficos de evaluación
datos_grafico <- data.frame(Observado = datos_modelo$Venta, Predicho = predicciones_completo)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "red") +
  labs(title = "Observado vs Predicho - Producto 3678055", x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

errores <- datos_modelo$Venta - predicciones_completo

hist(errores, 
     main = "Distribución de Errores - Producto 3678055 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue", breaks = 30)

ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Errores vs Predicción - Producto 3678055", x = "Venta Predicha", y = "Error") +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

8 Visualización de Métricas

# Definir los colores para cada modelo
colores_modelos <- c(
  "ARMA/SARIMA" = "#1f77b4",    # Azul
  "Regresión Lineal" = "#ff7f0e", # Naranja
  "Random Forest" = "#2ca02c",   # Verde
  "XGBoost" = "#d62728"         # Rojo
)

8.1 PRODUCTO 155001

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 155001:")
## [1] "Datos actuales para el producto 155001:"
print(metricas_comparativas %>% filter(Producto == "155001"))
##   Producto           Modelo      MAPE        RMSE
## 1   155001             ARMA 17.681643 224023.2444
## 2   155001 Regresión Lineal 14.496676    742.6409
## 3   155001    Random Forest  0.436948 434537.3986
## 4   155001          XGBoost  3.700244    252.7530
# Crear un dataframe manualmente con los 4 modelos para el producto 155001
# (con valores de ejemplo si es necesario)
datos_155001_completo <- data.frame(
  Producto = rep("155001", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_155001_completo <- left_join(
  datos_155001_completo,
  metricas_comparativas %>% filter(Producto == "155001"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Si tienes los valores, reemplaza los 0 con los valores correctos
# O toma nota de cuáles son NA para reemplazarlos con los valores reales

# Valores para Regresión Lineal (reemplaza estos con los valores reales)
if (is.na(datos_155001_completo$MAPE[2])) {
  datos_155001_completo$MAPE[2] <- mape_155001  # O el valor correcto
}
if (is.na(datos_155001_completo$RMSE[2])) {
  datos_155001_completo$RMSE[2] <- rmse_155001  # Ya no MSE
}




# Valores para Random Forest (reemplaza estos con los valores reales)
# Si ya ejecutaste la sección de Random Forest para el producto 155001,
# usa las variables r2_rf, rmse_rf, etc.
if (is.na(datos_155001_completo$MAPE[3]) && exists("mape_rf")) {
  datos_155001_completo$MAPE[3] <- mape_rf
}

if (is.na(datos_155001_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_155001_completo$RMSE[3] <- rmse_rf
}






# Valores para XGBoost (reemplaza estos con los valores reales)
# Si ya ejecutaste la sección de XGBoost para el producto 155001,
# usa las variables r2_completo, rmse_completo, etc.
if (is.na(datos_155001_completo$MAPE[4]) && exists("mape_completo")) {
  datos_155001_completo$MAPE[4] <- mape_completo
}

if (is.na(datos_155001_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_155001_completo$RMSE[4] <- rmse_completo
}


# Ver los datos completos
print("Datos completos para el producto 155001:")
## [1] "Datos completos para el producto 155001:"
print(datos_155001_completo)
##   Producto           Modelo      MAPE        RMSE
## 1   155001      ARMA/SARIMA        NA          NA
## 2   155001 Regresión Lineal 14.496676    742.6409
## 3   155001    Random Forest  0.436948 434537.3986
## 4   155001          XGBoost  3.700244    252.7530
# Gráfico para MAPE
ggplot(datos_155001_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155001",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) 
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para RMSE
ggplot(datos_155001_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155001",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_155001_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.2 PRODUCTO 3929788

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3929788:")
## [1] "Datos actuales para el producto 3929788:"
print(metricas_comparativas %>% filter(Producto == "3929788"))
##   Producto           Modelo       MAPE         RMSE
## 1  3929788             ARMA 12.8370507 224023.24439
## 2  3929788 Regresión Lineal 22.3767801    218.86553
## 3  3929788    Random Forest  0.5359169     66.13281
## 4  3929788          XGBoost  3.7002444    252.75302
# Crear un dataframe manualmente con los 4 modelos para el producto 3929788
datos_3929788_completo <- data.frame(
  Producto = rep("3929788", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_3929788_completo <- left_join(
  datos_3929788_completo,
  metricas_comparativas %>% filter(Producto == "3929788"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3929788_completo$MAPE[2])) {
  datos_3929788_completo$MAPE[2] <- mape_3929788
}

if (is.na(datos_3929788_completo$RMSE[2])) {
  datos_3929788_completo$RMSE[2] <- rmse_3929788
}

# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3929788
if (is.na(datos_3929788_completo$MAPE[3]) && exists("mape_rf")) {
  datos_3929788_completo$MAPE[3] <- mape_rf
}

if (is.na(datos_3929788_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_3929788_completo$RMSE[3] <- rmse_rf
}

# Valores para XGBoost
if (is.na(datos_3929788_completo$MAPE[4]) && exists("mape_completo")) {
  datos_3929788_completo$MAPE[4] <- mape_completo
}


if (is.na(datos_3929788_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_3929788_completo$RMSE[4] <- rmse_completo
}

# Ver los datos completos
print("Datos completos para el producto 3929788:")
## [1] "Datos completos para el producto 3929788:"
print(datos_3929788_completo)
##   Producto           Modelo       MAPE      RMSE
## 1  3929788      ARMA/SARIMA         NA        NA
## 2  3929788 Regresión Lineal 22.3767801 218.86553
## 3  3929788    Random Forest  0.5359169  66.13281
## 4  3929788          XGBoost  3.7002444 252.75302
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")

# Gráfico para RMSE
ggplot(datos_3929788_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3929788",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3929788_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_3929788_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3929788",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3929788_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.3 PRODUCTO 3904152

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3904152:")
## [1] "Datos actuales para el producto 3904152:"
print(metricas_comparativas %>% filter(Producto == "3904152"))
##   Producto           Modelo       MAPE        RMSE
## 1  3904152             ARMA 15.3567919 156981.1976
## 2  3904152 Regresión Lineal  3.3434409    376.4854
## 3  3904152    Random Forest  0.2195905    483.3678
## 4  3904152          XGBoost  1.5994533    252.7530
# Crear un dataframe manualmente con los 4 modelos para el producto 3904152
datos_3904152_completo <- data.frame(
  Producto = rep("3904152", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_3904152_completo <- left_join(
  datos_3904152_completo,
  metricas_comparativas %>% filter(Producto == "3904152"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3904152_completo$MAPE[2])) {
  datos_3904152_completo$MAPE[2] <- mape_3904152
}
if (is.na(datos_3904152_completo$RMSE[2])) {
  datos_3904152_completo$RMSE[2] <- rmse_3904152
}

# Valores para Random Forest
if (is.na(datos_3904152_completo$MAPE[3]) && exists("mape_rf")) {
  datos_3904152_completo$MAPE[3] <- mape_rf
}

if (is.na(datos_3904152_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_3904152_completo$RMSE[3] <- rmse_rf
}


# Valores para XGBoost
if (is.na(datos_3904152_completo$MAPE[4]) && exists("mape_completo")) {
  datos_3904152_completo$MAPE[4] <- mape_completo
}


if (is.na(datos_3904152_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_3904152_completo$RMSE[4] <- rmse_completo
}


# Ver los datos completos
print("Datos completos para el producto 3904152:")
## [1] "Datos completos para el producto 3904152:"
print(datos_3904152_completo)
##   Producto           Modelo      MAPE     RMSE
## 1  3904152      ARMA/SARIMA        NA       NA
## 2  3904152 Regresión Lineal 3.3434409 376.4854
## 3  3904152    Random Forest 0.2195905 483.3678
## 4  3904152          XGBoost 1.5994533 252.7530
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")

# Gráfico para MSE
ggplot(datos_3904152_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3904152",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3904152_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_3904152_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3904152",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3904152_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.4 PRODUCTO 155002

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 155002:")
## [1] "Datos actuales para el producto 155002:"
print(metricas_comparativas %>% filter(Producto == "155002"))
##   Producto           Modelo      MAPE        RMSE
## 1   155002             ARMA 25.833028 192121.8307
## 2   155002 Regresión Lineal 19.918004    890.2337
## 3   155002          XGBoost  6.835722    417.9467
# Crear un dataframe manualmente con los 4 modelos para el producto 155002
datos_155002_completo <- data.frame(
  Producto = rep("155002", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_155002_completo <- left_join(
  datos_155002_completo,
  metricas_comparativas %>% filter(Producto == "155002"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_155002_completo$MAPE[2])) {
  datos_155002_completo$MAPE[2] <- mape_155002
}

if (is.na(datos_155002_completo$RMSE[2])) {
  datos_155002_completo$RMSE[2] <- rmse_155002
}



# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 155002
if (is.na(datos_155002_completo$MAPE[3]) && exists("mape_rf")) {
  datos_155002_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_155002_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_155002_completo$RMSE[3] <- rmse_rf
}

# Valores para XGBoost
if (is.na(datos_155002_completo$MAPE[4]) && exists("mape_completo")) {
  datos_155002_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_155002_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_155002_completo$RMSE[4] <- rmse_completo
}

# Ver los datos completos
print("Datos completos para el producto 155002:")
## [1] "Datos completos para el producto 155002:"
print(datos_155002_completo)
##   Producto           Modelo       MAPE     RMSE
## 1   155002      ARMA/SARIMA         NA       NA
## 2   155002 Regresión Lineal 19.9180037 890.2337
## 3   155002    Random Forest  0.2187988 454.4609
## 4   155002          XGBoost  6.8357217 417.9467
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")

# Gráfico para MSE
ggplot(datos_155002_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155002",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_155002_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_155002_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155002",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_155002_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.5 PRODUCTO 3678055

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3678055:")
## [1] "Datos actuales para el producto 3678055:"
print(metricas_comparativas %>% filter(Producto == "3678055"))
##   Producto           Modelo       MAPE        RMSE
## 1  3678055             ARMA 22.7377450 177040.4724
## 2  3678055 Regresión Lineal  2.9008020 212205.2519
## 3  3678055    Random Forest  0.5501504    424.4274
## 4  3678055    Random Forest  0.2187988    454.4609
## 5  3678055          XGBoost  1.1898882    283.8719
# Crear un dataframe manualmente con los 4 modelos para el producto 3678055
datos_3678055_completo <- data.frame(
  Producto = rep("3678055", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_3678055_completo <- left_join(
  datos_3678055_completo,
  metricas_comparativas %>% filter(Producto == "3678055"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3678055_completo$MAPE[2])) {
  datos_3678055_completo$MAPE[2] <- mape_3678055
}
if (is.na(datos_3678055_completo$RMSE[2])) {
  datos_3678055_completo$RMSE[2] <- rmse_3678055
}

# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3678055
if (is.na(datos_3678055_completo$MAPE[3]) && exists("mape_rf")) {
  datos_3678055_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_3678055_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_3678055_completo$RMSE[3] <- rmse_rf
}

# Valores para XGBoost
if (is.na(datos_3678055_completo$MAPE[4]) && exists("mape_completo")) {
  datos_3678055_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_3678055_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_3678055_completo$RMSE[4] <- rmse_completo
}

# Ver los datos completos
print("Datos completos para el producto 3678055:")
## [1] "Datos completos para el producto 3678055:"
print(datos_3678055_completo)
##   Producto           Modelo      MAPE        RMSE
## 1  3678055      ARMA/SARIMA        NA          NA
## 2  3678055 Regresión Lineal 2.9008020 212205.2519
## 3  3678055    Random Forest 0.5501504    424.4274
## 4  3678055    Random Forest 0.2187988    454.4609
## 5  3678055          XGBoost 1.1898882    283.8719
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")
# Gráfico para MSE
ggplot(datos_3678055_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3678055",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3678055_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_3678055_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3678055",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3678055_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

9 ESTIMACIÓN DE PRECIOS

9.0.1 Preparación de datos

# Función para preparar datos de un producto
prepare_price_data <- function(df, product_id) {
  product_data <- df %>%
    filter(ID_Inventario == product_id) %>%
    arrange(Trx_Fecha) %>%
    select(
      Trx_Fecha, Precio_Final_Unitario, Cant, Venta, 
      Costo_Venta, Descuento_Porcentaje, Semana, Mes
    ) %>%
    mutate(
      Dia_Semana = wday(Trx_Fecha),
      Mes_Num = month(Trx_Fecha),
      Anio = year(Trx_Fecha),
      Dias_Desde_Inicio = as.numeric(difftime(Trx_Fecha, min(Trx_Fecha), units = "days")),
      Margen_Unitario = (Venta / Cant) - (Costo_Venta / Cant),
      Precio_Unitario_Calc = Venta / Cant,
      ID_Inventario = product_id
    )
  
  return(product_data)
}

# Asegúrate de que 'datos' sea tu data.frame cargado correctamente
# Por ejemplo, si vienes de un archivo .csv:
# datos <- read.csv("archivo.csv")

# Aplicar la función a todos los productos
ids <- unique(datos$ID_Inventario)

productos_preparados <- map_df(ids, function(id) {
  prepare_price_data(datos, id)
})

# Mostrar una parte del resultado
head(productos_preparados)
## # A tibble: 6 × 15
##   Trx_Fecha           Precio_Final_Unitario  Cant Venta Costo_Venta
##   <dttm>                              <dbl> <dbl> <dbl>       <dbl>
## 1 2023-01-02 00:00:00                   980     1   980        727.
## 2 2023-01-03 00:00:00                   728     1   728        905.
## 3 2023-01-03 00:00:00                   840     6  5040       3598.
## 4 2023-01-04 00:00:00                  1120     1  1120        577.
## 5 2023-01-04 00:00:00                   728     8  5824       6619.
## 6 2023-01-04 00:00:00                   980    10  9800       7273.
## # ℹ 10 more variables: Descuento_Porcentaje <dbl>, Semana <dbl>, Mes <dbl>,
## #   Dia_Semana <dbl>, Mes_Num <dbl>, Anio <dbl>, Dias_Desde_Inicio <dbl>,
## #   Margen_Unitario <dbl>, Precio_Unitario_Calc <dbl>, ID_Inventario <dbl>
# Vector con productos (debe ir primero)
productos_ids <- top_ids

# Función para entrenar modelo ARMA por producto
train_arma_model <- function(data, product_id) {
  library(forecast)  # Asegúrate de cargar forecast si no está cargado aún
  product_data <- data %>% filter(ID_Inventario == product_id)
  serie_ts <- ts(product_data$Venta, frequency = 12)
  modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
  return(modelo_arma)
}

# Crear lista de modelos ARMA por producto
modelos_arma_lista <- setNames(
  lapply(productos_ids, function(id) train_arma_model(datos, id)),
  as.character(productos_ids)
)

# Función para modelo regresión lineal
train_reg_model <- function(data, product_id) {
  product_data <- data %>% filter(ID_Inventario == product_id)
  modelo_reg <- lm(Venta ~ Precio_Final_Unitario, data = product_data)
  return(modelo_reg)
}

# Función para modelo Random Forest
train_rf_model <- function(data, product_id) {
  product_data <- data %>% filter(ID_Inventario == product_id)
  predictors <- c("Precio_Final_Unitario", "Cant", "Descuento_Porcentaje")
  rf_data <- product_data %>% select(all_of(predictors), Venta)
  modelo_rf <- randomForest(Venta ~ ., data = rf_data, ntree = 100)
  return(modelo_rf)
}

# Función para modelo XGBoost
train_xgb_model <- function(data, product_id) {
  product_data <- data %>% filter(ID_Inventario == product_id)
  predictors <- c("Precio_Final_Unitario", "Cant", "Descuento_Porcentaje")
  train_matrix <- xgb.DMatrix(data = as.matrix(product_data[, predictors]), label = product_data$Venta)
  params <- list(objective = "reg:squarederror")
  modelo_xgb <- xgb.train(params = params, data = train_matrix, nrounds = 50, verbose = 0)
  return(modelo_xgb)
}

# Crear listas de modelos
modelos_reg_lista <- setNames(lapply(productos_ids, function(id) train_reg_model(datos, id)), as.character(productos_ids))
modelos_rf_lista <- setNames(lapply(productos_ids, function(id) train_rf_model(datos, id)), as.character(productos_ids))
modelos_xgb_lista <- setNames(lapply(productos_ids, function(id) train_xgb_model(datos, id)), as.character(productos_ids))

9.0.2 Entrenar modelos de predicción de precios

# Función para entrenar modelos de predicción de precios
train_price_models <- function(data, product_id, test_size = 0.2) {
  price_data <- prepare_price_data(data, product_id) %>%
    drop_na() %>%
    select(
      Precio_Final_Unitario,
      Cant, Costo_Venta, Descuento_Porcentaje,
      Dia_Semana, Mes_Num, Anio, Dias_Desde_Inicio,
      Margen_Unitario
    )

  # Evitar fallos si hay muy pocos datos
  if (nrow(price_data) < 10) {
    warning(paste("Producto", product_id, "tiene menos de 10 registros. Se omite."))
    return(NULL)
  }

  set.seed(123)
  train_index <- createDataPartition(price_data$Precio_Final_Unitario, p = 1 - test_size, list = FALSE)
  train_data <- price_data[train_index, ]
  test_data <- price_data[-train_index, ]

  # 1. Regresión Lineal
  lm_model <- lm(Precio_Final_Unitario ~ ., data = train_data)

  # 2. Random Forest
  rf_model <- randomForest(
    Precio_Final_Unitario ~ .,
    data = train_data,
    ntree = 500,
    importance = TRUE
  )

  # 3. XGBoost
  features <- setdiff(names(train_data), "Precio_Final_Unitario")
  x_train <- as.matrix(train_data[, features])
  y_train <- train_data$Precio_Final_Unitario
  x_test <- as.matrix(test_data[, features])
  y_test <- test_data$Precio_Final_Unitario
  dtrain <- xgb.DMatrix(data = x_train, label = y_train)
  dtest <- xgb.DMatrix(data = x_test, label = y_test)

  xgb_params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = 0.1,
    max_depth = 6,
    min_child_weight = 3,
    subsample = 0.8,
    colsample_bytree = 0.8
  )

  xgb_model <- xgb.train(
    params = xgb_params,
    data = dtrain,
    nrounds = 100,
    watchlist = list(train = dtrain, test = dtest),
    early_stopping_rounds = 10,
    verbose = 0
  )

  # Evaluación
  lm_pred <- predict(lm_model, newdata = test_data)
  rf_pred <- predict(rf_model, newdata = test_data)
  xgb_pred <- predict(xgb_model, x_test)

  lm_rmse <- sqrt(mean((lm_pred - test_data$Precio_Final_Unitario)^2))
  rf_rmse <- sqrt(mean((rf_pred - test_data$Precio_Final_Unitario)^2))
  xgb_rmse <- sqrt(mean((xgb_pred - test_data$Precio_Final_Unitario)^2))

  lm_r2 <- 1 - sum((test_data$Precio_Final_Unitario - lm_pred)^2) /
    sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
  rf_r2 <- 1 - sum((test_data$Precio_Final_Unitario - rf_pred)^2) /
    sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
  xgb_r2 <- 1 - sum((test_data$Precio_Final_Unitario - xgb_pred)^2) /
    sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)

  metrics <- data.frame(
    Model = c("Linear Regression", "Random Forest", "XGBoost"),
    RMSE = c(lm_rmse, rf_rmse, xgb_rmse),
    R2 = c(lm_r2, rf_r2, xgb_r2)
  )

  return(list(metrics = metrics))
}

# IDs de los 5 productos a modelar
productos_ids <- c(155001, 3929788, 3904152, 155002, 3678055)

# Aplicar modelo a cada producto
resultados_modelos <- map(productos_ids, function(id) {
  resultado <- train_price_models(datos, product_id = id)
  if (!is.null(resultado)) {
    resultado$metrics %>% mutate(ID_Inventario = id)
  } else {
    NULL
  }
}) %>% compact() %>% bind_rows()

# Mostrar resultados
resultados_modelos
##                Model        RMSE        R2 ID_Inventario
## 1  Linear Regression  30.2314808 0.9301839        155001
## 2      Random Forest  20.3499737 0.9683653        155001
## 3            XGBoost  10.8625683 0.9909863        155001
## 4  Linear Regression   0.9961090 0.9745183       3929788
## 5      Random Forest   0.7463236 0.9856956       3929788
## 6            XGBoost   0.3634018 0.9966085       3929788
## 7  Linear Regression  63.2610620 0.8980785       3904152
## 8      Random Forest  11.9625578 0.9963555       3904152
## 9            XGBoost  11.4022456 0.9966889       3904152
## 10 Linear Regression  25.7977520 0.9226069        155002
## 11     Random Forest  10.0274489 0.9883072        155002
## 12           XGBoost   4.9295488 0.9971741        155002
## 13 Linear Regression 110.6053375 0.8924438       3678055
## 14     Random Forest  31.4628228 0.9912968       3678055
## 15           XGBoost  19.9533667 0.9964996       3678055
# Lista con los IDs de productos (puedes usar top_ids que ya definiste)
productos_ids <- top_ids

# Entrenar modelos para cada producto y guardar en lista
modelos_precio_lista <- setNames(
  lapply(productos_ids, function(id) train_price_models(datos, id)),
  as.character(productos_ids)
)

9.0.3 Estimar precios óptimos

estimate_optimal_prices <- function(data, product_id, price_models, demand_models = NULL, future_dates = NULL) {
  price_steps <- 20

  # Selección del mejor modelo de precio
  best_price_model_idx <- which.max(price_models$metrics$R2)
  best_price_model_name <- price_models$metrics$Model[best_price_model_idx]

  # Datos del producto
  product_data <- data %>% filter(ID_Inventario == product_id)

  # Rango de precios restringido a percentiles 5% - 95% y limitado a 1.5x la mediana
  min_price <- quantile(product_data$Precio_Final_Unitario, 0.05, na.rm = TRUE)
  max_price <- quantile(product_data$Precio_Final_Unitario, 0.95, na.rm = TRUE)
  price_range <- seq(min_price, max_price, length.out = price_steps)
  price_range <- pmin(price_range, 1.5 * median(product_data$Precio_Final_Unitario, na.rm = TRUE))

  # Inicializar escenarios futuros
  future_scenarios <- data.frame()

  for (future_date in future_dates) {
    future_date <- as.Date(future_date)
    mes_actual <- lubridate::month(future_date)

    mes_data <- product_data %>% filter(lubridate::month(Trx_Fecha) == mes_actual)
    if (nrow(mes_data) < 5) mes_data <- product_data

    costo_mes <- median(mes_data$Costo_Venta, na.rm = TRUE)
    cant_mes <- median(mes_data$Cant, na.rm = TRUE)
    desc_mes <- median(mes_data$Descuento_Porcentaje, na.rm = TRUE)

    if (is.na(costo_mes)) costo_mes <- median(product_data$Costo_Venta, na.rm = TRUE)
    if (is.na(cant_mes) || cant_mes == 0) cant_mes <- median(product_data$Cant, na.rm = TRUE)
    if (is.na(desc_mes)) desc_mes <- median(product_data$Descuento_Porcentaje, na.rm = TRUE)

    date_df <- data.frame(
      Trx_Fecha = rep(future_date, price_steps),
      Precio_Final_Unitario = price_range,
      Cant = cant_mes,
      Costo_Venta = costo_mes,
      Descuento_Porcentaje = desc_mes,
      Dia_Semana = lubridate::wday(future_date),
      Mes_Num = mes_actual,
      Anio = lubridate::year(future_date),
      Dias_Desde_Inicio = as.numeric(difftime(future_date, min(product_data$Trx_Fecha), units = "days")),
      Margen_Unitario = NA
    )

    future_scenarios <- rbind(future_scenarios, date_df)
  }

  # Calcular margen unitario simulado
  future_scenarios$Margen_Unitario <- future_scenarios$Precio_Final_Unitario -
    (future_scenarios$Costo_Venta / future_scenarios$Cant)

  # Estimar elasticidad histórica (con mínimo 15 puntos válidos)
  product_data <- product_data %>% arrange(Trx_Fecha)
  elasticity_df <- product_data %>%
    filter(!is.na(Cant) & !is.na(Precio_Final_Unitario)) %>%
    mutate(
      P_lag = lag(Precio_Final_Unitario),
      Q_lag = lag(Cant),
      dP = Precio_Final_Unitario - P_lag,
      dQ = Cant - Q_lag,
      elasticity_point = (dQ / Q_lag) / (dP / P_lag)
    ) %>%
    filter(!is.na(elasticity_point), is.finite(elasticity_point))

  elasticity <- median(elasticity_df$elasticity_point, na.rm = TRUE)
  if (nrow(elasticity_df) < 15 || is.na(elasticity) || !is.finite(elasticity)) {
    elasticity <- 1
  }

  # Estimar ventas y márgenes
  results <- future_scenarios %>%
    mutate(Venta_Esperada = 0, Margen_Total = 0)

  # Precio base sin descuentos (más robusto)
  baseline_price <- median(product_data %>% filter(Descuento_Porcentaje == 0) %>%
                             pull(Precio_Final_Unitario), na.rm = TRUE)
  if (is.na(baseline_price)) {
    baseline_price <- median(product_data$Precio_Final_Unitario, na.rm = TRUE)
  }

  for (i in 1:nrow(results)) {
    price_ratio <- baseline_price / results$Precio_Final_Unitario[i]
    adjusted_quantity <- results$Cant[i] * (price_ratio ^ elasticity)
    results$Venta_Esperada[i] <- results$Precio_Final_Unitario[i] * adjusted_quantity
    results$Margen_Total[i] <- adjusted_quantity * results$Margen_Unitario[i]
  }

  # Seleccionar precios óptimos (por fecha)
  optimal_prices <- results %>%
    group_by(Trx_Fecha) %>%
    slice_max(Venta_Esperada, n = 1) %>%
    select(Trx_Fecha, Precio_Optimal = Precio_Final_Unitario, Venta_Esperada, Margen_Total)

  # Validación de precios extremos
  precio_median <- median(product_data$Precio_Final_Unitario, na.rm = TRUE)
  if (any(optimal_prices$Precio_Optimal > 2 * precio_median)) {
    warning(paste(" Precio óptimo muy alto detectado para producto", product_id))
  }

  return(list(
    resultados = results,
    precios_optimos = optimal_prices,
    elasticidad = elasticity
  ))
}

9.0.4 Visualizar resultados

# Fechas futuras para simulación
dates_future <- seq(as.Date("2025-01-01"), by = "month", length.out = 6)

# Lista para guardar resultados por producto
precios_optimos_lista <- list()

# Iterar por productos
for (pid in productos_ids) {
  cat("PRODUCTO:", pid, "\n")
  
  modelo_precio <- modelos_precio_lista[[as.character(pid)]]
  
  if (!is.null(modelo_precio)) {
    resultado <- estimate_optimal_prices(
      data = datos,
      product_id = pid,
      price_models = modelo_precio,
      future_dates = dates_future
    )
    
    precios_optimos_lista[[as.character(pid)]] <- resultado
    
    cat("Elasticidad estimada:", round(resultado$elasticidad, 2), "\n\n")
    
    # Mostrar tabla manualmente fila por fila
    print(resultado$precios_optimos)
    
  } else {
    cat("No hay modelo de precios para el producto", pid, "\n")
  }
}
## PRODUCTO: 155001 
## Elasticidad estimada: -2.91 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01            630          3397.         606.
## 2 2025-02-01            630          3397.         637.
## 3 2025-03-01            630          3397.         733.
## 4 2025-04-01            630          3397.        1004.
## 5 2025-05-01            630          3397.        1094.
## 6 2025-06-01            630          3397.         911.
## PRODUCTO: 3929788 
## Elasticidad estimada: -3.1 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           51.6           162.         74.9
## 2 2025-02-01           51.6           130.         53.7
## 3 2025-03-01           51.6           130.         42.0
## 4 2025-04-01           51.6           130.         53.6
## 5 2025-05-01           51.6           130.         42.2
## 6 2025-06-01           51.6           130.         42.6
## PRODUCTO: 3904152 
## Elasticidad estimada: 0 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           3556           3556        1089.
## 2 2025-02-01           3556           3556        1088.
## 3 2025-03-01           3556           3556        1092.
## 4 2025-04-01           3556           3556        1092.
## 5 2025-05-01           3556           3556        1092.
## 6 2025-06-01           3556           3556        1113.
## PRODUCTO: 155002 
## Elasticidad estimada: -4.19 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           594.          6619.        1630.
## 2 2025-02-01           594.          4413.        -391.
## 3 2025-03-01           594.          6619.        1740.
## 4 2025-04-01           594.          6619.        2980.
## 5 2025-05-01           594.          6619.        2825.
## 6 2025-06-01           594.          6619.        2436.
## PRODUCTO: 3678055 
## Elasticidad estimada: 0 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           5908           5908        1794.
## 2 2025-02-01           5908           5908        1722.
## 3 2025-03-01           5908           5908        1695.
## 4 2025-04-01           5908           5908        1841.
## 5 2025-05-01           5908           5908        1807.
## 6 2025-06-01           5908           5908        1983.

9.0.5 Integración de precios óptimos y modelos

integrate_with_existing_models <- function(data, product_id, price_opt_results, xgb_model) {
  optimal_prices <- price_opt_results[[as.character(product_id)]]$precios_optimos
  
  if (is.null(optimal_prices) || nrow(optimal_prices) == 0) {
    warning(paste("No se encontraron precios óptimos para el producto", product_id))
    return(data.frame())
  }
  
  hist_data <- data %>%
    filter(ID_Inventario == product_id) %>%
    arrange(Trx_Fecha)
  
  future_features <- data.frame()
  
  for (i in 1:nrow(optimal_prices)) {
    future_date <- optimal_prices$Trx_Fecha[i]
    future_price <- optimal_prices$Precio_Optimal[i]
    
    mes_data <- hist_data %>%
      filter(lubridate::month(Trx_Fecha) == lubridate::month(future_date))
    
    if (nrow(mes_data) < 5) mes_data <- hist_data
    
    avg_features <- mes_data %>%
      summarise(
        Cant = median(Cant, na.rm = TRUE),
        Costo_Venta = median(Costo_Venta, na.rm = TRUE),
        Costo_Devolucion = median(Costo_Devolucion, na.rm = TRUE),
        Precio_Lista_Unitario = median(Precio_Lista_Unitario, na.rm = TRUE),
        Descuento_Porcentaje = median(Descuento_Porcentaje, na.rm = TRUE),
        Tiempo = as.numeric(difftime(future_date, min(hist_data$Trx_Fecha), units = "days")) / 30
      )
    
    # Variables de tendencia:
    avg_features$Precio_Final_Unitario <- future_price
    avg_features$Trx_Fecha <- future_date
    avg_features$Mes_Num <- lubridate::month(future_date)
    avg_features$Anio <- lubridate::year(future_date)
    avg_features$Mes_Desde_Inicio <- as.numeric(difftime(future_date, min(hist_data$Trx_Fecha), units = "days")) %/% 30
    
    future_features <- rbind(future_features, avg_features)
  }
  
  future_data <- data.frame(
    Fecha = future_features$Trx_Fecha,
    Precio_Final_Unitario = future_features$Precio_Final_Unitario
  )
  
  # === PREDICCIÓN CON XGBoost ===
  tryCatch({
    features <- xgb_model$feature_names
    if (is.null(features)) {
      features <- setdiff(names(future_features), "Venta")
    }
    xgb_matrix <- as.matrix(future_features[, features, drop = FALSE])
    future_data$Venta_XGBoost <- predict(xgb_model, xgb_matrix)
  }, error = function(e) {
    warning(paste("Error al predecir con XGBoost para producto", product_id, ":", e$message))
    future_data$Venta_XGBoost <- NA
  })
  
  # === MÉTRICAS ===
  avg_cost_per_unit <- median(hist_data$Costo_Venta / hist_data$Cant, na.rm = TRUE)
  
  future_data$Unidades_XGBoost <- future_data$Venta_XGBoost / future_data$Precio_Final_Unitario
  future_data$Costo_XGBoost <- future_data$Unidades_XGBoost * avg_cost_per_unit
  future_data$Margen_XGBoost <- future_data$Venta_XGBoost - future_data$Costo_XGBoost
  
  return(future_data)
}

9.0.6 Pipeline correcto

corregir_formato_fechas <- function(datos) {
  if ("Trx_Fecha" %in% colnames(datos)) {
    datos$Trx_Fecha_Original <- datos$Trx_Fecha

    if (is.character(datos$Trx_Fecha) &&
        any(grepl("^\\d{7}-\\d{2}-\\d{2}$", datos$Trx_Fecha))) {

      cat("Corrigiendo formato de fechas extraño...\n")

      datos$Trx_Fecha <- sapply(datos$Trx_Fecha, function(fecha) {
        if (is.na(fecha) || !is.character(fecha)) return(NA)

        partes <- strsplit(fecha, "-")[[1]]
        if (length(partes) == 3) {
          fecha_corregida <- paste("2023", partes[2], partes[3], sep = "-")
          return(fecha_corregida)
        } else {
          return(NA)
        }
      })

      datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)
      cat("Fechas corregidas exitosamente.\n")
    } else if (!inherits(datos$Trx_Fecha, "Date")) {
      cat("Intentando convertir fechas a formato Date...\n")
      datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)
    }
  }
  return(datos)
}

# Aplicar la corrección a tu dataframe antes de usarlo
datos_filtrados <- corregir_formato_fechas(datos_filtrados)
## Intentando convertir fechas a formato Date...
dates_future <- seq.Date(as.Date("2023-01-01"), by = "month", length.out = 6)
precios_optimos_lista <- list()

for (id in productos_ids) {
  cat("Estimando precios óptimos para producto:", id, "\n")

  modelo_precio <- modelos_precio_lista[[as.character(id)]]

  if (!is.null(modelo_precio)) {
    precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
      data = datos_filtrados,
      product_id = id,
      price_models = modelo_precio,
      future_dates = dates_future
    )
  }
}
## Estimando precios óptimos para producto: 155001 
## Estimando precios óptimos para producto: 3929788 
## Estimando precios óptimos para producto: 3904152 
## Estimando precios óptimos para producto: 155002 
## Estimando precios óptimos para producto: 3678055
for (id in names(precios_optimos_lista)) {
  df_optimo <- precios_optimos_lista[[id]]$precios_optimos

  if (!inherits(df_optimo$Trx_Fecha, "Date")) {
    df_optimo$Trx_Fecha <- as.Date(df_optimo$Trx_Fecha)
  }

  cat(paste0("\n### Producto: ", id, "\n"))

  print(
    ggplot(df_optimo, aes(x = Trx_Fecha, y = Precio_Optimal)) +
      geom_line(color = "#1f77b4", linewidth = 1.2) +
      geom_point(color = "#1f77b4", size = 2) +
      labs(
        title = paste("Precio Óptimo por Mes - Producto", id),
        x = "Fecha",
        y = "Precio Óptimo"
      ) +
      scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
      theme_minimal(base_size = 12) +
      theme(
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(angle = 45, hjust = 1)
      )
  )
}
## 
## ### Producto: 155001

## 
## ### Producto: 3929788

## 
## ### Producto: 3904152

## 
## ### Producto: 155002

## 
## ### Producto: 3678055

run_price_optimization <- function(data, product_ids, future_dates = NULL, modelos_precio_lista = NULL) {
  if (is.null(future_dates)) {
    future_dates <- seq.Date(Sys.Date(), by = "month", length.out = 6)
  }

  precios_optimos_lista <- list()

  for (id in product_ids) {
    cat("Estimando precios óptimos para producto:", id, "\n")

    price_model <- NULL
    if (!is.null(modelos_precio_lista)) {
      price_model <- modelos_precio_lista[[as.character(id)]]
    }

    precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
      data = data,
      product_id = id,
      price_models = price_model,
      future_dates = future_dates
    )
  }

  return(precios_optimos_lista)
}
# Función principal que integra todo el pipeline con solo XGBoost
run_complete_analysis <- function(data, top_ids, modelos_xgb, modelos_precio_lista = NULL) {
  # 1. Estimar precios óptimos
  all_results <- run_price_optimization(data, top_ids, modelos_precio_lista = modelos_precio_lista)

  # 2. Integrar con modelo XGBoost
  integrated_results <- list()

  for (i in seq_along(top_ids)) {
    pid <- top_ids[i]
    pid_str <- as.character(pid)

    xgb_model <- if (length(modelos_xgb) >= i) modelos_xgb[[i]] else NULL

    future_predictions <- integrate_with_existing_models(
      data = data,
      product_id = pid,
      price_opt_results = all_results,
      xgb_model = xgb_model
    )

    integrated_results[[pid_str]] <- future_predictions

    if (nrow(future_predictions) > 0) {
      p_sales <- ggplot(future_predictions, aes(x = Fecha, y = Venta_XGBoost)) +
        geom_line(color = "#1f77b4", linewidth = 1.2) +
        geom_point(size = 2) +
        labs(
          title = paste("Predicciones de ventas con precios óptimos - Producto", pid),
          x = "Fecha",
          y = "Ventas estimadas ($)"
        ) +
        theme_minimal()

      p_margins <- ggplot(future_predictions, aes(x = Fecha, y = Margen_XGBoost)) +
        geom_col(fill = "steelblue", width = 15) +
        geom_text(aes(label = round(Margen_XGBoost, 0)), vjust = -0.5, size = 3.5) +
        labs(
          title = paste("Margen esperado con precios óptimos - Producto", pid),
          x = "Fecha",
          y = "Margen estimado ($)"
        ) +
        theme_minimal()

      all_results[[pid_str]]$integrated_plots <- list(
        sales = p_sales,
        margins = p_margins
      )
    }
  }

  # 3. Visualización de precios óptimos
  all_optimal_prices <- data.frame()

  for (pid in top_ids) {
    pid_str <- as.character(pid)
    if (pid_str %in% names(all_results)) {
      opt_prices <- all_results[[pid_str]]$precios_optimos %>%
        mutate(ID_Inventario = pid)

      all_optimal_prices <- rbind(all_optimal_prices, opt_prices)
    }
  }

  p_comparison <- ggplot(all_optimal_prices,
                         aes(x = Trx_Fecha, y = Precio_Optimal, color = factor(ID_Inventario))) +
    geom_line(size = 1.2) +
    geom_point(size = 3) +
    labs(
      title = "Comparación de Precios Óptimos por Producto",
      x = "Fecha",
      y = "Precio Óptimo",
      color = "ID Producto"
    ) +
    theme_minimal()

  # 4. Métricas finales
  metricas_optimas <- data.frame()

  for (pid in top_ids) {
    pid_str <- as.character(pid)
    if (pid_str %in% names(integrated_results)) {
      pred_data <- integrated_results[[pid_str]]

      if ("Margen_XGBoost" %in% names(pred_data)) {
        metrics_row <- data.frame(
          ID_Inventario = pid,
          Precio_Promedio = mean(pred_data$Precio_Final_Unitario, na.rm = TRUE),
          Venta_Total = sum(pred_data$Venta_XGBoost, na.rm = TRUE),
          Margen_Total = sum(pred_data$Margen_XGBoost, na.rm = TRUE),
          Margen_Porcentual = 100 * sum(pred_data$Margen_XGBoost, na.rm = TRUE) /
            sum(pred_data$Venta_XGBoost, na.rm = TRUE)
        )
        metricas_optimas <- rbind(metricas_optimas, metrics_row)
      }
    }
  }

  return(list(
    resultados = all_results,
    integracion = integrated_results,
    precios_optimos = all_optimal_prices,
    metricas_optimas = metricas_optimas,
    grafico_comparativo = p_comparison
  ))
}
resultado_completo <- run_complete_analysis(
  data = datos,
  top_ids = productos_ids,
  modelos_xgb = modelos_xgb_lista,
  modelos_precio_lista = modelos_precio_lista
)
## Estimando precios óptimos para producto: 155001 
## Estimando precios óptimos para producto: 3929788 
## Estimando precios óptimos para producto: 3904152 
## Estimando precios óptimos para producto: 155002 
## Estimando precios óptimos para producto: 3678055

9.0.7 Gráfico comparativo de precios óptimos por producto:

# Mostrar métricas si estás en modo interactivo
if (interactive()) View(resultado_completo$metricas_optimas)

cat("Gráfico comparativo de precios óptimos por producto:\n")
## Gráfico comparativo de precios óptimos por producto:
print(resultado_completo$grafico_comparativo)

10 Predicción de ventas con precios optimos por producto

cat("Gráficos individuales por producto:\n")
## Gráficos individuales por producto:
for (pid in names(resultado_completo$resultados)) {
  plots <- resultado_completo$resultados[[pid]]$integrated_plots
  if (!is.null(plots)) {
    cat(paste0("## Producto: ", pid, "\n\n"))
    print(plots$sales)   # solo usa Venta_XGBoost internamente
    print(plots$margins) # solo usa Margen_XGBoost internamente
    
    cat("\n---\n\n")
  }
}
## ## Producto: 155001

## 
## ---
## 
## ## Producto: 3929788

## 
## ---
## 
## ## Producto: 3904152

## 
## ---
## 
## ## Producto: 155002

## 
## ---
## 
## ## Producto: 3678055

## 
## ---

10.0.1 TABLA FINAL

tabla_final <- data.frame()

for (pid in productos_ids) {
  pred <- resultado_completo$integracion[[as.character(pid)]]
  real <- datos_filtrados %>% filter(ID_Inventario == pid)

  if (!is.null(pred) && nrow(pred) > 0) {
    precio_promedio <- mean(real$Precio_Final_Unitario, na.rm = TRUE)
    margen_historico_promedio <- mean(real$Venta - real$Costo_Venta, na.rm = TRUE)

    # Extraer mes de las fechas futuras simuladas
    pred$Mes_Simulado <- lubridate::month(pred$Fecha)

    # Calcular venta promedio histórica por mes
    venta_historica_por_mes <- real %>%
      mutate(Mes = lubridate::month(Trx_Fecha)) %>%
      group_by(Mes) %>%
      summarise(Venta_Historica_Promedio = mean(Venta, na.rm = TRUE), .groups = "drop")

    # Agregar columna de venta esperada histórica al df de predicción
    pred <- pred %>%
      left_join(venta_historica_por_mes, by = c("Mes_Simulado" = "Mes"))

    tabla_producto <- pred %>%
      select(Fecha, Precio_Final_Unitario, Venta_XGBoost, Margen_XGBoost, Venta_Historica_Promedio) %>%
      rename(
        Precio_Optimo = Precio_Final_Unitario,
        Venta_Esperada_XGBoost = Venta_XGBoost,
        Margen_XGBoost = Margen_XGBoost,
        Venta_Esperada_Historica = Venta_Historica_Promedio
      ) %>%
      mutate(
        ID_Inventario = pid,
        Precio_Promedio_Historico = round(precio_promedio, 2),
        Margen_Historico_Promedio = round(margen_historico_promedio, 2)
      ) %>%
      select(ID_Inventario, Fecha, Precio_Promedio_Historico, Precio_Optimo,
             Venta_Esperada_Historica, Venta_Esperada_XGBoost,
             Margen_Historico_Promedio, Margen_XGBoost)

    tabla_final <- bind_rows(tabla_final, tabla_producto)
  }
}

print(tabla_final)
##    ID_Inventario      Fecha Precio_Promedio_Historico Precio_Optimo
## 1         155001 2025-06-05                    467.28      630.0000
## 2         155001 2025-07-05                    467.28      630.0000
## 3         155001 2025-08-05                    467.28      630.0000
## 4         155001 2025-09-05                    467.28      630.0000
## 5         155001 2025-10-05                    467.28      630.0000
## 6         155001 2025-11-05                    467.28      630.0000
## 7        3929788 2025-06-05                     40.83       51.5872
## 8        3929788 2025-07-05                     40.83       51.5872
## 9        3929788 2025-08-05                     40.83       51.5872
## 10       3929788 2025-09-05                     40.83       51.5872
## 11       3929788 2025-10-05                     40.83       51.5872
## 12       3929788 2025-11-05                     40.83       51.5872
## 13       3904152 2025-06-05                   3157.60     3556.0000
## 14       3904152 2025-07-05                   3157.60     3556.0000
## 15       3904152 2025-08-05                   3157.60     3556.0000
## 16       3904152 2025-09-05                   3157.60     3556.0000
## 17       3904152 2025-10-05                   3157.60     3556.0000
## 18       3904152 2025-11-05                   3157.60     3556.0000
## 19        155002 2025-06-05                    458.03      593.6000
## 20        155002 2025-07-05                    458.03      593.6000
## 21        155002 2025-08-05                    458.03      593.6000
## 22        155002 2025-09-05                    458.03      593.6000
## 23        155002 2025-10-05                    458.03      593.6000
## 24        155002 2025-11-05                    458.03      593.6000
## 25       3678055 2025-06-05                   5316.65     5908.0000
## 26       3678055 2025-07-05                   5316.65     5908.0000
## 27       3678055 2025-08-05                   5316.65     5908.0000
## 28       3678055 2025-09-05                   5316.65     5908.0000
## 29       3678055 2025-10-05                   5316.65     5908.0000
## 30       3678055 2025-11-05                   5316.65     5908.0000
##    Venta_Esperada_Historica Venta_Esperada_XGBoost Margen_Historico_Promedio
## 1                  2773.289               1253.110                    354.20
## 2                  3824.150               1255.131                    354.20
## 3                  3309.438               1793.815                    354.20
## 4                  2906.493               1255.131                    354.20
## 5                  2853.639               1255.131                    354.20
## 6                  2933.349               1255.131                    354.20
## 7                  1658.104               1044.346                    358.30
## 8                  1796.298               1044.346                    358.30
## 9                  1715.712               1044.775                    358.30
## 10                 1568.125               1044.346                    358.30
## 11                 1647.976               1274.590                    358.30
## 12                 1481.254               1044.346                    358.30
## 13                 7427.639               3551.571                   1770.29
## 14                 6116.994               3554.225                   1770.29
## 15                 7902.450               3558.141                   1770.29
## 16                 7601.677               7108.436                   1770.29
## 17                 7653.351               3538.695                   1770.29
## 18                10330.334               3558.141                   1770.29
## 19                 3240.199               1703.100                    281.58
## 20                 3341.957               1703.100                    281.58
## 21                 2821.299               1703.100                    281.58
## 22                 2747.125               1099.524                    281.58
## 23                 2730.078               1099.524                    281.58
## 24                 2855.958               1099.524                    281.58
## 25                10800.705               5894.670                   2438.95
## 26                10692.688               5894.670                   2438.95
## 27                10281.788               5894.670                   2438.95
## 28                10671.964               5924.096                   2438.95
## 29                 9284.032               5915.545                   2438.95
## 30                10459.696               5897.285                   2438.95
##    Margen_XGBoost
## 1        551.2630
## 2        552.1520
## 3        789.1277
## 4        552.1520
## 5        552.1520
## 6        552.1520
## 7        431.6053
## 8        431.6053
## 9        431.7824
## 10       431.6053
## 11       526.7599
## 12       431.6053
## 13      1212.4252
## 14      1213.3312
## 15      1214.6681
## 16      2426.6575
## 17      1208.0296
## 18      1214.6681
## 19       669.6821
## 20       669.6821
## 21       669.6821
## 22       432.3477
## 23       432.3477
## 24       432.3477
## 25      1991.6554
## 26      1991.6554
## 27      1991.6554
## 28      2001.5978
## 29      1998.7085
## 30      1992.5389
str(df_ecdf)
## tibble [14,311 × 2] (S3: tbl_df/tbl/data.frame)
##  $ Precio_Final_Unitario: num [1:14311] 594 532 532 532 594 ...
##  $ Producto             : chr [1:14311] "155001" "155001" "155001" "155001" ...
summary(df_ecdf)
##  Precio_Final_Unitario   Producto        
##  Min.   : 336.0        Length:14311      
##  1st Qu.: 392.0        Class :character  
##  Median : 448.0        Mode  :character  
##  Mean   : 463.6                          
##  3rd Qu.: 504.0                          
##  Max.   :2095.8
LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogYnJvd247Jz5FVklERU5DSUEgTk9WRU08L3NwYW4+Ig0KYXV0aG9yOiAiRXF1aXBvIDQiDQpkYXRlOiAiMjAyNC0wMy0wNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUgDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQplZGl0b3JfczogDQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+ICBMaWJyZXJpYXMgPC9zcGFuPiANCg0KDQpgYGB7cn0NCiMgTGlicmVyw61hcyBuZWNlc2FyaWFzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkoa25pdHIpDQojaW5zdGFsbC5wYWNrYWdlcygia2FibGVFeHRyYSIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGdncGxvdDIpDQojaW5zdGFsbC5wYWNrYWdlcygiaWdyYXBoIikNCmxpYnJhcnkoaWdyYXBoKQ0KI2luc3RhbGwucGFja2FnZXMoImZvcmVjYXN0IikNCiNpbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKQ0KbGlicmFyeShmb3JlY2FzdCkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KI2luc3RhbGwucGFja2FnZXMoImdnY29ycnBsb3QiKQ0KbGlicmFyeShnZ2NvcnJwbG90KQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoY2FyKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQojaW5zdGFsbC5wYWNrYWdlcygieGdib29zdCIpDQpsaWJyYXJ5KHhnYm9vc3QpDQojaW5zdGFsbC5wYWNrYWdlcygicGF0Y2h3b3JrIikNCmxpYnJhcnkocGF0Y2h3b3JrKQ0KYGBgDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgQ2FyZ2EgZGUgZGF0b3MgPC9zcGFuPiANCg0KYGBge3J9DQojIENhcmdhciBhcmNoaXZvIEV4Y2VsIGRlc2RlIHJ1dGEgbG9jYWwNCnJ1dGEgPC0gIkM6XFxVc2Vyc1xcYWxtYWlcXERvd25sb2Fkc1xcZmlsdGVyZWRfZGF0YS54bHN4Ig0KDQpkYXRvcyA8LSByZWFkX2V4Y2VsKHJ1dGEpDQoNCiMgVmlzdGEgZ2VuZXJhDQpoZWFkKGRhdG9zKQ0Kc3RyKGRhdG9zKQ0KYGBgDQoNCmBgYHtyfQ0KIyBPYnRlbmVyIGxvcyA1IHByb2R1Y3RvcyBtw6FzIHZlbmRpZG9zIChwb3IgdmFsb3IpDQp0b3BfaWRzIDwtIGRhdG9zICU+JQ0KICBncm91cF9ieShJRF9JbnZlbnRhcmlvKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhc19Ub3RhbGVzID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShkZXNjKFZlbnRhc19Ub3RhbGVzKSkgJT4lDQogIHNsaWNlX2hlYWQobiA9IDUpICU+JQ0KICBwdWxsKElEX0ludmVudGFyaW8pDQoNCnByaW50KCJUb3AgNSBwcm9kdWN0b3MgbcOhcyB2ZW5kaWRvcyAoSURfSW52ZW50YXJpbyk6IikNCnByaW50KHRvcF9pZHMpDQoNCmBgYA0KDQpgYGB7cn0NCiMgRmlsdHJhciBkYXRvcyB2w6FsaWRvcw0KZGF0b3NfZmlsdHJhZG9zIDwtIGRhdG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyAlaW4lIHRvcF9pZHMpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFByZWNpb19GaW5hbF9Vbml0YXJpbykpDQoNCiMgQ29udGFyIG9ic2VydmFjaW9uZXMgcG9yIHByb2R1Y3RvDQpjb250ZW8gPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBjb3VudChJRF9JbnZlbnRhcmlvLCBzb3J0ID0gVFJVRSkNCg0KcHJpbnQoIk7Dum1lcm8gZGUgcmVnaXN0cm9zIHBvciBwcm9kdWN0byBlbiBkYXRvc19maWx0cmFkb3M6IikNCnByaW50KGNvbnRlbykNCg0KIyBWZXJpZmljYSBzaSBoYXkgc3VmaWNpZW50ZXMgZGF0b3MNCmlmIChucm93KGRhdG9zX2ZpbHRyYWRvcykgPT0gMCkgew0KICBzdG9wKCJObyBoYXkgZGF0b3Mgc3VmaWNpZW50ZXMgbHVlZ28gZGUgZmlsdHJhciBwb3IgdG9wX2lkcyB5IHByZWNpb3MgdsOhbGlkb3MuIikNCn0NCg0KYGBgDQoNCmBgYHtyfQ0KIyBDb21iaW5hY2lvbmVzIGRlIHBhcmVzDQpwcm9kdWN0b3MgPC0gdW5pcXVlKGRhdG9zX2ZpbHRyYWRvcyRJRF9JbnZlbnRhcmlvKQ0KcGFyZXNfcHJvZHVjdG9zIDwtIGNvbWJuKHByb2R1Y3RvcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkNCg0KIyBJbmljaWFsaXphciByZXN1bHRhZG9zDQpyZXN1bHRhZG9zX2tzIDwtIG1hcF9kZihwYXJlc19wcm9kdWN0b3MsIGZ1bmN0aW9uKHBhcikgew0KICBwcm9kMSA8LSBwYXJbMV0NCiAgcHJvZDIgPC0gcGFyWzJdDQogIA0KICBwcmVjaW9zMSA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZDEpICU+JQ0KICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQ0KICANCiAgcHJlY2lvczIgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QyKSAlPiUNCiAgICBwdWxsKFByZWNpb19GaW5hbF9Vbml0YXJpbykNCiAgDQogIHByaW50KHBhc3RlKCJDb21wYXJhbmRvIHByb2R1Y3RvcyIsIHByb2QxLCAidnMiLCBwcm9kMikpDQogIHByaW50KHBhc3RlKCJDYW50aWRhZCBkZSBwcmVjaW9zOiIsIGxlbmd0aChwcmVjaW9zMSksICJ5IiwgbGVuZ3RoKHByZWNpb3MyKSkpDQogIA0KICBpZiAobGVuZ3RoKHByZWNpb3MxKSA+PSA1ICYgbGVuZ3RoKHByZWNpb3MyKSA+PSA1KSB7DQogICAgcHJ1ZWJhIDwtIHN1cHByZXNzV2FybmluZ3Moa3MudGVzdChwcmVjaW9zMSwgcHJlY2lvczIpKQ0KICAgIGRhdGEuZnJhbWUoDQogICAgICBQcm9kdWN0b18xID0gcHJvZDEsDQogICAgICBQcm9kdWN0b18yID0gcHJvZDIsDQogICAgICBEID0gcm91bmQocHJ1ZWJhJHN0YXRpc3RpYywgNCksDQogICAgICBwX3ZhbHVlID0gcm91bmQocHJ1ZWJhJHAudmFsdWUsIDQpLA0KICAgICAgQ29uY2x1c2lvbiA9IGlmZWxzZShwcnVlYmEkcC52YWx1ZSA+IDAuMDUsICJEaXN0cmlidWNpb25lcyBzaW1pbGFyZXMiLCAiRGlzdHJpYnVjaW9uZXMgZGlmZXJlbnRlcyIpDQogICAgKQ0KICB9IGVsc2Ugew0KICAgIGRhdGEuZnJhbWUoDQogICAgICBQcm9kdWN0b18xID0gcHJvZDEsDQogICAgICBQcm9kdWN0b18yID0gcHJvZDIsDQogICAgICBEID0gTkEsDQogICAgICBwX3ZhbHVlID0gTkEsDQogICAgICBDb25jbHVzaW9uID0gIkRhdG9zIGluc3VmaWNpZW50ZXMiDQogICAgKQ0KICB9DQp9KQ0KDQpwcmludCgiUmVzdWx0YWRvcyBkZSBsYSBwcnVlYmEgS1M6IikNCnByaW50KHJlc3VsdGFkb3Nfa3MpDQoNCmBgYA0KDQpgYGB7cn0NCnRyeShkZXYub2ZmKCksIHNpbGVudCA9IFRSVUUpDQpgYGANCg0KDQpgYGB7cn0NCg0KIyBGaWx0cmFyIGxvcyBwcm9kdWN0b3MNCmRmXzE1NTAwMSA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMSkgJT4lDQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQ0KICBtdXRhdGUoUHJvZHVjdG8gPSAiMTU1MDAxIikNCg0KZGZfMTU1MDAyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUNCiAgc2VsZWN0KFByZWNpb19GaW5hbF9Vbml0YXJpbykgJT4lDQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDIiKQ0KDQojIFVuaXIgZW4gdW4gc29sbyBkYXRhZnJhbWUNCmRmX2VjZGYgPC0gYmluZF9yb3dzKGRmXzE1NTAwMSwgZGZfMTU1MDAyKQ0KDQojIEdyYWZpY2FyIEVDREYNCmdncGxvdChkZl9lY2RmLCBhZXMoeCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgY29sb3IgPSBQcm9kdWN0bykpICsNCiAgc3RhdF9lY2RmKGdlb20gPSAic3RlcCIsIHNpemUgPSAxKSArDQogIGxhYnModGl0bGUgPSAiRUNERiBkZSBQcmVjaW8gRmluYWwgVW5pdGFyaW86IFByb2R1Y3RvcyAxNTUwMDEgdnMgMTU1MDAyIiwNCiAgICAgICB4ID0gIlByZWNpbyBGaW5hbCBVbml0YXJpbyIsDQogICAgICAgeSA9ICJGdW5jacOzbiBkZSBEaXN0cmlidWNpw7NuIEFjdW11bGFkYSAoRUNERikiLA0KICAgICAgIGNvbG9yID0gIlByb2R1Y3RvIikgKw0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KQ0KDQpgYGANCg0KDQo8IS0tIEFSTUEgLS0+DQojIEFSTUEgDQoNCiMgUFJFRElDQ0lPTkVTIERFIFZFTlRBUw0KDQoNCjwhLS0gUFJPRFVDVE8gMTU1MDAxIC0tPg0KIyMgUFJPRFVDVE8gMTU1MDAxDQoNCmBgYHtyIGFybWEtMTU1MDAxfQ0KIyBQcm9kdWN0byAxNTUwMDENCmlkX3Byb2QgPC0gMTU1MDAxDQoNCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwNCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBpZF9wcm9kKSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUNCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQ0KICBzdW1tYXJpc2UoVmVudGEgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSkpICU+JQ0KICBhcnJhbmdlKEZlY2hhKQ0KDQpzZXJpZV90cyA8LSB0cyh2ZW50YXNfbWVuc3VhbGVzJFZlbnRhLCBmcmVxdWVuY3kgPSAxMiwNCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQ0KDQojIE1vZGVsbyBBUk1BDQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpDQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobW9kZWxvX2FybWEsIGggPSAzKQ0KDQojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbw0KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvKSArDQogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksDQogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KZml0dGVkX3ZhbHVlcyA8LSBmaXR0ZWQobW9kZWxvX2FybWEpDQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMA0Kcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpDQoNCg0KIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMNCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gaWRfcHJvZCwNCiAgTW9kZWxvID0gIkFSTUEiLA0KICBNQVBFID0gbWFwZSwNCiAgUk1TRSA9IHJtc2UNCikpDQoNCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8NCnRhaWwobWV0cmljYXNfY29tcGFyYXRpdmFzLCAxKSAlPiUNCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSBwYXN0ZSgiTcOpdHJpY2FzIGRlbCBtb2RlbG8gQVJNQSBwYXJhIFByb2R1Y3RvIiwgaWRfcHJvZCkpICU+JQ0KICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQoNCjwhLS0gUFJPRFVDVE8gMzkyOTc4OCAtLT4NCiMjIFBST0RVQ1RPIDM5Mjk3ODgNCg0KYGBge3IgYXJtYS0zOTI5Nzg4fQ0KIyBQcm9kdWN0byAzOTI5Nzg4DQoNCmlkX3Byb2QgPC0gMzkyOTc4OA0KDQojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsDQp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lDQogIGdyb3VwX2J5KEZlY2hhKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShGZWNoYSkNCg0Kc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsDQogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBtb250aChtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpKSkNCg0KIyBNb2RlbG8gQVJNQQ0KbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykNCg0KIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28NCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJWZW50YXMgKCQpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMNCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQ0KbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDANCm1zZSA8LSBtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpDQoNCiMgQ3JlYXIgdGFibGEgZGUgbcOpdHJpY2FzDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9IGlkX3Byb2QsDQogIE1vZGVsbyA9ICJBUk1BIiwNCiAgTUFQRSA9IG1hcGUsDQogIFJNU0UgID0gcm1zZQ0KKSkNCg0KIyBNb3N0cmFyIHRhYmxhIHBhcmEgZXN0ZSBwcm9kdWN0bw0KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQ0KICBrbml0cjo6a2FibGUoY2FwdGlvbiA9IHBhc3RlKCJNw6l0cmljYXMgZGVsIG1vZGVsbyBBUk1BIHBhcmEgUHJvZHVjdG8iLCBpZF9wcm9kKSkgJT4lDQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNCg0KPCEtLSBQUk9EVUNUTyAzOTA0MTUyIC0tPg0KIyMgUFJPRFVDVE8gMzkwNDE1Mg0KDQpgYGB7ciBhcm1hLTM5MDQxNTJ9DQojIFByb2R1Y3RvIDM5MDQxNTINCmlkX3Byb2QgPC0gMzkwNDE1Mg0KDQojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsDQp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lDQogIGdyb3VwX2J5KEZlY2hhKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShGZWNoYSkNCg0Kc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsDQogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBtb250aChtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpKSkNCg0KIyBNb2RlbG8gQVJNQQ0KbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykNCg0KIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28NCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJWZW50YXMgKCQpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMNCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQ0KbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDANCnJtc2UgPC0gc3FydChtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpKQ0KDQoNCiMgQ3JlYXIgdGFibGEgZGUgbcOpdHJpY2FzDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9IGlkX3Byb2QsDQogIE1vZGVsbyA9ICJBUk1BIiwNCiAgTUFQRSA9IG1hcGUsDQogIFJNU0UgPSBybXNlDQopKQ0KDQojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvDQp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUNCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KDQo8IS0tIFBST0RVQ1RPIDE1NTAwMiAtLT4NCiMjIFBST0RVQ1RPIDE1NTAwMg0KDQpgYGB7ciBhcm1hLTE1NTAwMn0NCiMgUHJvZHVjdG8gMTU1MDAyDQppZF9wcm9kIDwtIDE1NTAwMg0KDQojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsDQp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lDQogIGdyb3VwX2J5KEZlY2hhKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShGZWNoYSkNCg0Kc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsDQogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBtb250aChtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpKSkNCg0KIyBNb2RlbG8gQVJNQQ0KbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykNCg0KIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28NCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJWZW50YXMgKCQpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMNCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQ0KbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDANCnJtc2UgPC0gc3FydChtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpKQ0KDQoNCiMgQ3JlYXIgdGFibGEgZGUgbcOpdHJpY2FzDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9IGlkX3Byb2QsDQogIE1vZGVsbyA9ICJBUk1BIiwNCiAgTUFQRSA9IG1hcGUsDQogIFJNU0UgPSBybXNlDQopKQ0KDQojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvDQp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUNCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KDQo8IS0tUHJvZHVjdG8gMzY3ODA1NSAtLT4NCiMjIFBST0RVQ1RPIDM2NzgwNTUNCmBgYHtyIGFybWEtMzY3ODA1NX0NCiMgUHJvZHVjdG8gMzY3ODA1NQ0KaWRfcHJvZCA8LSAzNjc4MDU1DQoNCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwNCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBpZF9wcm9kKSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUNCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQ0KICBzdW1tYXJpc2UoVmVudGEgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSkpICU+JQ0KICBhcnJhbmdlKEZlY2hhKQ0KDQpzZXJpZV90cyA8LSB0cyh2ZW50YXNfbWVuc3VhbGVzJFZlbnRhLCBmcmVxdWVuY3kgPSAxMiwNCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQ0KDQojIE1vZGVsbyBBUk1BDQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpDQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobW9kZWxvX2FybWEsIGggPSAzKQ0KDQojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbw0KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvKSArDQogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksDQogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KZml0dGVkX3ZhbHVlcyA8LSBmaXR0ZWQobW9kZWxvX2FybWEpDQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMA0Kcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpDQoNCiMgQ3JlYXIgdGFibGEgZGUgbcOpdHJpY2FzDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9IGlkX3Byb2QsDQogIE1vZGVsbyA9ICJBUk1BIiwNCiAgTUFQRSA9IG1hcGUsDQogIFJNU0UgPSBybXNlDQopKQ0KDQojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvDQp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUNCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KDQojIFJFR1JFU0lPTiBMSU5FQUwNCiMjIE1BUEEgREUgQ0FMT1INCg0KYGBge3IgbWFwYV9jYWxvcl9jb3JyZWxhY2lvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVmFyaWFibGVzIG51bcOpcmljYXMgcmVsZXZhbnRlcw0KdmFyc19udW1lcmljYXMgPC0gYygiQ2FudCIsICJWZW50YSIsICJDb3N0b19WZW50YSIsDQogICAgICAgICAgICAgICAgICAgICJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQ0KDQojIFByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3MNCmRhdG9zX2NvciA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIHNlbGVjdChhbGxfb2YodmFyc19udW1lcmljYXMpKSAlPiUNCiAgbmEub21pdCgpDQoNCiMgR2VuZXJhciBsYSBtYXRyaXogZGUgY29ycmVsYWNpw7NuDQptYXRyaXpfY29yIDwtIGNvcihkYXRvc19jb3IpDQoNCiMgQWp1c3RlIGRlbCBncsOhZmljbyBzaW4gbWFyDQpnZ2NvcnJwbG90KG1hdHJpel9jb3IsDQogICAgICAgICAgIG1ldGhvZCA9ICJzcXVhcmUiLA0KICAgICAgICAgICB0eXBlID0gInVwcGVyIiwNCiAgICAgICAgICAgbGFiID0gVFJVRSwgDQogICAgICAgICAgIGxhYl9zaXplID0gMiwgICAgICAgICAgICAgICAgICAgIyBNZWpvciB0YW1hw7FvIGRlIGxvcyBjb2VmaWNpZW50ZXMNCiAgICAgICAgICAgdGwuY2V4ID0gMTAsICAgICAgICAgICAgICAgICAgICAjIFRhbWHDsW8gZGUgZXRpcXVldGFzIG3DoXMgZ3JhbmRlDQogICAgICAgICAgIHRsLnNydCA9IDQ1LCAgICAgICAgICAgICAgICAgICAgIyBSb3RhY2nDs24gZGUgNDXCsCBkZSBldGlxdWV0YXMNCiAgICAgICAgICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IiksDQogICAgICAgICAgIHRpdGxlID0gIk1hcGEgZGUgQ29ycmVsYWNpw7NuIC0gVmFyaWFibGVzIE51bcOpcmljYXMiLA0KICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNCkgKw0KICAgICAgICAgICAgIHRoZW1lKA0KICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAxKSkNCikNCmBgYA0KDQojIyBQUk9EVUNUTyAxNTUwMDENCmBgYHtyfQ0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxDQpkYXRvc18xNTUwMDEgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQ0KICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLA0KICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUNCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BDQoNCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzE1NTAwMSA8LSBkYXRvc18xNTUwMDEgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgICAjIEFzZWfDunJhdGUgZGUgcXVlIGxhIGZlY2hhIGVzdMOpIGVuIGZvcm1hdG8gRGF0ZQ0KICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpDQoNCiMgVmVyaWZpY2FyIGxhcyBwcmltZXJhcyBmaWxhcyBwYXJhIGFzZWd1cmFybm9zIGRlIHF1ZSBsYSB2YXJpYWJsZSBkZSB0aWVtcG8gZXN0w6kgYmllbiBjcmVhZGENCmhlYWQoZGF0b3NfMTU1MDAxKQ0KDQpgYGANCg0KYGBge3J9DQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsDQptb2RlbG9fcmVncmVzaW9uXzE1NTAwMSA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdG9zXzE1NTAwMSkNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18xNTUwMDEpDQoNCiMgUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8NCnByZWRpY2Npb25lc18xNTUwMDEgPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zXzE1NTAwMSkNCg0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpDQptYXBlXzE1NTAwMSA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMSkgLyBkYXRvc18xNTUwMDEkVmVudGEpKSAqIDEwMA0KDQoNCiMgQ2FsY3VsYXIgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IpDQpybXNlXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDEpXjIpKQ0KDQoNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAxOiAiLCBtYXBlXzE1NTAwMSwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAxOiAiLCBybXNlXzE1NTAwMSwgIlxuIikNCg0KIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbw0KcGFyKG1mcm93ID0gYygyLCAyKSkNCnBsb3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEpDQpgYGANCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQ0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjE1NTAwMSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLA0KICBNQVBFID0gbWFwZV8xNTUwMDEsDQogIFJNU0UgPSBybXNlXzE1NTAwMQ0KDQopKQ0KYGBgDQoNCg0KIyMgUFJPRFVDVE8gMzkyOTc4OA0KDQpgYGB7cn0NCiMgRmlsdHJhciBzb2xvIGxvcyBkYXRvcyBwYXJhIGVsIHByb2R1Y3RvIDM5Mjk3ODgNCmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTI5Nzg4KSAlPiUNCiAgc2VsZWN0KFZlbnRhLCBDYW50LCBDb3N0b19WZW50YSwNCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lDQogIG5hLm9taXQoKSAgIyBFbGltaW5hciBmaWxhcyBjb24gdmFsb3JlcyBOQQ0KDQojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhDQpkYXRvc18zOTI5Nzg4IDwtIGRhdG9zXzM5Mjk3ODggJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgICAjIEFzZWfDunJhdGUgZGUgcXVlIGxhIGZlY2hhIGVzdMOpIGVuIGZvcm1hdG8gRGF0ZQ0KICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpDQoNCiMgVmVyaWZpY2FyIGxhcyBwcmltZXJhcyBmaWxhcyBwYXJhIGFzZWd1cmFybm9zIGRlIHF1ZSBsYSB2YXJpYWJsZSBkZSB0aWVtcG8gZXN0w6kgYmllbiBjcmVhZGENCmhlYWQoZGF0b3NfMzkyOTc4OCkNCmBgYA0KDQpgYGB7cn0NCiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwNCm1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18zOTI5Nzg4KQ0KDQojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8NCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4KQ0KYGBgDQoNCmBgYHtyfQ0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbw0KcHJlZGljY2lvbmVzXzM5Mjk3ODggPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODgsIG5ld2RhdGEgPSBkYXRvc18zOTI5Nzg4KQ0KDQojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikNCiMgQcOxYWRpbW9zIHByb3RlY2Npw7NuIGNvbnRyYSBkaXZpc2nDs24gcG9yIGNlcm8NCm1hcGVfMzkyOTc4OCA8LSBtZWFuKGFicygoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4KSAvIHBtYXgoZGF0b3NfMzkyOTc4OCRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQojIENhbGN1bGFyIE1TRSAoTWVhbiBTcXVhcmVkIEVycm9yKQ0Kcm1zZV8zOTI5Nzg4IDwtIHNxcnQobWVhbigoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4KV4yKSkNCg0KDQojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcw0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTI5Nzg4OiAiLCBtYXBlXzM5Mjk3ODgsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM5Mjk3ODg6ICIsIHJtc2VfMzkyOTc4OCwgIlxuIikNCg0KIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbw0KcGFyKG1mcm93ID0gYygyLCAyKSkNCnBsb3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4KQ0KYGBgDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwNCiAgTUFQRSA9IG1hcGVfMzkyOTc4OCwNCiAgUk1TRSA9IHJtc2VfMzkyOTc4OA0KKSkNCmBgYA0KDQoNCiMjIFBST0RVQ1RPIDM5MDQxNTINCg0KYGBge3J9DQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTA0MTUyDQpkYXRvc18zOTA0MTUyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMzkwNDE1MikgJT4lDQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsDQogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQ0KICBuYS5vbWl0KCkgICMgRWxpbWluYXIgZmlsYXMgY29uIHZhbG9yZXMgTkENCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM5MDQxNTIgPC0gZGF0b3NfMzkwNDE1MiAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18zOTA0MTUyKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyIDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzkwNDE1MikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikNCmBgYA0KDQpgYGB7cn0NCiMgUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8NCnByZWRpY2Npb25lc18zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfMzkwNDE1MikNCg0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpDQojIEHDsWFkaW1vcyBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvDQptYXBlXzM5MDQxNTIgPC0gbWVhbihhYnMoKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MikgLyBwbWF4KGRhdG9zXzM5MDQxNTIkVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MileMikpDQoNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkwNDE1MjogIiwgbWFwZV8zOTA0MTUyLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyOiAiLCBybXNlXzM5MDQxNTIsICJcbiIpDQoNCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8NCnBhcihtZnJvdyA9IGMoMiwgMikpDQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIzOTA0MTUyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCIsDQogIE1BUEUgPSBtYXBlXzM5MDQxNTIsDQogIFJNU0UgPSBybXNlXzM5MDQxNTINCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAxNTUwMDINCg0KYGBge3J9DQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDINCmRhdG9zXzE1NTAwMiA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMikgJT4lDQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsDQogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQ0KICBuYS5vbWl0KCkgICMgRWxpbWluYXIgZmlsYXMgY29uIHZhbG9yZXMgTkENCg0KIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQ0KZGF0b3NfMTU1MDAyIDwtIGRhdG9zXzE1NTAwMiAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18xNTUwMDIpDQpgYGANCg0KDQpgYGB7cn0NCiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwNCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyIDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAyKQ0KDQojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8NCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIpDQpgYGANCg0KYGBge3J9DQojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvDQpwcmVkaWNjaW9uZXNfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpDQoNCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQ0KIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2Vybw0KbWFwZV8xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMiRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDIpIC8gcG1heChkYXRvc18xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfMTU1MDAyIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMileMikpDQoNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAyOiAiLCBtYXBlXzE1NTAwMiwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAyOiAiLCBybXNlXzE1NTAwMiwgIlxuIikNCg0KIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbw0KcGFyKG1mcm93ID0gYygyLCAyKSkNCnBsb3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIpDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIxNTUwMDIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwNCiAgTUFQRSA9IG1hcGVfMTU1MDAyLA0KICBSTVNFID0gcm1zZV8xNTUwMDINCiAgKSkNCmBgYA0KDQoNCiMjIFBST0RVQ1RPIDM2NzgwNTUNCmBgYHtyfQ0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQ0KZGF0b3NfMzY3ODA1NSA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQ0KICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLA0KICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUNCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BDQoNCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfMzY3ODA1NSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18zNjc4MDU1KQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzY3ODA1NSkNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkNCmBgYA0KDQpgYGB7cn0NCiNQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbw0KcHJlZGljY2lvbmVzXzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUsIG5ld2RhdGEgPSBkYXRvc18zNjc4MDU1KQ0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpDQojIEHDsWFkaW1vcyBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvDQptYXBlXzM2NzgwNTUgPC0gbWVhbihhYnMoKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NSkgLyBwbWF4KGRhdG9zXzM2NzgwNTUkVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCg0Kcm1zZV8zNjc4MDU1IDwtIG1lYW4oKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NSleMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzY3ODA1NTogIiwgbWFwZV8zNjc4MDU1LCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1OiAiLCBybXNlXzM2NzgwNTUsICJcbiIpDQoNCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8NCnBhcihtZnJvdyA9IGMoMiwgMikpDQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM2NzgwNTUiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwNCiAgTUFQRSA9IG1hcGVfMzY3ODA1NSwNCiAgUk1TRSA9IHJtc2VfMzY3ODA1NQ0KKSkNCmBgYA0KDQojIyBBTkFMSVNJUyBERSBWQVJJQUJMRVMgSU1QT1JUQU5URVMNCmBgYHtyfQ0KIyBGdW5jacOzbiBzaW1wbGlmaWNhZGEgcGFyYSBhbmFsaXphciBjb2VmaWNpZW50ZXMNCmFuYWxpemFyX2NvZWZpY2llbnRlcyA8LSBmdW5jdGlvbihtb2RlbG8sIG5vbWJyZV9wcm9kdWN0bykgew0KICByZXN1bWVuIDwtIHN1bW1hcnkobW9kZWxvKQ0KICBjb2VmX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzdW1lbiRjb2VmZmljaWVudHMpDQogIGNvbG5hbWVzKGNvZWZfZGYpIDwtIGMoIkVzdGltYXRlIiwgIlN0ZC5FcnJvciIsICJ0LnZhbHVlIiwgInAudmFsdWUiKQ0KICBjb2VmX2RmJFZhcmlhYmxlIDwtIHJvd25hbWVzKGNvZWZfZGYpDQogIGNvZWZfZGYkUHJvZHVjdG8gPC0gbm9tYnJlX3Byb2R1Y3RvDQogIGNvZWZfZGYkU2lnbmlmaWNhdGl2byA8LSBpZmVsc2UoY29lZl9kZiRwLnZhbHVlIDwgMC4wNSwgIlPDrSIsICJObyIpDQogIA0KICByZXR1cm4oY29lZl9kZiAlPiUNCiAgICAgICAgICAgc2VsZWN0KFByb2R1Y3RvLCBWYXJpYWJsZSwgRXN0aW1hdGUsIHAudmFsdWUsIFNpZ25pZmljYXRpdm8pICU+JQ0KICAgICAgICAgICBhcnJhbmdlKGRlc2MoYWJzKEVzdGltYXRlKSkpKQ0KfQ0KDQojIEFwbGljYXIgYSBjYWRhIG1vZGVsbw0KY29lZl8xNTUwMDEgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMTU1MDAxLCAiMTU1MDAxIikNCmNvZWZfMTU1MDAyIDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzE1NTAwMiwgIjE1NTAwMiIpDQpjb2VmXzM2NzgwNTUgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSwgIjM2NzgwNTUiKQ0KY29lZl8zOTA0MTUyIDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM5MDQxNTIsICIzOTA0MTUyIikNCmNvZWZfMzkyOTc4OCA8LSBhbmFsaXphcl9jb2VmaWNpZW50ZXMobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4LCAiMzkyOTc4OCIpDQoNCiMgQ29tYmluYXIgdG9kb3MgbG9zIGNvZWZpY2llbnRlcw0KdG9kb3NfY29lZmljaWVudGVzIDwtIGJpbmRfcm93cyhjb2VmXzE1NTAwMSwgY29lZl8xNTUwMDIsIGNvZWZfMzY3ODA1NSwgY29lZl8zOTA0MTUyLCBjb2VmXzM5Mjk3ODgpDQoNCiMgVGFibGEgY29uIHZhcmlhYmxlcyBpbXBvcnRhbnRlcyBpbmNsdXllbmRvIHNpZ25pZmljYW5jaWENCnZhcmlhYmxlc19pbXBvcnRhbnRlcyA8LSB0b2Rvc19jb2VmaWNpZW50ZXMgJT4lDQogIGZpbHRlcihWYXJpYWJsZSAhPSAiKEludGVyY2VwdCkiKSAlPiUNCiAgZ3JvdXBfYnkoUHJvZHVjdG8pICU+JQ0KICBhcnJhbmdlKFByb2R1Y3RvLCBkZXNjKGFicyhFc3RpbWF0ZSkpKSAlPiUNCiAgbXV0YXRlKEltcGFjdG8gPSBpZmVsc2UoRXN0aW1hdGUgPiAwLCAiUG9zaXRpdm8iLCAiTmVnYXRpdm8iKSkNCg0KIyBUYWJsYSBjb21wbGV0YSBjb24gdG9kYXMgbGFzIHZhcmlhYmxlcyBpbXBvcnRhbnRlcw0Ka2FibGUodmFyaWFibGVzX2ltcG9ydGFudGVzICU+JSANCiAgICAgICAgc2VsZWN0KFByb2R1Y3RvLCBWYXJpYWJsZSwgRXN0aW1hdGUsIHAudmFsdWUsIFNpZ25pZmljYXRpdm8sIEltcGFjdG8pLA0KICAgICAgY2FwdGlvbiA9ICJWYXJpYWJsZXMgaW1wb3J0YW50ZXMgcG9yIHByb2R1Y3RvIiwNCiAgICAgIGNvbC5uYW1lcyA9IGMoIlByb2R1Y3RvIiwgIlZhcmlhYmxlIiwgIkNvZWZpY2llbnRlIiwgInAtdmFsdWUiLCAiU2lnbmlmaWNhdGl2byIsICJJbXBhY3RvIiksDQogICAgICBkaWdpdHMgPSBjKDAsIDAsIDQsIDQsIDAsIDApKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpDQoNCiMgVGFibGEgcmVzdW1lbiBjb24gdG9wIDMgcG9yIHByb2R1Y3RvDQp0b3BfcG9yX3Byb2R1Y3RvIDwtIHZhcmlhYmxlc19pbXBvcnRhbnRlcyAlPiUNCiAgZ3JvdXBfYnkoUHJvZHVjdG8pICU+JQ0KICBzbGljZV9oZWFkKG4gPSAzKSAlPiUNCiAgc2VsZWN0KFByb2R1Y3RvLCBWYXJpYWJsZSwgRXN0aW1hdGUsIHAudmFsdWUsIFNpZ25pZmljYXRpdm8sIEltcGFjdG8pDQoNCmthYmxlKHRvcF9wb3JfcHJvZHVjdG8sDQogICAgICBjYXB0aW9uID0gIlRvcCAzIHZhcmlhYmxlcyBtw6FzIGltcG9ydGFudGVzIHBvciBwcm9kdWN0byIsDQogICAgICBjb2wubmFtZXMgPSBjKCJQcm9kdWN0byIsICJWYXJpYWJsZSIsICJDb2VmaWNpZW50ZSIsICJwLXZhbHVlIiwgIlNpZ25pZmljYXRpdm8iLCAiSW1wYWN0byIpLA0KICAgICAgZGlnaXRzID0gYygwLCAwLCA0LCA0LCAwLCAwKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKQ0KYGBgDQoNCg0KIyBSQU5ET00gRk9SRVNUDQoNCg0KIyMgUFJPRFVDVE8gMTU1MDAxDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0NCiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpDQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMTU1MDAxICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQptb2RlbG9fcmZfMTU1MDAxIDwtIHJhbmRvbUZvcmVzdCgNCiAgVmVudGEgfiAuLCANCiAgZGF0YSA9IGRhdG9zX21vZGVsbywNCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcw0KICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsbykgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0DQogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KKQ0KDQojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8NCnByaW50KG1vZGVsb19yZl8xNTUwMDEpDQoNCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMNCnByZWRpY2Npb25lc19yZiA8LSBwcmVkaWN0KG1vZGVsb19yZl8xNTUwMDEsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzDQojIE1BUEUNCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQojIFJNU0UNCnJtc2VfcmYgPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKQ0KDQojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcw0KY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMVxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuXG4iKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzE1NTAwMSkNCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpDQoNCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQp2YXJJbXBQbG90KG1vZGVsb19yZl8xNTUwMDEsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMTU1MDAxIikNCg0KIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYNCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMSIsDQogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsDQogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgTlVFVk9TIEFOw4FMSVNJUyBBw5FBRElET1MNCg0KIyBBbsOhbGlzaXMgZGVsIGVycm9yDQplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDEiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzDQpjYXQoIkVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzOlxuIikNCmNhdCgiTWVkaWEgZGUgZXJyb3JlczoiLCBtZWFuKGVycm9yZXMpLCAiXG4iKQ0KY2F0KCJEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgZXJyb3JlczoiLCBzZChlcnJvcmVzKSwgIlxuIikNCmNhdCgiTcOtbmltbzoiLCBtaW4oZXJyb3JlcyksICJcbiIpDQpjYXQoIk3DoXhpbW86IiwgbWF4KGVycm9yZXMpLCAiXG4iKQ0KY2F0KCJNZWRpYW5hOiIsIG1lZGlhbihlcnJvcmVzKSwgIlxuIikNCg0KIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24NCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAxIiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIxNTUwMDEiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLA0KICBNQVBFID0gbWFwZV9yZiwNCiAgUk1TRSA9IHJtc2VfcmYNCikpDQpgYGANCg0KIyMgUFJPRFVDVE8gMzkyOTc4OA0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9DQojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhDQpkYXRvc18zOTI5Nzg4IDwtIGRhdG9zXzM5Mjk3ODggJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMNCg0KIyBNb3N0cmFyIHVuIHJlc3VtZW4gZGUgbG9zIGRhdG9zDQpzdW1tYXJ5KGRhdG9zXzM5Mjk3ODgpDQoNCiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpDQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzkyOTc4OCAlPiUNCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkNCg0KIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0DQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZA0KbW9kZWxvX3JmXzM5Mjk3ODggPC0gcmFuZG9tRm9yZXN0KA0KICBWZW50YSB+IC4sIA0KICBkYXRhID0gZGF0b3NfbW9kZWxvLA0KICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzDQogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQNCiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQopDQoNCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbw0KcHJpbnQobW9kZWxvX3JmXzM5Mjk3ODgpDQoNCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMNCnByZWRpY2Npb25lc19yZiA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTI5Nzg4LCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBNQVBFDQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBSTVNFDQoNCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQ0KDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzkyOTc4OFxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuXG4iKQ0KDQoNCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmltcG9ydGFuY2lhX3ZhcnMgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMzkyOTc4OCkNCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpDQoNCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTI5Nzg4LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5Mjk3ODgiKQ0KDQojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMNCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgNCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLA0KICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZg0KKQ0KDQpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsDQogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgQW7DoWxpc2lzIGRlbCBlcnJvcg0KZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8NCmhpc3QoZXJyb3JlcywgDQogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgYnJlYWtzID0gMzApDQoNCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuDQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5Mjk3ODgiLA0KICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsDQogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsDQogIE1BUEUgPSBtYXBlX3JmLA0KICBSTVNFID0gcm1zZV9yZg0KDQopKQ0KYGBgDQoNCg0KIyMgUFJPRFVDVE8gMzkwNDE1Mg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9DQojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhDQpkYXRvc18zOTA0MTUyIDwtIGRhdG9zXzM5MDQxNTIgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMNCg0KIyBNb3N0cmFyIHVuIHJlc3VtZW4gZGUgbG9zIGRhdG9zDQpzdW1tYXJ5KGRhdG9zXzM5MDQxNTIpDQoNCiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpDQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzkwNDE1MiAlPiUNCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkNCg0KIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0DQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZA0KbW9kZWxvX3JmXzM5MDQxNTIgPC0gcmFuZG9tRm9yZXN0KA0KICBWZW50YSB+IC4sIA0KICBkYXRhID0gZGF0b3NfbW9kZWxvLA0KICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzDQogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQNCiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQopDQoNCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbw0KcHJpbnQobW9kZWxvX3JmXzM5MDQxNTIpDQoNCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMNCnByZWRpY2Npb25lc19yZiA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBNQVBFDQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBSTVNFDQpybXNlX3JmIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSkNCg0KDQojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcw0KY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDM5MDQxNTJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1hcGVfcmYsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHJtc2VfcmYsICJcblxuIikNCg0KIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zOTA0MTUyKQ0KcHJpbnQoaW1wb3J0YW5jaWFfdmFycykNCg0KIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCnZhckltcFBsb3QobW9kZWxvX3JmXzM5MDQxNTIsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzkwNDE1MiIpDQoNCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcw0KZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKA0KICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsDQogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmDQopDQoNCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAzOTA0MTUyIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBBbsOhbGlzaXMgZGVsIGVycm9yDQplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzOTA0MTUyIiwNCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwNCiAgICAgY29sID0gInNreWJsdWUiLA0KICAgICBicmVha3MgPSAzMCkNCg0KIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24NCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkwNDE1MiIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQ0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQoNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIzOTA0MTUyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwNCiAgTUFQRSA9IG1hcGVfcmYsDQogIFJNU0UgPSBybXNlX3JmDQoNCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAxNTUwMDINCg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9DQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQ0KZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMiAlPiUNCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkNCg0KIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0DQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZA0KbW9kZWxvX3JmXzE1NTAwMiA8LSByYW5kb21Gb3Jlc3QoDQogIFZlbnRhIH4gLiwgDQogIGRhdGEgPSBkYXRvc19tb2RlbG8sDQogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMNCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdA0KICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpwcmludChtb2RlbG9fcmZfMTU1MDAyKQ0KDQojIE9idGVuZXIgcHJlZGljY2lvbmVzDQpwcmVkaWNjaW9uZXNfcmYgPC0gcHJlZGljdChtb2RlbG9fcmZfMTU1MDAyLCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBNQVBFDQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQ0KDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAyXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG5cbiIpDQoNCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmltcG9ydGFuY2lhX3ZhcnMgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMTU1MDAyKQ0KcHJpbnQoaW1wb3J0YW5jaWFfdmFycykNCg0KIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMiwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIiKQ0KDQojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMNCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgNCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLA0KICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZg0KKQ0KDQpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAyIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBBbsOhbGlzaXMgZGVsIGVycm9yDQplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDIiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbg0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDIiLA0KICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsDQogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAzNjc4MDU1DQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwNCiAgTUFQRSA9IG1hcGVfcmYsDQogIFJNU0UgPSBybXNlX3JmDQoNCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAzNjc4MDU1DQoNCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zNjc4MDU1ICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQptb2RlbG9fcmZfMzY3ODA1NSA8LSByYW5kb21Gb3Jlc3QoDQogIFZlbnRhIH4gLiwgDQogIGRhdGEgPSBkYXRvc19tb2RlbG8sDQogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMNCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdA0KICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpwcmludChtb2RlbG9fcmZfMzY3ODA1NSkNCg0KIyBPYnRlbmVyIHByZWRpY2Npb25lcw0KcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM2NzgwNTUsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzDQojIE1BUEUNCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQojIFJNU0UNCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQ0KDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzY3ODA1NVxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuXG4iKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM2NzgwNTUpDQpwcmludChpbXBvcnRhbmNpYV92YXJzKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KdmFySW1wUGxvdChtb2RlbG9fcmZfMzY3ODA1NSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzNjc4MDU1IikNCg0KIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYNCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM2NzgwNTUiLA0KICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLA0KICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIEFuw6FsaXNpcyBkZWwgZXJyb3INCmVycm9yZXMgPC0gZGF0b3NfZ3JhZmljbyRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvJFByZWRpY2hvDQpoaXN0KGVycm9yZXMsIA0KICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM2NzgwNTUiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbg0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzNjc4MDU1IiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQ0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsDQogIE1BUEUgPSBtYXBlX3JmLA0KICBSTVNFID0gcm1zZV9yZg0KKSkNCmBgYA0KDQoNCiMgWEdCT09TVA0KDQojIyBQUk9EVUNUTyAxNTUwMDENCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDEgJT4lDQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpDQoNCiMgRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQppbmRpY2VzX3RyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQpkYXRvc190cmFpbiA8LSBkYXRvc19tb2RlbG9baW5kaWNlc190cmFpbiwgXQ0KZGF0b3NfdGVzdCA8LSBkYXRvc19tb2RlbG9bLWluZGljZXNfdHJhaW4sIF0NCg0KIyBTZXBhcmFyIHZhcmlhYmxlcyBwcmVkaWN0b3JhcyB5IHZhcmlhYmxlIG9iamV0aXZvDQpYX3RyYWluIDwtIGFzLm1hdHJpeChkYXRvc190cmFpblssIGNvbG5hbWVzKGRhdG9zX3RyYWluKSAhPSAiVmVudGEiXSkNCnlfdHJhaW4gPC0gZGF0b3NfdHJhaW4kVmVudGENCg0KWF90ZXN0IDwtIGFzLm1hdHJpeChkYXRvc190ZXN0WywgY29sbmFtZXMoZGF0b3NfdGVzdCkgIT0gIlZlbnRhIl0pDQp5X3Rlc3QgPC0gZGF0b3NfdGVzdCRWZW50YQ0KDQojIENyZWFyIG1hdHJpY2VzIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW4sIGxhYmVsID0geV90cmFpbikNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3QsIGxhYmVsID0geV90ZXN0KQ0KDQojIERlZmluaXIgdW5hIHJlamlsbGEgY29tcGxldGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIGLDunNxdWVkYQ0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgIyBMZWFybmluZyByYXRlDQogIG1heF9kZXB0aCA9IGMoMywgNSwgNywgOSksICAgICAgICAgICAgICMgUHJvZnVuZGlkYWQgbcOheGltYQ0KICBzdWJzYW1wbGUgPSBjKDAuNiwgMC44LCAxLjApLCAgICAgICAgICAjIFN1Ym11ZXN0cmEgZGUgb2JzZXJ2YWNpb25lcw0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjYsIDAuOCwgMS4wKSwgICAjIFN1Ym11ZXN0cmEgZGUgdmFyaWFibGVzDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICMgUGVzbyBtw61uaW1vIGVuIG5vZG9zIGhpam9zDQogIGdhbW1hID0gYygwLCAwLjEsIDAuMykgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hDQopDQoNCiMgTW9zdHJhciBjdcOhbnRhcyBjb21iaW5hY2lvbmVzIHRlbmVtb3MNCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuIikNCg0KIyBQYXJhIGVzdGUgZWplbXBsbywgdmFtb3MgYSBsaW1pdGFyIGVsIG7Dum1lcm8gZGUgY29tYmluYWNpb25lcw0KIyBTZWxlY2Npb25hbmRvIHVuIHN1YmNvbmp1bnRvIGFsZWF0b3JpbyBkZSBjb21iaW5hY2lvbmVzICgyMCBjb21iaW5hY2lvbmVzKQ0Kc2V0LnNlZWQoMTIzKQ0KaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAyMCkgew0KICBtdWVzdHJhX2luZGljZXMgPC0gc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMjApDQogIHBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFttdWVzdHJhX2luZGljZXMsIF0NCn0gZWxzZSB7DQogIHBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZA0KfQ0KDQpjYXQoIk7Dum1lcm8gZGUgY29tYmluYWNpb25lcyBhIGV2YWx1YXI6IiwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSwgIlxuIikNCg0KIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgdW4gY29uanVudG8gZGUgaGlwZXJwYXLDoW1ldHJvcyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQ0KZXZhbHVhdGVfcGFyYW1zIDwtIGZ1bmN0aW9uKHBhcmFtc19yb3cpIHsNCiAgcGFyYW1zIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLA0KICAgIGV0YSA9IHBhcmFtc19yb3ckZXRhLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtc19yb3ckc3Vic2FtcGxlLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUsDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwNCiAgICBnYW1tYSA9IHBhcmFtc19yb3ckZ2FtbWENCiAgKQ0KICANCiAgIyBSZWFsaXphciB2YWxpZGFjacOzbiBjcnV6YWRhDQogIGN2X3Jlc3VsdHMgPC0geGdiLmN2KA0KICAgIHBhcmFtcyA9IHBhcmFtcywNCiAgICBkYXRhID0gZHRyYWluLA0KICAgIG5yb3VuZHMgPSAxMDAsDQogICAgbmZvbGQgPSA1LCAgIyA1LWZvbGQgdmFsaWRhY2nDs24gY3J1emFkYQ0KICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLA0KICAgIHZlcmJvc2UgPSAwDQogICkNCiAgDQogICMgRXh0cmFlciBlbCBtZWpvciBSTVNFIHkgZWwgbsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcw0KICBiZXN0X3Jtc2UgPC0gbWluKGN2X3Jlc3VsdHMkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pDQogIGJlc3RfbnJvdW5kcyA8LSB3aGljaC5taW4oY3ZfcmVzdWx0cyRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikNCiAgDQogIHJldHVybihsaXN0KHJtc2UgPSBiZXN0X3Jtc2UsIG5yb3VuZHMgPSBiZXN0X25yb3VuZHMsIHBhcmFtcyA9IHBhcmFtcykpDQp9DQoNCiMgSW5pY2lhbGl6YXIgdGFibGEgcGFyYSBhbG1hY2VuYXIgcmVzdWx0YWRvcw0KcmVzdWx0YWRvc19ncmlkIDwtIGRhdGEuZnJhbWUoDQogIGV0YSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksDQogIG1heF9kZXB0aCA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksDQogIHN1YnNhbXBsZSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksDQogIGNvbHNhbXBsZV9ieXRyZWUgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwNCiAgZ2FtbWEgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLA0KICBucm91bmRzID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwNCiAgcm1zZSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkNCikNCg0KDQoNCg0KDQojIFJlYWxpemFyIGxhIGLDunNxdWVkYSBlbiBjdWFkcsOtY3VsYSAoZXN0byBwdWVkZSB0YXJkYXIgdmFyaW9zIG1pbnV0b3MpDQpjYXQoIkluaWNpYW5kbyBiw7pzcXVlZGEgZW4gY3VhZHLDrWN1bGEuLi5cbiIpDQoNCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpIHsNCiAgY2F0KHNwcmludGYoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24gJWQgZGUgJWRcbiIsIGksIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpKQ0KICANCiAgIyBPYnRlbmVyIGZpbGEgZGUgcGFyw6FtZXRyb3MgYWN0dWFsDQogIHBhcmFtc19yb3cgPC0gcGFyYW1fZ3JpZF9yZWR1Y2lkYVtpLCBdDQogIA0KICAjIEV2YWx1YXIgY29tYmluYWNpw7NuIGFjdHVhbA0KICByZXN1bHQgPC0gZXZhbHVhdGVfcGFyYW1zKHBhcmFtc19yb3cpDQogIA0KICAjIEd1YXJkYXIgcmVzdWx0YWRvcw0KICByZXN1bHRhZG9zX2dyaWQkZXRhW2ldIDwtIHBhcmFtc19yb3ckZXRhDQogIHJlc3VsdGFkb3NfZ3JpZCRtYXhfZGVwdGhbaV0gPC0gcGFyYW1zX3JvdyRtYXhfZGVwdGgNCiAgcmVzdWx0YWRvc19ncmlkJHN1YnNhbXBsZVtpXSA8LSBwYXJhbXNfcm93JHN1YnNhbXBsZQ0KICByZXN1bHRhZG9zX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSA8LSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUNCiAgcmVzdWx0YWRvc19ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0gPC0gcGFyYW1zX3JvdyRtaW5fY2hpbGRfd2VpZ2h0DQogIHJlc3VsdGFkb3NfZ3JpZCRnYW1tYVtpXSA8LSBwYXJhbXNfcm93JGdhbW1hDQogIHJlc3VsdGFkb3NfZ3JpZCRucm91bmRzW2ldIDwtIHJlc3VsdCRucm91bmRzDQogIHJlc3VsdGFkb3NfZ3JpZCRybXNlW2ldIDwtIHJlc3VsdCRybXNlDQp9DQoNCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQ0KcmVzdWx0YWRvc19ncmlkIDwtIHJlc3VsdGFkb3NfZ3JpZFtvcmRlcihyZXN1bHRhZG9zX2dyaWQkcm1zZSksIF0NCg0KIyBNb3N0cmFyIGxvcyA1IG1lam9yZXMgY29uanVudG9zIGRlIGhpcGVycGFyw6FtZXRyb3MNCmNhdCgiXG5Mb3MgNSBtZWpvcmVzIGNvbmp1bnRvcyBkZSBoaXBlcnBhcsOhbWV0cm9zOlxuIikNCnByaW50KGhlYWQocmVzdWx0YWRvc19ncmlkLCA1KSkNCg0KIyBPYnRlbmVyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MNCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoDQogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwNCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsDQogIGV0YSA9IHJlc3VsdGFkb3NfZ3JpZCRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3NfZ3JpZCRtYXhfZGVwdGhbMV0sDQogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3NfZ3JpZCRzdWJzYW1wbGVbMV0sDQogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zX2dyaWQkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3NfZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBnYW1tYSA9IHJlc3VsdGFkb3NfZ3JpZCRnYW1tYVsxXQ0KKQ0KDQptZWpvcl9ucm91bmRzIDwtIHJlc3VsdGFkb3NfZ3JpZCRucm91bmRzWzFdDQoNCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQ0KcHJpbnQobWVqb3Jlc19wYXJhbXMpDQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuIikNCmNhdCgiUk1TRSBlbiB2YWxpZGFjacOzbiBjcnV6YWRhOiIsIHJlc3VsdGFkb3NfZ3JpZCRybXNlWzFdLCAiXG5cbiIpDQoNCiMgRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQptb2RlbG9feGdiXzE1NTAwMSA8LSB4Z2IudHJhaW4oDQogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLA0KICBkYXRhID0gZHRyYWluLA0KICBucm91bmRzID0gbWVqb3JfbnJvdW5kcywNCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwNCiAgdmVyYm9zZSA9IDANCikNCg0KIyBIYWNlciBwcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhDQpwcmVkaWNjaW9uZXNfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxLCBkdGVzdCkNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhDQojIE1BUEUNCm1hcGVfdGVzdCA8LSBtZWFuKGFicygoeV90ZXN0IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh5X3Rlc3QsIDAuMDEpKSkgKiAxMDANCg0KIyBSTVNFDQpybXNlX3Rlc3QgPC0gc3FydChtZWFuKCh5X3Rlc3QgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikpDQoNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhDQpjYXQoIk3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmE6XG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfdGVzdCwgIlxuXG4iKQ0KDQoNCiMgQWhvcmEgaGFjZXIgcHJlZGljY2lvbmVzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvIHBhcmEgY29tcGFyYWJpbGlkYWQgY29uIG90cm9zIG1vZGVsb3MNClhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pDQpwcmVkaWNjaW9uZXNfY29tcGxldGFzIDwtIHByZWRpY3QobW9kZWxvX3hnYl8xNTUwMDEsIFhfY29tcGxldG8pDQoNCg0KDQoNCg0KDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvDQojIE1BUEUNCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXMpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCnJtc2VfY29tcGxldG8gPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldGFzKV4yKSkNCg0KDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvDQpjYXQoIk3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bzpcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfY29tcGxldG8sICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfY29tcGxldG8sICJcblxuIikNCg0KDQojIEltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoDQogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG8pW2NvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0sDQogIG1vZGVsID0gbW9kZWxvX3hnYl8xNTUwMDENCikNCnByaW50KGltcG9ydGFuY2lhKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhLCANCiAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiKQ0KDQojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMNCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgNCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLA0KICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0YXMNCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiLA0KICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLA0KICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCiMgQW7DoWxpc2lzIGRlbCBlcnJvcg0KZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8NCmhpc3QoZXJyb3JlcywgDQogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAxIChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgYnJlYWtzID0gMzApDQoNCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuDQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0YXMsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQ0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJYR0Jvb3N0IiwNCiAgTUFQRSA9IG1hcGVfY29tcGxldG8sDQogIFJNU0UgPSBybXNlX2NvbXBsZXRvDQoNCikpDQpgYGANCg0KDQoNCiMjIFBST0RVQ1RPIDM5Mjk3ODgNCg0KYGBge3J9DQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQ0KZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5Mjk3ODggJT4lDQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpDQoNCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQ0Kc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQNCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQ0KdGVzdF9kYXRhIDwtIGRhdG9zX21vZGVsb1stdHJhaW5faW5kZXgsIF0NCg0KIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QNCnRyYWluX3ggPC0gYXMubWF0cml4KHRyYWluX2RhdGFbLCBjb2xuYW1lcyh0cmFpbl9kYXRhKSAhPSAiVmVudGEiXSkNCnRyYWluX3kgPC0gdHJhaW5fZGF0YSRWZW50YQ0KDQp0ZXN0X3ggPC0gYXMubWF0cml4KHRlc3RfZGF0YVssIGNvbG5hbWVzKHRlc3RfZGF0YSkgIT0gIlZlbnRhIl0pDQp0ZXN0X3kgPC0gdGVzdF9kYXRhJFZlbnRhDQoNCiMgQ3JlYXIgRE1hdHJpeCBwYXJhIFhHQm9vc3QNCmR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdHJhaW5feCwgbGFiZWwgPSB0cmFpbl95KQ0KZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpDQoNCiMgUGFzbyAzOiBEZWZpbmlyIGxhIHJlamlsbGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIEdyaWQgU2VhcmNoDQpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKA0KICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwgICAgICAgICAgIyBMZWFybmluZyByYXRlDQogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksICAgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIG3DoXhpbWENCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG8gaGlqbw0KICBzdWJzYW1wbGUgPSBjKDAuNywgMC45KSwgICAgICAgICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSBvYnNlcnZhY2lvbmVzDQogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIHZhcmlhYmxlcw0KICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWENCikNCg0KIyBNb3N0cmFyIGRpbWVuc2lvbmVzIGRlIGxhIHJlamlsbGENCmNhdCgiR3JpZCBTZWFyY2ggcGFyYSBYR0Jvb3N0IC0gUHJvZHVjdG8gMzkyOTc4OFxuIikNCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuXG4iKQ0KDQojIFBhcmEgZXN0ZSBlamVtcGxvLCBsaW1pdGFyIGEgMTIgY29tYmluYWNpb25lcyBwYXJhIGFob3JyYXIgdGllbXBvDQojIEVuIHVuIGVzY2VuYXJpbyByZWFsLCBwb2Ryw61hcyBldmFsdWFyIHRvZGFzIG8gdXNhciB1bmEgZXN0cmF0ZWdpYSBtw6FzIGVmaWNpZW50ZQ0Kc2V0LnNlZWQoNDU2KQ0KaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAxMikgew0KICBzZWxlY3RlZF9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEyKQ0KICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2VsZWN0ZWRfaW5kaWNlcywgXQ0KICBjYXQoIlNlbGVjY2lvbmFuZG8gMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzIHBhcmEgZXZhbHVhY2nDs24uXG5cbiIpDQp9DQoNCiMgUGFzbyA0OiBJbXBsZW1lbnRhciBHcmlkIFNlYXJjaA0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkNCg0KY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpDQoNCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsNCiAgIyBFeHRyYWVyIHBhcsOhbWV0cm9zIGRlIGxhIGNvbWJpbmFjacOzbiBhY3R1YWwNCiAgcGFyYW1zIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLCAgICAgICMgT2JqZXRpdm8gZGUgcmVncmVzacOzbg0KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLCAgICAgICAgICAgICAgICMgTcOpdHJpY2EgZGUgZXZhbHVhY2nDs24NCiAgICBldGEgPSBwYXJhbV9ncmlkJGV0YVtpXSwNCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwNCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldLA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtX2dyaWQkc3Vic2FtcGxlW2ldLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sDQogICAgZ2FtbWEgPSBwYXJhbV9ncmlkJGdhbW1hW2ldDQogICkNCiAgDQogIGNhdCgiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiIsIGksICJkZSIsIG5yb3cocGFyYW1fZ3JpZCksICI6XG4iKQ0KICBjYXQoIiAgZXRhID0iLCBwYXJhbXMkZXRhLCANCiAgICAgICIsIG1heF9kZXB0aCA9IiwgcGFyYW1zJG1heF9kZXB0aCwgDQogICAgICAiLCBtaW5fY2hpbGRfd2VpZ2h0ID0iLCBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwgDQogICAgICAiLCBzdWJzYW1wbGUgPSIsIHBhcmFtcyRzdWJzYW1wbGUsIA0KICAgICAgIiwgY29sc2FtcGxlX2J5dHJlZSA9IiwgcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsDQogICAgICAiLCBnYW1tYSA9IiwgcGFyYW1zJGdhbW1hLCAiXG4iKQ0KICANCiAgIyBWYWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgZW5jb250cmFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBpdGVyYWNpb25lcw0KICBjdl9tb2RlbCA8LSB4Z2IuY3YoDQogICAgcGFyYW1zID0gcGFyYW1zLA0KICAgIGRhdGEgPSBkdHJhaW4sDQogICAgbnJvdW5kcyA9IDIwMCwgICAgICAgICAgICAgICAgICAgICMgTcOheGltbyBuw7ptZXJvIGRlIGl0ZXJhY2lvbmVzDQogICAgbmZvbGQgPSA1LCAgICAgICAgICAgICAgICAgICAgICAgICMgNS1mb2xkIHZhbGlkYWNpw7NuIGNydXphZGENCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwgICAgICAgIyBEZXRlbmVyIHNpIG5vIGhheSBtZWpvcmEgZW4gMjAgcm9uZGFzDQogICAgdmVyYm9zZSA9IDAgICAgICAgICAgICAgICAgICAgICAgICMgU3VwcmltaXIgbWVuc2FqZXMNCiAgKQ0KICANCiAgIyBFeHRyYWVyIG1lam9yIGl0ZXJhY2nDs24geSBzdSBSTVNFDQogIGJlc3RfaXRlcmF0aW9uIDwtIGN2X21vZGVsJGJlc3RfaXRlcmF0aW9uDQogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pDQogIA0KICBjYXQoIiAgTWVqb3IgaXRlcmFjacOzbjoiLCBiZXN0X2l0ZXJhdGlvbiwgIlxuIikNCiAgY2F0KCIgIFJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCBiZXN0X3Jtc2UsICJcblxuIikNCiAgDQogICMgR3VhcmRhciByZXN1bHRhZG9zDQogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgNCiAgICBldGEgPSBwYXJhbXMkZXRhLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtcyRzdWJzYW1wbGUsDQogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLA0KICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLA0KICAgIG5yb3VuZHMgPSBiZXN0X2l0ZXJhdGlvbiwNCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlDQogICkNCiAgDQogIHJlc3VsdGFkb3MgPC0gcmJpbmQocmVzdWx0YWRvcywgcmVzdWx0YWRvX2FjdHVhbCkNCn0NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQ0KcmVzdWx0YWRvcyA8LSByZXN1bHRhZG9zW29yZGVyKHJlc3VsdGFkb3Mkcm1zZV9jdiksIF0NCg0KIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gNCmNhdCgiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggb3JkZW5hZG9zIHBvciBSTVNFOlxuIikNCnByaW50KHJlc3VsdGFkb3MpDQoNCiMgVmlzdWFsaXphciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaA0KZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsDQogICAgeSA9ICJSTVNFIGVuIFZhbGlkYWNpw7NuIENydXphZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdDQopDQoNCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdDQoNCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQ0KcHJpbnQobWVqb3Jlc19wYXJhbXMpDQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuXG4iKQ0KDQojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQ0KDQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQojIEdlbmVyYXIgcHJlZGljY2lvbmVzIHNvYnJlIFRPRE8gZWwgY29uanVudG8gZGUgZGF0b3MNCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pDQpkY29tcGxldG8gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHhfY29tcGxldG8pDQpwcmVkaWNjaW9uZXNfY29tcGxldG8gPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIG5ld2RhdGEgPSBkY29tcGxldG8pDQoNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQoNCiMgR3LDoWZpY28gMjogQW7DoWxpc2lzIGRlIHJlc2lkdW9zDQplcnJvcmVzIDwtIGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bw0KDQojIEhpc3RvZ3JhbWEgZGUgZXJyb3Jlcw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzOTI5Nzg4IChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgYnJlYWtzID0gMzApDQoNCiMgR3LDoWZpY28gMzogRXJyb3JlcyB2cyBQcmVkaWNjaW9uZXMNCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkyOTc4OCAoWEdCb29zdCkiLA0KICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsDQogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBYR0Jvb3N0IHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJYR0Jvb3N0IiwNCiAgTUFQRSA9IG1hcGVfY29tcGxldG8sDQogIFJNU0UgPSBybXNlX2NvbXBsZXRvDQopKQ0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCiMjIFBST0RVQ1RPIDM5MDQxNTINCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zOTA0MTUyICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIFBhc28gMjogRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KdHJhaW5fZGF0YSA8LSBkYXRvc19tb2RlbG9bdHJhaW5faW5kZXgsIF0NCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdDQoNCiMgUHJlcGFyYXIgbWF0cmljZXMgcGFyYSBYR0Jvb3N0DQp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pDQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGENCg0KdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQ0KdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQ0KDQojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3gsIGxhYmVsID0gdGVzdF95KQ0KDQojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQ0KICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvIGhpam8NCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksICAgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgb2JzZXJ2YWNpb25lcw0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMNCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKSAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hDQopDQoNCiMgTW9zdHJhciBkaW1lbnNpb25lcyBkZSBsYSByZWppbGxhDQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDM5MDQxNTJcbiIpDQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikNCg0KIyBQYXJhIGVzdGUgZWplbXBsbywgbGltaXRhciBhIDEyIGNvbWJpbmFjaW9uZXMgcGFyYSBhaG9ycmFyIHRpZW1wbw0KIyBFbiB1biBlc2NlbmFyaW8gcmVhbCwgcG9kcsOtYXMgZXZhbHVhciB0b2RhcyBvIHVzYXIgdW5hIGVzdHJhdGVnaWEgbcOhcyBlZmljaWVudGUNCnNldC5zZWVkKDQ1NikNCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsNCiAgc2VsZWN0ZWRfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMikNCiAgcGFyYW1fZ3JpZCA8LSBwYXJhbV9ncmlkW3NlbGVjdGVkX2luZGljZXMsIF0NCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQ0KfQ0KDQojIFBhc28gNDogSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gNCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgpDQoNCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQ0KDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWQpKSB7DQogICMgRXh0cmFlciBwYXLDoW1ldHJvcyBkZSBsYSBjb21iaW5hY2nDs24gYWN0dWFsDQogIHBhcmFtcyA8LSBsaXN0KA0KICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwgICAgICAjIE9iamV0aXZvIGRlIHJlZ3Jlc2nDs24NCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwgICAgICAgICAgICAgICAjIE3DqXRyaWNhIGRlIGV2YWx1YWNpw7NuDQogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sDQogICAgbWF4X2RlcHRoID0gcGFyYW1fZ3JpZCRtYXhfZGVwdGhbaV0sDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSwNCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwNCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1fZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldLA0KICAgIGdhbW1hID0gcGFyYW1fZ3JpZCRnYW1tYVtpXQ0KICApDQogIA0KICBjYXQoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24iLCBpLCAiZGUiLCBucm93KHBhcmFtX2dyaWQpLCAiOlxuIikNCiAgY2F0KCIgIGV0YSA9IiwgcGFyYW1zJGV0YSwgDQogICAgICAiLCBtYXhfZGVwdGggPSIsIHBhcmFtcyRtYXhfZGVwdGgsIA0KICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIA0KICAgICAgIiwgc3Vic2FtcGxlID0iLCBwYXJhbXMkc3Vic2FtcGxlLCANCiAgICAgICIsIGNvbHNhbXBsZV9ieXRyZWUgPSIsIHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLA0KICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikNCiAgDQogICMgVmFsaWRhY2nDs24gY3J1emFkYSBwYXJhIGVuY29udHJhciBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgaXRlcmFjaW9uZXMNCiAgY3ZfbW9kZWwgPC0geGdiLmN2KA0KICAgIHBhcmFtcyA9IHBhcmFtcywNCiAgICBkYXRhID0gZHRyYWluLA0KICAgIG5yb3VuZHMgPSAyMDAsICAgICAgICAgICAgICAgICAgICAjIE3DoXhpbW8gbsO6bWVybyBkZSBpdGVyYWNpb25lcw0KICAgIG5mb2xkID0gNSwgICAgICAgICAgICAgICAgICAgICAgICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhDQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsICAgICAgICMgRGV0ZW5lciBzaSBubyBoYXkgbWVqb3JhIGVuIDIwIHJvbmRhcw0KICAgIHZlcmJvc2UgPSAwICAgICAgICAgICAgICAgICAgICAgICAjIFN1cHJpbWlyIG1lbnNhamVzDQogICkNCiAgDQogICMgRXh0cmFlciBtZWpvciBpdGVyYWNpw7NuIHkgc3UgUk1TRQ0KICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbg0KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQ0KICANCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpDQogIGNhdCgiICBSTVNFIGVuIHZhbGlkYWNpw7NuIGNydXphZGE6IiwgYmVzdF9ybXNlLCAiXG5cbiIpDQogIA0KICAjIEd1YXJkYXIgcmVzdWx0YWRvcw0KICByZXN1bHRhZG9fYWN0dWFsIDwtIGRhdGEuZnJhbWUoDQogICAgZXRhID0gcGFyYW1zJGV0YSwNCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLA0KICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwNCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwNCiAgICBnYW1tYSA9IHBhcmFtcyRnYW1tYSwNCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sDQogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQ0KICApDQogIA0KICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpDQp9DQoNCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQ0KcmVzdWx0YWRvcyA8LSByZXN1bHRhZG9zW29yZGVyKHJlc3VsdGFkb3Mkcm1zZV9jdiksIF0NCg0KIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gNCmNhdCgiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggb3JkZW5hZG9zIHBvciBSTVNFOlxuIikNCnByaW50KHJlc3VsdGFkb3MpDQoNCiMgVmlzdWFsaXphciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaA0KZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkwNDE1MiIsDQogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsDQogICAgeSA9ICJSTVNFIGVuIFZhbGlkYWNpw7NuIENydXphZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdDQopDQoNCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdDQoNCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQ0KcHJpbnQobWVqb3Jlc19wYXJhbXMpDQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuXG4iKQ0KDQojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQ0KDQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQoNCm1vZGVsb194Z2JfMzkwNDE1MiA8LSBtb2RlbG9fZmluYWwNCg0KDQojIFBhc28gODogRXZhbHVhciBlbCBtb2RlbG8NCiMgUHJlZGljY2lvbmVzIGVuIGNvbmp1bnRvIGRlIHBydWViYQ0KcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIGR0ZXN0KQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBNQVBFDQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KSAvIHBtYXgodGVzdF95LCAwLjAxKSkpICogMTAwDQoNCiMgUk1TRQ0Kcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQ0KDQoNCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhDQpjYXQoIlxuTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfdGVzdCwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV90ZXN0LCAiXG5cbiIpDQoNCiMgUGFzbyA5OiBQcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gY29tcGxldG8NCiMgSGFjZXIgcHJlZGljY2lvbmVzIGVuIHRvZG8gZWwgY29uanVudG8gZGUgZGF0b3MgcGFyYSBjb21wYXJhY2nDs24gY29uIG90cm9zIG1vZGVsb3MNCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pDQpwcmVkaWNjaW9uZXNfY29tcGxldG8gPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIHhfY29tcGxldG8pDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvDQptYXBlX2NvbXBsZXRvIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pIC8gDQogICAgICAgICAgICAgICAgICAgICAgICBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQptc2VfY29tcGxldG8gPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKQ0KDQojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvDQpjYXQoIk3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bzpcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfY29tcGxldG8sICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfY29tcGxldG8sICJcblxuIikNCg0KIyBQYXNvIDEwOiBBbsOhbGlzaXMgZGUgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQojIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoDQogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG8pW2NvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0sDQogIG1vZGVsID0gbW9kZWxvX2ZpbmFsDQopDQoNCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikNCnByaW50KGltcG9ydGFuY2lhKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhLCANCiAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBWaXN1YWxpemFjaW9uZXMgcGFyYSBldmFsdWFjacOzbg0KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBHcsOhZmljbyAyOiBBbsOhbGlzaXMgZGUgcmVzaWR1b3MNCmVycm9yZXMgPC0gZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvDQoNCiMgSGlzdG9ncmFtYSBkZSBlcnJvcmVzDQpoaXN0KGVycm9yZXMsIA0KICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwNCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwNCiAgICAgY29sID0gInNreWJsdWUiLA0KICAgICBicmVha3MgPSAzMCkNCg0KIyBHcsOhZmljbyAzOiBFcnJvcmVzIHZzIFByZWRpY2Npb25lcw0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8sIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIA0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiWEdCb29zdCIsDQogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLA0KICBSTVNFID0gcm1zZV9jb21wbGV0bw0KKSkNCmBgYA0KDQojIyBQUk9EVUNUTyAxNTUwMDINCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDIgJT4lDQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpDQoNCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQ0Kc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQNCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQ0KdGVzdF9kYXRhIDwtIGRhdG9zX21vZGVsb1stdHJhaW5faW5kZXgsIF0NCg0KIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QNCnRyYWluX3ggPC0gYXMubWF0cml4KHRyYWluX2RhdGFbLCBjb2xuYW1lcyh0cmFpbl9kYXRhKSAhPSAiVmVudGEiXSkNCnRyYWluX3kgPC0gdHJhaW5fZGF0YSRWZW50YQ0KDQp0ZXN0X3ggPC0gYXMubWF0cml4KHRlc3RfZGF0YVssIGNvbG5hbWVzKHRlc3RfZGF0YSkgIT0gIlZlbnRhIl0pDQp0ZXN0X3kgPC0gdGVzdF9kYXRhJFZlbnRhDQoNCiMgQ3JlYXIgRE1hdHJpeCBwYXJhIFhHQm9vc3QNCmR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdHJhaW5feCwgbGFiZWwgPSB0cmFpbl95KQ0KZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpDQoNCiMgUGFzbyAzOiBEZWZpbmlyIGxhIHJlamlsbGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIEdyaWQgU2VhcmNoDQpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKA0KICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwgICAgICAgICAgIyBMZWFybmluZyByYXRlDQogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksICAgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIG3DoXhpbWENCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG8gaGlqbw0KICBzdWJzYW1wbGUgPSBjKDAuNywgMC45KSwgICAgICAgICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSBvYnNlcnZhY2lvbmVzDQogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIHZhcmlhYmxlcw0KICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWENCikNCg0KIyBNb3N0cmFyIGRpbWVuc2lvbmVzIGRlIGxhIHJlamlsbGENCmNhdCgiR3JpZCBTZWFyY2ggcGFyYSBYR0Jvb3N0IC0gUHJvZHVjdG8gMTU1MDAyXG4iKQ0KY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvczoiLCBucm93KHBhcmFtX2dyaWQpLCAiXG5cbiIpDQoNCiMgUGFyYSBlc3RlIGVqZW1wbG8sIGxpbWl0YXIgYSAxMiBjb21iaW5hY2lvbmVzIHBhcmEgYWhvcnJhciB0aWVtcG8NCiMgRW4gdW4gZXNjZW5hcmlvIHJlYWwsIHBvZHLDrWFzIGV2YWx1YXIgdG9kYXMgbyB1c2FyIHVuYSBlc3RyYXRlZ2lhIG3DoXMgZWZpY2llbnRlDQpzZXQuc2VlZCg0NTYpDQppZiAobnJvdyhwYXJhbV9ncmlkKSA+IDEyKSB7DQogIHNlbGVjdGVkX2luZGljZXMgPC0gc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTIpDQogIHBhcmFtX2dyaWQgPC0gcGFyYW1fZ3JpZFtzZWxlY3RlZF9pbmRpY2VzLCBdDQogIGNhdCgiU2VsZWNjaW9uYW5kbyAxMiBjb21iaW5hY2lvbmVzIGFsZWF0b3JpYXMgcGFyYSBldmFsdWFjacOzbi5cblxuIikNCn0NCg0KIyBQYXNvIDQ6IEltcGxlbWVudGFyIEdyaWQgU2VhcmNoDQpyZXN1bHRhZG9zIDwtIGRhdGEuZnJhbWUoKQ0KDQpjYXQoIkluaWNpYW5kbyBHcmlkIFNlYXJjaC4uLlxuIikNCg0KZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkKSkgew0KICAjIEV4dHJhZXIgcGFyw6FtZXRyb3MgZGUgbGEgY29tYmluYWNpw7NuIGFjdHVhbA0KICBwYXJhbXMgPC0gbGlzdCgNCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsICAgICAgIyBPYmpldGl2byBkZSByZWdyZXNpw7NuDQogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsICAgICAgICAgICAgICAgIyBNw6l0cmljYSBkZSBldmFsdWFjacOzbg0KICAgIGV0YSA9IHBhcmFtX2dyaWQkZXRhW2ldLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWQkbWF4X2RlcHRoW2ldLA0KICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sDQogICAgc3Vic2FtcGxlID0gcGFyYW1fZ3JpZCRzdWJzYW1wbGVbaV0sDQogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSwNCiAgICBnYW1tYSA9IHBhcmFtX2dyaWQkZ2FtbWFbaV0NCiAgKQ0KICANCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkKSwgIjpcbiIpDQogIGNhdCgiICBldGEgPSIsIHBhcmFtcyRldGEsIA0KICAgICAgIiwgbWF4X2RlcHRoID0iLCBwYXJhbXMkbWF4X2RlcHRoLCANCiAgICAgICIsIG1pbl9jaGlsZF93ZWlnaHQgPSIsIHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LCANCiAgICAgICIsIHN1YnNhbXBsZSA9IiwgcGFyYW1zJHN1YnNhbXBsZSwgDQogICAgICAiLCBjb2xzYW1wbGVfYnl0cmVlID0iLCBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwNCiAgICAgICIsIGdhbW1hID0iLCBwYXJhbXMkZ2FtbWEsICJcbiIpDQogIA0KICAjIFZhbGlkYWNpw7NuIGNydXphZGEgcGFyYSBlbmNvbnRyYXIgZWwgbsO6bWVybyDDs3B0aW1vIGRlIGl0ZXJhY2lvbmVzDQogIGN2X21vZGVsIDwtIHhnYi5jdigNCiAgICBwYXJhbXMgPSBwYXJhbXMsDQogICAgZGF0YSA9IGR0cmFpbiwNCiAgICBucm91bmRzID0gMjAwLCAgICAgICAgICAgICAgICAgICAgIyBNw6F4aW1vIG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMNCiAgICBuZm9sZCA9IDUsICAgICAgICAgICAgICAgICAgICAgICAgIyA1LWZvbGQgdmFsaWRhY2nDs24gY3J1emFkYQ0KICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDIwLCAgICAgICAjIERldGVuZXIgc2kgbm8gaGF5IG1lam9yYSBlbiAyMCByb25kYXMNCiAgICB2ZXJib3NlID0gMCAgICAgICAgICAgICAgICAgICAgICAgIyBTdXByaW1pciBtZW5zYWplcw0KICApDQogIA0KICAjIEV4dHJhZXIgbWVqb3IgaXRlcmFjacOzbiB5IHN1IFJNU0UNCiAgYmVzdF9pdGVyYXRpb24gPC0gY3ZfbW9kZWwkYmVzdF9pdGVyYXRpb24NCiAgYmVzdF9ybXNlIDwtIG1pbihjdl9tb2RlbCRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikNCiAgDQogIGNhdCgiICBNZWpvciBpdGVyYWNpw7NuOiIsIGJlc3RfaXRlcmF0aW9uLCAiXG4iKQ0KICBjYXQoIiAgUk1TRSBlbiB2YWxpZGFjacOzbiBjcnV6YWRhOiIsIGJlc3Rfcm1zZSwgIlxuXG4iKQ0KICANCiAgIyBHdWFyZGFyIHJlc3VsdGFkb3MNCiAgcmVzdWx0YWRvX2FjdHVhbCA8LSBkYXRhLmZyYW1lKA0KICAgIGV0YSA9IHBhcmFtcyRldGEsDQogICAgbWF4X2RlcHRoID0gcGFyYW1zJG1heF9kZXB0aCwNCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsDQogICAgc3Vic2FtcGxlID0gcGFyYW1zJHN1YnNhbXBsZSwNCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsDQogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsDQogICAgbnJvdW5kcyA9IGJlc3RfaXRlcmF0aW9uLA0KICAgIHJtc2VfY3YgPSBiZXN0X3Jtc2UNCiAgKQ0KICANCiAgcmVzdWx0YWRvcyA8LSByYmluZChyZXN1bHRhZG9zLCByZXN1bHRhZG9fYWN0dWFsKQ0KfQ0KDQojIE9yZGVuYXIgcmVzdWx0YWRvcyBwb3IgUk1TRSAoZGUgbWVub3IgYSBtYXlvcikNCnJlc3VsdGFkb3MgPC0gcmVzdWx0YWRvc1tvcmRlcihyZXN1bHRhZG9zJHJtc2VfY3YpLCBdDQoNCiMgUGFzbyA1OiBNb3N0cmFyIHJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoDQpjYXQoIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIG9yZGVuYWRvcyBwb3IgUk1TRTpcbiIpDQpwcmludChyZXN1bHRhZG9zKQ0KDQojIFZpc3VhbGl6YXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gNCmdncGxvdChyZXN1bHRhZG9zLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvcykpLCBybXNlX2N2KSwgeSA9IHJtc2VfY3YpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCAtIFByb2R1Y3RvIDE1NTAwMiIsDQogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsDQogICAgeSA9ICJSTVNFIGVuIFZhbGlkYWNpw7NuIENydXphZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdDQopDQoNCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdDQoNCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQ0KcHJpbnQobWVqb3Jlc19wYXJhbXMpDQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuXG4iKQ0KDQojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQ0KDQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQoNCm1vZGVsb194Z2JfMTU1MDAyIDwtIG1vZGVsb19maW5hbA0KDQojIFBhc28gODogRXZhbHVhciBlbCBtb2RlbG8NCiMgUHJlZGljY2lvbmVzIGVuIGNvbmp1bnRvIGRlIHBydWViYQ0KcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIGR0ZXN0KQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBNQVBFDQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KSAvIHBtYXgodGVzdF95LCAwLjAxKSkpICogMTAwDQoNCiMgUk1TRQ0Kcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQ0KDQoNCg0KIyBNb3N0cmFyIG3DqXRyaWNhcyBlbiBjb25qdW50byBkZSBwcnVlYmENCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0LCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBybXNlX3Rlc3QsICJcblxuIikNCg0KIyBQYXNvIDk6IFByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0bw0KIyBIYWNlciBwcmVkaWNjaW9uZXMgZW4gdG9kbyBlbCBjb25qdW50byBkZSBkYXRvcyBwYXJhIGNvbXBhcmFjacOzbiBjb24gb3Ryb3MgbW9kZWxvcw0KeF9jb21wbGV0byA8LSBhcy5tYXRyaXgoZGF0b3NfbW9kZWxvWywgY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSkNCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8NCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyANCiAgICAgICAgICAgICAgICAgICAgICAgIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwDQoNCnJtc2VfY29tcGxldG8gPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpKQ0KDQoNCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8NCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0bywgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV9jb21wbGV0bywgIlxuXG4iKQ0KDQoNCiMgUGFzbyAxMDogQW7DoWxpc2lzIGRlIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmltcG9ydGFuY2lhIDwtIHhnYi5pbXBvcnRhbmNlKA0KICBmZWF0dXJlX25hbWVzID0gY29sbmFtZXMoZGF0b3NfbW9kZWxvKVtjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdLA0KICBtb2RlbCA9IG1vZGVsb19maW5hbA0KKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQpjYXQoIkltcG9ydGFuY2lhIGRlIHZhcmlhYmxlczpcbiIpDQpwcmludChpbXBvcnRhbmNpYSkNCg0KIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXggPSBpbXBvcnRhbmNpYSwgDQogICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIgKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBWaXN1YWxpemFjaW9uZXMgcGFyYSBldmFsdWFjacOzbg0KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMiAoWEdCb29zdCkiLA0KICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLA0KICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIEdyw6FmaWNvIDI6IEFuw6FsaXNpcyBkZSByZXNpZHVvcw0KZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCg0KIyBIaXN0b2dyYW1hIGRlIGVycm9yZXMNCmhpc3QoZXJyb3JlcywgDQogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgYnJlYWtzID0gMzApDQoNCiMgR3LDoWZpY28gMzogRXJyb3JlcyB2cyBQcmVkaWNjaW9uZXMNCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjE1NTAwMiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiWEdCb29zdCIsDQogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLA0KICBSTVNFID0gcm1zZV9jb21wbGV0bw0KKSkNCmBgYA0KDQojIyBQUk9EVUNUTyAzNjc4MDU1DQoNCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zNjc4MDU1ICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIFBhc28gMjogRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KdHJhaW5fZGF0YSA8LSBkYXRvc19tb2RlbG9bdHJhaW5faW5kZXgsIF0NCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdDQoNCiMgUHJlcGFyYXIgbWF0cmljZXMgcGFyYSBYR0Jvb3N0DQp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pDQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGENCg0KdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQ0KdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQ0KDQojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3gsIGxhYmVsID0gdGVzdF95KQ0KDQojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MNCnBhcmFtX2dyaWQgPC0gZXhwYW5kLmdyaWQoDQogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLA0KICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzLCA1KSwNCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksDQogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwNCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKQ0KKQ0KDQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDM2NzgwNTVcbiIpDQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikNCg0KIyBTZWxlY2Npw7NuIGFsZWF0b3JpYSBkZSAxMiBjb21iaW5hY2lvbmVzIChzaSBzZSBkZXNlYSBsaW1pdGFyKQ0Kc2V0LnNlZWQoNDU2KQ0KaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAxMikgew0KICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTIpLCBdDQogIGNhdCgiU2VsZWNjaW9uYW5kbyAxMiBjb21iaW5hY2lvbmVzIGFsZWF0b3JpYXMgcGFyYSBldmFsdWFjacOzbi5cblxuIikNCn0NCg0KIyBQYXNvIDQ6IEltcGxlbWVudGFyIEdyaWQgU2VhcmNoDQpyZXN1bHRhZG9zIDwtIGRhdGEuZnJhbWUoKQ0KY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpDQoNCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsNCiAgcGFyYW1zIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLA0KICAgIGV0YSA9IHBhcmFtX2dyaWQkZXRhW2ldLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWQkbWF4X2RlcHRoW2ldLA0KICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sDQogICAgc3Vic2FtcGxlID0gcGFyYW1fZ3JpZCRzdWJzYW1wbGVbaV0sDQogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSwNCiAgICBnYW1tYSA9IHBhcmFtX2dyaWQkZ2FtbWFbaV0NCiAgKQ0KICANCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkKSwgIi4uLlxuIikNCiAgDQogIGN2X21vZGVsIDwtIHhnYi5jdigNCiAgICBwYXJhbXMgPSBwYXJhbXMsDQogICAgZGF0YSA9IGR0cmFpbiwNCiAgICBucm91bmRzID0gMjAwLA0KICAgIG5mb2xkID0gNSwNCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwNCiAgICB2ZXJib3NlID0gMA0KICApDQogIA0KICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbg0KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQ0KICANCiAgcmVzdWx0YWRvX2FjdHVhbCA8LSBkYXRhLmZyYW1lKA0KICAgIGV0YSA9IHBhcmFtcyRldGEsDQogICAgbWF4X2RlcHRoID0gcGFyYW1zJG1heF9kZXB0aCwNCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsDQogICAgc3Vic2FtcGxlID0gcGFyYW1zJHN1YnNhbXBsZSwNCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsDQogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsDQogICAgbnJvdW5kcyA9IGJlc3RfaXRlcmF0aW9uLA0KICAgIHJtc2VfY3YgPSBiZXN0X3Jtc2UNCiAgKQ0KICANCiAgcmVzdWx0YWRvcyA8LSByYmluZChyZXN1bHRhZG9zLCByZXN1bHRhZG9fYWN0dWFsKQ0KfQ0KDQpyZXN1bHRhZG9zIDwtIHJlc3VsdGFkb3Nbb3JkZXIocmVzdWx0YWRvcyRybXNlX2N2KSwgXQ0KDQojIFBhc28gNTogTW9zdHJhciByZXN1bHRhZG9zDQpwcmludChyZXN1bHRhZG9zKQ0KDQojIFZpc3VhbGl6YWNpw7NuDQpnZ3Bsb3QocmVzdWx0YWRvcywgYWVzKHggPSByZW9yZGVyKHBhc3RlKCJDb21iIiwgMTpucm93KHJlc3VsdGFkb3MpKSwgcm1zZV9jdiksIHkgPSBybXNlX2N2KSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggLSBQcm9kdWN0byAzNjc4MDU1IiwNCiAgICB4ID0gIkNvbWJpbmFjacOzbiBkZSBIaXBlcnBhcsOhbWV0cm9zIiwNCiAgICB5ID0gIlJNU0UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdDQopDQoNCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdDQoNCiMgUGFzbyA3OiBFbnRyZW5hciBtb2RlbG8gZmluYWwNCm1vZGVsb19maW5hbCA8LSB4Z2IudHJhaW4oDQogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLA0KICBkYXRhID0gZHRyYWluLA0KICBucm91bmRzID0gbWVqb3JfbnJvdW5kcywNCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwNCiAgdmVyYm9zZSA9IDANCikNCg0KbW9kZWxvX3hnYl8zNjc4MDU1IDwtIG1vZGVsb19maW5hbA0KDQoNCiMgUGFzbyA4OiBFdmFsdWFyIG1vZGVsbw0KcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIGR0ZXN0KQ0KbWFwZV90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCkgLyBwbWF4KHRlc3RfeSwgMC4wMSkpKSAqIDEwMA0Kcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQ0KDQoNCg0KIyBQYXNvIDk6IFByZWRpY2Npw7NuIGVuIHRvZG8gZWwgY29uanVudG8NCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pDQpwcmVkaWNjaW9uZXNfY29tcGxldG8gPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIHhfY29tcGxldG8pDQoNCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0Kcm1zZV9jb21wbGV0byA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikpDQoNCg0KDQoNCiMgUGFzbyAxMDogSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYSA8LSB4Z2IuaW1wb3J0YW5jZSgNCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwNCiAgbW9kZWwgPSBtb2RlbG9fZmluYWwNCikNCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jaWEsIA0KICAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM2NzgwNTUgKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBHcsOhZmljb3MgZGUgZXZhbHVhY2nDs24NCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZShPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKQ0KDQpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAzNjc4MDU1IiwgeCA9ICJWZW50YSBPYnNlcnZhZGEiLCB5ID0gIlZlbnRhIFByZWRpY2hhIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCg0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzNjc4MDU1IChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwgYnJlYWtzID0gMzApDQoNCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKHRpdGxlID0gIkVycm9yZXMgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzNjc4MDU1IiwgeCA9ICJWZW50YSBQcmVkaWNoYSIsIHkgPSAiRXJyb3IiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM2NzgwNTUiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlhHQm9vc3QiLA0KICBNQVBFID0gbWFwZV9jb21wbGV0bywNCiAgUk1TRSA9IHJtc2VfY29tcGxldG8NCikpDQpgYGANCg0KIyBWaXN1YWxpemFjacOzbiBkZSBNw6l0cmljYXMNCg0KYGBge3J9DQojIERlZmluaXIgbG9zIGNvbG9yZXMgcGFyYSBjYWRhIG1vZGVsbw0KY29sb3Jlc19tb2RlbG9zIDwtIGMoDQogICJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsICAgICMgQXp1bA0KICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAjIE5hcmFuamENCiAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAgICMgVmVyZGUNCiAgIlhHQm9vc3QiID0gIiNkNjI3MjgiICAgICAgICAgIyBSb2pvDQopDQpgYGANCg0KIyMgUFJPRFVDVE8gMTU1MDAxDQpgYGB7cn0NCiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUNCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMToiKQ0KcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMSIpKQ0KDQojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMQ0KIyAoY29uIHZhbG9yZXMgZGUgZWplbXBsbyBzaSBlcyBuZWNlc2FyaW8pDQpkYXRvc18xNTUwMDFfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSByZXAoIjE1NTAwMSIsIDQpLA0KICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMNCmRhdG9zXzE1NTAwMV9jb21wbGV0byA8LSBsZWZ0X2pvaW4oDQogIGRhdG9zXzE1NTAwMV9jb21wbGV0bywNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMSIpLA0KICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpDQopDQoNCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcw0KIyBTaSB0aWVuZXMgbG9zIHZhbG9yZXMsIHJlZW1wbGF6YSBsb3MgMCBjb24gbG9zIHZhbG9yZXMgY29ycmVjdG9zDQojIE8gdG9tYSBub3RhIGRlIGN1w6FsZXMgc29uIE5BIHBhcmEgcmVlbXBsYXphcmxvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzDQoNCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsIChyZWVtcGxhemEgZXN0b3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcykNCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVsyXSkpIHsNCiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8xNTUwMDEgICMgTyBlbCB2YWxvciBjb3JyZWN0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRSTVNFWzJdKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVsyXSA8LSBybXNlXzE1NTAwMSAgIyBZYSBubyBNU0UNCn0NCg0KDQoNCg0KIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdCAocmVlbXBsYXphIGVzdG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMpDQojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgUmFuZG9tIEZvcmVzdCBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMSwNCiMgdXNhIGxhcyB2YXJpYWJsZXMgcjJfcmYsIHJtc2VfcmYsIGV0Yy4NCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsNCiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbM10gPC0gbWFwZV9yZg0KfQ0KDQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbM10pICYmIGV4aXN0cygicm1zZV9yZiIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRSTVNFWzNdIDwtIHJtc2VfcmYNCn0NCg0KDQoNCg0KDQoNCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QgKHJlZW1wbGF6YSBlc3RvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzKQ0KIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFhHQm9vc3QgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEsDQojIHVzYSBsYXMgdmFyaWFibGVzIHIyX2NvbXBsZXRvLCBybXNlX2NvbXBsZXRvLCBldGMuDQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8NCn0NCg0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvDQp9DQoNCg0KIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcw0KcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMToiKQ0KcHJpbnQoZGF0b3NfMTU1MDAxX2NvbXBsZXRvKQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzE1NTAwMV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAxIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIk1BUEUgKCUpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApIA0KDQojIEdyw6FmaWNvIHBhcmEgUk1TRQ0KZ2dwbG90KGRhdG9zXzE1NTAwMV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAxIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIlJNU0UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCmBgYA0KDQoNCiMjIFBST0RVQ1RPIDM5Mjk3ODgNCmBgYHtyfQ0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQ0KcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4ODoiKQ0KcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5Mjk3ODgiKSkNCg0KIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4DQpkYXRvc18zOTI5Nzg4X2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gcmVwKCIzOTI5Nzg4IiwgNCksDQogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KIyBVbmlyIGNvbiBsb3MgZGF0b3MgZXhpc3RlbnRlcw0KZGF0b3NfMzkyOTc4OF9jb21wbGV0byA8LSBsZWZ0X2pvaW4oDQogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sDQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzOTI5Nzg4IiksDQogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikNCikNCg0KIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzDQojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbA0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVsyXSkpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMzkyOTc4OA0KfQ0KDQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzJdKSkgew0KICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8zOTI5Nzg4DQp9DQoNCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QNCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4OA0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYNCn0NCg0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzNdIDwtIHJtc2VfcmYNCn0NCg0KIyBWYWxvcmVzIHBhcmEgWEdCb29zdA0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8NCn0NCg0KDQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bw0KfQ0KDQojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zDQpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4ODoiKQ0KcHJpbnQoZGF0b3NfMzkyOTc4OF9jb21wbGV0bykNCg0KIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcw0KY29sb3Jlc19tb2RlbG9zIDwtIGMoIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgDQogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCANCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgDQogICAgICAgICAgICAgICAgICAgICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIpDQoNCiMgR3LDoWZpY28gcGFyYSBSTVNFDQpnZ3Bsb3QoZGF0b3NfMzkyOTc4OF9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFJNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJSTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5Mjk3ODgiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiTUFQRSAoJSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCmBgYA0KDQojIyBQUk9EVUNUTyAzOTA0MTUyDQpgYGB7cn0NCiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUNCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTI6IikNCnByaW50KG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzOTA0MTUyIikpDQoNCiMgQ3JlYXIgdW4gZGF0YWZyYW1lIG1hbnVhbG1lbnRlIGNvbiBsb3MgNCBtb2RlbG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1Mg0KZGF0b3NfMzkwNDE1Ml9jb21wbGV0byA8LSBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9IHJlcCgiMzkwNDE1MiIsIDQpLA0KICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMNCmRhdG9zXzM5MDQxNTJfY29tcGxldG8gPC0gbGVmdF9qb2luKA0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvLA0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkwNDE1MiIpLA0KICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpDQopDQoNCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcw0KIyBWYWxvcmVzIHBhcmEgUmVncmVzacOzbiBMaW5lYWwNCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbMl0pKSB7DQogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzM5MDQxNTINCn0NCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbMl0pKSB7DQogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVsyXSA8LSBybXNlXzM5MDQxNTINCn0NCg0KIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdA0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsNCiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYNCn0NCg0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsNCiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzNdIDwtIHJtc2VfcmYNCn0NCg0KDQojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzRdKSAmJiBleGlzdHMoIm1hcGVfY29tcGxldG8iKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bw0KfQ0KDQoNCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvDQp9DQoNCg0KIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcw0KcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTI6IikNCnByaW50KGRhdG9zXzM5MDQxNTJfY29tcGxldG8pDQoNCiMgRGVmaW5pciBjb2xvcmVzIHBhcmEgbG9zIG1vZGVsb3MNCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIA0KICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgDQogICAgICAgICAgICAgICAgICAgICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsIA0KICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQ0KDQojIEdyw6FmaWNvIHBhcmEgTVNFDQpnZ3Bsb3QoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkwNDE1MiIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFJNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJSTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzM5MDQxNTJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5MDQxNTIiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiTUFQRSAoJSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQpgYGANCg0KIyMgUFJPRFVDVE8gMTU1MDAyDQpgYGB7cn0NCiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUNCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMjoiKQ0KcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMiIpKQ0KDQojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMg0KZGF0b3NfMTU1MDAyX2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gcmVwKCIxNTUwMDIiLCA0KSwNCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzDQpkYXRvc18xNTUwMDJfY29tcGxldG8gPC0gbGVmdF9qb2luKA0KICBkYXRvc18xNTUwMDJfY29tcGxldG8sDQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIxNTUwMDIiKSwNCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQ0KKQ0KDQojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMNCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsDQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbMl0pKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMTU1MDAyDQp9DQoNCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVsyXSkpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8xNTUwMDINCn0NCg0KDQoNCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QNCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyDQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYNCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbM10gPC0gcm1zZV9yZg0KfQ0KDQojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0DQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVs0XSkgJiYgZXhpc3RzKCJybXNlX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bw0KfQ0KDQojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zDQpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyOiIpDQpwcmludChkYXRvc18xNTUwMDJfY29tcGxldG8pDQoNCiMgRGVmaW5pciBjb2xvcmVzIHBhcmEgbG9zIG1vZGVsb3MNCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIA0KICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgDQogICAgICAgICAgICAgICAgICAgICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsIA0KICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQ0KDQojIEdyw6FmaWNvIHBhcmEgTVNFDQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUk1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAxNTUwMDIiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiUk1TRSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzE1NTAwMl9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzE1NTAwMl9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAyIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIk1BUEUgKCUpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQpgYGANCg0KIyMgUFJPRFVDVE8gMzY3ODA1NQ0KYGBge3J9DQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlDQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1OiIpDQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzY3ODA1NSIpKQ0KDQojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDM2NzgwNTUNCmRhdG9zXzM2NzgwNTVfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSByZXAoIjM2NzgwNTUiLCA0KSwNCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzDQpkYXRvc18zNjc4MDU1X2NvbXBsZXRvIDwtIGxlZnRfam9pbigNCiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0bywNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM2NzgwNTUiKSwNCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQ0KKQ0KDQojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMNCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsDQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzJdKSkgew0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8zNjc4MDU1DQp9DQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFWzJdKSkgew0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8zNjc4MDU1DQp9DQoNCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QNCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQ0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsNCiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYNCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbM10pICYmIGV4aXN0cygicm1zZV9yZiIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmDQp9DQoNCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QNCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVs0XSA8LSBtYXBlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bw0KfQ0KDQojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zDQpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NToiKQ0KcHJpbnQoZGF0b3NfMzY3ODA1NV9jb21wbGV0bykNCg0KIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcw0KY29sb3Jlc19tb2RlbG9zIDwtIGMoIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgDQogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCANCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgDQogICAgICAgICAgICAgICAgICAgICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIpDQojIEdyw6FmaWNvIHBhcmEgTVNFDQpnZ3Bsb3QoZGF0b3NfMzY3ODA1NV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzY3ODA1NSIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFJNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJSTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzM2NzgwNTVfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiTUFQRSAoJSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQpgYGANCg0KIyBFU1RJTUFDScOTTiBERSBQUkVDSU9TDQoNCiMjIyBQcmVwYXJhY2nDs24gZGUgZGF0b3MNCmBgYHtyfQ0KIyBGdW5jacOzbiBwYXJhIHByZXBhcmFyIGRhdG9zIGRlIHVuIHByb2R1Y3RvDQpwcmVwYXJlX3ByaWNlX2RhdGEgPC0gZnVuY3Rpb24oZGYsIHByb2R1Y3RfaWQpIHsNCiAgcHJvZHVjdF9kYXRhIDwtIGRmICU+JQ0KICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpICU+JQ0KICAgIGFycmFuZ2UoVHJ4X0ZlY2hhKSAlPiUNCiAgICBzZWxlY3QoDQogICAgICBUcnhfRmVjaGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgQ2FudCwgVmVudGEsIA0KICAgICAgQ29zdG9fVmVudGEsIERlc2N1ZW50b19Qb3JjZW50YWplLCBTZW1hbmEsIE1lcw0KICAgICkgJT4lDQogICAgbXV0YXRlKA0KICAgICAgRGlhX1NlbWFuYSA9IHdkYXkoVHJ4X0ZlY2hhKSwNCiAgICAgIE1lc19OdW0gPSBtb250aChUcnhfRmVjaGEpLA0KICAgICAgQW5pbyA9IHllYXIoVHJ4X0ZlY2hhKSwNCiAgICAgIERpYXNfRGVzZGVfSW5pY2lvID0gYXMubnVtZXJpYyhkaWZmdGltZShUcnhfRmVjaGEsIG1pbihUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpLA0KICAgICAgTWFyZ2VuX1VuaXRhcmlvID0gKFZlbnRhIC8gQ2FudCkgLSAoQ29zdG9fVmVudGEgLyBDYW50KSwNCiAgICAgIFByZWNpb19Vbml0YXJpb19DYWxjID0gVmVudGEgLyBDYW50LA0KICAgICAgSURfSW52ZW50YXJpbyA9IHByb2R1Y3RfaWQNCiAgICApDQogIA0KICByZXR1cm4ocHJvZHVjdF9kYXRhKQ0KfQ0KDQojIEFzZWfDunJhdGUgZGUgcXVlICdkYXRvcycgc2VhIHR1IGRhdGEuZnJhbWUgY2FyZ2FkbyBjb3JyZWN0YW1lbnRlDQojIFBvciBlamVtcGxvLCBzaSB2aWVuZXMgZGUgdW4gYXJjaGl2byAuY3N2Og0KIyBkYXRvcyA8LSByZWFkLmNzdigiYXJjaGl2by5jc3YiKQ0KDQojIEFwbGljYXIgbGEgZnVuY2nDs24gYSB0b2RvcyBsb3MgcHJvZHVjdG9zDQppZHMgPC0gdW5pcXVlKGRhdG9zJElEX0ludmVudGFyaW8pDQoNCnByb2R1Y3Rvc19wcmVwYXJhZG9zIDwtIG1hcF9kZihpZHMsIGZ1bmN0aW9uKGlkKSB7DQogIHByZXBhcmVfcHJpY2VfZGF0YShkYXRvcywgaWQpDQp9KQ0KDQojIE1vc3RyYXIgdW5hIHBhcnRlIGRlbCByZXN1bHRhZG8NCmhlYWQocHJvZHVjdG9zX3ByZXBhcmFkb3MpDQpgYGANCg0KDQoNCg0KYGBge3J9DQojIFZlY3RvciBjb24gcHJvZHVjdG9zIChkZWJlIGlyIHByaW1lcm8pDQpwcm9kdWN0b3NfaWRzIDwtIHRvcF9pZHMNCg0KIyBGdW5jacOzbiBwYXJhIGVudHJlbmFyIG1vZGVsbyBBUk1BIHBvciBwcm9kdWN0bw0KdHJhaW5fYXJtYV9tb2RlbCA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkKSB7DQogIGxpYnJhcnkoZm9yZWNhc3QpICAjIEFzZWfDunJhdGUgZGUgY2FyZ2FyIGZvcmVjYXN0IHNpIG5vIGVzdMOhIGNhcmdhZG8gYcO6bg0KICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkNCiAgc2VyaWVfdHMgPC0gdHMocHJvZHVjdF9kYXRhJFZlbnRhLCBmcmVxdWVuY3kgPSAxMikNCiAgbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KICByZXR1cm4obW9kZWxvX2FybWEpDQp9DQoNCiMgQ3JlYXIgbGlzdGEgZGUgbW9kZWxvcyBBUk1BIHBvciBwcm9kdWN0bw0KbW9kZWxvc19hcm1hX2xpc3RhIDwtIHNldE5hbWVzKA0KICBsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX2FybWFfbW9kZWwoZGF0b3MsIGlkKSksDQogIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKQ0KKQ0KDQojIEZ1bmNpw7NuIHBhcmEgbW9kZWxvIHJlZ3Jlc2nDs24gbGluZWFsDQp0cmFpbl9yZWdfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgew0KICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkNCiAgbW9kZWxvX3JlZyA8LSBsbShWZW50YSB+IFByZWNpb19GaW5hbF9Vbml0YXJpbywgZGF0YSA9IHByb2R1Y3RfZGF0YSkNCiAgcmV0dXJuKG1vZGVsb19yZWcpDQp9DQoNCiMgRnVuY2nDs24gcGFyYSBtb2RlbG8gUmFuZG9tIEZvcmVzdA0KdHJhaW5fcmZfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgew0KICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkNCiAgcHJlZGljdG9ycyA8LSBjKCJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAiQ2FudCIsICJEZXNjdWVudG9fUG9yY2VudGFqZSIpDQogIHJmX2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBzZWxlY3QoYWxsX29mKHByZWRpY3RvcnMpLCBWZW50YSkNCiAgbW9kZWxvX3JmIDwtIHJhbmRvbUZvcmVzdChWZW50YSB+IC4sIGRhdGEgPSByZl9kYXRhLCBudHJlZSA9IDEwMCkNCiAgcmV0dXJuKG1vZGVsb19yZikNCn0NCg0KIyBGdW5jacOzbiBwYXJhIG1vZGVsbyBYR0Jvb3N0DQp0cmFpbl94Z2JfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgew0KICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkNCiAgcHJlZGljdG9ycyA8LSBjKCJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAiQ2FudCIsICJEZXNjdWVudG9fUG9yY2VudGFqZSIpDQogIHRyYWluX21hdHJpeCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gYXMubWF0cml4KHByb2R1Y3RfZGF0YVssIHByZWRpY3RvcnNdKSwgbGFiZWwgPSBwcm9kdWN0X2RhdGEkVmVudGEpDQogIHBhcmFtcyA8LSBsaXN0KG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIikNCiAgbW9kZWxvX3hnYiA8LSB4Z2IudHJhaW4ocGFyYW1zID0gcGFyYW1zLCBkYXRhID0gdHJhaW5fbWF0cml4LCBucm91bmRzID0gNTAsIHZlcmJvc2UgPSAwKQ0KICByZXR1cm4obW9kZWxvX3hnYikNCn0NCg0KIyBDcmVhciBsaXN0YXMgZGUgbW9kZWxvcw0KbW9kZWxvc19yZWdfbGlzdGEgPC0gc2V0TmFtZXMobGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl9yZWdfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkNCm1vZGVsb3NfcmZfbGlzdGEgPC0gc2V0TmFtZXMobGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl9yZl9tb2RlbChkYXRvcywgaWQpKSwgYXMuY2hhcmFjdGVyKHByb2R1Y3Rvc19pZHMpKQ0KbW9kZWxvc194Z2JfbGlzdGEgPC0gc2V0TmFtZXMobGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl94Z2JfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkNCmBgYA0KDQojIyMgRW50cmVuYXIgbW9kZWxvcyBkZSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zDQpgYGB7cn0NCiMgRnVuY2nDs24gcGFyYSBlbnRyZW5hciBtb2RlbG9zIGRlIHByZWRpY2Npw7NuIGRlIHByZWNpb3MNCnRyYWluX3ByaWNlX21vZGVscyA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkLCB0ZXN0X3NpemUgPSAwLjIpIHsNCiAgcHJpY2VfZGF0YSA8LSBwcmVwYXJlX3ByaWNlX2RhdGEoZGF0YSwgcHJvZHVjdF9pZCkgJT4lDQogICAgZHJvcF9uYSgpICU+JQ0KICAgIHNlbGVjdCgNCiAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywNCiAgICAgIENhbnQsIENvc3RvX1ZlbnRhLCBEZXNjdWVudG9fUG9yY2VudGFqZSwNCiAgICAgIERpYV9TZW1hbmEsIE1lc19OdW0sIEFuaW8sIERpYXNfRGVzZGVfSW5pY2lvLA0KICAgICAgTWFyZ2VuX1VuaXRhcmlvDQogICAgKQ0KDQogICMgRXZpdGFyIGZhbGxvcyBzaSBoYXkgbXV5IHBvY29zIGRhdG9zDQogIGlmIChucm93KHByaWNlX2RhdGEpIDwgMTApIHsNCiAgICB3YXJuaW5nKHBhc3RlKCJQcm9kdWN0byIsIHByb2R1Y3RfaWQsICJ0aWVuZSBtZW5vcyBkZSAxMCByZWdpc3Ryb3MuIFNlIG9taXRlLiIpKQ0KICAgIHJldHVybihOVUxMKQ0KICB9DQoNCiAgc2V0LnNlZWQoMTIzKQ0KICB0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHByaWNlX2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBwID0gMSAtIHRlc3Rfc2l6ZSwgbGlzdCA9IEZBTFNFKQ0KICB0cmFpbl9kYXRhIDwtIHByaWNlX2RhdGFbdHJhaW5faW5kZXgsIF0NCiAgdGVzdF9kYXRhIDwtIHByaWNlX2RhdGFbLXRyYWluX2luZGV4LCBdDQoNCiAgIyAxLiBSZWdyZXNpw7NuIExpbmVhbA0KICBsbV9tb2RlbCA8LSBsbShQcmVjaW9fRmluYWxfVW5pdGFyaW8gfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkNCg0KICAjIDIuIFJhbmRvbSBGb3Jlc3QNCiAgcmZfbW9kZWwgPC0gcmFuZG9tRm9yZXN0KA0KICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyB+IC4sDQogICAgZGF0YSA9IHRyYWluX2RhdGEsDQogICAgbnRyZWUgPSA1MDAsDQogICAgaW1wb3J0YW5jZSA9IFRSVUUNCiAgKQ0KDQogICMgMy4gWEdCb29zdA0KICBmZWF0dXJlcyA8LSBzZXRkaWZmKG5hbWVzKHRyYWluX2RhdGEpLCAiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIikNCiAgeF90cmFpbiA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGZlYXR1cmVzXSkNCiAgeV90cmFpbiA8LSB0cmFpbl9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbw0KICB4X3Rlc3QgPC0gYXMubWF0cml4KHRlc3RfZGF0YVssIGZlYXR1cmVzXSkNCiAgeV90ZXN0IDwtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8NCiAgZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSB4X3RyYWluLCBsYWJlbCA9IHlfdHJhaW4pDQogIGR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB4X3Rlc3QsIGxhYmVsID0geV90ZXN0KQ0KDQogIHhnYl9wYXJhbXMgPC0gbGlzdCgNCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsDQogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsDQogICAgZXRhID0gMC4xLA0KICAgIG1heF9kZXB0aCA9IDYsDQogICAgbWluX2NoaWxkX3dlaWdodCA9IDMsDQogICAgc3Vic2FtcGxlID0gMC44LA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgNCiAgKQ0KDQogIHhnYl9tb2RlbCA8LSB4Z2IudHJhaW4oDQogICAgcGFyYW1zID0geGdiX3BhcmFtcywNCiAgICBkYXRhID0gZHRyYWluLA0KICAgIG5yb3VuZHMgPSAxMDAsDQogICAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwNCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAxMCwNCiAgICB2ZXJib3NlID0gMA0KICApDQoNCiAgIyBFdmFsdWFjacOzbg0KICBsbV9wcmVkIDwtIHByZWRpY3QobG1fbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpDQogIHJmX3ByZWQgPC0gcHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkNCiAgeGdiX3ByZWQgPC0gcHJlZGljdCh4Z2JfbW9kZWwsIHhfdGVzdCkNCg0KICBsbV9ybXNlIDwtIHNxcnQobWVhbigobG1fcHJlZCAtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pXjIpKQ0KICByZl9ybXNlIDwtIHNxcnQobWVhbigocmZfcHJlZCAtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pXjIpKQ0KICB4Z2Jfcm1zZSA8LSBzcXJ0KG1lYW4oKHhnYl9wcmVkIC0gdGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyleMikpDQoNCiAgbG1fcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIGxtX3ByZWQpXjIpIC8NCiAgICBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBtZWFuKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pKV4yKQ0KICByZl9yMiA8LSAxIC0gc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gcmZfcHJlZCleMikgLw0KICAgIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIG1lYW4odGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbykpXjIpDQogIHhnYl9yMiA8LSAxIC0gc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0geGdiX3ByZWQpXjIpIC8NCiAgICBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBtZWFuKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pKV4yKQ0KDQogIG1ldHJpY3MgPC0gZGF0YS5mcmFtZSgNCiAgICBNb2RlbCA9IGMoIkxpbmVhciBSZWdyZXNzaW9uIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLA0KICAgIFJNU0UgPSBjKGxtX3Jtc2UsIHJmX3Jtc2UsIHhnYl9ybXNlKSwNCiAgICBSMiA9IGMobG1fcjIsIHJmX3IyLCB4Z2JfcjIpDQogICkNCg0KICByZXR1cm4obGlzdChtZXRyaWNzID0gbWV0cmljcykpDQp9DQoNCiMgSURzIGRlIGxvcyA1IHByb2R1Y3RvcyBhIG1vZGVsYXINCnByb2R1Y3Rvc19pZHMgPC0gYygxNTUwMDEsIDM5Mjk3ODgsIDM5MDQxNTIsIDE1NTAwMiwgMzY3ODA1NSkNCg0KIyBBcGxpY2FyIG1vZGVsbyBhIGNhZGEgcHJvZHVjdG8NCnJlc3VsdGFkb3NfbW9kZWxvcyA8LSBtYXAocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHsNCiAgcmVzdWx0YWRvIDwtIHRyYWluX3ByaWNlX21vZGVscyhkYXRvcywgcHJvZHVjdF9pZCA9IGlkKQ0KICBpZiAoIWlzLm51bGwocmVzdWx0YWRvKSkgew0KICAgIHJlc3VsdGFkbyRtZXRyaWNzICU+JSBtdXRhdGUoSURfSW52ZW50YXJpbyA9IGlkKQ0KICB9IGVsc2Ugew0KICAgIE5VTEwNCiAgfQ0KfSkgJT4lIGNvbXBhY3QoKSAlPiUgYmluZF9yb3dzKCkNCg0KIyBNb3N0cmFyIHJlc3VsdGFkb3MNCnJlc3VsdGFkb3NfbW9kZWxvcw0KYGBgDQoNCmBgYHtyfQ0KIyBMaXN0YSBjb24gbG9zIElEcyBkZSBwcm9kdWN0b3MgKHB1ZWRlcyB1c2FyIHRvcF9pZHMgcXVlIHlhIGRlZmluaXN0ZSkNCnByb2R1Y3Rvc19pZHMgPC0gdG9wX2lkcw0KDQojIEVudHJlbmFyIG1vZGVsb3MgcGFyYSBjYWRhIHByb2R1Y3RvIHkgZ3VhcmRhciBlbiBsaXN0YQ0KbW9kZWxvc19wcmVjaW9fbGlzdGEgPC0gc2V0TmFtZXMoDQogIGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcHJpY2VfbW9kZWxzKGRhdG9zLCBpZCkpLA0KICBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykNCikNCmBgYA0KDQojIyMgRXN0aW1hciBwcmVjaW9zIMOzcHRpbW9zDQpgYGB7cn0NCmVzdGltYXRlX29wdGltYWxfcHJpY2VzIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQsIHByaWNlX21vZGVscywgZGVtYW5kX21vZGVscyA9IE5VTEwsIGZ1dHVyZV9kYXRlcyA9IE5VTEwpIHsNCiAgcHJpY2Vfc3RlcHMgPC0gMjANCg0KICAjIFNlbGVjY2nDs24gZGVsIG1lam9yIG1vZGVsbyBkZSBwcmVjaW8NCiAgYmVzdF9wcmljZV9tb2RlbF9pZHggPC0gd2hpY2gubWF4KHByaWNlX21vZGVscyRtZXRyaWNzJFIyKQ0KICBiZXN0X3ByaWNlX21vZGVsX25hbWUgPC0gcHJpY2VfbW9kZWxzJG1ldHJpY3MkTW9kZWxbYmVzdF9wcmljZV9tb2RlbF9pZHhdDQoNCiAgIyBEYXRvcyBkZWwgcHJvZHVjdG8NCiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpDQoNCiAgIyBSYW5nbyBkZSBwcmVjaW9zIHJlc3RyaW5naWRvIGEgcGVyY2VudGlsZXMgNSUgLSA5NSUgeSBsaW1pdGFkbyBhIDEuNXggbGEgbWVkaWFuYQ0KICBtaW5fcHJpY2UgPC0gcXVhbnRpbGUocHJvZHVjdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgMC4wNSwgbmEucm0gPSBUUlVFKQ0KICBtYXhfcHJpY2UgPC0gcXVhbnRpbGUocHJvZHVjdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgMC45NSwgbmEucm0gPSBUUlVFKQ0KICBwcmljZV9yYW5nZSA8LSBzZXEobWluX3ByaWNlLCBtYXhfcHJpY2UsIGxlbmd0aC5vdXQgPSBwcmljZV9zdGVwcykNCiAgcHJpY2VfcmFuZ2UgPC0gcG1pbihwcmljZV9yYW5nZSwgMS41ICogbWVkaWFuKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkpDQoNCiAgIyBJbmljaWFsaXphciBlc2NlbmFyaW9zIGZ1dHVyb3MNCiAgZnV0dXJlX3NjZW5hcmlvcyA8LSBkYXRhLmZyYW1lKCkNCg0KICBmb3IgKGZ1dHVyZV9kYXRlIGluIGZ1dHVyZV9kYXRlcykgew0KICAgIGZ1dHVyZV9kYXRlIDwtIGFzLkRhdGUoZnV0dXJlX2RhdGUpDQogICAgbWVzX2FjdHVhbCA8LSBsdWJyaWRhdGU6Om1vbnRoKGZ1dHVyZV9kYXRlKQ0KDQogICAgbWVzX2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBmaWx0ZXIobHVicmlkYXRlOjptb250aChUcnhfRmVjaGEpID09IG1lc19hY3R1YWwpDQogICAgaWYgKG5yb3cobWVzX2RhdGEpIDwgNSkgbWVzX2RhdGEgPC0gcHJvZHVjdF9kYXRhDQoNCiAgICBjb3N0b19tZXMgPC0gbWVkaWFuKG1lc19kYXRhJENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpDQogICAgY2FudF9tZXMgPC0gbWVkaWFuKG1lc19kYXRhJENhbnQsIG5hLnJtID0gVFJVRSkNCiAgICBkZXNjX21lcyA8LSBtZWRpYW4obWVzX2RhdGEkRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSkNCg0KICAgIGlmIChpcy5uYShjb3N0b19tZXMpKSBjb3N0b19tZXMgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQ0KICAgIGlmIChpcy5uYShjYW50X21lcykgfHwgY2FudF9tZXMgPT0gMCkgY2FudF9tZXMgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRDYW50LCBuYS5ybSA9IFRSVUUpDQogICAgaWYgKGlzLm5hKGRlc2NfbWVzKSkgZGVzY19tZXMgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSREZXNjdWVudG9fUG9yY2VudGFqZSwgbmEucm0gPSBUUlVFKQ0KDQogICAgZGF0ZV9kZiA8LSBkYXRhLmZyYW1lKA0KICAgICAgVHJ4X0ZlY2hhID0gcmVwKGZ1dHVyZV9kYXRlLCBwcmljZV9zdGVwcyksDQogICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBwcmljZV9yYW5nZSwNCiAgICAgIENhbnQgPSBjYW50X21lcywNCiAgICAgIENvc3RvX1ZlbnRhID0gY29zdG9fbWVzLA0KICAgICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBkZXNjX21lcywNCiAgICAgIERpYV9TZW1hbmEgPSBsdWJyaWRhdGU6OndkYXkoZnV0dXJlX2RhdGUpLA0KICAgICAgTWVzX051bSA9IG1lc19hY3R1YWwsDQogICAgICBBbmlvID0gbHVicmlkYXRlOjp5ZWFyKGZ1dHVyZV9kYXRlKSwNCiAgICAgIERpYXNfRGVzZGVfSW5pY2lvID0gYXMubnVtZXJpYyhkaWZmdGltZShmdXR1cmVfZGF0ZSwgbWluKHByb2R1Y3RfZGF0YSRUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpLA0KICAgICAgTWFyZ2VuX1VuaXRhcmlvID0gTkENCiAgICApDQoNCiAgICBmdXR1cmVfc2NlbmFyaW9zIDwtIHJiaW5kKGZ1dHVyZV9zY2VuYXJpb3MsIGRhdGVfZGYpDQogIH0NCg0KICAjIENhbGN1bGFyIG1hcmdlbiB1bml0YXJpbyBzaW11bGFkbw0KICBmdXR1cmVfc2NlbmFyaW9zJE1hcmdlbl9Vbml0YXJpbyA8LSBmdXR1cmVfc2NlbmFyaW9zJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtDQogICAgKGZ1dHVyZV9zY2VuYXJpb3MkQ29zdG9fVmVudGEgLyBmdXR1cmVfc2NlbmFyaW9zJENhbnQpDQoNCiAgIyBFc3RpbWFyIGVsYXN0aWNpZGFkIGhpc3TDs3JpY2EgKGNvbiBtw61uaW1vIDE1IHB1bnRvcyB2w6FsaWRvcykNCiAgcHJvZHVjdF9kYXRhIDwtIHByb2R1Y3RfZGF0YSAlPiUgYXJyYW5nZShUcnhfRmVjaGEpDQogIGVsYXN0aWNpdHlfZGYgPC0gcHJvZHVjdF9kYXRhICU+JQ0KICAgIGZpbHRlcighaXMubmEoQ2FudCkgJiAhaXMubmEoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSkgJT4lDQogICAgbXV0YXRlKA0KICAgICAgUF9sYWcgPSBsYWcoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSwNCiAgICAgIFFfbGFnID0gbGFnKENhbnQpLA0KICAgICAgZFAgPSBQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBQX2xhZywNCiAgICAgIGRRID0gQ2FudCAtIFFfbGFnLA0KICAgICAgZWxhc3RpY2l0eV9wb2ludCA9IChkUSAvIFFfbGFnKSAvIChkUCAvIFBfbGFnKQ0KICAgICkgJT4lDQogICAgZmlsdGVyKCFpcy5uYShlbGFzdGljaXR5X3BvaW50KSwgaXMuZmluaXRlKGVsYXN0aWNpdHlfcG9pbnQpKQ0KDQogIGVsYXN0aWNpdHkgPC0gbWVkaWFuKGVsYXN0aWNpdHlfZGYkZWxhc3RpY2l0eV9wb2ludCwgbmEucm0gPSBUUlVFKQ0KICBpZiAobnJvdyhlbGFzdGljaXR5X2RmKSA8IDE1IHx8IGlzLm5hKGVsYXN0aWNpdHkpIHx8ICFpcy5maW5pdGUoZWxhc3RpY2l0eSkpIHsNCiAgICBlbGFzdGljaXR5IDwtIDENCiAgfQ0KDQogICMgRXN0aW1hciB2ZW50YXMgeSBtw6FyZ2VuZXMNCiAgcmVzdWx0cyA8LSBmdXR1cmVfc2NlbmFyaW9zICU+JQ0KICAgIG11dGF0ZShWZW50YV9Fc3BlcmFkYSA9IDAsIE1hcmdlbl9Ub3RhbCA9IDApDQoNCiAgIyBQcmVjaW8gYmFzZSBzaW4gZGVzY3VlbnRvcyAobcOhcyByb2J1c3RvKQ0KICBiYXNlbGluZV9wcmljZSA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhICU+JSBmaWx0ZXIoRGVzY3VlbnRvX1BvcmNlbnRhamUgPT0gMCkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSwgbmEucm0gPSBUUlVFKQ0KICBpZiAoaXMubmEoYmFzZWxpbmVfcHJpY2UpKSB7DQogICAgYmFzZWxpbmVfcHJpY2UgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkNCiAgfQ0KDQogIGZvciAoaSBpbiAxOm5yb3cocmVzdWx0cykpIHsNCiAgICBwcmljZV9yYXRpbyA8LSBiYXNlbGluZV9wcmljZSAvIHJlc3VsdHMkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvW2ldDQogICAgYWRqdXN0ZWRfcXVhbnRpdHkgPC0gcmVzdWx0cyRDYW50W2ldICogKHByaWNlX3JhdGlvIF4gZWxhc3RpY2l0eSkNCiAgICByZXN1bHRzJFZlbnRhX0VzcGVyYWRhW2ldIDwtIHJlc3VsdHMkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvW2ldICogYWRqdXN0ZWRfcXVhbnRpdHkNCiAgICByZXN1bHRzJE1hcmdlbl9Ub3RhbFtpXSA8LSBhZGp1c3RlZF9xdWFudGl0eSAqIHJlc3VsdHMkTWFyZ2VuX1VuaXRhcmlvW2ldDQogIH0NCg0KICAjIFNlbGVjY2lvbmFyIHByZWNpb3Mgw7NwdGltb3MgKHBvciBmZWNoYSkNCiAgb3B0aW1hbF9wcmljZXMgPC0gcmVzdWx0cyAlPiUNCiAgICBncm91cF9ieShUcnhfRmVjaGEpICU+JQ0KICAgIHNsaWNlX21heChWZW50YV9Fc3BlcmFkYSwgbiA9IDEpICU+JQ0KICAgIHNlbGVjdChUcnhfRmVjaGEsIFByZWNpb19PcHRpbWFsID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBWZW50YV9Fc3BlcmFkYSwgTWFyZ2VuX1RvdGFsKQ0KDQogICMgVmFsaWRhY2nDs24gZGUgcHJlY2lvcyBleHRyZW1vcw0KICBwcmVjaW9fbWVkaWFuIDwtIG1lZGlhbihwcm9kdWN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpDQogIGlmIChhbnkob3B0aW1hbF9wcmljZXMkUHJlY2lvX09wdGltYWwgPiAyICogcHJlY2lvX21lZGlhbikpIHsNCiAgICB3YXJuaW5nKHBhc3RlKCIgUHJlY2lvIMOzcHRpbW8gbXV5IGFsdG8gZGV0ZWN0YWRvIHBhcmEgcHJvZHVjdG8iLCBwcm9kdWN0X2lkKSkNCiAgfQ0KDQogIHJldHVybihsaXN0KA0KICAgIHJlc3VsdGFkb3MgPSByZXN1bHRzLA0KICAgIHByZWNpb3Nfb3B0aW1vcyA9IG9wdGltYWxfcHJpY2VzLA0KICAgIGVsYXN0aWNpZGFkID0gZWxhc3RpY2l0eQ0KICApKQ0KfQ0KDQpgYGANCg0KIyMjIFZpc3VhbGl6YXIgcmVzdWx0YWRvcw0KYGBge3J9DQojIEZlY2hhcyBmdXR1cmFzIHBhcmEgc2ltdWxhY2nDs24NCmRhdGVzX2Z1dHVyZSA8LSBzZXEoYXMuRGF0ZSgiMjAyNS0wMS0wMSIpLCBieSA9ICJtb250aCIsIGxlbmd0aC5vdXQgPSA2KQ0KDQojIExpc3RhIHBhcmEgZ3VhcmRhciByZXN1bHRhZG9zIHBvciBwcm9kdWN0bw0KcHJlY2lvc19vcHRpbW9zX2xpc3RhIDwtIGxpc3QoKQ0KDQojIEl0ZXJhciBwb3IgcHJvZHVjdG9zDQpmb3IgKHBpZCBpbiBwcm9kdWN0b3NfaWRzKSB7DQogIGNhdCgiUFJPRFVDVE86IiwgcGlkLCAiXG4iKQ0KICANCiAgbW9kZWxvX3ByZWNpbyA8LSBtb2RlbG9zX3ByZWNpb19saXN0YVtbYXMuY2hhcmFjdGVyKHBpZCldXQ0KICANCiAgaWYgKCFpcy5udWxsKG1vZGVsb19wcmVjaW8pKSB7DQogICAgcmVzdWx0YWRvIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKA0KICAgICAgZGF0YSA9IGRhdG9zLA0KICAgICAgcHJvZHVjdF9pZCA9IHBpZCwNCiAgICAgIHByaWNlX21vZGVscyA9IG1vZGVsb19wcmVjaW8sDQogICAgICBmdXR1cmVfZGF0ZXMgPSBkYXRlc19mdXR1cmUNCiAgICApDQogICAgDQogICAgcHJlY2lvc19vcHRpbW9zX2xpc3RhW1thcy5jaGFyYWN0ZXIocGlkKV1dIDwtIHJlc3VsdGFkbw0KICAgIA0KICAgIGNhdCgiRWxhc3RpY2lkYWQgZXN0aW1hZGE6Iiwgcm91bmQocmVzdWx0YWRvJGVsYXN0aWNpZGFkLCAyKSwgIlxuXG4iKQ0KICAgIA0KICAgICMgTW9zdHJhciB0YWJsYSBtYW51YWxtZW50ZSBmaWxhIHBvciBmaWxhDQogICAgcHJpbnQocmVzdWx0YWRvJHByZWNpb3Nfb3B0aW1vcykNCiAgICANCiAgfSBlbHNlIHsNCiAgICBjYXQoIk5vIGhheSBtb2RlbG8gZGUgcHJlY2lvcyBwYXJhIGVsIHByb2R1Y3RvIiwgcGlkLCAiXG4iKQ0KICB9DQp9DQpgYGANCg0KDQoNCiMjIyBJbnRlZ3JhY2nDs24gZGUgcHJlY2lvcyDDs3B0aW1vcyB5IG1vZGVsb3MNCmBgYHtyfQ0KaW50ZWdyYXRlX3dpdGhfZXhpc3RpbmdfbW9kZWxzIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQsIHByaWNlX29wdF9yZXN1bHRzLCB4Z2JfbW9kZWwpIHsNCiAgb3B0aW1hbF9wcmljZXMgPC0gcHJpY2Vfb3B0X3Jlc3VsdHNbW2FzLmNoYXJhY3Rlcihwcm9kdWN0X2lkKV1dJHByZWNpb3Nfb3B0aW1vcw0KICANCiAgaWYgKGlzLm51bGwob3B0aW1hbF9wcmljZXMpIHx8IG5yb3cob3B0aW1hbF9wcmljZXMpID09IDApIHsNCiAgICB3YXJuaW5nKHBhc3RlKCJObyBzZSBlbmNvbnRyYXJvbiBwcmVjaW9zIMOzcHRpbW9zIHBhcmEgZWwgcHJvZHVjdG8iLCBwcm9kdWN0X2lkKSkNCiAgICByZXR1cm4oZGF0YS5mcmFtZSgpKQ0KICB9DQogIA0KICBoaXN0X2RhdGEgPC0gZGF0YSAlPiUNCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKSAlPiUNCiAgICBhcnJhbmdlKFRyeF9GZWNoYSkNCiAgDQogIGZ1dHVyZV9mZWF0dXJlcyA8LSBkYXRhLmZyYW1lKCkNCiAgDQogIGZvciAoaSBpbiAxOm5yb3cob3B0aW1hbF9wcmljZXMpKSB7DQogICAgZnV0dXJlX2RhdGUgPC0gb3B0aW1hbF9wcmljZXMkVHJ4X0ZlY2hhW2ldDQogICAgZnV0dXJlX3ByaWNlIDwtIG9wdGltYWxfcHJpY2VzJFByZWNpb19PcHRpbWFsW2ldDQogICAgDQogICAgbWVzX2RhdGEgPC0gaGlzdF9kYXRhICU+JQ0KICAgICAgZmlsdGVyKGx1YnJpZGF0ZTo6bW9udGgoVHJ4X0ZlY2hhKSA9PSBsdWJyaWRhdGU6Om1vbnRoKGZ1dHVyZV9kYXRlKSkNCiAgICANCiAgICBpZiAobnJvdyhtZXNfZGF0YSkgPCA1KSBtZXNfZGF0YSA8LSBoaXN0X2RhdGENCiAgICANCiAgICBhdmdfZmVhdHVyZXMgPC0gbWVzX2RhdGEgJT4lDQogICAgICBzdW1tYXJpc2UoDQogICAgICAgIENhbnQgPSBtZWRpYW4oQ2FudCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgQ29zdG9fVmVudGEgPSBtZWRpYW4oQ29zdG9fVmVudGEsIG5hLnJtID0gVFJVRSksDQogICAgICAgIENvc3RvX0Rldm9sdWNpb24gPSBtZWRpYW4oQ29zdG9fRGV2b2x1Y2lvbiwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgUHJlY2lvX0xpc3RhX1VuaXRhcmlvID0gbWVkaWFuKFByZWNpb19MaXN0YV9Vbml0YXJpbywgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBtZWRpYW4oRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSksDQogICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoZGlmZnRpbWUoZnV0dXJlX2RhdGUsIG1pbihoaXN0X2RhdGEkVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSAvIDMwDQogICAgICApDQogICAgDQogICAgIyBWYXJpYWJsZXMgZGUgdGVuZGVuY2lhOg0KICAgIGF2Z19mZWF0dXJlcyRQcmVjaW9fRmluYWxfVW5pdGFyaW8gPC0gZnV0dXJlX3ByaWNlDQogICAgYXZnX2ZlYXR1cmVzJFRyeF9GZWNoYSA8LSBmdXR1cmVfZGF0ZQ0KICAgIGF2Z19mZWF0dXJlcyRNZXNfTnVtIDwtIGx1YnJpZGF0ZTo6bW9udGgoZnV0dXJlX2RhdGUpDQogICAgYXZnX2ZlYXR1cmVzJEFuaW8gPC0gbHVicmlkYXRlOjp5ZWFyKGZ1dHVyZV9kYXRlKQ0KICAgIGF2Z19mZWF0dXJlcyRNZXNfRGVzZGVfSW5pY2lvIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZnV0dXJlX2RhdGUsIG1pbihoaXN0X2RhdGEkVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSAlLyUgMzANCiAgICANCiAgICBmdXR1cmVfZmVhdHVyZXMgPC0gcmJpbmQoZnV0dXJlX2ZlYXR1cmVzLCBhdmdfZmVhdHVyZXMpDQogIH0NCiAgDQogIGZ1dHVyZV9kYXRhIDwtIGRhdGEuZnJhbWUoDQogICAgRmVjaGEgPSBmdXR1cmVfZmVhdHVyZXMkVHJ4X0ZlY2hhLA0KICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IGZ1dHVyZV9mZWF0dXJlcyRQcmVjaW9fRmluYWxfVW5pdGFyaW8NCiAgKQ0KICANCiAgIyA9PT0gUFJFRElDQ0nDk04gQ09OIFhHQm9vc3QgPT09DQogIHRyeUNhdGNoKHsNCiAgICBmZWF0dXJlcyA8LSB4Z2JfbW9kZWwkZmVhdHVyZV9uYW1lcw0KICAgIGlmIChpcy5udWxsKGZlYXR1cmVzKSkgew0KICAgICAgZmVhdHVyZXMgPC0gc2V0ZGlmZihuYW1lcyhmdXR1cmVfZmVhdHVyZXMpLCAiVmVudGEiKQ0KICAgIH0NCiAgICB4Z2JfbWF0cml4IDwtIGFzLm1hdHJpeChmdXR1cmVfZmVhdHVyZXNbLCBmZWF0dXJlcywgZHJvcCA9IEZBTFNFXSkNCiAgICBmdXR1cmVfZGF0YSRWZW50YV9YR0Jvb3N0IDwtIHByZWRpY3QoeGdiX21vZGVsLCB4Z2JfbWF0cml4KQ0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICB3YXJuaW5nKHBhc3RlKCJFcnJvciBhbCBwcmVkZWNpciBjb24gWEdCb29zdCBwYXJhIHByb2R1Y3RvIiwgcHJvZHVjdF9pZCwgIjoiLCBlJG1lc3NhZ2UpKQ0KICAgIGZ1dHVyZV9kYXRhJFZlbnRhX1hHQm9vc3QgPC0gTkENCiAgfSkNCiAgDQogICMgPT09IE3DiVRSSUNBUyA9PT0NCiAgYXZnX2Nvc3RfcGVyX3VuaXQgPC0gbWVkaWFuKGhpc3RfZGF0YSRDb3N0b19WZW50YSAvIGhpc3RfZGF0YSRDYW50LCBuYS5ybSA9IFRSVUUpDQogIA0KICBmdXR1cmVfZGF0YSRVbmlkYWRlc19YR0Jvb3N0IDwtIGZ1dHVyZV9kYXRhJFZlbnRhX1hHQm9vc3QgLyBmdXR1cmVfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8NCiAgZnV0dXJlX2RhdGEkQ29zdG9fWEdCb29zdCA8LSBmdXR1cmVfZGF0YSRVbmlkYWRlc19YR0Jvb3N0ICogYXZnX2Nvc3RfcGVyX3VuaXQNCiAgZnV0dXJlX2RhdGEkTWFyZ2VuX1hHQm9vc3QgPC0gZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCAtIGZ1dHVyZV9kYXRhJENvc3RvX1hHQm9vc3QNCiAgDQogIHJldHVybihmdXR1cmVfZGF0YSkNCn0NCg0KYGBgDQoNCiMjIyBQaXBlbGluZSBjb3JyZWN0bw0KYGBge3J9DQpjb3JyZWdpcl9mb3JtYXRvX2ZlY2hhcyA8LSBmdW5jdGlvbihkYXRvcykgew0KICBpZiAoIlRyeF9GZWNoYSIgJWluJSBjb2xuYW1lcyhkYXRvcykpIHsNCiAgICBkYXRvcyRUcnhfRmVjaGFfT3JpZ2luYWwgPC0gZGF0b3MkVHJ4X0ZlY2hhDQoNCiAgICBpZiAoaXMuY2hhcmFjdGVyKGRhdG9zJFRyeF9GZWNoYSkgJiYNCiAgICAgICAgYW55KGdyZXBsKCJeXFxkezd9LVxcZHsyfS1cXGR7Mn0kIiwgZGF0b3MkVHJ4X0ZlY2hhKSkpIHsNCg0KICAgICAgY2F0KCJDb3JyaWdpZW5kbyBmb3JtYXRvIGRlIGZlY2hhcyBleHRyYcOxby4uLlxuIikNCg0KICAgICAgZGF0b3MkVHJ4X0ZlY2hhIDwtIHNhcHBseShkYXRvcyRUcnhfRmVjaGEsIGZ1bmN0aW9uKGZlY2hhKSB7DQogICAgICAgIGlmIChpcy5uYShmZWNoYSkgfHwgIWlzLmNoYXJhY3RlcihmZWNoYSkpIHJldHVybihOQSkNCg0KICAgICAgICBwYXJ0ZXMgPC0gc3Ryc3BsaXQoZmVjaGEsICItIilbWzFdXQ0KICAgICAgICBpZiAobGVuZ3RoKHBhcnRlcykgPT0gMykgew0KICAgICAgICAgIGZlY2hhX2NvcnJlZ2lkYSA8LSBwYXN0ZSgiMjAyMyIsIHBhcnRlc1syXSwgcGFydGVzWzNdLCBzZXAgPSAiLSIpDQogICAgICAgICAgcmV0dXJuKGZlY2hhX2NvcnJlZ2lkYSkNCiAgICAgICAgfSBlbHNlIHsNCiAgICAgICAgICByZXR1cm4oTkEpDQogICAgICAgIH0NCiAgICAgIH0pDQoNCiAgICAgIGRhdG9zJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRhdG9zJFRyeF9GZWNoYSkNCiAgICAgIGNhdCgiRmVjaGFzIGNvcnJlZ2lkYXMgZXhpdG9zYW1lbnRlLlxuIikNCiAgICB9IGVsc2UgaWYgKCFpbmhlcml0cyhkYXRvcyRUcnhfRmVjaGEsICJEYXRlIikpIHsNCiAgICAgIGNhdCgiSW50ZW50YW5kbyBjb252ZXJ0aXIgZmVjaGFzIGEgZm9ybWF0byBEYXRlLi4uXG4iKQ0KICAgICAgZGF0b3MkVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGF0b3MkVHJ4X0ZlY2hhKQ0KICAgIH0NCiAgfQ0KICByZXR1cm4oZGF0b3MpDQp9DQoNCiMgQXBsaWNhciBsYSBjb3JyZWNjacOzbiBhIHR1IGRhdGFmcmFtZSBhbnRlcyBkZSB1c2FybG8NCmRhdG9zX2ZpbHRyYWRvcyA8LSBjb3JyZWdpcl9mb3JtYXRvX2ZlY2hhcyhkYXRvc19maWx0cmFkb3MpDQpgYGANCg0KYGBge3J9DQpkYXRlc19mdXR1cmUgPC0gc2VxLkRhdGUoYXMuRGF0ZSgiMjAyMy0wMS0wMSIpLCBieSA9ICJtb250aCIsIGxlbmd0aC5vdXQgPSA2KQ0KcHJlY2lvc19vcHRpbW9zX2xpc3RhIDwtIGxpc3QoKQ0KDQpmb3IgKGlkIGluIHByb2R1Y3Rvc19pZHMpIHsNCiAgY2F0KCJFc3RpbWFuZG8gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQ0KDQogIG1vZGVsb19wcmVjaW8gPC0gbW9kZWxvc19wcmVjaW9fbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXQ0KDQogIGlmICghaXMubnVsbChtb2RlbG9fcHJlY2lvKSkgew0KICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKA0KICAgICAgZGF0YSA9IGRhdG9zX2ZpbHRyYWRvcywNCiAgICAgIHByb2R1Y3RfaWQgPSBpZCwNCiAgICAgIHByaWNlX21vZGVscyA9IG1vZGVsb19wcmVjaW8sDQogICAgICBmdXR1cmVfZGF0ZXMgPSBkYXRlc19mdXR1cmUNCiAgICApDQogIH0NCn0NCmBgYA0KDQpgYGB7cn0NCmZvciAoaWQgaW4gbmFtZXMocHJlY2lvc19vcHRpbW9zX2xpc3RhKSkgew0KICBkZl9vcHRpbW8gPC0gcHJlY2lvc19vcHRpbW9zX2xpc3RhW1tpZF1dJHByZWNpb3Nfb3B0aW1vcw0KDQogIGlmICghaW5oZXJpdHMoZGZfb3B0aW1vJFRyeF9GZWNoYSwgIkRhdGUiKSkgew0KICAgIGRmX29wdGltbyRUcnhfRmVjaGEgPC0gYXMuRGF0ZShkZl9vcHRpbW8kVHJ4X0ZlY2hhKQ0KICB9DQoNCiAgY2F0KHBhc3RlMCgiXG4jIyMgUHJvZHVjdG86ICIsIGlkLCAiXG4iKSkNCg0KICBwcmludCgNCiAgICBnZ3Bsb3QoZGZfb3B0aW1vLCBhZXMoeCA9IFRyeF9GZWNoYSwgeSA9IFByZWNpb19PcHRpbWFsKSkgKw0KICAgICAgZ2VvbV9saW5lKGNvbG9yID0gIiMxZjc3YjQiLCBsaW5ld2lkdGggPSAxLjIpICsNCiAgICAgIGdlb21fcG9pbnQoY29sb3IgPSAiIzFmNzdiNCIsIHNpemUgPSAyKSArDQogICAgICBsYWJzKA0KICAgICAgICB0aXRsZSA9IHBhc3RlKCJQcmVjaW8gw5NwdGltbyBwb3IgTWVzIC0gUHJvZHVjdG8iLCBpZCksDQogICAgICAgIHggPSAiRmVjaGEiLA0KICAgICAgICB5ID0gIlByZWNpbyDDk3B0aW1vIg0KICAgICAgKSArDQogICAgICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJWIgJVkiLCBkYXRlX2JyZWFrcyA9ICIxIG1vbnRoIikgKw0KICAgICAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKw0KICAgICAgdGhlbWUoDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgICAgICkNCiAgKQ0KfQ0KDQpgYGANCg0KYGBge3J9DQpydW5fcHJpY2Vfb3B0aW1pemF0aW9uIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWRzLCBmdXR1cmVfZGF0ZXMgPSBOVUxMLCBtb2RlbG9zX3ByZWNpb19saXN0YSA9IE5VTEwpIHsNCiAgaWYgKGlzLm51bGwoZnV0dXJlX2RhdGVzKSkgew0KICAgIGZ1dHVyZV9kYXRlcyA8LSBzZXEuRGF0ZShTeXMuRGF0ZSgpLCBieSA9ICJtb250aCIsIGxlbmd0aC5vdXQgPSA2KQ0KICB9DQoNCiAgcHJlY2lvc19vcHRpbW9zX2xpc3RhIDwtIGxpc3QoKQ0KDQogIGZvciAoaWQgaW4gcHJvZHVjdF9pZHMpIHsNCiAgICBjYXQoIkVzdGltYW5kbyBwcmVjaW9zIMOzcHRpbW9zIHBhcmEgcHJvZHVjdG86IiwgaWQsICJcbiIpDQoNCiAgICBwcmljZV9tb2RlbCA8LSBOVUxMDQogICAgaWYgKCFpcy5udWxsKG1vZGVsb3NfcHJlY2lvX2xpc3RhKSkgew0KICAgICAgcHJpY2VfbW9kZWwgPC0gbW9kZWxvc19wcmVjaW9fbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXQ0KICAgIH0NCg0KICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKA0KICAgICAgZGF0YSA9IGRhdGEsDQogICAgICBwcm9kdWN0X2lkID0gaWQsDQogICAgICBwcmljZV9tb2RlbHMgPSBwcmljZV9tb2RlbCwNCiAgICAgIGZ1dHVyZV9kYXRlcyA9IGZ1dHVyZV9kYXRlcw0KICAgICkNCiAgfQ0KDQogIHJldHVybihwcmVjaW9zX29wdGltb3NfbGlzdGEpDQp9DQoNCmBgYA0KDQpgYGB7cn0NCiMgRnVuY2nDs24gcHJpbmNpcGFsIHF1ZSBpbnRlZ3JhIHRvZG8gZWwgcGlwZWxpbmUgY29uIHNvbG8gWEdCb29zdA0KcnVuX2NvbXBsZXRlX2FuYWx5c2lzIDwtIGZ1bmN0aW9uKGRhdGEsIHRvcF9pZHMsIG1vZGVsb3NfeGdiLCBtb2RlbG9zX3ByZWNpb19saXN0YSA9IE5VTEwpIHsNCiAgIyAxLiBFc3RpbWFyIHByZWNpb3Mgw7NwdGltb3MNCiAgYWxsX3Jlc3VsdHMgPC0gcnVuX3ByaWNlX29wdGltaXphdGlvbihkYXRhLCB0b3BfaWRzLCBtb2RlbG9zX3ByZWNpb19saXN0YSA9IG1vZGVsb3NfcHJlY2lvX2xpc3RhKQ0KDQogICMgMi4gSW50ZWdyYXIgY29uIG1vZGVsbyBYR0Jvb3N0DQogIGludGVncmF0ZWRfcmVzdWx0cyA8LSBsaXN0KCkNCg0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHRvcF9pZHMpKSB7DQogICAgcGlkIDwtIHRvcF9pZHNbaV0NCiAgICBwaWRfc3RyIDwtIGFzLmNoYXJhY3RlcihwaWQpDQoNCiAgICB4Z2JfbW9kZWwgPC0gaWYgKGxlbmd0aChtb2RlbG9zX3hnYikgPj0gaSkgbW9kZWxvc194Z2JbW2ldXSBlbHNlIE5VTEwNCg0KICAgIGZ1dHVyZV9wcmVkaWN0aW9ucyA8LSBpbnRlZ3JhdGVfd2l0aF9leGlzdGluZ19tb2RlbHMoDQogICAgICBkYXRhID0gZGF0YSwNCiAgICAgIHByb2R1Y3RfaWQgPSBwaWQsDQogICAgICBwcmljZV9vcHRfcmVzdWx0cyA9IGFsbF9yZXN1bHRzLA0KICAgICAgeGdiX21vZGVsID0geGdiX21vZGVsDQogICAgKQ0KDQogICAgaW50ZWdyYXRlZF9yZXN1bHRzW1twaWRfc3RyXV0gPC0gZnV0dXJlX3ByZWRpY3Rpb25zDQoNCiAgICBpZiAobnJvdyhmdXR1cmVfcHJlZGljdGlvbnMpID4gMCkgew0KICAgICAgcF9zYWxlcyA8LSBnZ3Bsb3QoZnV0dXJlX3ByZWRpY3Rpb25zLCBhZXMoeCA9IEZlY2hhLCB5ID0gVmVudGFfWEdCb29zdCkpICsNCiAgICAgICAgZ2VvbV9saW5lKGNvbG9yID0gIiMxZjc3YjQiLCBsaW5ld2lkdGggPSAxLjIpICsNCiAgICAgICAgZ2VvbV9wb2ludChzaXplID0gMikgKw0KICAgICAgICBsYWJzKA0KICAgICAgICAgIHRpdGxlID0gcGFzdGUoIlByZWRpY2Npb25lcyBkZSB2ZW50YXMgY29uIHByZWNpb3Mgw7NwdGltb3MgLSBQcm9kdWN0byIsIHBpZCksDQogICAgICAgICAgeCA9ICJGZWNoYSIsDQogICAgICAgICAgeSA9ICJWZW50YXMgZXN0aW1hZGFzICgkKSINCiAgICAgICAgKSArDQogICAgICAgIHRoZW1lX21pbmltYWwoKQ0KDQogICAgICBwX21hcmdpbnMgPC0gZ2dwbG90KGZ1dHVyZV9wcmVkaWN0aW9ucywgYWVzKHggPSBGZWNoYSwgeSA9IE1hcmdlbl9YR0Jvb3N0KSkgKw0KICAgICAgICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIsIHdpZHRoID0gMTUpICsNCiAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1hcmdlbl9YR0Jvb3N0LCAwKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICAgICAgICBsYWJzKA0KICAgICAgICAgIHRpdGxlID0gcGFzdGUoIk1hcmdlbiBlc3BlcmFkbyBjb24gcHJlY2lvcyDDs3B0aW1vcyAtIFByb2R1Y3RvIiwgcGlkKSwNCiAgICAgICAgICB4ID0gIkZlY2hhIiwNCiAgICAgICAgICB5ID0gIk1hcmdlbiBlc3RpbWFkbyAoJCkiDQogICAgICAgICkgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKCkNCg0KICAgICAgYWxsX3Jlc3VsdHNbW3BpZF9zdHJdXSRpbnRlZ3JhdGVkX3Bsb3RzIDwtIGxpc3QoDQogICAgICAgIHNhbGVzID0gcF9zYWxlcywNCiAgICAgICAgbWFyZ2lucyA9IHBfbWFyZ2lucw0KICAgICAgKQ0KICAgIH0NCiAgfQ0KDQogICMgMy4gVmlzdWFsaXphY2nDs24gZGUgcHJlY2lvcyDDs3B0aW1vcw0KICBhbGxfb3B0aW1hbF9wcmljZXMgPC0gZGF0YS5mcmFtZSgpDQoNCiAgZm9yIChwaWQgaW4gdG9wX2lkcykgew0KICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkNCiAgICBpZiAocGlkX3N0ciAlaW4lIG5hbWVzKGFsbF9yZXN1bHRzKSkgew0KICAgICAgb3B0X3ByaWNlcyA8LSBhbGxfcmVzdWx0c1tbcGlkX3N0cl1dJHByZWNpb3Nfb3B0aW1vcyAlPiUNCiAgICAgICAgbXV0YXRlKElEX0ludmVudGFyaW8gPSBwaWQpDQoNCiAgICAgIGFsbF9vcHRpbWFsX3ByaWNlcyA8LSByYmluZChhbGxfb3B0aW1hbF9wcmljZXMsIG9wdF9wcmljZXMpDQogICAgfQ0KICB9DQoNCiAgcF9jb21wYXJpc29uIDwtIGdncGxvdChhbGxfb3B0aW1hbF9wcmljZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBUcnhfRmVjaGEsIHkgPSBQcmVjaW9fT3B0aW1hbCwgY29sb3IgPSBmYWN0b3IoSURfSW52ZW50YXJpbykpKSArDQogICAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBQcmVjaW9zIMOTcHRpbW9zIHBvciBQcm9kdWN0byIsDQogICAgICB4ID0gIkZlY2hhIiwNCiAgICAgIHkgPSAiUHJlY2lvIMOTcHRpbW8iLA0KICAgICAgY29sb3IgPSAiSUQgUHJvZHVjdG8iDQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpDQoNCiAgIyA0LiBNw6l0cmljYXMgZmluYWxlcw0KICBtZXRyaWNhc19vcHRpbWFzIDwtIGRhdGEuZnJhbWUoKQ0KDQogIGZvciAocGlkIGluIHRvcF9pZHMpIHsNCiAgICBwaWRfc3RyIDwtIGFzLmNoYXJhY3RlcihwaWQpDQogICAgaWYgKHBpZF9zdHIgJWluJSBuYW1lcyhpbnRlZ3JhdGVkX3Jlc3VsdHMpKSB7DQogICAgICBwcmVkX2RhdGEgPC0gaW50ZWdyYXRlZF9yZXN1bHRzW1twaWRfc3RyXV0NCg0KICAgICAgaWYgKCJNYXJnZW5fWEdCb29zdCIgJWluJSBuYW1lcyhwcmVkX2RhdGEpKSB7DQogICAgICAgIG1ldHJpY3Nfcm93IDwtIGRhdGEuZnJhbWUoDQogICAgICAgICAgSURfSW52ZW50YXJpbyA9IHBpZCwNCiAgICAgICAgICBQcmVjaW9fUHJvbWVkaW8gPSBtZWFuKHByZWRfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgVmVudGFfVG90YWwgPSBzdW0ocHJlZF9kYXRhJFZlbnRhX1hHQm9vc3QsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgTWFyZ2VuX1RvdGFsID0gc3VtKHByZWRfZGF0YSRNYXJnZW5fWEdCb29zdCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICBNYXJnZW5fUG9yY2VudHVhbCA9IDEwMCAqIHN1bShwcmVkX2RhdGEkTWFyZ2VuX1hHQm9vc3QsIG5hLnJtID0gVFJVRSkgLw0KICAgICAgICAgICAgc3VtKHByZWRfZGF0YSRWZW50YV9YR0Jvb3N0LCBuYS5ybSA9IFRSVUUpDQogICAgICAgICkNCiAgICAgICAgbWV0cmljYXNfb3B0aW1hcyA8LSByYmluZChtZXRyaWNhc19vcHRpbWFzLCBtZXRyaWNzX3JvdykNCiAgICAgIH0NCiAgICB9DQogIH0NCg0KICByZXR1cm4obGlzdCgNCiAgICByZXN1bHRhZG9zID0gYWxsX3Jlc3VsdHMsDQogICAgaW50ZWdyYWNpb24gPSBpbnRlZ3JhdGVkX3Jlc3VsdHMsDQogICAgcHJlY2lvc19vcHRpbW9zID0gYWxsX29wdGltYWxfcHJpY2VzLA0KICAgIG1ldHJpY2FzX29wdGltYXMgPSBtZXRyaWNhc19vcHRpbWFzLA0KICAgIGdyYWZpY29fY29tcGFyYXRpdm8gPSBwX2NvbXBhcmlzb24NCiAgKSkNCn0NCg0KYGBgDQoNCmBgYHtyfQ0KcmVzdWx0YWRvX2NvbXBsZXRvIDwtIHJ1bl9jb21wbGV0ZV9hbmFseXNpcygNCiAgZGF0YSA9IGRhdG9zLA0KICB0b3BfaWRzID0gcHJvZHVjdG9zX2lkcywNCiAgbW9kZWxvc194Z2IgPSBtb2RlbG9zX3hnYl9saXN0YSwNCiAgbW9kZWxvc19wcmVjaW9fbGlzdGEgPSBtb2RlbG9zX3ByZWNpb19saXN0YQ0KKQ0KYGBgDQoNCg0KIyMjIEdyw6FmaWNvIGNvbXBhcmF0aXZvIGRlIHByZWNpb3Mgw7NwdGltb3MgcG9yIHByb2R1Y3RvOg0KYGBge3J9DQojIE1vc3RyYXIgbcOpdHJpY2FzIHNpIGVzdMOhcyBlbiBtb2RvIGludGVyYWN0aXZvDQppZiAoaW50ZXJhY3RpdmUoKSkgVmlldyhyZXN1bHRhZG9fY29tcGxldG8kbWV0cmljYXNfb3B0aW1hcykNCg0KY2F0KCJHcsOhZmljbyBjb21wYXJhdGl2byBkZSBwcmVjaW9zIMOzcHRpbW9zIHBvciBwcm9kdWN0bzpcbiIpDQpgYGANCg0KYGBge3J9DQpwcmludChyZXN1bHRhZG9fY29tcGxldG8kZ3JhZmljb19jb21wYXJhdGl2bykNCmBgYA0KDQojIFByZWRpY2Npw7NuIGRlIHZlbnRhcyBjb24gcHJlY2lvcyBvcHRpbW9zIHBvciBwcm9kdWN0bw0KDQoNCmBgYHtyfQ0KY2F0KCJHcsOhZmljb3MgaW5kaXZpZHVhbGVzIHBvciBwcm9kdWN0bzpcbiIpDQpgYGANCg0KDQpgYGB7cn0NCmZvciAocGlkIGluIG5hbWVzKHJlc3VsdGFkb19jb21wbGV0byRyZXN1bHRhZG9zKSkgew0KICBwbG90cyA8LSByZXN1bHRhZG9fY29tcGxldG8kcmVzdWx0YWRvc1tbcGlkXV0kaW50ZWdyYXRlZF9wbG90cw0KICBpZiAoIWlzLm51bGwocGxvdHMpKSB7DQogICAgY2F0KHBhc3RlMCgiIyMgUHJvZHVjdG86ICIsIHBpZCwgIlxuXG4iKSkNCiAgICBwcmludChwbG90cyRzYWxlcykgICAjIHNvbG8gdXNhIFZlbnRhX1hHQm9vc3QgaW50ZXJuYW1lbnRlDQogICAgcHJpbnQocGxvdHMkbWFyZ2lucykgIyBzb2xvIHVzYSBNYXJnZW5fWEdCb29zdCBpbnRlcm5hbWVudGUNCiAgICANCiAgICBjYXQoIlxuLS0tXG5cbiIpDQogIH0NCn0NCmBgYA0KDQoNCiMjIyBUQUJMQSBGSU5BTCANCmBgYHtyfQ0KdGFibGFfZmluYWwgPC0gZGF0YS5mcmFtZSgpDQoNCmZvciAocGlkIGluIHByb2R1Y3Rvc19pZHMpIHsNCiAgcHJlZCA8LSByZXN1bHRhZG9fY29tcGxldG8kaW50ZWdyYWNpb25bW2FzLmNoYXJhY3RlcihwaWQpXV0NCiAgcmVhbCA8LSBkYXRvc19maWx0cmFkb3MgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHBpZCkNCg0KICBpZiAoIWlzLm51bGwocHJlZCkgJiYgbnJvdyhwcmVkKSA+IDApIHsNCiAgICBwcmVjaW9fcHJvbWVkaW8gPC0gbWVhbihyZWFsJFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKQ0KICAgIG1hcmdlbl9oaXN0b3JpY29fcHJvbWVkaW8gPC0gbWVhbihyZWFsJFZlbnRhIC0gcmVhbCRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQ0KDQogICAgIyBFeHRyYWVyIG1lcyBkZSBsYXMgZmVjaGFzIGZ1dHVyYXMgc2ltdWxhZGFzDQogICAgcHJlZCRNZXNfU2ltdWxhZG8gPC0gbHVicmlkYXRlOjptb250aChwcmVkJEZlY2hhKQ0KDQogICAgIyBDYWxjdWxhciB2ZW50YSBwcm9tZWRpbyBoaXN0w7NyaWNhIHBvciBtZXMNCiAgICB2ZW50YV9oaXN0b3JpY2FfcG9yX21lcyA8LSByZWFsICU+JQ0KICAgICAgbXV0YXRlKE1lcyA9IGx1YnJpZGF0ZTo6bW9udGgoVHJ4X0ZlY2hhKSkgJT4lDQogICAgICBncm91cF9ieShNZXMpICU+JQ0KICAgICAgc3VtbWFyaXNlKFZlbnRhX0hpc3RvcmljYV9Qcm9tZWRpbyA9IG1lYW4oVmVudGEsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpDQoNCiAgICAjIEFncmVnYXIgY29sdW1uYSBkZSB2ZW50YSBlc3BlcmFkYSBoaXN0w7NyaWNhIGFsIGRmIGRlIHByZWRpY2Npw7NuDQogICAgcHJlZCA8LSBwcmVkICU+JQ0KICAgICAgbGVmdF9qb2luKHZlbnRhX2hpc3RvcmljYV9wb3JfbWVzLCBieSA9IGMoIk1lc19TaW11bGFkbyIgPSAiTWVzIikpDQoNCiAgICB0YWJsYV9wcm9kdWN0byA8LSBwcmVkICU+JQ0KICAgICAgc2VsZWN0KEZlY2hhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFZlbnRhX1hHQm9vc3QsIE1hcmdlbl9YR0Jvb3N0LCBWZW50YV9IaXN0b3JpY2FfUHJvbWVkaW8pICU+JQ0KICAgICAgcmVuYW1lKA0KICAgICAgICBQcmVjaW9fT3B0aW1vID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLA0KICAgICAgICBWZW50YV9Fc3BlcmFkYV9YR0Jvb3N0ID0gVmVudGFfWEdCb29zdCwNCiAgICAgICAgTWFyZ2VuX1hHQm9vc3QgPSBNYXJnZW5fWEdCb29zdCwNCiAgICAgICAgVmVudGFfRXNwZXJhZGFfSGlzdG9yaWNhID0gVmVudGFfSGlzdG9yaWNhX1Byb21lZGlvDQogICAgICApICU+JQ0KICAgICAgbXV0YXRlKA0KICAgICAgICBJRF9JbnZlbnRhcmlvID0gcGlkLA0KICAgICAgICBQcmVjaW9fUHJvbWVkaW9fSGlzdG9yaWNvID0gcm91bmQocHJlY2lvX3Byb21lZGlvLCAyKSwNCiAgICAgICAgTWFyZ2VuX0hpc3Rvcmljb19Qcm9tZWRpbyA9IHJvdW5kKG1hcmdlbl9oaXN0b3JpY29fcHJvbWVkaW8sIDIpDQogICAgICApICU+JQ0KICAgICAgc2VsZWN0KElEX0ludmVudGFyaW8sIEZlY2hhLCBQcmVjaW9fUHJvbWVkaW9fSGlzdG9yaWNvLCBQcmVjaW9fT3B0aW1vLA0KICAgICAgICAgICAgIFZlbnRhX0VzcGVyYWRhX0hpc3RvcmljYSwgVmVudGFfRXNwZXJhZGFfWEdCb29zdCwNCiAgICAgICAgICAgICBNYXJnZW5fSGlzdG9yaWNvX1Byb21lZGlvLCBNYXJnZW5fWEdCb29zdCkNCg0KICAgIHRhYmxhX2ZpbmFsIDwtIGJpbmRfcm93cyh0YWJsYV9maW5hbCwgdGFibGFfcHJvZHVjdG8pDQogIH0NCn0NCg0KcHJpbnQodGFibGFfZmluYWwpDQpgYGANCg0KDQoNCg0KDQoNCg0KYGBge3J9DQpzdHIoZGZfZWNkZikNCnN1bW1hcnkoZGZfZWNkZikNCg0KYGBgDQoNCg0KDQo=