0. SETUP ###================================================================

## 0.1 SET PATHS ---------------------------------------------------------------
# DEFINE ROOT DIRECTORY
root_path <- "C:/Users/alden/Dissertation/TP53_isoforms_project"

# SET OTHER DIRECTORIES RELATIVE TO ROOT
scripts_dir <- file.path(root_path, "scripts")
input_dir   <- file.path(root_path, "input")
output_dir  <- file.path(root_path, "output")

## 0.2 OTHER -------------------------------------------------------------------
# SET SEED FOR REPRODUCABILITY
set.seed(42)

1. INSTALL PACKAGES ###=====================================================

## 1.1 INSTALL PACKAGES --------------------------------------------------------
# INSTALL BIOCMANAGER
if (!requireNamespace("BiocManager", quietly = TRUE)) {
    install.packages("BiocManager")
}

# LIST PACKAGES
packages <- c(
  "BiocManager", 
  "MultiAssayExperiment", 
  "SummarizedExperiment", 
  "GenomicRanges",
  "snapcount", 
  "edgeR", 
  "limma", 
  "GSVA", 
  "BiocParallel", 
  "MOFA2", 
  "factoextra", 
  "infotheo", 
  "bnlearn", 
  "survival", 
  "gridExtra",
  "dplyr", 
  "tidyr", 
  "ggplot2", 
  "stringr", 
  "tibble", 
  "data.table", 
  "readxl"
)

# CHECK FOR MISSING PACKAGES
missing_pkgs <- packages[!(packages %in% rownames(installed.packages()))]

# INSTALL
if (length(missing_pkgs) > 0) {
    BiocManager::install(missing_pkgs, update = FALSE, ask = FALSE)
}

# LOAD
invisible(lapply(packages, library, character.only = TRUE))

PART A - BUILDING THE MAE

2. GET ISOFORM COUNTS ###===================================================

## 2.1 DEFINE TARGET REGION ----------------------------------------------------
targets <- GenomicRanges::GRanges(
    seqnames = "chr17",
    ranges = IRanges(
        start = c(7673267, 7673207, 7685260, 7675239, 7674526),
        end = c(7673339, 7673266, 7686371, 7675493, 7674858)
    ),
    strand = "-",
    label = c("exon 9B", "exon 9y", "intron 2", "intron 4", "intron 6")
)

## 2.2 GET DATA ----------------------------------------------------------------
# INITIALIZE QUERY FOR TP53 ACROSS ALL TCGA SAMPLES
qb <- snapcount::QueryBuilder(compilation = "tcga", regions = "TP53")
rse <- snapcount::query_exon(qb, return_rse = TRUE)

# IDENTIFY OVERLAPS THAT MATCH hg38 COORDINATES
hits <- findOverlaps(targets, rowRanges(rse), type = "equal")

# EXTRACT COUNTS TO MATRIX AND APPLY LABLES
filtered_counts <- as.matrix(assay(rse)[subjectHits(hits), , drop = FALSE])
rownames(filtered_counts) <- targets$label[queryHits(hits)]

# AS MATRIX
isoform_counts <- as.matrix(filtered_counts)

## 2.3 SUMMARISE + SAVE --------------------------------------------------------
# GET HEAD AND DIMENSION
head(isoform_counts[, 1:10], 10)
         rail_59761 rail_59762 rail_59763 rail_59764 rail_59765
exon 9B        1262        335        275          0         59
exon 9y         851         99        373          0         41
intron 2      22219       1857      19857       2196       4192
intron 4       2422         48        152          0         48
intron 6       3312        126        153         78         92
         rail_59766 rail_59767 rail_59768 rail_59769 rail_59770
exon 9B          97        222        142        490          0
exon 9y         124         65         62        494          0
intron 2       7174       2275      16291       7602       1949
intron 4          0          0          0        209          0
intron 6         67        116         74        878          0
dim(isoform_counts)
[1]     5 11284
# SAVE
saveRDS(isoform_counts, file = file.path(output_dir, "TP53_isoform_counts.rds"))

3. GET METADATA ###=========================================================

## 3.1 DEFINE METADATA ---------------------------------------------------------
# CREATE METADATA LIST AND RENAME
meta_cols <- c(
  
    # IDENTIFIERS
    barcode_full  =   "gdc_cases.samples.portions.analytes.aliquots.submitter_id",
    project       =   "gdc_cases.project.project_id",
    
    # LIBRARY INFORMATION
    lib.size      =   "mapped_read_count",
    a260_a280     =   "gdc_cases.samples.portions.analytes.a260_a280_ratio",
    
    # BRCA HORMONE STATUS
    er_status     =   "xml_breast_carcinoma_estrogen_receptor_status", 
    pr_status     =   "xml_breast_carcinoma_progesterone_receptor_status", 
    her2_ish      =   "xml_lab_procedure_her2_neu_in_situ_hybrid_outcome_type", 
    her2_ihc      =   "xml_lab_proc_her2_neu_immunohistochemistry_receptor_status",
    
    # THERAPY INFORMATION
    drug_name     =   "cgc_drug_therapy_drug_name",
    drug_type     =   "cgc_drug_therapy_pharmaceutical_therapy_type"
)

## 3.2 EXTRACT METADATA --------------------------------------------------------
meta <- as.data.frame(colData(rse)) %>%
  dplyr::select(any_of(meta_cols)) %>%
  dplyr::rename(any_of(meta_cols)) %>%
  
# DERIVE PATIENT IDENTIFIERS
  dplyr::mutate(
    rail_id            = rownames(.), 
    patient_barcode_15 = substr(barcode_full, 1, 15),
    patient_barcode_12 = substr(barcode_full, 1, 12),
    batch              = stringr::str_split_i(barcode_full, "-", 6)
  ) %>%
  
# CONSOLIDATE SUBTYPE
  dplyr::mutate(
    her2_consolidated = dplyr::case_when(
      her2_ish %in% c("Positive", "Negative") ~ her2_ish,
      her2_ihc %in% c("Positive", "Negative") ~ her2_ihc,
      TRUE ~ ""
    ),
    hr_consolidated = dplyr::case_when(
      er_status == "Positive" | pr_status == "Positive" ~ "Positive", 
      er_status == "Negative" & pr_status == "Negative" ~ "Negative",
      TRUE ~ ""
    ),

# CLASSIFY CANCER SUBTYPE (PAM50)
    breast_cancer_subtype = dplyr::case_when(
      project != "TCGA-BRCA" ~ "Non-BRCA",
      hr_consolidated == "Positive" & her2_consolidated == "Negative" ~ "LuminalA",
      hr_consolidated == "Positive" & her2_consolidated == "Positive" ~ "LuminalB",
      hr_consolidated == "Negative" & her2_consolidated == "Positive" ~ "HER2-enriched",
      hr_consolidated == "Negative" & her2_consolidated == "Negative" ~ "Triple-negative",
      TRUE ~ "Unclassified BRCA"
    )
  ) %>%

# DEDUPLICATE ALIQUOTS
  dplyr::distinct(patient_barcode_15, .keep_all = TRUE) %>%

# UPDATE ROWNAMES
  tibble::remove_rownames() %>%
  tibble::column_to_rownames("patient_barcode_15")

## 3.3 UPDATE "isoform_counts" -------------------------------------------------

# RENAME USING TCGA BARCODE
isoform_counts <- isoform_counts[, meta$rail_id] # EXPLICIT ORDERING FOR CORRECT MAPPING
colnames(isoform_counts) <- rownames(meta)

## 3.4 SUBSET TO BRCA ----------------------------------------------------------

# SUBSET "meta"
meta <- meta %>%
  dplyr::filter(project == "TCGA-BRCA")

# SAVE SAMPLE LIST
brca_samples <- rownames(meta)

# SUBSET "isoform_counts"
isoform_counts <- isoform_counts[, brca_samples]

## 3.3 SUMMARISE ---------------------------------------------------------------
# GET ISOFORM MEANS
isoform_means <- rowMeans(isoform_counts)
print(isoform_means)
  exon 9B   exon 9y  intron 2  intron 4  intron 6 
 158.2591  115.4488 4274.9299  126.4282  224.2822 
# GET "meta" HEAD AND DIMENSION
head(meta)
dim(meta)
[1] 1212   16
# GET "isoform_counts" HEAD AND DIMENSION
head(isoform_counts[, 1:10], 10)
         TCGA-A2-A0CL-01 TCGA-A8-A08G-01 TCGA-A2-A0D2-01
exon 9B              490               0             235
exon 9y              494               0             112
intron 2            7602            2266            8967
intron 4             209               0             153
intron 6             878              55              87
         TCGA-A7-A0DB-01 TCGA-BH-A0BT-11 TCGA-AN-A04D-01
exon 9B               68              63             204
exon 9y              111              87              73
intron 2            1194            4390            6909
intron 4              53              92             238
intron 6             174             118             362
         TCGA-BH-A0DO-11 TCGA-BH-A0B5-11 TCGA-AO-A0J3-01
exon 9B              368              78              29
exon 9y              236               5              55
intron 2            2632             699            6399
intron 4              50               0              50
intron 6             444             161              70
         TCGA-B6-A0RE-01
exon 9B               20
exon 9y              169
intron 2            3938
intron 4             100
intron 6             235
dim(isoform_counts)
[1]    5 1212

4. NORMALISATION ###========================================================

# PREPARE DGE LIST
dge <- edgeR::DGEList(
    counts = isoform_counts, 
    samples = meta, 
    lib.size = meta$lib.size
)

# TMM NORMALISATION
dge <- edgeR::calcNormFactors(dge, method = "TMM")

# LOGCPM NORMALISATION
isoform_logcpm <- edgeR::cpm(dge, log = TRUE, prior.count = 1)

BATCH CORRECTION (limma::removeBatchEffect) REDUCED MUTUAL INFORMATION BETWEEN ISOFORMS AND SURVIVAL

6. PCA ###==================================================================

## 6.1 COMPUTE PCA -------------------------------------------------------------
isoform_pca <- prcomp(t(isoform_logcpm), scale. = TRUE)
head(isoform_pca$rotation, 10)
                 PC1        PC2        PC3         PC4          PC5
exon 9B  -0.59484692 -0.2692719  0.2526056 -0.09666798 -0.707457072
exon 9y  -0.59611159 -0.2607293  0.2434739 -0.13704115  0.706124211
intron 2 -0.07122502  0.7164767  0.6734204  0.16754286  0.004741838
intron 4 -0.32466305  0.5533393 -0.4727046 -0.60364507 -0.023928730
intron 6 -0.42465163  0.1999743 -0.4471758  0.76119448  0.017263268
## 6.2 SCREE PLOT --------------------------------------------------------------
factoextra::fviz_eig(
    isoform_pca, 
    choice = "variance",  
    geom = "line",        
    addlabels = TRUE,     
) +
theme_minimal() +
theme(
    panel.grid.major = element_blank(), 
    panel.grid.minor = element_blank(),
    axis.line = element_line(colour = "black"),
    panel.border = element_blank()
)


## 6.3 BIPLOTS -----------------------------------------------------------------

# FUNCTION TI CREATE BIPLOTS
create_biplot <- function(pca_obj, pc_x, pc_y) {
  
  # PLOT 
  p <- factoextra::fviz_pca_var(
    pca_obj,
    axes = c(pc_x, pc_y),
    col.var = "contrib",
    gradient.cols = c("#006EAE", "#CA9B23", "#C5373D"),
    repel = TRUE,
    title = paste("PC", pc_x, "vs PC", pc_y)
  )
  
  # THEME
  p <- p +
    theme_minimal() +
    theme(
      panel.grid.major = element_blank(), 
      panel.grid.minor = element_blank(),
      axis.line = element_line(colour = "black"),
      plot.title = element_text(size = 10, face = "bold"),
      
      # SET ALL BACKGROUNDS TO TRANSPARENT
      plot.background = element_rect(fill = "transparent", color = NA),
      panel.background = element_rect(fill = "transparent", color = NA),
      legend.background = element_rect(fill = "transparent", color = NA)
    )
  
  return(p)
}

# RUN FUNCTION IN LOOP
plot_list <- list()
num_pcs <- ncol(isoform_pca$x)

for (i in 1:min(4, (num_pcs - 1))) {
  plot_list[[i]] <- create_biplot(isoform_pca, i, i + 1)
}

# ARANGE IN 2X2 GRID
combined_pca_plot <- gridExtra::grid.arrange(
  grobs = plot_list, 
  ncol = 2, 
  nrow = 2,
  top = grid::textGrob("TP53 Isoform PCA Loading Comparisons", 
                       gp = grid::gpar(fontsize = 14, fontface = "bold"))
)


# SAVE TO OUTPUT DIRECTORY
ggplot2::ggsave(
  filename = file.path(output_dir, "TP53_PCA_grid_TRANSPARENT.png"),
  plot = combined_pca_plot,
  width = 10, 
  height = 10, 
  dpi = 300,
  bg = "transparent" 
)

# 6.4 EXTRACT PATIENT SCORES ------------------------------------------------------------------
isoform_pca <- as.data.frame(isoform_pca$x)

7. LOAD TCGA DATA ###=======================================================

## 7.1 RNASEQ ------------------------------------------------------------------
rnaseq_file <- "EB++AdjustPANCAN_IlluminaHiSeq_RNASeqV2.geneExp.xena.gz"
rnaseq <- data.table::fread(file.path(input_dir, rnaseq_file))
rnaseq <- as.matrix(rnaseq[, -1, with = FALSE], rownames = rnaseq[[1]])
rnaseq <- rnaseq[, intersect(colnames(rnaseq), brca_samples)]
rnaseq <- as.matrix(rnaseq[!duplicated(rownames(rnaseq)), ])

## 7.2 MUTATION ----------------------------------------------------------------
mutation_file <- "mc3.v0.2.8.PUBLIC.nonsilentGene.xena.gz"
mutation <- data.table::fread(file.path(input_dir, mutation_file))
mutation <- as.matrix(mutation[, -1, with = FALSE], rownames = mutation[[1]])
mutation <- mutation[, intersect(colnames(mutation), brca_samples)]

## 7.3 RPPA --------------------------------------------------------------------
rppa_file <- "TCGA-RPPA-pancan-clean.xena.gz"
rppa <- data.table::fread(file.path(input_dir, rppa_file))
rppa <- as.matrix(rppa[, -1, with = FALSE], rownames = rppa[[1]])
rppa <- rppa[, intersect(colnames(rppa), brca_samples)]

## 7.4 STEMNESS ----------------------------------------------------------------
stemness_file <- "StemnessScores_RNAexp_20170127.2.tsv.gz"
stemness <- data.table::fread(file.path(input_dir, stemness_file))
stemness <- as.matrix(stemness[, -1, with = FALSE], rownames = stemness[[1]])
stemness <- stemness[, intersect(colnames(stemness), brca_samples)]

## 7.5 HDR ---------------------------------------------------------------------
hdr_file <- "TCGA.HRD_withSampleID.txt.gz"
hdr <- data.table::fread(file.path(input_dir, hdr_file))
hdr <- as.matrix(hdr[, -1, with = FALSE], rownames = hdr[[1]])
hdr <- hdr[, intersect(colnames(hdr), brca_samples)]

## 7.6 IMMUNE ------------------------------------------------------------------
immune_subtype_file <- "TCGA_pancancer_10852whitelistsamples_68ImmuneSigs.xena.gz"
immune <- data.table::fread(file.path(input_dir, immune_subtype_file))
immune <- as.matrix(immune[, -1, with = FALSE], rownames = immune[[1]])
immune <- immune[, intersect(colnames(immune), brca_samples)]

8. LOAD TCGA METADATA ###===================================================

## 8.1 SURVIVAL DATA -----------------------------------------------------------
survival_file <- "Survival_SupplementalTable_S1_20171025_xena_sp"
survival <- data.table::fread(file.path(input_dir, survival_file))

# RENAME
survival <- survival %>%
  dplyr::select(
    sample, 
    age = age_at_initial_pathologic_diagnosis, 
    histology = histological_type,
    menopause = menopause_status,
    tumor_status,
    OS, OS.time, DSS, DSS.time, DFI, DFI.time, PFI, PFI.time,
    ajcc_stage = ajcc_pathologic_tumor_stage
  )

# COLLAPSE STAGE INTO 4
survival <- survival %>%
  dplyr::mutate(
    stage = dplyr::case_when(
      stringr::str_detect(ajcc_stage, "^Stage I[A-B]?$") ~ "Stage 1",
      stringr::str_detect(ajcc_stage, "^Stage II[A-B]?$") ~ "Stage 2",
      stringr::str_detect(ajcc_stage, "^Stage III[A-C]?$") ~ "Stage 3",
      stringr::str_detect(ajcc_stage, "^Stage IV$") ~ "Stage 4",
      TRUE ~ NA_character_
    ),
    stage = factor(stage, levels = c("Stage 1", "Stage 2", "Stage 3", "Stage 4"))
  )

# SUBSET
survival <- survival %>%
  dplyr::filter(sample %in% brca_samples) %>%
  dplyr::distinct(sample, .keep_all = TRUE) %>%
  tibble::column_to_rownames("sample")

## 8.4 MERGE WITH "meta" -------------------------------------------------------
meta <- merge(meta, survival, by = "row.names", all.x = TRUE) %>% tibble::column_to_rownames("Row.names")

9. MOFA FACTORS ###=========================================================

## 9.1 FEATURE SELECTION -------------------------------------------------------

# RNASEQ: TOP 5000 BY VARIANCE
rna_vars <- apply(rnaseq, 1, var, na.rm = TRUE)
mofa_rna_features <- names(sort(rna_vars, decreasing = TRUE))[1:5000]

# MUTATION: 1% FREQUENCY
mut_freq <- rowMeans(mutation, na.rm = TRUE)
mofa_mut_features <- names(mut_freq[mut_freq >= 0.01])

# RPPA: REMOVE EMPTY ROWS
mofa_protein_features <- rownames(rppa)[rowSums(!is.na(rppa)) > 0]

## 9.2 INITIALIZE MOFA ---------------------------------------------------------

# GET UNIQUE SAMPLES
all_samples <- unique(c(colnames(rnaseq), colnames(mutation), colnames(rppa), colnames(immune)))

# SUBSET DATA
mofa_rna <- rnaseq[mofa_rna_features, ]
mofa_mut <- mutation[mofa_mut_features, ]
mofa_prot <- rppa[mofa_protein_features, ]
mofa_imm  <- immune

# ALIGN
mofa_input <- list(
  RNAseq   = mofa_rna[, match(all_samples, colnames(mofa_rna))],
  Mutation = mofa_mut[, match(all_samples, colnames(mofa_mut))],
  Protein  = mofa_prot[, match(all_samples, colnames(mofa_prot))],
  Immune   = mofa_imm[, match(all_samples, colnames(mofa_imm))]
)

# FIX COLNAMES
for(i in 1:4) { 
  colnames(mofa_input[[i]]) <- all_samples 
}

# CREATE MOFA OBJECT
MOFAobj <- MOFA2::create_mofa(mofa_input)

## 9.3 TRAIN MODEL -------------------------------------------------------------

# SETTINGS
model_opts <- MOFA2::get_default_model_options(MOFAobj)
model_opts$num_factors <- 24 
model_opts$likelihoods[["Mutation"]] <- "bernoulli"

MOFAobj <- MOFA2::prepare_mofa(MOFAobj, model_options = model_opts) %>% 
  MOFA2::run_mofa(use_basilisk = TRUE)

## 9.4 SCREE PLOT --------------------------------------------------------------

# EXTRACT VARIANCE EXPLAINED
vars <- MOFA2::get_variance_explained(MOFAobj)$r2_per_factor[[1]]

# CALCULATE TOTAL VARIANCE PER FACTOR
scree_data <- data.frame(
  Factor = paste0("Factor", 1:nrow(vars)),
  Variance = rowSums(vars)
) %>%
  mutate(Factor = factor(Factor, levels = Factor))

# SCREE PLOT
ggplot(scree_data, aes(x = Factor, y = Variance, group = 1)) +
  geom_line(color = "steelblue", size = 1) +
  geom_point(color = "darkblue", size = 3) +
  theme_minimal() +
  labs(title = "MOFA Scree Plot",
       subtitle = "Total Variance Explained across all Omics Views",
       y = "Total Variance Explained (%)",
       x = "Latent Factors") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# EXTRACT FACTORS
mofa_factors <- MOFA2::get_factors(MOFAobj, factors = "all")[[1]]
colnames(mofa_factors) <- paste0("MOFA_", colnames(mofa_factors))

## 9.5 FACTOR LOADINGS ---------------------------------------------------------

# GET FACTOR LOADINGS (EXAMPLE USAGE)
imm_weights <- MOFA2::get_weights(MOFAobj, views = "Immune", factors = "Factor3")[[1]]
imm_weights

## 9.6 K-MEANS CLUSTERING ------------------------------------------------------

# EXTRACT TOP 8 FACTORS
mofa_factors_mat <- MOFA2::get_factors(MOFAobj)[[1]][, 1:8]

# RUN UMAP
MOFAobj <- MOFA2::run_umap(MOFAobj, factors = 1:8, n_neighbors = 15, min_dist = 0.1)

# DIAGNOSTIC PLOTS
p_elbow <- factoextra::fviz_nbclust(mofa_factors_mat, kmeans, method = "wss") +
  labs(title = "Elbow Method (WSS)", x = "Number of Clusters (k)") +
  theme_minimal()

p_sil <- factoextra::fviz_nbclust(mofa_factors_mat, kmeans, method = "silhouette") +
  labs(title = "Silhouette Analysis", x = "Number of Clusters (k)") +
  theme_minimal()

# SAVE DIAGNOSTIC PLOTS
diag_combined <- gridExtra::arrangeGrob(p_elbow, p_sil, ncol = 2)
ggplot2::ggsave(file.path(output_dir, "MOFA_Clustering_Diagnostics_Top8.png"), 
                diag_combined, width = 10, height = 5, bg = "transparent")

# RUN K-MEANS CLUSTERING (K=4)
km_res <- kmeans(mofa_factors_mat, centers = 3, nstart = 25)

# SYNC CLUSTERS TO METADATA AND MOFA OBJ
meta$mofa_cluster <- as.factor(paste0("Cluster_", km_res$cluster[match(rownames(meta), names(km_res$cluster))]))

MOFA2::samples_metadata(MOFAobj) <- meta %>%
  mutate(sample = rownames(.)) %>%
  relocate(sample)

# DEFINE THEME
clean_theme <- theme_minimal() + 
  theme(
    panel.grid.major = element_blank(), 
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "transparent", color = NA),
    plot.background  = element_rect(fill = "transparent", color = NA),
    axis.line        = element_line(color = "black"),
    legend.background = element_rect(fill = "transparent", color = NA)
  )

# PLOT UMAP (MOFA CLUSTERS)
p1 <- MOFA2::plot_dimred(
  MOFAobj, 
  method = "UMAP", 
  color_by = "mofa_cluster", 
  dot_size = 2
) + 
  scale_color_brewer(palette = "Set1") +
  clean_theme +
  labs(title = "MOFA UMAP: Multi-omic Clusters", subtitle = "K-means (K=4) on Top 8 Factors")

# PLOT UMAP (PAM50 SUBTYPE)
p2 <- MOFA2::plot_dimred(
  MOFAobj, 
  method = "UMAP", 
  color_by = "breast_cancer_subtype", 
  dot_size = 2
) + 
  scale_color_brewer(palette = "Dark2") +
  clean_theme +
  labs(title = "MOFA UMAP: PAM50 Subtypes", subtitle = "Clinical Label Comparison")

# SAVE UMAPS
umap_combined <- gridExtra::arrangeGrob(p1, p2, ncol = 2)
ggplot2::ggsave(file.path(output_dir, "MOFA_UMAP_Clusters_vs_Subtypes.png"), 
                umap_combined, width = 12, height = 6, bg = "transparent")

# DISPLAY UMAPS
grid::grid.draw(umap_combined)

10. MARTINGALE RESIDUALS ###=================================================

## 10.1 EXTRACT RESIDUALS ------------------------------------------------------
target_outcomes <- list(os = "OS", pfi = "PFI")

for (m in names(target_outcomes)) {
  
  # 1. CONSTRUCT FORMULA
  surv_formula <- as.formula(paste0("survival::Surv(", target_outcomes[[m]], ".time, ", 
                                    target_outcomes[[m]], ") ~ 1"))
  
  # 2. FIT COX MODEL
  fit <- survival::coxph(surv_formula, data = meta, na.action = na.exclude)
  
  # 3. ATTACH TO META
  meta[[paste0(m, "_risk_score")]] <- residuals(fit, type = "martingale")
}

11. BUILD MAE OBJECT ###=====================================================

## 11.1 TRANSPOSE --------------------------------------------------------------
isoform_pca <- t(isoform_pca)
mofa_factors <- t(mofa_factors)

## 11.2 PREPARE EXPERIMENT LIST ------------------------------------------------
EXP_LIST <- list(
    isoform_pca    = isoform_pca,  
    isoform_logcpm = isoform_logcpm,  
    rnaseq         = rnaseq,          
    mutation       = mutation,        
    rppa           = rppa,           
    stemness       = stemness,        
    hrd            = hdr,             
    immune         = immune,          
    mofa_factors   = mofa_factors
)

## 11.3 CREATE MAE OBJECT ------------------------------------------------------
mae <- MultiAssayExperiment::MultiAssayExperiment(
    experiments = EXP_LIST,
    colData     = meta
)

# PRINT SUMMARY
print(mae)
A MultiAssayExperiment object of 9 listed
 experiments with user-defined names and respective classes.
 Containing an ExperimentList class object of length 9:
 [1] isoform_pca: matrix with 5 rows and 1212 columns
 [2] isoform_logcpm: matrix with 5 rows and 1212 columns
 [3] rnaseq: matrix with 20530 rows and 1212 columns
 [4] mutation: matrix with 40543 rows and 788 columns
 [5] rppa: matrix with 258 rows and 874 columns
 [6] stemness: matrix with 2 rows and 1187 columns
 [7] hrd: matrix with 4 rows and 1053 columns
 [8] immune: matrix with 68 rows and 1187 columns
 [9] mofa_factors: matrix with 24 rows and 1212 columns
Functionality:
 experiments() - obtain the ExperimentList instance
 colData() - the primary/phenotype DataFrame
 sampleMap() - the sample coordination DataFrame
 `$`, `[`, `[[` - extract colData columns, subset, or experiment
 *Format() - convert into a long or wide DataFrame
 assays() - convert ExperimentList to a SimpleList of matrices
 exportClass() - save data to flat files
## 11.4 SAVING & CLEANUP -------------------------------------------------------
saveRDS(mae, file = file.path(output_dir, "TP53_Isoforms_MAE.rds"))

12. FUNCTION TO EXTRACT DATA ###=============================================

## 12.1 FUNCTION TO RETRIEVE DATA -----------------------------------------------
get_data <- function(mae, config, clinical = NULL) {
  
  extracted <- list()
  for (assay in names(config)) {
    items <- config[[assay]]
    if (isTRUE(items)) {
      extracted[[assay]] <- mae[[assay]]
    } else {
      valid <- intersect(items, rownames(mae[[assay]]))
      extracted[[assay]] <- mae[[assay]][valid, , drop = FALSE]
    }
  }
  
  temp_mae <- MultiAssayExperiment(experiments = extracted, colData = colData(mae))
  
  df <- as.data.frame(longForm(temp_mae, colDataCols = clinical)) %>%
    dplyr::select(-assay, -colname) %>%  # Drop these to allow collapsing
    tidyr::pivot_wider(names_from = "rowname", values_from = "value") %>%
    # CONVERT BARCODE COLUMN TO ROWNAMES
    tibble::column_to_rownames("primary")
  
  return(df)
}

PART B - BAYESIAN NETWORK INFERENCE

13. MAIN FUNCTION ###=======================================================

#' RUN BAYESIAN NETWORKS (REFACTORED)
#' @param df_raw DATAFRAME
#' @param n_restarts NUMBER OF RANDOM RESTARTS
#' @param bl BLACKLIST
#' @param run_name FOLDER NAME OF OUTPUT
#' @param ref_color REFERENCE COLOUR (AIC)
#' @param comp_color COMPARISON COLOUR (MATCHES/MB)

#' RUN BAYESIAN NETWORKS (REFACTORED V3)
run_networks <- function(df_raw, n_restarts = 50, bl = NULL, 
                         run_name = "Final_TP53_Analysis",
                         ref_color = "orange3",
                         comp_color = "orange") {
  
  # --- 1. RUN 6 BNs -------------------------------
  run_path <- file.path(output_dir, run_name)
  dir.create(run_path, recursive = TRUE, showWarnings = FALSE)
  
  cat("================================================\n")
  cat("BN PIPELINE RUN: ", run_name, "\n")
  cat("SAMPLE SIZE (N):", nrow(df_raw), "\n")
  cat("================================================\n")
  
  disc_levels  <- c(2, 3)
  scores       <- c("aic", "bic", "bde")
  all_results  <- list()
  ref_id       <- "2disc.aic"
  target_node  <- "RISK"
  master_nodes <- colnames(df_raw)

  for (d in disc_levels) {
    df_disc <- bnlearn::discretize(df_raw, method = 'quantile', breaks = d)
    df_disc <- as.data.frame(lapply(df_disc, droplevels))[, master_nodes]

    for (s in scores) {
      id <- paste0(d, "disc.", s)
      cat("\n>>> PROCESSING CONFIGURATION:", id, "\n")
      
      set.seed(123)
      starts <- c(list(empty.graph(master_nodes)),
                  random.graph(nodes = master_nodes, num = n_restarts - 1, method = "ic-dag", max.degree = 1))
      
      net_list <- lapply(starts, function(g) {
        tryCatch({ structural.em(df_disc, maximize = "hc", start = g, 
                                 maximize.args = list(score = s, blacklist = bl))
        }, error = function(e) return(NULL))
      })
      
      net_list <- net_list[!sapply(net_list, is.null)]
      avg_dag  <- averaged.network(custom.strength(net_list, nodes = master_nodes))
      
      print(avg_dag) 
      
      mb_nodes <- mb(avg_dag, target_node)
      curr_amat <- amat(avg_dag)[master_nodes, master_nodes]
      
      res_obj <- list(dag = avg_dag, adjacency = curr_amat, nodes = master_nodes, data = df_disc, mb = mb_nodes)
      all_results[[id]] <- res_obj
      
      saveRDS(res_obj, file.path(run_path, paste0(id, ".rds")))
      assign(paste0(run_name, ".", id), res_obj, envir = .GlobalEnv)
      
      # HIGH RES BN PLOTS
      png(file.path(run_path, paste0(id, ".png")), width = 2400, height = 2100, res = 300, bg = "transparent", type = "cairo")
      if (length(mb_nodes) > 0) {
        graphviz.plot(avg_dag, highlight = list(nodes = mb_nodes, fill = comp_color, col = "black"), 
                      main = paste(id, "| MB of", target_node))
      } else { 
        graphviz.plot(avg_dag, main = paste(id, "| No MB for", target_node)) 
      }
      dev.off()
    }
  }

  # --- 2. ADJACENCY MATRIX -------------------------------
  # (Keeping your original adjacency grid logic as requested)
  adj_mats <- lapply(all_results, function(x) x$adjacency)
  summed_mat <- Reduce("+", adj_mats)
  universal_mat <- (summed_mat == length(adj_mats)) 
  ref_mat <- all_results[[ref_id]]$adjacency
  plot_list <- list()
  config_names <- names(all_results)

  for (i in seq_along(config_names)) {
    id <- config_names[i]
    curr_mat <- all_results[[id]]$adjacency
    
    plot_df <- as.data.frame(curr_mat) %>%
      tibble::rownames_to_column("from") %>%
      tidyr::pivot_longer(-from, names_to = "to", values_to = "exists") %>%
      rowwise() %>%
      mutate(
        is_univ = universal_mat[from, to],
        is_ref  = ref_mat[from, to],
        category = case_when(
          exists == 1  & is_univ == TRUE ~ "Universal",
          id == ref_id & exists == 1 & is_univ == FALSE ~ "Reference Only",
          exists == 1  & is_ref == 1 & is_univ == FALSE ~ "Match Ref",
          exists == 1  & is_ref == 0 & is_univ == FALSE ~ "New Edge",
          TRUE ~ "None"
        )
      ) %>% ungroup()

    is_left_col   <- i %in% c(1, 4)
    is_bottom_row <- i %in% c(4, 5, 6)

    p <- ggplot(plot_df, aes(x = factor(to, levels = master_nodes), 
                             y = factor(from, levels = rev(master_nodes)))) +
      geom_tile(aes(fill = category), color = "gray85", linewidth = 0.2) +
      scale_fill_manual(
        values = c("Universal" = "black", "Reference Only" = ref_color, 
                   "Match Ref" = comp_color, "New Edge" = "lightgray", "None" = "white"),
        guide = "none"
      ) +
      labs(title = id, x = NULL, y = NULL) +
      theme_minimal() +
      theme(
        aspect.ratio = 1,
        panel.border = element_rect(color = "black", fill = NA, linewidth = 0.8),
        panel.grid   = element_blank(),
        plot.title   = element_text(hjust = 0.5, size = 10, face = "bold"),
        axis.text.x  = if(is_bottom_row) element_text(angle = 90, vjust = 0.5, hjust = 1, size = 7) else element_blank(),
        axis.ticks.x = if(is_bottom_row) element_line(color = "black") else element_line(color = NA),
        axis.text.y  = if(is_left_col) element_text(size = 7) else element_blank(),
        axis.ticks.y = if(is_left_col) element_line(color = "black") else element_line(color = NA),
        axis.ticks.length = unit(3, "pt"),
        plot.margin = margin(5, 5, 5, 5),
        plot.background = element_rect(fill = "transparent", color = NA)
      )
    plot_list[[i]] <- p
  }

  final_grid <- patchwork::wrap_plots(plot_list, ncol = 3, nrow = 2) + 
    plot_annotation(
      title = paste("Structural Stability Analysis:", run_name),
      subtitle = "Row 1: 2-level Discretization | Row 2: 3-level Discretization",
      theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
                    plot.subtitle = element_text(size = 11, hjust = 0.5),
                    plot.background = element_rect(fill = "transparent", color = NA))
    )
  
  ggsave(file.path(run_path, "Adjacency_Comparison_Grid_Final.png"), 
         plot = final_grid, width = 11, height = 7.5, bg = "transparent", type = "cairo")

  # --- 3. CPT (REFERENCE ONLY) -------------------------------
  ref_res  <- all_results[[ref_id]]
  mb_nodes <- ref_res$mb
  
  # Identify variables and order them (putting PC3 before Risk if present)
  pc_in_mb <- grep("^PC[1-5]$", mb_nodes, value = TRUE)
  other_mb <- setdiff(mb_nodes, pc_in_mb)
  
  if ("PC3" %in% pc_in_mb) {
    ordered_vars <- c(other_mb, setdiff(pc_in_mb, "PC3"), "PC3", target_node)
  } else {
    ordered_vars <- c(other_mb, pc_in_mb, target_node)
  }
  
  # 1. Generate Frequency Table (N) and Probability Table
  raw_counts   <- table(ref_res$data[, ordered_vars])
  prob_table   <- prop.table(raw_counts, margin = 1:(length(ordered_vars) - 1))
  
  # 2. Convert to DataFrames
  df_probs  <- as.data.frame(prob_table)
  df_counts <- as.data.frame(raw_counts)
  
  # 3. Merge Probabilities and Sample Numbers (N)
  final_cpt_output <- df_probs %>%
    dplyr::rename(Probability = Freq) %>%
    dplyr::mutate(N = df_counts$Freq)

  # 4. Save CSV
  write.csv(final_cpt_output, file.path(run_path, "Reference_Risk_CPT_with_N.csv"), row.names = FALSE)

  # --- 4. MOSAIC PLOT (REFERENCE ONLY) -------------------------------
  n_configs <- prod(dim(counts_table)[-length(dim(counts_table))])
  p_high    <- as.vector(cpt_table)[(n_configs + 1):(2 * n_configs)]
  p_high[is.na(p_high)] <- 0
  
  p_contrast <- 1 / (1 + exp(-10 * (p_high - 0.5))) 
  grad_pal   <- colorRampPalette(c("#F5F5F5", ref_color))(100)
  risk_colors <- grad_pal[round(p_contrast * 99) + 1]
  color_array <- array(c(rep("#FFFFFF00", n_configs), risk_colors), dim = dim(counts_table))

  # MOSAIC PLOT WITH THIN BLACK OUTLINES
  png(file.path(run_path, "Reference_Mosaic_Risk_Final.png"), width = 3600, height = 2700, res = 300, bg = "transparent", type = "cairo")
  mosaic(counts_table, 
         gp = gpar(fill = color_array, col = "black", lwd = 0.5), # col = "black" for thin black outlines
         main = paste("Reference Run:", run_name, "Prognostic Risk Hierarchy"),
         labeling = labeling_border(gp_labels = gpar(fontsize = 9, fontface = "bold"), rot_labels = c(0, 90, 0, 90)))
  dev.off()

  cat("\nDONE. All outputs saved to:", run_path, "\n")
  return(all_results)
}

14. MOFA FACTORS ###========================================================

## 2.1 GET DATA
# DEFINE VARIABLES
selection <- list(
  isoform_pca  = TRUE, 
  mofa_factors = paste0("Factor", 1:8), 
  hrd          = "HRD",
  stemness     = "RNAss"
)

# DEFINE METADATA
meta_vars <- c("os_risk_score")

# RUN get_data
mofa_data <- as.data.frame(get_data(mae, selection, meta_vars)) %>%
  rename(RISK = os_risk_score)

## 2.2 BLACKLIST
mofa_bl <- data.frame(
  from = "RISK", 
  to   = setdiff(colnames(mofa_data), "RISK")
)

## 2.3 RUN MAIN FUNCTION
my_nets <- run_networks(
  df_raw = mofa_data,
  n_restarts = 100,
  bl = mofa_bl,
  run_name = "mofa",
  ref_color = "#006EAE",
  comp_color = "#9BCAE9"
)
================================================
BN PIPELINE RUN:  mofa 
SAMPLE SIZE (N): 1212 
================================================

>>> PROCESSING CONFIGURATION: 2disc.aic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 16 
  arcs:                                  55 
    undirected arcs:                     1 
    directed arcs:                       54 
  average markov blanket size:           8.25 
  average neighbourhood size:            6.88 
  average branching factor:              3.38 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.89 


>>> PROCESSING CONFIGURATION: 2disc.bic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 16 
  arcs:                                  29 
    undirected arcs:                     5 
    directed arcs:                       24 
  average markov blanket size:           4.25 
  average neighbourhood size:            3.62 
  average branching factor:              1.50 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.33 


>>> PROCESSING CONFIGURATION: 2disc.bde 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 16 
  arcs:                                  27 
    undirected arcs:                     3 
    directed arcs:                       24 
  average markov blanket size:           4.25 
  average neighbourhood size:            3.38 
  average branching factor:              1.50 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.62 


>>> PROCESSING CONFIGURATION: 3disc.aic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 16 
  arcs:                                  35 
    undirected arcs:                     4 
    directed arcs:                       31 
  average markov blanket size:           5.00 
  average neighbourhood size:            4.38 
  average branching factor:              1.94 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.03 


>>> PROCESSING CONFIGURATION: 3disc.bic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 16 
  arcs:                                  20 
    undirected arcs:                     4 
    directed arcs:                       16 
  average markov blanket size:           2.75 
  average neighbourhood size:            2.50 
  average branching factor:              1.00 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.01 


>>> PROCESSING CONFIGURATION: 3disc.bde 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 16 
  arcs:                                  16 
    undirected arcs:                     6 
    directed arcs:                       10 
  average markov blanket size:           2.12 
  average neighbourhood size:            2.00 
  average branching factor:              0.62 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.01 


DONE. All outputs saved to: C:/Users/alden/Dissertation/TP53_isoforms_project/output/mofa 

15. FIT EVIDENCE ###========================================================

# 1. PREPARE THE TEMPLATE AND GLOBAL DATA --------------------------------------
# EXTRACT THE COMPLETED DIRECTED ACYCLIC GRAPH
global_dag <- bnlearn::cextend(mofa.2disc.aic$dag)
DAG_NODES  <- bnlearn::nodes(global_dag)

# CRITICAL: RE-SYNC MOFA CLUSTERS TO YOUR DATA FRAME
# ASSUMING 'mae' IS YOUR MASTER EXPERIMENT OBJECT AND CLUSTERS ARE IN colData
mofa_data$mofa_cluster <- colData(mae)[rownames(mofa_data), "mofa_cluster"]

# PERFORM GLOBAL DISCRETIZATION ONLY ON DAG NODES
# THIS ENSURES NO EXTRA COLUMNS (LIKE CLUSTER) BREAK THE BN.FIT LATER
full_disc <- bnlearn::discretize(
  mofa_data[, intersect(colnames(mofa_data), DAG_NODES)], 
  method = 'quantile', 
  breaks = 2
)

# RE-ATTACH CLUSTER ASSIGNMENTS TO THE DISCRETIZED DATA
full_disc$mofa_cluster <- mofa_data$mofa_cluster

# VERIFY CLUSTERS ARE PRESENT BEFORE PROCEEDING
# IF THIS PRINTS 0, THE CLUSTER ASSIGNMENT ABOVE FAILED
cat("UNIQUE CLUSTERS FOUND:", length(unique(na.omit(full_disc$mofa_cluster))), "\n")
UNIQUE CLUSTERS FOUND: 3 
# DEFINE TARGET OUTCOME AND PRIMARY DRIVER NODES
target_node     <- "RISK"
driver_node     <- "PC3"
high_risk_label <- levels(full_disc[[target_node]])[2]
low_pc3_label   <- levels(full_disc[[driver_node]])[1]
high_pc3_label  <- levels(full_disc[[driver_node]])[2]

# 2. THE REFIT LOOP ------------------------------------------------------------
# IDENTIFY UNIQUE CLUSTERS WHILE IGNORING NA VALUES
clusters <- as.character(unique(na.omit(full_disc$mofa_cluster)))
refit_results <- list()

# EXTRACT EXACT NODE NAMES REQUIRED BY THE GLOBAL DAG
dag_nodes <- bnlearn::nodes(global_dag)

for (cl in clusters) {
    cat(">>> REFITTING GLOBAL GRAPH FOR:", cl, "\n")
    
    # A. SUBSET DATA FOR THE CURRENT CLUSTER
    cl_data  <- full_disc[full_disc$mofa_cluster == cl, ]
    cl_n     <- nrow(cl_data)
    
    # B. ENSURE DATA ONLY CONTAINS DAG NODES TO PREVENT DIMENSION ERRORS
    cl_input <- cl_data[, intersect(colnames(cl_data), dag_nodes)]
    
    # VALIDATE THAT ALL REQUIRED NODES ARE PRESENT IN THE SUBSET
    missing_nodes <- setdiff(dag_nodes, colnames(cl_input))
    if(length(missing_nodes) > 0) {
        stop(paste("DATA IS MISSING NODES REQUIRED BY DAG:", paste(missing_nodes, collapse=", ")))
    }

    # C. REFIT PARAMETERS USING BAYESIAN ESTIMATION (ISS=1 HANDLES SPARSE DATA)
    cl_fit <- bnlearn::bn.fit(global_dag, cl_input, method = "bayes", iss = 1)
    
    # D. CALCULATE MARGINAL EFFECTS VIA CONDITIONAL PROBABILITY TABLE ANALYSIS
    cpt_risk <- as.data.frame(cl_fit[[target_node]]$prob)
    cpt_high <- cpt_risk[cpt_risk[[target_node]] == high_risk_label, ]
    
    # PIVOT TABLE TO COMPARE PC3 HIGH VS LOW ACROSS ALL PARENT STATES
    wide_cpt <- tidyr::pivot_wider(
      cpt_high, 
      names_from = !!sym(driver_node), 
      values_from = Freq, 
      names_prefix = "PC3_"
    )
    
    col_low  <- paste0("PC3_", low_pc3_label)
    col_high <- paste0("PC3_", high_pc3_label)
    
    if (col_low %in% colnames(wide_cpt) & col_high %in% colnames(wide_cpt)) {
        # COMPUTE DIFFERENCE IN RISK FOR EVERY BACKGROUND CONFIGURATION
        all_deltas <- wide_cpt[[col_high]] - wide_cpt[[col_low]]
        
        # 1. CALCULATE MEAN NET EFFECT (DIRECTIONAL IMPACT)
        mean_delta <- mean(all_deltas, na.rm = TRUE)
        
        # 2. CALCULATE MEAN ABSOLUTE INFLUENCE (TOTAL BIOLOGICAL WEIGHT)
        mean_abs_delta <- mean(abs(all_deltas), na.rm = TRUE)
        
        # 3. CALCULATE INTERACTION INDEX (SD OF EFFECTS / CONTEXT DEPENDENCY)
        interaction_idx <- sd(all_deltas, na.rm = TRUE)
        
        cat("    MEAN DELTA RISK:", round(mean_delta, 4), "\n")
        cat("    MEAN ABSOLUTE INFLUENCE:", round(mean_abs_delta, 4), "\n")
    } else {
        mean_delta <- NA; mean_abs_delta <- NA; interaction_idx <- NA
    }
    
    # E. STORE RESULTS IN DATAFRAME
    refit_results[[cl]] <- data.frame(
        Cluster = cl,
        N = cl_n,
        Mean_Net_Effect = mean_delta,
        Mean_Absolute_Influence = mean_abs_delta,
        Interaction_Index = interaction_idx
    )
    
    # SAVE THE CLUSTER-SPECIFIC FITTED OBJECT
    saveRDS(cl_fit, file.path(output_dir, "mofa", paste0("Refitted_Graph_", cl, ".rds")))
}
>>> REFITTING GLOBAL GRAPH FOR: Cluster_2 
    MEAN DELTA RISK: -0.1505 
    MEAN ABSOLUTE INFLUENCE: 0.241 
>>> REFITTING GLOBAL GRAPH FOR: Cluster_3 
    MEAN DELTA RISK: -0.0541 
    MEAN ABSOLUTE INFLUENCE: 0.1563 
>>> REFITTING GLOBAL GRAPH FOR: Cluster_1 
    MEAN DELTA RISK: 0.0324 
    MEAN ABSOLUTE INFLUENCE: 0.1136 
# 3. FINAL SUMMARY AND EXPORT --------------------------------------------------
final_delta_df <- do.call(rbind, refit_results)
rownames(final_delta_df) <- NULL
print(final_delta_df)

write.csv(final_delta_df, file.path(output_dir, "mofa", "Cluster_PC3_Marginal_Effects_Full.csv"), row.names = FALSE)

16. VIOLIN PLOTS ###========================================================

# 1. DATA EXTRACTION -----------------------------------------------------------
SELECTION_HALLMARKS <- list(
   rppa = c("ERALPHA", "PR", "HER2", "GATA3"),
   rnaseq = c("SOX10", "MKI67") 
)
HALLMARK_DATA <- as.data.frame(get_data(mae, SELECTION_HALLMARKS, c("mofa_cluster")))

# 2. LONG FORMAT PREP ----------------------------------------------------------
TARGET_FEATURES <- c("ERALPHA", "PR", "HER2", "GATA3", "SOX10", "MKI67")
HALLMARK_LONG <- HALLMARK_DATA %>%
  select(mofa_cluster, all_of(TARGET_FEATURES)) %>%
  filter(!is.na(mofa_cluster)) %>%
  mutate(mofa_cluster = factor(mofa_cluster)) %>%
  pivot_longer(cols = all_of(TARGET_FEATURES), 
               names_to = "Feature", 
               values_to = "Level")

# 3. SETTINGS ------------------------------------------------------------------
MY_PALETTE <- c("#006EAE", "#CA9B23", "#C5373D")
MY_COMPARISONS <- combn(levels(HALLMARK_LONG$mofa_cluster), 2, simplify = FALSE)

# 4. PLOT GENERATION -----------------------------------------------------------
P_HALLMARKS_FINAL <- ggplot(HALLMARK_LONG, aes(x = mofa_cluster, y = Level, fill = mofa_cluster)) +
  geom_violin(trim = FALSE, alpha = 0.7, color = "black") +
  geom_boxplot(width = 0.1, fill = "white", outlier.shape = NA, color = "black") +
  
  facet_wrap(~Feature, scales = "free", ncol = 3) +
  
  stat_compare_means(comparisons = MY_COMPARISONS, 
                     label = "p.signif", 
                     method = "wilcox.test",
                     step.increase = 0.15,
                     vjust = -1.1) +
  
  stat_compare_means(method = "kruskal.test", 
                     label = "p.format", 
                     label.x.npc = 0.05, 
                     label.y.npc = 0.95, 
                     size = 3) +
  
  # Sample sizes at the bottom
  stat_summary(fun.data = function(x) {
    return(data.frame(y = min(x) - (diff(range(x)) * 0.1), label = paste0("n=", length(x))))
  }, geom = "text", size = 2.5, fontface = "italic") +

  coord_cartesian(clip = "off") + 
  scale_y_continuous(expand = expansion(mult = c(0.15, 0.35))) +
  scale_fill_manual(values = MY_PALETTE) +
  
  theme_pubr() + 
  theme(
    legend.position = "none",
    # --- THE "TRUE TRANSPARENCY" TRINITY ---
    panel.background = element_rect(fill = "transparent", colour = NA), 
    plot.background  = element_rect(fill = "transparent", colour = NA),
    legend.background = element_rect(fill = "transparent", colour = NA),
    # ---------------------------------------
    strip.background = element_blank(),
    strip.text = element_text(face = "bold", size = 11),
    axis.text.x = element_text(angle = 45, hjust = 1),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.spacing.y = unit(3, "lines"),
    plot.margin = margin(t = 30, r = 15, b = 15, l = 15)
  ) +
  labs(title = "Clinical Hallmark Expression by Cluster",
       x = NULL, y = "Relative Level")

# 5. SAVE COMMAND --------------------------------------------------------------
# Make sure to include bg = "transparent" here too!
ggsave(file.path(output_dir, "mofa", "Hallmarks_Final_Pure_Transparent.png"), 
       P_HALLMARKS_FINAL, 
       width = 12, height = 10, 
       dpi = 300, 
       bg = "transparent")

17. STEMNESS ###============================================================

## 17.1 GET DATA
# DEFINE VARIABLES
selection <- list(
  isoform_pca  = TRUE, 
  stemness     = "RNAss",
  mutation     = "TP53",
  rnaseq       = c(    
    
    # p53 CONTEXT
    "TP63", "TP73","PPP1R13B", "TP53BP2", "TP53BP1", "PPP1R13L", "MDM2", 
    
    # NEUROENDOCRINE MARKETS
    "ASCL1", "INSM1", "SYP", "CHGA", "GATA3", "VGF",
    
    # STEMNESS
    "SOX10", "SOX2", "MYC", "PROM1",
    
    # EMT
    "SNAI1", "ZEB1", "CDH1")
)

# DEFINE METADATA
meta_vars <- c("os_risk_score", "mofa_cluster")

# RUN get_data
stemness_data <- as.data.frame(get_data(mae, selection, meta_vars)) %>%
  rename(RISK = os_risk_score) %>%
  filter(mofa_cluster == "Cluster_2") %>%
  select(-mofa_cluster)

## 17.2 BLACKLIST
stemness_bl <- data.frame(
  from = "RISK", 
  to   = setdiff(colnames(mofa_data), "RISK")
)

## 17.3 AS FACTOR
stemness_data$TP53 <- as.factor(stemness_data$TP53)

## 17.4 RUN MAIN FUNCTION
my_nets <- run_networks(
  df_raw = stemness_data,
  n_restarts = 100,
  bl = stemness_bl,
  run_name = "stemness",
  ref_color = "#CA9B23",
  comp_color = "#F6DC87"
)
================================================
BN PIPELINE RUN:  stemness 
SAMPLE SIZE (N): 861 
================================================

>>> PROCESSING CONFIGURATION: 2disc.aic 

  Consensus Bayesian network

  model:
   [PC1][PC2|PC1][PC5|PC1][PC3|PC1:PC2:PC5][RNAss|PC2][PC4|PC1:PC2:PC3][TP53|RNAss][TP63|PC1:PC4:RNAss:TP53]
   [ZEB1|PC2:RNAss:TP63][TP73|PC3:PC5:ZEB1][TP53BP2|PC3:RNAss:TP73:ZEB1][CDH1|RNAss:TP73:TP53BP2:ZEB1]
   [PPP1R13B|TP63:TP73:CDH1][TP53BP1|PC3:TP53BP2:CDH1][INSM1|PC4:TP53:CDH1][PPP1R13L|RNAss:TP63:PPP1R13B:TP53BP1:ZEB1]
   [SYP|PC5:TP53BP1:INSM1][GATA3|PC2:RNAss:TP53:TP53BP1:ZEB1][CHGA|RNAss:TP63:SYP][SOX2|PC5:RNAss:INSM1:GATA3]
   [MDM2|TP53:PPP1R13L:CHGA:CDH1][VGF|PC2:RNAss:TP53BP2:SYP:CHGA][MYC|RNAss:TP63:CHGA:VGF:CDH1]
   [SNAI1|TP53:TP53BP1:MDM2:ZEB1][ASCL1|RNAss:TP53:TP53BP2:PPP1R13L:SNAI1][SOX10|RNAss:TP63:TP73:VGF:MYC]
   [RISK|PC3:PC5:SOX10][PROM1|TP63:SYP:SOX10:MYC:CDH1]
  nodes:                                 28 
  arcs:                                  93 
    undirected arcs:                     0 
    directed arcs:                       93 
  average markov blanket size:           11.00 
  average neighbourhood size:            6.64 
  average branching factor:              3.32 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.49 


>>> PROCESSING CONFIGURATION: 2disc.bic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 28 
  arcs:                                  45 
    undirected arcs:                     2 
    directed arcs:                       43 
  average markov blanket size:           4.29 
  average neighbourhood size:            3.21 
  average branching factor:              1.54 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.56 


>>> PROCESSING CONFIGURATION: 2disc.bde 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 28 
  arcs:                                  41 
    undirected arcs:                     2 
    directed arcs:                       39 
  average markov blanket size:           3.71 
  average neighbourhood size:            2.93 
  average branching factor:              1.39 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.73 


>>> PROCESSING CONFIGURATION: 3disc.aic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 28 
  arcs:                                  57 
    undirected arcs:                     3 
    directed arcs:                       54 
  average markov blanket size:           5.79 
  average neighbourhood size:            4.07 
  average branching factor:              1.93 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.46 


>>> PROCESSING CONFIGURATION: 3disc.bic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 28 
  arcs:                                  27 
    undirected arcs:                     12 
    directed arcs:                       15 
  average markov blanket size:           2.00 
  average neighbourhood size:            1.93 
  average branching factor:              0.54 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.11 


>>> PROCESSING CONFIGURATION: 3disc.bde 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 28 
  arcs:                                  21 
    undirected arcs:                     10 
    directed arcs:                       11 
  average markov blanket size:           1.64 
  average neighbourhood size:            1.50 
  average branching factor:              0.39 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.99 


DONE. All outputs saved to: C:/Users/alden/Dissertation/TP53_isoforms_project/output/stemness 

18. IMMUNE ###==============================================================

## 18.1 GET DATA
# DEFINE VARIABLES
selection <- list(
  isoform_pca  = TRUE, 
  mutation     = "TP53",
  rnaseq       = c(    
        "TP63", "TP73","PPP1R13B", "TP53BP2", "TP53BP1", "PPP1R13L", "MDM2"
    
    ),
  immune = c(
        "TAMsurr_score", "Tcell_receptors_score", "Bcell_receptors_score", "PD1_PDL1_score",
    "IFNG_score_21050467", "MHC2_21978456", "TGFB_PCA_17349583", "Troester_WoundSig_19887484",
    "Chemokine12_score", "Module11_Prolif_score"
    )
)

# DEFINE METADATA
meta_vars <- c("os_risk_score", "mofa_cluster")

# RUN get_data
immune_data <- as.data.frame(get_data(mae, selection, meta_vars)) %>%
  rename(
    # NEW_NAME = OLD_NAME
    RISK           = os_risk_score,
    TAMsurr        = TAMsurr_score,
    TCell_Rec      = Tcell_receptors_score,
    BCell_Rec      = Bcell_receptors_score,
    PD1_PDL1       = PD1_PDL1_score,
    IFNG           = IFNG_score_21050467,
    MHC2           = MHC2_21978456,
    TGFB           = TGFB_PCA_17349583,
    Wound_Healing  = Troester_WoundSig_19887484,
    Chemokine12    = Chemokine12_score,
    Proliferation  = Module11_Prolif_score
  ) %>%
  filter(mofa_cluster == "Cluster_2") %>%
  select(-mofa_cluster)

## 18.2 BLACKLIST
immune_bl <- data.frame(
  from = "RISK", 
  to   = setdiff(colnames(immune_data), "RISK")
)

## 18.3 AS FACTOR
immune_data$TP53 <- as.factor(immune_data$TP53)

## 18.4 RUN MAIN FUNCTION
my_nets <- run_networks(
  df_raw = immune_data,
  n_restarts = 100,
  bl = immune_bl,
  run_name = "immune",
  ref_color = "#C5373D",
  comp_color = "#E9A0A5"
)
================================================
BN PIPELINE RUN:  immune 
SAMPLE SIZE (N): 861 
================================================

>>> PROCESSING CONFIGURATION: 2disc.aic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 24 
  arcs:                                  78 
    undirected arcs:                     1 
    directed arcs:                       77 
  average markov blanket size:           10.50 
  average neighbourhood size:            6.50 
  average branching factor:              3.21 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.5 


>>> PROCESSING CONFIGURATION: 2disc.bic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 24 
  arcs:                                  42 
    undirected arcs:                     1 
    directed arcs:                       41 
  average markov blanket size:           4.83 
  average neighbourhood size:            3.50 
  average branching factor:              1.71 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.43 


>>> PROCESSING CONFIGURATION: 2disc.bde 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 24 
  arcs:                                  40 
    undirected arcs:                     2 
    directed arcs:                       38 
  average markov blanket size:           4.50 
  average neighbourhood size:            3.33 
  average branching factor:              1.58 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.51 


>>> PROCESSING CONFIGURATION: 3disc.aic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 24 
  arcs:                                  50 
    undirected arcs:                     5 
    directed arcs:                       45 
  average markov blanket size:           6.08 
  average neighbourhood size:            4.17 
  average branching factor:              1.88 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.49 


>>> PROCESSING CONFIGURATION: 3disc.bic 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 24 
  arcs:                                  28 
    undirected arcs:                     7 
    directed arcs:                       21 
  average markov blanket size:           2.67 
  average neighbourhood size:            2.33 
  average branching factor:              0.88 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.49 


>>> PROCESSING CONFIGURATION: 3disc.bde 

  Consensus Bayesian network

  model:
    [partially directed graph]
  nodes:                                 24 
  arcs:                                  26 
    undirected arcs:                     11 
    directed arcs:                       15 
  average markov blanket size:           2.42 
  average neighbourhood size:            2.17 
  average branching factor:              0.62 

  generation algorithm:                  Model Averaging 
  significance threshold:                0.25 


DONE. All outputs saved to: C:/Users/alden/Dissertation/TP53_isoforms_project/output/immune 

19. RFS ###=================================================================

## ============================================================
## 19. RANDOM FOREST SURVIVAL — FULL PIPELINE
## ============================================================

library(survival)
library(survminer)
library(randomForestSRC)
library(ggplot2)
library(patchwork)
library(dplyr)
library(scales)

## ============================================================
## 19.1 GET DATA
## ============================================================

# DEFINE VARIABLES
selection <- list(
  isoform_pca  = c("PC3", "PC5"),
  mofa_factors = c("Factor2", "Factor3", "Factor8"),
  stemness     = "RNAss",
  rnaseq       = c("SOX10"),
  immune       = c("Troester_WoundSig_19887484"),
  mutation     = "TP53"
)

# DEFINE METADATA
meta_vars <- c("OS", "OS.time", "mofa_cluster")

# RUN get_data
rfs_data <- as.data.frame(get_data(mae, selection, meta_vars)) %>%
  filter(mofa_cluster == "Cluster_2") %>%
  select(-mofa_cluster)

## ── PRE-FILTER DATA (7-Year Window) ─────────────────────────────────────────
rfs_clean <- rfs_data %>%
  filter(OS.time <= 2555) %>%
  drop_na()

cat(sprintf("\nSample size after filtering: N = %d\n", nrow(rfs_clean)))

Sample size after filtering: N = 517
## ── CREATE OUTPUT DIRECTORY ──────────────────────────────────────────────────

survival_dir <- file.path(output_dir, "survival")
if (!dir.exists(survival_dir)) dir.create(survival_dir, recursive = TRUE)
cat(sprintf("Saving plots to: %s\n", survival_dir))
Saving plots to: C:/Users/alden/Dissertation/TP53_isoforms_project/output/survival
## ── Shared save helper ───────────────────────────────────────────────────────
# Single function so dpi/bg are consistent everywhere

save_png <- function(plot, filename, width = 10, height = 6) {
  ggsave(
    filename = file.path(survival_dir, filename),
    plot     = plot,
    width    = width,
    height   = height,
    dpi      = 300,
    bg       = "transparent"
  )
  cat(sprintf("Saved: %s\n", filename))
}

## ============================================================
## 19.2 DEFINE FEATURE SETS
## ============================================================

all_features    <- colnames(rfs_clean)[!colnames(rfs_clean) %in% c("OS", "OS.time", "PC3", "PC5")]
features_full   <- c("PC3", "PC5", all_features)
features_no_p53 <- all_features

## ============================================================
## 19.3 MONTE CARLO CROSS-VALIDATION (20 × 50:50 Split)
## ============================================================

all_model_results <- list()
all_km_data       <- list()

for (model_type in c("Full", "No_p53")) {

  current_features <- if (model_type == "Full") features_full else features_no_p53
  rf_formula <- as.formula(
    paste("Surv(OS.time, OS) ~", paste(current_features, collapse = "+"))
  )

  results_list <- list()
  km_data_list <- list()

  for (i in 1:20) {

    set.seed(i)

    # A. 50:50 random split
    train_idx <- sample(seq_len(nrow(rfs_clean)), size = floor(0.50 * nrow(rfs_clean)))
    train_set <- rfs_clean[train_idx, ]
    test_set  <- rfs_clean[-train_idx, ]

    # B. Train RSF
    rf_model <- randomForestSRC::rfsrc(rf_formula, data = train_set, ntree = 1000)

    # C. Predict on test set
    rf_pred <- randomForestSRC:::predict.rfsrc(rf_model, test_set)
    test_set$Predicted_Risk <- rf_pred$predicted

    # D. Optimal cutpoint
    res_cut <- try(
      survminer::surv_cutpoint(test_set,
                               time      = "OS.time",
                               event     = "OS",
                               variables = "Predicted_Risk"),
      silent = TRUE
    )

    if (!inherits(res_cut, "try-error")) {

      optimal_cut    <- res_cut$cutpoint$cutpoint
      test_set$Group <- factor(
        ifelse(test_set$Predicted_Risk > optimal_cut, "High", "Low"),
        levels = c("Low", "High")
      )

      # E. Discrete Cox model
      cox_mod <- survival::coxph(Surv(OS.time, OS) ~ Group, data = test_set)
      sum_cox <- summary(cox_mod)

      results_list[[i]] <- data.frame(
        Iteration = i,
        Model     = model_type,
        P_Value   = sum_cox$logtest["pvalue"],
        HR        = sum_cox$conf.int[1],
        Lower_CI  = sum_cox$conf.int[3],
        Upper_CI  = sum_cox$conf.int[4],
        C_Index   = 1 - rf_model$err.rate[rf_model$ntree]
      )

      # F. Store test set for KM
      km_data_list[[i]] <- test_set %>%
        select(OS.time, OS, Group) %>%
        mutate(Iteration = i)
    }
  }

  all_model_results[[model_type]] <- dplyr::bind_rows(results_list)
  all_km_data[[model_type]]       <- dplyr::bind_rows(km_data_list)
}

## ============================================================
## 19.4 ITERATION TABLE — ALL 20 FOLDS + SUMMARY
## ============================================================

iteration_table <- dplyr::bind_rows(all_model_results) %>%
  filter(HR < 1000) %>%
  select(Model, Iteration, HR, Lower_CI, Upper_CI, P_Value, C_Index) %>%
  arrange(Model, Iteration)

cat("\n════════════════════════════════════════════════════════════════\n")

════════════════════════════════════════════════════════════════
cat("  PER-ITERATION RESULTS (all 20 folds, both models)\n")
  PER-ITERATION RESULTS (all 20 folds, both models)
cat("════════════════════════════════════════════════════════════════\n")
════════════════════════════════════════════════════════════════
print(iteration_table, row.names = FALSE, digits = 3)

summary_table <- iteration_table %>%
  group_by(Model) %>%
  summarise(
    N           = n(),
    Mean_HR     = mean(HR,       na.rm = TRUE),
    SD_HR       = sd(HR,         na.rm = TRUE),
    Mean_LCI    = mean(Lower_CI, na.rm = TRUE),
    SD_LCI      = sd(Lower_CI,   na.rm = TRUE),
    Mean_UCI    = mean(Upper_CI, na.rm = TRUE),
    SD_UCI      = sd(Upper_CI,   na.rm = TRUE),
    Mean_P      = mean(P_Value,  na.rm = TRUE),
    SD_P        = sd(P_Value,    na.rm = TRUE),
    Mean_CIndex = mean(C_Index,  na.rm = TRUE),
    SD_CIndex   = sd(C_Index,    na.rm = TRUE),
    .groups     = "drop"
  )

cat("\n════════════════════════════════════════════════════════════════\n")

════════════════════════════════════════════════════════════════
cat("  SUMMARY: MEAN ± SD ACROSS 20 ITERATIONS\n")
  SUMMARY: MEAN ± SD ACROSS 20 ITERATIONS
cat("════════════════════════════════════════════════════════════════\n")
════════════════════════════════════════════════════════════════
print(as.data.frame(summary_table), row.names = FALSE, digits = 3)

# ── Save tables as CSVs ───────────────────────────────────────────────────────

write.csv(iteration_table,
          file.path(survival_dir, "iterations_all_folds.csv"),
          row.names = FALSE)
cat("Saved: iterations_all_folds.csv\n")
Saved: iterations_all_folds.csv
write.csv(summary_table,
          file.path(survival_dir, "iterations_summary.csv"),
          row.names = FALSE)
cat("Saved: iterations_summary.csv\n")
Saved: iterations_summary.csv
## ============================================================
## 19.5 KAPLAN-MEIER: 20 ITERATION CURVES + MEAN 95% CI
## ============================================================

time_grid <- seq(0, 2555, by = 5)

# ── Helper: per-iteration step curves ────────────────────────────────────────

build_iter_curves <- function(km_data_model) {
  lapply(split(km_data_model, km_data_model$Iteration), function(iter_df) {
    lapply(c("Low", "High"), function(grp) {
      sub <- iter_df[iter_df$Group == grp, ]
      if (nrow(sub) < 2) return(NULL)
      fit <- survfit(Surv(OS.time, OS) ~ 1, data = sub)
      sf  <- stepfun(fit$time, c(1, fit$surv))
      data.frame(
        time      = time_grid,
        surv      = sf(time_grid),
        Group     = grp,
        Iteration = unique(iter_df$Iteration)
      )
    }) %>% dplyr::bind_rows()
  }) %>% dplyr::bind_rows()
}

# ── Helper: mean survival + 95% CI ───────────────────────────────────────────

build_mean_ci <- function(curves_df) {
  curves_df %>%
    group_by(Group, time) %>%
    summarise(
      mean_surv = mean(surv, na.rm = TRUE),
      sd_surv   = sd(surv,   na.rm = TRUE),
      n         = sum(!is.na(surv)),
      se_surv   = sd_surv / sqrt(n),
      ci_lo     = pmax(mean_surv - 1.96 * se_surv, 0),
      ci_hi     = pmin(mean_surv + 1.96 * se_surv, 1),
      .groups   = "drop"
    )
}

curves_full   <- build_iter_curves(all_km_data[["Full"]])
curves_nop53  <- build_iter_curves(all_km_data[["No_p53"]])
mean_ci_full  <- build_mean_ci(curves_full)
mean_ci_nop53 <- build_mean_ci(curves_nop53)

# ── Plot function ─────────────────────────────────────────────────────────────

plot_20_km <- function(curves_df, mean_ci_df, model_results_df, title_text) {

  hr_summary <- model_results_df %>%
    filter(HR < 1000) %>%
    summarise(
      mHR  = mean(HR,       na.rm = TRUE),
      sdHR = sd(HR,         na.rm = TRUE),
      mLCI = mean(Lower_CI, na.rm = TRUE),
      mUCI = mean(Upper_CI, na.rm = TRUE),
      mP   = mean(P_Value,  na.rm = TRUE),
      mCI  = mean(C_Index,  na.rm = TRUE)
    )

  subtitle_text <- sprintf(
    "Mean HR = %.2f (SD %.2f)  |  Mean 95%% CI [%.2f–%.2f]  |  Mean p = %.3f  |  Mean C-index = %.3f",
    hr_summary$mHR, hr_summary$sdHR,
    hr_summary$mLCI, hr_summary$mUCI,
    hr_summary$mP,   hr_summary$mCI
  )

  ggplot() +
    geom_step(data = curves_df,
              aes(x = time, y = surv, colour = Group,
                  group = interaction(Group, Iteration)),
              alpha = 0.50, linewidth = 0.45) +
    geom_ribbon(data = mean_ci_df,
                aes(x = time, ymin = ci_lo, ymax = ci_hi,
                    fill = Group, group = Group),
                alpha = 0.20) +
    geom_step(data = mean_ci_df,
              aes(x = time, y = mean_surv, colour = Group, group = Group),
              linewidth = 1.3) +
    scale_colour_manual(
      values = c(High = "#E84855", Low = "#2E86AB"),
      labels = c(High = "High Risk", Low = "Low Risk")
    ) +
    scale_fill_manual(
      values = c(High = "#E84855", Low = "#2E86AB"),
      labels = c(High = "High Risk", Low = "Low Risk")
    ) +
    scale_x_continuous(
      breaks = seq(0, 2555, by = 365),
      labels = paste0(0:7, "y")
    ) +
    scale_y_continuous(
      limits = c(0, 1),
      labels = scales::percent_format(accuracy = 1)
    ) +
    labs(
      title    = title_text,
      subtitle = subtitle_text,
      x        = "Time",
      y        = "Overall Survival Probability",
      colour   = "Risk Group",
      fill     = "Risk Group",
      caption  = "Faint lines = 20 Monte Carlo iterations (50:50 split) | Bold = mean | Ribbon = mean 95% CI"
    ) +
    theme_classic(base_size = 13) +
    theme(
      panel.background  = element_rect(fill = "transparent", colour = NA),
      plot.background   = element_rect(fill = "transparent", colour = NA),
      legend.background = element_rect(fill = "transparent", colour = NA),
      legend.key        = element_rect(fill = "transparent", colour = NA),
      plot.title        = element_text(face = "bold", size = 14),
      plot.subtitle     = element_text(size = 9.5, colour = "grey35"),
      plot.caption      = element_text(size = 8,   colour = "grey55"),
      legend.position   = "bottom",
      legend.title      = element_text(face = "bold")
    )
}

p_full  <- plot_20_km(curves_full,  mean_ci_full,
                      all_model_results[["Full"]],
                      "RFS Model: Full (with PC3/PC5)")

p_nop53 <- plot_20_km(curves_nop53, mean_ci_nop53,
                      all_model_results[["No_p53"]],
                      "RFS Model: No PC3/PC5")

print(p_full / p_nop53)

save_png(p_full,            "km_full_model.png",    width = 10, height = 6)
Saved: km_full_model.png
save_png(p_nop53,           "km_no_pc35_model.png", width = 10, height = 6)
Saved: km_no_pc35_model.png
save_png(p_full / p_nop53,  "km_both_models.png",   width = 10, height = 12)
Saved: km_both_models.png

## ============================================================
## 19.6 FINAL MODEL (Full) — VIMP + PARTIAL DEPENDENCE PLOTS
## ============================================================

cat("\nTraining final model on full dataset for VIMP and PDP...\n")

Training final model on full dataset for VIMP and PDP...
set.seed(42)
rf_formula_full <- as.formula(
  paste("Surv(OS.time, OS) ~", paste(features_full, collapse = "+"))
)

final_model <- randomForestSRC::rfsrc(
  rf_formula_full,
  data       = rfs_clean,
  ntree      = 1000,
  importance = TRUE
)

# ── VIMP table ────────────────────────────────────────────────────────────────

vimp_df <- data.frame(
  Feature = names(final_model$importance),
  VIMP    = as.numeric(final_model$importance)
) %>%
  arrange(desc(VIMP))

cat("\n════════════════════════════════════════\n")

════════════════════════════════════════
cat("  VARIABLE IMPORTANCE (VIMP) — Full Model\n")
  VARIABLE IMPORTANCE (VIMP) — Full Model
cat("════════════════════════════════════════\n")
════════════════════════════════════════
print(vimp_df, row.names = FALSE, digits = 4)

write.csv(vimp_df,
          file.path(survival_dir, "vimp_full_model.csv"),
          row.names = FALSE)
cat("Saved: vimp_full_model.csv\n")
Saved: vimp_full_model.csv
# ── VIMP plot ─────────────────────────────────────────────────────────────────

p_vimp <- ggplot(vimp_df,
                 aes(x = reorder(Feature, VIMP), y = VIMP, fill = VIMP > 0)) +
  geom_col(width = 0.7) +
  geom_hline(yintercept = 0, linetype = "dashed", colour = "grey50") +
  scale_fill_manual(
    values = c(`TRUE` = "#2E86AB", `FALSE` = "#E84855"),
    guide  = "none"
  ) +
  coord_flip() +
  labs(
    title   = "Variable Importance (VIMP) — Full RFS Model",
    x       = NULL,
    y       = "VIMP",
    caption = "Blue = positive importance  |  Red = feature may add noise"
  ) +
  theme_classic(base_size = 13) +
  theme(
    panel.background  = element_rect(fill = "transparent", colour = NA),
    plot.background   = element_rect(fill = "transparent", colour = NA),
    legend.background = element_rect(fill = "transparent", colour = NA),
    legend.key        = element_rect(fill = "transparent", colour = NA),
    plot.title        = element_text(face = "bold")
  )

print(p_vimp)
save_png(p_vimp, "vimp_full_model.png", width = 8, height = 6)
Saved: vimp_full_model.png
# ── Partial dependence plots ──────────────────────────────────────────────────
# plot.variable() is base R graphics; png() / dev.off() is the correct
# capture method. bg = "transparent" sets the device background.
# Note: the plot panels themselves will retain a white fill as this is
# controlled internally by randomForestSRC and cannot be overridden
# without reimplementing PDPs in ggplot2.

png(file.path(survival_dir, "pdp_full_model.png"),
    width  = 10, height = 8,
    units  = "in",
    res    = 300,
    bg     = "transparent")
  randomForestSRC::plot.variable(
    final_model,
    partial        = TRUE,
    sorted         = TRUE,
    plots.per.page = 3,
    main           = "Partial Dependence — Full RFS Model (with PC3/PC5)"
  )
dev.off()
png 
  2 

cat("Saved: pdp_full_model.png\n")
Saved: pdp_full_model.png
# Also render to screen
randomForestSRC::plot.variable(
  final_model,
  partial        = TRUE,
  sorted         = TRUE,
  plots.per.page = 3,
  main           = "Partial Dependence — Full RFS Model (with PC3/PC5)"
)


## ── Final summary ─────────────────────────────────────────────────────────────

cat("\n════════════════════════════════════════════════════════════════\n")

════════════════════════════════════════════════════════════════
cat("  ALL FILES SAVED TO:", survival_dir, "\n")
  ALL FILES SAVED TO: C:/Users/alden/Dissertation/TP53_isoforms_project/output/survival 
cat("════════════════════════════════════════════════════════════════\n")
════════════════════════════════════════════════════════════════
cat("  iterations_all_folds.csv\n")
  iterations_all_folds.csv
cat("  iterations_summary.csv\n")
  iterations_summary.csv
cat("  km_full_model.png\n")
  km_full_model.png
cat("  km_no_pc35_model.png\n")
  km_no_pc35_model.png
cat("  km_both_models.png\n")
  km_both_models.png
cat("  vimp_full_model.csv\n")
  vimp_full_model.csv
cat("  vimp_full_model.png\n")
  vimp_full_model.png
cat("  pdp_full_model.png\n")
  pdp_full_model.png
cat("════════════════════════════════════════════════════════════════\n")
════════════════════════════════════════════════════════════════
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyMgMC4gU0VUVVAgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMjIDAuMSBTRVQgUEFUSFMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIERFRklORSBST09UIERJUkVDVE9SWQ0Kcm9vdF9wYXRoIDwtICJDOi9Vc2Vycy9hbGRlbi9EaXNzZXJ0YXRpb24vVFA1M19pc29mb3Jtc19wcm9qZWN0Ig0KDQojIFNFVCBPVEhFUiBESVJFQ1RPUklFUyBSRUxBVElWRSBUTyBST09UDQpzY3JpcHRzX2RpciA8LSBmaWxlLnBhdGgocm9vdF9wYXRoLCAic2NyaXB0cyIpDQppbnB1dF9kaXIgICA8LSBmaWxlLnBhdGgocm9vdF9wYXRoLCAiaW5wdXQiKQ0Kb3V0cHV0X2RpciAgPC0gZmlsZS5wYXRoKHJvb3RfcGF0aCwgIm91dHB1dCIpDQoNCiMjIDAuMiBPVEhFUiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIFNFVCBTRUVEIEZPUiBSRVBST0RVQ0FCSUxJVFkNCnNldC5zZWVkKDQyKQ0KYGBgDQoNCiMjIyAxLiBJTlNUQUxMIFBBQ0tBR0VTICMjIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgMS4xIElOU1RBTEwgUEFDS0FHRVMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgSU5TVEFMTCBCSU9DTUFOQUdFUg0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCn0NCg0KIyBMSVNUIFBBQ0tBR0VTDQpwYWNrYWdlcyA8LSBjKA0KICAiQmlvY01hbmFnZXIiLCANCiAgIk11bHRpQXNzYXlFeHBlcmltZW50IiwgDQogICJTdW1tYXJpemVkRXhwZXJpbWVudCIsIA0KICAiR2Vub21pY1JhbmdlcyIsDQogICJzbmFwY291bnQiLCANCiAgImVkZ2VSIiwgDQogICJsaW1tYSIsIA0KICAiR1NWQSIsIA0KICAiQmlvY1BhcmFsbGVsIiwgDQogICJNT0ZBMiIsIA0KICAiZmFjdG9leHRyYSIsIA0KICAiaW5mb3RoZW8iLCANCiAgImJubGVhcm4iLCANCiAgInN1cnZpdmFsIiwgDQogICJncmlkRXh0cmEiLA0KICAiZHBseXIiLCANCiAgInRpZHlyIiwgDQogICJnZ3Bsb3QyIiwgDQogICJzdHJpbmdyIiwgDQogICJ0aWJibGUiLCANCiAgImRhdGEudGFibGUiLCANCiAgInJlYWR4bCINCikNCg0KIyBDSEVDSyBGT1IgTUlTU0lORyBQQUNLQUdFUw0KbWlzc2luZ19wa2dzIDwtIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkpXQ0KDQojIElOU1RBTEwNCmlmIChsZW5ndGgobWlzc2luZ19wa2dzKSA+IDApIHsNCiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbChtaXNzaW5nX3BrZ3MsIHVwZGF0ZSA9IEZBTFNFLCBhc2sgPSBGQUxTRSkNCn0NCg0KIyBMT0FEDQppbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KYGBgDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMgUEFSVCBBIC0gQlVJTERJTkcgVEhFIE1BRQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojIyMgMi4gR0VUIElTT0ZPUk0gQ09VTlRTICMjIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMjIDIuMSBERUZJTkUgVEFSR0VUIFJFR0lPTiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQp0YXJnZXRzIDwtIEdlbm9taWNSYW5nZXM6OkdSYW5nZXMoDQogICAgc2VxbmFtZXMgPSAiY2hyMTciLA0KICAgIHJhbmdlcyA9IElSYW5nZXMoDQogICAgICAgIHN0YXJ0ID0gYyg3NjczMjY3LCA3NjczMjA3LCA3Njg1MjYwLCA3Njc1MjM5LCA3Njc0NTI2KSwNCiAgICAgICAgZW5kID0gYyg3NjczMzM5LCA3NjczMjY2LCA3Njg2MzcxLCA3Njc1NDkzLCA3Njc0ODU4KQ0KICAgICksDQogICAgc3RyYW5kID0gIi0iLA0KICAgIGxhYmVsID0gYygiZXhvbiA5QiIsICJleG9uIDl5IiwgImludHJvbiAyIiwgImludHJvbiA0IiwgImludHJvbiA2IikNCikNCg0KIyMgMi4yIEdFVCBEQVRBIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgSU5JVElBTElaRSBRVUVSWSBGT1IgVFA1MyBBQ1JPU1MgQUxMIFRDR0EgU0FNUExFUw0KcWIgPC0gc25hcGNvdW50OjpRdWVyeUJ1aWxkZXIoY29tcGlsYXRpb24gPSAidGNnYSIsIHJlZ2lvbnMgPSAiVFA1MyIpDQpyc2UgPC0gc25hcGNvdW50OjpxdWVyeV9leG9uKHFiLCByZXR1cm5fcnNlID0gVFJVRSkNCg0KIyBJREVOVElGWSBPVkVSTEFQUyBUSEFUIE1BVENIIGhnMzggQ09PUkRJTkFURVMNCmhpdHMgPC0gZmluZE92ZXJsYXBzKHRhcmdldHMsIHJvd1Jhbmdlcyhyc2UpLCB0eXBlID0gImVxdWFsIikNCg0KIyBFWFRSQUNUIENPVU5UUyBUTyBNQVRSSVggQU5EIEFQUExZIExBQkxFUw0KZmlsdGVyZWRfY291bnRzIDwtIGFzLm1hdHJpeChhc3NheShyc2UpW3N1YmplY3RIaXRzKGhpdHMpLCAsIGRyb3AgPSBGQUxTRV0pDQpyb3duYW1lcyhmaWx0ZXJlZF9jb3VudHMpIDwtIHRhcmdldHMkbGFiZWxbcXVlcnlIaXRzKGhpdHMpXQ0KDQojIEFTIE1BVFJJWA0KaXNvZm9ybV9jb3VudHMgPC0gYXMubWF0cml4KGZpbHRlcmVkX2NvdW50cykNCg0KIyMgMi4zIFNVTU1BUklTRSArIFNBVkUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgR0VUIEhFQUQgQU5EIERJTUVOU0lPTg0KaGVhZChpc29mb3JtX2NvdW50c1ssIDE6MTBdLCAxMCkNCmRpbShpc29mb3JtX2NvdW50cykNCg0KIyBTQVZFDQpzYXZlUkRTKGlzb2Zvcm1fY291bnRzLCBmaWxlID0gZmlsZS5wYXRoKG91dHB1dF9kaXIsICJUUDUzX2lzb2Zvcm1fY291bnRzLnJkcyIpKQ0KYGBgDQojIyMgMy4gR0VUIE1FVEFEQVRBICMjIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMjIDMuMSBERUZJTkUgTUVUQURBVEEgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIENSRUFURSBNRVRBREFUQSBMSVNUIEFORCBSRU5BTUUNCm1ldGFfY29scyA8LSBjKA0KICANCiAgICAjIElERU5USUZJRVJTDQogICAgYmFyY29kZV9mdWxsICA9ICAgImdkY19jYXNlcy5zYW1wbGVzLnBvcnRpb25zLmFuYWx5dGVzLmFsaXF1b3RzLnN1Ym1pdHRlcl9pZCIsDQogICAgcHJvamVjdCAgICAgICA9ICAgImdkY19jYXNlcy5wcm9qZWN0LnByb2plY3RfaWQiLA0KICAgIA0KICAgICMgTElCUkFSWSBJTkZPUk1BVElPTg0KICAgIGxpYi5zaXplICAgICAgPSAgICJtYXBwZWRfcmVhZF9jb3VudCIsDQogICAgYTI2MF9hMjgwICAgICA9ICAgImdkY19jYXNlcy5zYW1wbGVzLnBvcnRpb25zLmFuYWx5dGVzLmEyNjBfYTI4MF9yYXRpbyIsDQogICAgDQogICAgIyBCUkNBIEhPUk1PTkUgU1RBVFVTDQogICAgZXJfc3RhdHVzICAgICA9ICAgInhtbF9icmVhc3RfY2FyY2lub21hX2VzdHJvZ2VuX3JlY2VwdG9yX3N0YXR1cyIsIA0KICAgIHByX3N0YXR1cyAgICAgPSAgICJ4bWxfYnJlYXN0X2NhcmNpbm9tYV9wcm9nZXN0ZXJvbmVfcmVjZXB0b3Jfc3RhdHVzIiwgDQogICAgaGVyMl9pc2ggICAgICA9ICAgInhtbF9sYWJfcHJvY2VkdXJlX2hlcjJfbmV1X2luX3NpdHVfaHlicmlkX291dGNvbWVfdHlwZSIsIA0KICAgIGhlcjJfaWhjICAgICAgPSAgICJ4bWxfbGFiX3Byb2NfaGVyMl9uZXVfaW1tdW5vaGlzdG9jaGVtaXN0cnlfcmVjZXB0b3Jfc3RhdHVzIiwNCiAgICANCiAgICAjIFRIRVJBUFkgSU5GT1JNQVRJT04NCiAgICBkcnVnX25hbWUgICAgID0gICAiY2djX2RydWdfdGhlcmFweV9kcnVnX25hbWUiLA0KICAgIGRydWdfdHlwZSAgICAgPSAgICJjZ2NfZHJ1Z190aGVyYXB5X3BoYXJtYWNldXRpY2FsX3RoZXJhcHlfdHlwZSINCikNCg0KIyMgMy4yIEVYVFJBQ1QgTUVUQURBVEEgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm1ldGEgPC0gYXMuZGF0YS5mcmFtZShjb2xEYXRhKHJzZSkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGFueV9vZihtZXRhX2NvbHMpKSAlPiUNCiAgZHBseXI6OnJlbmFtZShhbnlfb2YobWV0YV9jb2xzKSkgJT4lDQogIA0KIyBERVJJVkUgUEFUSUVOVCBJREVOVElGSUVSUw0KICBkcGx5cjo6bXV0YXRlKA0KICAgIHJhaWxfaWQgICAgICAgICAgICA9IHJvd25hbWVzKC4pLCANCiAgICBwYXRpZW50X2JhcmNvZGVfMTUgPSBzdWJzdHIoYmFyY29kZV9mdWxsLCAxLCAxNSksDQogICAgcGF0aWVudF9iYXJjb2RlXzEyID0gc3Vic3RyKGJhcmNvZGVfZnVsbCwgMSwgMTIpLA0KICAgIGJhdGNoICAgICAgICAgICAgICA9IHN0cmluZ3I6OnN0cl9zcGxpdF9pKGJhcmNvZGVfZnVsbCwgIi0iLCA2KQ0KICApICU+JQ0KICANCiMgQ09OU09MSURBVEUgU1VCVFlQRQ0KICBkcGx5cjo6bXV0YXRlKA0KICAgIGhlcjJfY29uc29saWRhdGVkID0gZHBseXI6OmNhc2Vfd2hlbigNCiAgICAgIGhlcjJfaXNoICVpbiUgYygiUG9zaXRpdmUiLCAiTmVnYXRpdmUiKSB+IGhlcjJfaXNoLA0KICAgICAgaGVyMl9paGMgJWluJSBjKCJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpIH4gaGVyMl9paGMsDQogICAgICBUUlVFIH4gIiINCiAgICApLA0KICAgIGhyX2NvbnNvbGlkYXRlZCA9IGRwbHlyOjpjYXNlX3doZW4oDQogICAgICBlcl9zdGF0dXMgPT0gIlBvc2l0aXZlIiB8IHByX3N0YXR1cyA9PSAiUG9zaXRpdmUiIH4gIlBvc2l0aXZlIiwgDQogICAgICBlcl9zdGF0dXMgPT0gIk5lZ2F0aXZlIiAmIHByX3N0YXR1cyA9PSAiTmVnYXRpdmUiIH4gIk5lZ2F0aXZlIiwNCiAgICAgIFRSVUUgfiAiIg0KICAgICksDQoNCiMgQ0xBU1NJRlkgQ0FOQ0VSIFNVQlRZUEUgKFBBTTUwKQ0KICAgIGJyZWFzdF9jYW5jZXJfc3VidHlwZSA9IGRwbHlyOjpjYXNlX3doZW4oDQogICAgICBwcm9qZWN0ICE9ICJUQ0dBLUJSQ0EiIH4gIk5vbi1CUkNBIiwNCiAgICAgIGhyX2NvbnNvbGlkYXRlZCA9PSAiUG9zaXRpdmUiICYgaGVyMl9jb25zb2xpZGF0ZWQgPT0gIk5lZ2F0aXZlIiB+ICJMdW1pbmFsQSIsDQogICAgICBocl9jb25zb2xpZGF0ZWQgPT0gIlBvc2l0aXZlIiAmIGhlcjJfY29uc29saWRhdGVkID09ICJQb3NpdGl2ZSIgfiAiTHVtaW5hbEIiLA0KICAgICAgaHJfY29uc29saWRhdGVkID09ICJOZWdhdGl2ZSIgJiBoZXIyX2NvbnNvbGlkYXRlZCA9PSAiUG9zaXRpdmUiIH4gIkhFUjItZW5yaWNoZWQiLA0KICAgICAgaHJfY29uc29saWRhdGVkID09ICJOZWdhdGl2ZSIgJiBoZXIyX2NvbnNvbGlkYXRlZCA9PSAiTmVnYXRpdmUiIH4gIlRyaXBsZS1uZWdhdGl2ZSIsDQogICAgICBUUlVFIH4gIlVuY2xhc3NpZmllZCBCUkNBIg0KICAgICkNCiAgKSAlPiUNCg0KIyBERURVUExJQ0FURSBBTElRVU9UUw0KICBkcGx5cjo6ZGlzdGluY3QocGF0aWVudF9iYXJjb2RlXzE1LCAua2VlcF9hbGwgPSBUUlVFKSAlPiUNCg0KIyBVUERBVEUgUk9XTkFNRVMNCiAgdGliYmxlOjpyZW1vdmVfcm93bmFtZXMoKSAlPiUNCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoInBhdGllbnRfYmFyY29kZV8xNSIpDQoNCiMjIDMuMyBVUERBVEUgImlzb2Zvcm1fY291bnRzIiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgUkVOQU1FIFVTSU5HIFRDR0EgQkFSQ09ERQ0KaXNvZm9ybV9jb3VudHMgPC0gaXNvZm9ybV9jb3VudHNbLCBtZXRhJHJhaWxfaWRdICMgRVhQTElDSVQgT1JERVJJTkcgRk9SIENPUlJFQ1QgTUFQUElORw0KY29sbmFtZXMoaXNvZm9ybV9jb3VudHMpIDwtIHJvd25hbWVzKG1ldGEpDQoNCiMjIDMuNCBTVUJTRVQgVE8gQlJDQSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgU1VCU0VUICJtZXRhIg0KbWV0YSA8LSBtZXRhICU+JQ0KICBkcGx5cjo6ZmlsdGVyKHByb2plY3QgPT0gIlRDR0EtQlJDQSIpDQoNCiMgU0FWRSBTQU1QTEUgTElTVA0KYnJjYV9zYW1wbGVzIDwtIHJvd25hbWVzKG1ldGEpDQoNCiMgU1VCU0VUICJpc29mb3JtX2NvdW50cyINCmlzb2Zvcm1fY291bnRzIDwtIGlzb2Zvcm1fY291bnRzWywgYnJjYV9zYW1wbGVzXQ0KDQojIyAzLjMgU1VNTUFSSVNFIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBHRVQgSVNPRk9STSBNRUFOUw0KaXNvZm9ybV9tZWFucyA8LSByb3dNZWFucyhpc29mb3JtX2NvdW50cykNCnByaW50KGlzb2Zvcm1fbWVhbnMpDQoNCiMgR0VUICJtZXRhIiBIRUFEIEFORCBESU1FTlNJT04NCmhlYWQobWV0YSkNCmRpbShtZXRhKQ0KDQojIEdFVCAiaXNvZm9ybV9jb3VudHMiIEhFQUQgQU5EIERJTUVOU0lPTg0KaGVhZChpc29mb3JtX2NvdW50c1ssIDE6MTBdLCAxMCkNCmRpbShpc29mb3JtX2NvdW50cykNCmBgYA0KDQojIyMgNC4gTk9STUFMSVNBVElPTiAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMgUFJFUEFSRSBER0UgTElTVA0KZGdlIDwtIGVkZ2VSOjpER0VMaXN0KA0KICAgIGNvdW50cyA9IGlzb2Zvcm1fY291bnRzLCANCiAgICBzYW1wbGVzID0gbWV0YSwgDQogICAgbGliLnNpemUgPSBtZXRhJGxpYi5zaXplDQopDQoNCiMgVE1NIE5PUk1BTElTQVRJT04NCmRnZSA8LSBlZGdlUjo6Y2FsY05vcm1GYWN0b3JzKGRnZSwgbWV0aG9kID0gIlRNTSIpDQoNCiMgTE9HQ1BNIE5PUk1BTElTQVRJT04NCmlzb2Zvcm1fbG9nY3BtIDwtIGVkZ2VSOjpjcG0oZGdlLCBsb2cgPSBUUlVFLCBwcmlvci5jb3VudCA9IDEpDQpgYGANCg0KIyBCQVRDSCBDT1JSRUNUSU9OIChsaW1tYTo6cmVtb3ZlQmF0Y2hFZmZlY3QpIFJFRFVDRUQgTVVUVUFMIElORk9STUFUSU9OIEJFVFdFRU4gSVNPRk9STVMgQU5EIFNVUlZJVkFMDQoNCiMjIyA2LiBQQ0EgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgNi4xIENPTVBVVEUgUENBIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmlzb2Zvcm1fcGNhIDwtIHByY29tcCh0KGlzb2Zvcm1fbG9nY3BtKSwgc2NhbGUuID0gVFJVRSkNCmhlYWQoaXNvZm9ybV9wY2Ekcm90YXRpb24sIDEwKQ0KDQojIyA2LjIgU0NSRUUgUExPVCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZmFjdG9leHRyYTo6ZnZpel9laWcoDQogICAgaXNvZm9ybV9wY2EsIA0KICAgIGNob2ljZSA9ICJ2YXJpYW5jZSIsICANCiAgICBnZW9tID0gImxpbmUiLCAgICAgICAgDQogICAgYWRkbGFiZWxzID0gVFJVRSwgICAgIA0KKSArDQp0aGVtZV9taW5pbWFsKCkgKw0KdGhlbWUoDQogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksDQogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpDQopDQoNCiMjIDYuMyBCSVBMT1RTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgRlVOQ1RJT04gVEkgQ1JFQVRFIEJJUExPVFMNCmNyZWF0ZV9iaXBsb3QgPC0gZnVuY3Rpb24ocGNhX29iaiwgcGNfeCwgcGNfeSkgew0KICANCiAgIyBQTE9UIA0KICBwIDwtIGZhY3RvZXh0cmE6OmZ2aXpfcGNhX3ZhcigNCiAgICBwY2Ffb2JqLA0KICAgIGF4ZXMgPSBjKHBjX3gsIHBjX3kpLA0KICAgIGNvbC52YXIgPSAiY29udHJpYiIsDQogICAgZ3JhZGllbnQuY29scyA9IGMoIiMwMDZFQUUiLCAiI0NBOUIyMyIsICIjQzUzNzNEIiksDQogICAgcmVwZWwgPSBUUlVFLA0KICAgIHRpdGxlID0gcGFzdGUoIlBDIiwgcGNfeCwgInZzIFBDIiwgcGNfeSkNCiAgKQ0KICANCiAgIyBUSEVNRQ0KICBwIDwtIHAgKw0KICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgdGhlbWUoDQogICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksDQogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksDQogICAgICANCiAgICAgICMgU0VUIEFMTCBCQUNLR1JPVU5EUyBUTyBUUkFOU1BBUkVOVA0KICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvciA9IE5BKSwNCiAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG9yID0gTkEpLA0KICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG9yID0gTkEpDQogICAgKQ0KICANCiAgcmV0dXJuKHApDQp9DQoNCiMgUlVOIEZVTkNUSU9OIElOIExPT1ANCnBsb3RfbGlzdCA8LSBsaXN0KCkNCm51bV9wY3MgPC0gbmNvbChpc29mb3JtX3BjYSR4KQ0KDQpmb3IgKGkgaW4gMTptaW4oNCwgKG51bV9wY3MgLSAxKSkpIHsNCiAgcGxvdF9saXN0W1tpXV0gPC0gY3JlYXRlX2JpcGxvdChpc29mb3JtX3BjYSwgaSwgaSArIDEpDQp9DQoNCiMgQVJBTkdFIElOIDJYMiBHUklEDQpjb21iaW5lZF9wY2FfcGxvdCA8LSBncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSgNCiAgZ3JvYnMgPSBwbG90X2xpc3QsIA0KICBuY29sID0gMiwgDQogIG5yb3cgPSAyLA0KICB0b3AgPSBncmlkOjp0ZXh0R3JvYigiVFA1MyBJc29mb3JtIFBDQSBMb2FkaW5nIENvbXBhcmlzb25zIiwgDQogICAgICAgICAgICAgICAgICAgICAgIGdwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDE0LCBmb250ZmFjZSA9ICJib2xkIikpDQopDQoNCiMgU0FWRSBUTyBPVVRQVVQgRElSRUNUT1JZDQpnZ3Bsb3QyOjpnZ3NhdmUoDQogIGZpbGVuYW1lID0gZmlsZS5wYXRoKG91dHB1dF9kaXIsICJUUDUzX1BDQV9ncmlkX1RSQU5TUEFSRU5ULnBuZyIpLA0KICBwbG90ID0gY29tYmluZWRfcGNhX3Bsb3QsDQogIHdpZHRoID0gMTAsIA0KICBoZWlnaHQgPSAxMCwgDQogIGRwaSA9IDMwMCwNCiAgYmcgPSAidHJhbnNwYXJlbnQiIA0KKQ0KDQojIDYuNCBFWFRSQUNUIFBBVElFTlQgU0NPUkVTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KaXNvZm9ybV9wY2EgPC0gYXMuZGF0YS5mcmFtZShpc29mb3JtX3BjYSR4KQ0KYGBgDQoNCiMjIyA3LiBMT0FEIFRDR0EgREFUQSAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgNy4xIFJOQVNFUSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnJuYXNlcV9maWxlIDwtICJFQisrQWRqdXN0UEFOQ0FOX0lsbHVtaW5hSGlTZXFfUk5BU2VxVjIuZ2VuZUV4cC54ZW5hLmd6Ig0Kcm5hc2VxIDwtIGRhdGEudGFibGU6OmZyZWFkKGZpbGUucGF0aChpbnB1dF9kaXIsIHJuYXNlcV9maWxlKSkNCnJuYXNlcSA8LSBhcy5tYXRyaXgocm5hc2VxWywgLTEsIHdpdGggPSBGQUxTRV0sIHJvd25hbWVzID0gcm5hc2VxW1sxXV0pDQpybmFzZXEgPC0gcm5hc2VxWywgaW50ZXJzZWN0KGNvbG5hbWVzKHJuYXNlcSksIGJyY2Ffc2FtcGxlcyldDQpybmFzZXEgPC0gYXMubWF0cml4KHJuYXNlcVshZHVwbGljYXRlZChyb3duYW1lcyhybmFzZXEpKSwgXSkNCg0KIyMgNy4yIE1VVEFUSU9OIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm11dGF0aW9uX2ZpbGUgPC0gIm1jMy52MC4yLjguUFVCTElDLm5vbnNpbGVudEdlbmUueGVuYS5neiINCm11dGF0aW9uIDwtIGRhdGEudGFibGU6OmZyZWFkKGZpbGUucGF0aChpbnB1dF9kaXIsIG11dGF0aW9uX2ZpbGUpKQ0KbXV0YXRpb24gPC0gYXMubWF0cml4KG11dGF0aW9uWywgLTEsIHdpdGggPSBGQUxTRV0sIHJvd25hbWVzID0gbXV0YXRpb25bWzFdXSkNCm11dGF0aW9uIDwtIG11dGF0aW9uWywgaW50ZXJzZWN0KGNvbG5hbWVzKG11dGF0aW9uKSwgYnJjYV9zYW1wbGVzKV0NCg0KIyMgNy4zIFJQUEEgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnJwcGFfZmlsZSA8LSAiVENHQS1SUFBBLXBhbmNhbi1jbGVhbi54ZW5hLmd6Ig0KcnBwYSA8LSBkYXRhLnRhYmxlOjpmcmVhZChmaWxlLnBhdGgoaW5wdXRfZGlyLCBycHBhX2ZpbGUpKQ0KcnBwYSA8LSBhcy5tYXRyaXgocnBwYVssIC0xLCB3aXRoID0gRkFMU0VdLCByb3duYW1lcyA9IHJwcGFbWzFdXSkNCnJwcGEgPC0gcnBwYVssIGludGVyc2VjdChjb2xuYW1lcyhycHBhKSwgYnJjYV9zYW1wbGVzKV0NCg0KIyMgNy40IFNURU1ORVNTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnN0ZW1uZXNzX2ZpbGUgPC0gIlN0ZW1uZXNzU2NvcmVzX1JOQWV4cF8yMDE3MDEyNy4yLnRzdi5neiINCnN0ZW1uZXNzIDwtIGRhdGEudGFibGU6OmZyZWFkKGZpbGUucGF0aChpbnB1dF9kaXIsIHN0ZW1uZXNzX2ZpbGUpKQ0Kc3RlbW5lc3MgPC0gYXMubWF0cml4KHN0ZW1uZXNzWywgLTEsIHdpdGggPSBGQUxTRV0sIHJvd25hbWVzID0gc3RlbW5lc3NbWzFdXSkNCnN0ZW1uZXNzIDwtIHN0ZW1uZXNzWywgaW50ZXJzZWN0KGNvbG5hbWVzKHN0ZW1uZXNzKSwgYnJjYV9zYW1wbGVzKV0NCg0KIyMgNy41IEhEUiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmhkcl9maWxlIDwtICJUQ0dBLkhSRF93aXRoU2FtcGxlSUQudHh0Lmd6Ig0KaGRyIDwtIGRhdGEudGFibGU6OmZyZWFkKGZpbGUucGF0aChpbnB1dF9kaXIsIGhkcl9maWxlKSkNCmhkciA8LSBhcy5tYXRyaXgoaGRyWywgLTEsIHdpdGggPSBGQUxTRV0sIHJvd25hbWVzID0gaGRyW1sxXV0pDQpoZHIgPC0gaGRyWywgaW50ZXJzZWN0KGNvbG5hbWVzKGhkciksIGJyY2Ffc2FtcGxlcyldDQoNCiMjIDcuNiBJTU1VTkUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQppbW11bmVfc3VidHlwZV9maWxlIDwtICJUQ0dBX3BhbmNhbmNlcl8xMDg1MndoaXRlbGlzdHNhbXBsZXNfNjhJbW11bmVTaWdzLnhlbmEuZ3oiDQppbW11bmUgPC0gZGF0YS50YWJsZTo6ZnJlYWQoZmlsZS5wYXRoKGlucHV0X2RpciwgaW1tdW5lX3N1YnR5cGVfZmlsZSkpDQppbW11bmUgPC0gYXMubWF0cml4KGltbXVuZVssIC0xLCB3aXRoID0gRkFMU0VdLCByb3duYW1lcyA9IGltbXVuZVtbMV1dKQ0KaW1tdW5lIDwtIGltbXVuZVssIGludGVyc2VjdChjb2xuYW1lcyhpbW11bmUpLCBicmNhX3NhbXBsZXMpXQ0KYGBgDQoNCiMjIyA4LiBMT0FEIFRDR0EgTUVUQURBVEEgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgOC4xIFNVUlZJVkFMIERBVEEgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnN1cnZpdmFsX2ZpbGUgPC0gIlN1cnZpdmFsX1N1cHBsZW1lbnRhbFRhYmxlX1MxXzIwMTcxMDI1X3hlbmFfc3AiDQpzdXJ2aXZhbCA8LSBkYXRhLnRhYmxlOjpmcmVhZChmaWxlLnBhdGgoaW5wdXRfZGlyLCBzdXJ2aXZhbF9maWxlKSkNCg0KIyBSRU5BTUUNCnN1cnZpdmFsIDwtIHN1cnZpdmFsICU+JQ0KICBkcGx5cjo6c2VsZWN0KA0KICAgIHNhbXBsZSwgDQogICAgYWdlID0gYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMsIA0KICAgIGhpc3RvbG9neSA9IGhpc3RvbG9naWNhbF90eXBlLA0KICAgIG1lbm9wYXVzZSA9IG1lbm9wYXVzZV9zdGF0dXMsDQogICAgdHVtb3Jfc3RhdHVzLA0KICAgIE9TLCBPUy50aW1lLCBEU1MsIERTUy50aW1lLCBERkksIERGSS50aW1lLCBQRkksIFBGSS50aW1lLA0KICAgIGFqY2Nfc3RhZ2UgPSBhamNjX3BhdGhvbG9naWNfdHVtb3Jfc3RhZ2UNCiAgKQ0KDQojIENPTExBUFNFIFNUQUdFIElOVE8gNA0Kc3Vydml2YWwgPC0gc3Vydml2YWwgJT4lDQogIGRwbHlyOjptdXRhdGUoDQogICAgc3RhZ2UgPSBkcGx5cjo6Y2FzZV93aGVuKA0KICAgICAgc3RyaW5ncjo6c3RyX2RldGVjdChhamNjX3N0YWdlLCAiXlN0YWdlIElbQS1CXT8kIikgfiAiU3RhZ2UgMSIsDQogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGFqY2Nfc3RhZ2UsICJeU3RhZ2UgSUlbQS1CXT8kIikgfiAiU3RhZ2UgMiIsDQogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGFqY2Nfc3RhZ2UsICJeU3RhZ2UgSUlJW0EtQ10/JCIpIH4gIlN0YWdlIDMiLA0KICAgICAgc3RyaW5ncjo6c3RyX2RldGVjdChhamNjX3N0YWdlLCAiXlN0YWdlIElWJCIpIH4gIlN0YWdlIDQiLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIHN0YWdlID0gZmFjdG9yKHN0YWdlLCBsZXZlbHMgPSBjKCJTdGFnZSAxIiwgIlN0YWdlIDIiLCAiU3RhZ2UgMyIsICJTdGFnZSA0IikpDQogICkNCg0KIyBTVUJTRVQNCnN1cnZpdmFsIDwtIHN1cnZpdmFsICU+JQ0KICBkcGx5cjo6ZmlsdGVyKHNhbXBsZSAlaW4lIGJyY2Ffc2FtcGxlcykgJT4lDQogIGRwbHlyOjpkaXN0aW5jdChzYW1wbGUsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQ0KICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygic2FtcGxlIikNCg0KIyMgOC40IE1FUkdFIFdJVEggIm1ldGEiIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm1ldGEgPC0gbWVyZ2UobWV0YSwgc3Vydml2YWwsIGJ5ID0gInJvdy5uYW1lcyIsIGFsbC54ID0gVFJVRSkgJT4lIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJSb3cubmFtZXMiKQ0KYGBgDQoNCiMjIyA5LiBNT0ZBIEZBQ1RPUlMgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgOS4xIEZFQVRVUkUgU0VMRUNUSU9OIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyBSTkFTRVE6IFRPUCA1MDAwIEJZIFZBUklBTkNFDQpybmFfdmFycyA8LSBhcHBseShybmFzZXEsIDEsIHZhciwgbmEucm0gPSBUUlVFKQ0KbW9mYV9ybmFfZmVhdHVyZXMgPC0gbmFtZXMoc29ydChybmFfdmFycywgZGVjcmVhc2luZyA9IFRSVUUpKVsxOjUwMDBdDQoNCiMgTVVUQVRJT046IDElIEZSRVFVRU5DWQ0KbXV0X2ZyZXEgPC0gcm93TWVhbnMobXV0YXRpb24sIG5hLnJtID0gVFJVRSkNCm1vZmFfbXV0X2ZlYXR1cmVzIDwtIG5hbWVzKG11dF9mcmVxW211dF9mcmVxID49IDAuMDFdKQ0KDQojIFJQUEE6IFJFTU9WRSBFTVBUWSBST1dTDQptb2ZhX3Byb3RlaW5fZmVhdHVyZXMgPC0gcm93bmFtZXMocnBwYSlbcm93U3VtcyghaXMubmEocnBwYSkpID4gMF0NCg0KIyMgOS4yIElOSVRJQUxJWkUgTU9GQSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyBHRVQgVU5JUVVFIFNBTVBMRVMNCmFsbF9zYW1wbGVzIDwtIHVuaXF1ZShjKGNvbG5hbWVzKHJuYXNlcSksIGNvbG5hbWVzKG11dGF0aW9uKSwgY29sbmFtZXMocnBwYSksIGNvbG5hbWVzKGltbXVuZSkpKQ0KDQojIFNVQlNFVCBEQVRBDQptb2ZhX3JuYSA8LSBybmFzZXFbbW9mYV9ybmFfZmVhdHVyZXMsIF0NCm1vZmFfbXV0IDwtIG11dGF0aW9uW21vZmFfbXV0X2ZlYXR1cmVzLCBdDQptb2ZhX3Byb3QgPC0gcnBwYVttb2ZhX3Byb3RlaW5fZmVhdHVyZXMsIF0NCm1vZmFfaW1tICA8LSBpbW11bmUNCg0KIyBBTElHTg0KbW9mYV9pbnB1dCA8LSBsaXN0KA0KICBSTkFzZXEgICA9IG1vZmFfcm5hWywgbWF0Y2goYWxsX3NhbXBsZXMsIGNvbG5hbWVzKG1vZmFfcm5hKSldLA0KICBNdXRhdGlvbiA9IG1vZmFfbXV0WywgbWF0Y2goYWxsX3NhbXBsZXMsIGNvbG5hbWVzKG1vZmFfbXV0KSldLA0KICBQcm90ZWluICA9IG1vZmFfcHJvdFssIG1hdGNoKGFsbF9zYW1wbGVzLCBjb2xuYW1lcyhtb2ZhX3Byb3QpKV0sDQogIEltbXVuZSAgID0gbW9mYV9pbW1bLCBtYXRjaChhbGxfc2FtcGxlcywgY29sbmFtZXMobW9mYV9pbW0pKV0NCikNCg0KIyBGSVggQ09MTkFNRVMNCmZvcihpIGluIDE6NCkgeyANCiAgY29sbmFtZXMobW9mYV9pbnB1dFtbaV1dKSA8LSBhbGxfc2FtcGxlcyANCn0NCg0KIyBDUkVBVEUgTU9GQSBPQkpFQ1QNCk1PRkFvYmogPC0gTU9GQTI6OmNyZWF0ZV9tb2ZhKG1vZmFfaW5wdXQpDQoNCiMjIDkuMyBUUkFJTiBNT0RFTCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgU0VUVElOR1MNCm1vZGVsX29wdHMgPC0gTU9GQTI6OmdldF9kZWZhdWx0X21vZGVsX29wdGlvbnMoTU9GQW9iaikNCm1vZGVsX29wdHMkbnVtX2ZhY3RvcnMgPC0gMjQgDQptb2RlbF9vcHRzJGxpa2VsaWhvb2RzW1siTXV0YXRpb24iXV0gPC0gImJlcm5vdWxsaSINCg0KTU9GQW9iaiA8LSBNT0ZBMjo6cHJlcGFyZV9tb2ZhKE1PRkFvYmosIG1vZGVsX29wdGlvbnMgPSBtb2RlbF9vcHRzKSAlPiUgDQogIE1PRkEyOjpydW5fbW9mYSh1c2VfYmFzaWxpc2sgPSBUUlVFKQ0KDQojIyA5LjQgU0NSRUUgUExPVCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIEVYVFJBQ1QgVkFSSUFOQ0UgRVhQTEFJTkVEDQp2YXJzIDwtIE1PRkEyOjpnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKE1PRkFvYmopJHIyX3Blcl9mYWN0b3JbWzFdXQ0KDQojIENBTENVTEFURSBUT1RBTCBWQVJJQU5DRSBQRVIgRkFDVE9SDQpzY3JlZV9kYXRhIDwtIGRhdGEuZnJhbWUoDQogIEZhY3RvciA9IHBhc3RlMCgiRmFjdG9yIiwgMTpucm93KHZhcnMpKSwNCiAgVmFyaWFuY2UgPSByb3dTdW1zKHZhcnMpDQopICU+JQ0KICBtdXRhdGUoRmFjdG9yID0gZmFjdG9yKEZhY3RvciwgbGV2ZWxzID0gRmFjdG9yKSkNCg0KIyBTQ1JFRSBQTE9UDQpnZ3Bsb3Qoc2NyZWVfZGF0YSwgYWVzKHggPSBGYWN0b3IsIHkgPSBWYXJpYW5jZSwgZ3JvdXAgPSAxKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAic3RlZWxibHVlIiwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJkYXJrYmx1ZSIsIHNpemUgPSAzKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiTU9GQSBTY3JlZSBQbG90IiwNCiAgICAgICBzdWJ0aXRsZSA9ICJUb3RhbCBWYXJpYW5jZSBFeHBsYWluZWQgYWNyb3NzIGFsbCBPbWljcyBWaWV3cyIsDQogICAgICAgeSA9ICJUb3RhbCBWYXJpYW5jZSBFeHBsYWluZWQgKCUpIiwNCiAgICAgICB4ID0gIkxhdGVudCBGYWN0b3JzIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIEVYVFJBQ1QgRkFDVE9SUw0KbW9mYV9mYWN0b3JzIDwtIE1PRkEyOjpnZXRfZmFjdG9ycyhNT0ZBb2JqLCBmYWN0b3JzID0gImFsbCIpW1sxXV0NCmNvbG5hbWVzKG1vZmFfZmFjdG9ycykgPC0gcGFzdGUwKCJNT0ZBXyIsIGNvbG5hbWVzKG1vZmFfZmFjdG9ycykpDQoNCiMjIDkuNSBGQUNUT1IgTE9BRElOR1MgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgR0VUIEZBQ1RPUiBMT0FESU5HUyAoRVhBTVBMRSBVU0FHRSkNCmltbV93ZWlnaHRzIDwtIE1PRkEyOjpnZXRfd2VpZ2h0cyhNT0ZBb2JqLCB2aWV3cyA9ICJJbW11bmUiLCBmYWN0b3JzID0gIkZhY3RvcjMiKVtbMV1dDQppbW1fd2VpZ2h0cw0KDQojIyA5LjYgSy1NRUFOUyBDTFVTVEVSSU5HIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIEVYVFJBQ1QgVE9QIDggRkFDVE9SUw0KbW9mYV9mYWN0b3JzX21hdCA8LSBNT0ZBMjo6Z2V0X2ZhY3RvcnMoTU9GQW9iailbWzFdXVssIDE6OF0NCg0KIyBSVU4gVU1BUA0KTU9GQW9iaiA8LSBNT0ZBMjo6cnVuX3VtYXAoTU9GQW9iaiwgZmFjdG9ycyA9IDE6OCwgbl9uZWlnaGJvcnMgPSAxNSwgbWluX2Rpc3QgPSAwLjEpDQoNCiMgRElBR05PU1RJQyBQTE9UUw0KcF9lbGJvdyA8LSBmYWN0b2V4dHJhOjpmdml6X25iY2x1c3QobW9mYV9mYWN0b3JzX21hdCwga21lYW5zLCBtZXRob2QgPSAid3NzIikgKw0KICBsYWJzKHRpdGxlID0gIkVsYm93IE1ldGhvZCAoV1NTKSIsIHggPSAiTnVtYmVyIG9mIENsdXN0ZXJzIChrKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnBfc2lsIDwtIGZhY3RvZXh0cmE6OmZ2aXpfbmJjbHVzdChtb2ZhX2ZhY3RvcnNfbWF0LCBrbWVhbnMsIG1ldGhvZCA9ICJzaWxob3VldHRlIikgKw0KICBsYWJzKHRpdGxlID0gIlNpbGhvdWV0dGUgQW5hbHlzaXMiLCB4ID0gIk51bWJlciBvZiBDbHVzdGVycyAoaykiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIFNBVkUgRElBR05PU1RJQyBQTE9UUw0KZGlhZ19jb21iaW5lZCA8LSBncmlkRXh0cmE6OmFycmFuZ2VHcm9iKHBfZWxib3csIHBfc2lsLCBuY29sID0gMikNCmdncGxvdDI6Omdnc2F2ZShmaWxlLnBhdGgob3V0cHV0X2RpciwgIk1PRkFfQ2x1c3RlcmluZ19EaWFnbm9zdGljc19Ub3A4LnBuZyIpLCANCiAgICAgICAgICAgICAgICBkaWFnX2NvbWJpbmVkLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA1LCBiZyA9ICJ0cmFuc3BhcmVudCIpDQoNCiMgUlVOIEstTUVBTlMgQ0xVU1RFUklORyAoSz00KQ0Ka21fcmVzIDwtIGttZWFucyhtb2ZhX2ZhY3RvcnNfbWF0LCBjZW50ZXJzID0gMywgbnN0YXJ0ID0gMjUpDQoNCiMgU1lOQyBDTFVTVEVSUyBUTyBNRVRBREFUQSBBTkQgTU9GQSBPQkoNCm1ldGEkbW9mYV9jbHVzdGVyIDwtIGFzLmZhY3RvcihwYXN0ZTAoIkNsdXN0ZXJfIiwga21fcmVzJGNsdXN0ZXJbbWF0Y2gocm93bmFtZXMobWV0YSksIG5hbWVzKGttX3JlcyRjbHVzdGVyKSldKSkNCg0KTU9GQTI6OnNhbXBsZXNfbWV0YWRhdGEoTU9GQW9iaikgPC0gbWV0YSAlPiUNCiAgbXV0YXRlKHNhbXBsZSA9IHJvd25hbWVzKC4pKSAlPiUNCiAgcmVsb2NhdGUoc2FtcGxlKQ0KDQojIERFRklORSBUSEVNRQ0KY2xlYW5fdGhlbWUgPC0gdGhlbWVfbWluaW1hbCgpICsgDQogIHRoZW1lKA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3IgPSBOQSksDQogICAgcGxvdC5iYWNrZ3JvdW5kICA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3IgPSBOQSksDQogICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJibGFjayIpLA0KICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvciA9IE5BKQ0KICApDQoNCiMgUExPVCBVTUFQIChNT0ZBIENMVVNURVJTKQ0KcDEgPC0gTU9GQTI6OnBsb3RfZGltcmVkKA0KICBNT0ZBb2JqLCANCiAgbWV0aG9kID0gIlVNQVAiLCANCiAgY29sb3JfYnkgPSAibW9mYV9jbHVzdGVyIiwgDQogIGRvdF9zaXplID0gMg0KKSArIA0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKw0KICBjbGVhbl90aGVtZSArDQogIGxhYnModGl0bGUgPSAiTU9GQSBVTUFQOiBNdWx0aS1vbWljIENsdXN0ZXJzIiwgc3VidGl0bGUgPSAiSy1tZWFucyAoSz00KSBvbiBUb3AgOCBGYWN0b3JzIikNCg0KIyBQTE9UIFVNQVAgKFBBTTUwIFNVQlRZUEUpDQpwMiA8LSBNT0ZBMjo6cGxvdF9kaW1yZWQoDQogIE1PRkFvYmosIA0KICBtZXRob2QgPSAiVU1BUCIsIA0KICBjb2xvcl9ieSA9ICJicmVhc3RfY2FuY2VyX3N1YnR5cGUiLCANCiAgZG90X3NpemUgPSAyDQopICsgDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKw0KICBjbGVhbl90aGVtZSArDQogIGxhYnModGl0bGUgPSAiTU9GQSBVTUFQOiBQQU01MCBTdWJ0eXBlcyIsIHN1YnRpdGxlID0gIkNsaW5pY2FsIExhYmVsIENvbXBhcmlzb24iKQ0KDQojIFNBVkUgVU1BUFMNCnVtYXBfY29tYmluZWQgPC0gZ3JpZEV4dHJhOjphcnJhbmdlR3JvYihwMSwgcDIsIG5jb2wgPSAyKQ0KZ2dwbG90Mjo6Z2dzYXZlKGZpbGUucGF0aChvdXRwdXRfZGlyLCAiTU9GQV9VTUFQX0NsdXN0ZXJzX3ZzX1N1YnR5cGVzLnBuZyIpLCANCiAgICAgICAgICAgICAgICB1bWFwX2NvbWJpbmVkLCB3aWR0aCA9IDEyLCBoZWlnaHQgPSA2LCBiZyA9ICJ0cmFuc3BhcmVudCIpDQoNCiMgRElTUExBWSBVTUFQUw0KZ3JpZDo6Z3JpZC5kcmF3KHVtYXBfY29tYmluZWQpDQpgYGANCg0KIyMjIDEwLiBNQVJUSU5HQUxFIFJFU0lEVUFMUyAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgMTAuMSBFWFRSQUNUIFJFU0lEVUFMUyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnRhcmdldF9vdXRjb21lcyA8LSBsaXN0KG9zID0gIk9TIiwgcGZpID0gIlBGSSIpDQoNCmZvciAobSBpbiBuYW1lcyh0YXJnZXRfb3V0Y29tZXMpKSB7DQogIA0KICAjIDEuIENPTlNUUlVDVCBGT1JNVUxBDQogIHN1cnZfZm9ybXVsYSA8LSBhcy5mb3JtdWxhKHBhc3RlMCgic3Vydml2YWw6OlN1cnYoIiwgdGFyZ2V0X291dGNvbWVzW1ttXV0sICIudGltZSwgIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfb3V0Y29tZXNbW21dXSwgIikgfiAxIikpDQogIA0KICAjIDIuIEZJVCBDT1ggTU9ERUwNCiAgZml0IDwtIHN1cnZpdmFsOjpjb3hwaChzdXJ2X2Zvcm11bGEsIGRhdGEgPSBtZXRhLCBuYS5hY3Rpb24gPSBuYS5leGNsdWRlKQ0KICANCiAgIyAzLiBBVFRBQ0ggVE8gTUVUQQ0KICBtZXRhW1twYXN0ZTAobSwgIl9yaXNrX3Njb3JlIildXSA8LSByZXNpZHVhbHMoZml0LCB0eXBlID0gIm1hcnRpbmdhbGUiKQ0KfQ0KYGBgDQoNCiMjIyAxMS4gQlVJTEQgTUFFIE9CSkVDVCAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMjIDExLjEgVFJBTlNQT1NFIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQppc29mb3JtX3BjYSA8LSB0KGlzb2Zvcm1fcGNhKQ0KbW9mYV9mYWN0b3JzIDwtIHQobW9mYV9mYWN0b3JzKQ0KDQojIyAxMS4yIFBSRVBBUkUgRVhQRVJJTUVOVCBMSVNUIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KRVhQX0xJU1QgPC0gbGlzdCgNCiAgICBpc29mb3JtX3BjYSAgICA9IGlzb2Zvcm1fcGNhLCAgDQogICAgaXNvZm9ybV9sb2djcG0gPSBpc29mb3JtX2xvZ2NwbSwgIA0KICAgIHJuYXNlcSAgICAgICAgID0gcm5hc2VxLCAgICAgICAgICANCiAgICBtdXRhdGlvbiAgICAgICA9IG11dGF0aW9uLCAgICAgICAgDQogICAgcnBwYSAgICAgICAgICAgPSBycHBhLCAgICAgICAgICAgDQogICAgc3RlbW5lc3MgICAgICAgPSBzdGVtbmVzcywgICAgICAgIA0KICAgIGhyZCAgICAgICAgICAgID0gaGRyLCAgICAgICAgICAgICANCiAgICBpbW11bmUgICAgICAgICA9IGltbXVuZSwgICAgICAgICAgDQogICAgbW9mYV9mYWN0b3JzICAgPSBtb2ZhX2ZhY3RvcnMNCikNCg0KIyMgMTEuMyBDUkVBVEUgTUFFIE9CSkVDVCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm1hZSA8LSBNdWx0aUFzc2F5RXhwZXJpbWVudDo6TXVsdGlBc3NheUV4cGVyaW1lbnQoDQogICAgZXhwZXJpbWVudHMgPSBFWFBfTElTVCwNCiAgICBjb2xEYXRhICAgICA9IG1ldGENCikNCg0KIyBQUklOVCBTVU1NQVJZDQpwcmludChtYWUpDQoNCiMjIDExLjQgU0FWSU5HICYgQ0xFQU5VUCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpzYXZlUkRTKG1hZSwgZmlsZSA9IGZpbGUucGF0aChvdXRwdXRfZGlyLCAiVFA1M19Jc29mb3Jtc19NQUUucmRzIikpDQpgYGANCg0KIyMjIDEyLiBGVU5DVElPTiBUTyBFWFRSQUNUIERBVEEgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgMTIuMSBGVU5DVElPTiBUTyBSRVRSSUVWRSBEQVRBIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpnZXRfZGF0YSA8LSBmdW5jdGlvbihtYWUsIGNvbmZpZywgY2xpbmljYWwgPSBOVUxMKSB7DQogIA0KICBleHRyYWN0ZWQgPC0gbGlzdCgpDQogIGZvciAoYXNzYXkgaW4gbmFtZXMoY29uZmlnKSkgew0KICAgIGl0ZW1zIDwtIGNvbmZpZ1tbYXNzYXldXQ0KICAgIGlmIChpc1RSVUUoaXRlbXMpKSB7DQogICAgICBleHRyYWN0ZWRbW2Fzc2F5XV0gPC0gbWFlW1thc3NheV1dDQogICAgfSBlbHNlIHsNCiAgICAgIHZhbGlkIDwtIGludGVyc2VjdChpdGVtcywgcm93bmFtZXMobWFlW1thc3NheV1dKSkNCiAgICAgIGV4dHJhY3RlZFtbYXNzYXldXSA8LSBtYWVbW2Fzc2F5XV1bdmFsaWQsICwgZHJvcCA9IEZBTFNFXQ0KICAgIH0NCiAgfQ0KICANCiAgdGVtcF9tYWUgPC0gTXVsdGlBc3NheUV4cGVyaW1lbnQoZXhwZXJpbWVudHMgPSBleHRyYWN0ZWQsIGNvbERhdGEgPSBjb2xEYXRhKG1hZSkpDQogIA0KICBkZiA8LSBhcy5kYXRhLmZyYW1lKGxvbmdGb3JtKHRlbXBfbWFlLCBjb2xEYXRhQ29scyA9IGNsaW5pY2FsKSkgJT4lDQogICAgZHBseXI6OnNlbGVjdCgtYXNzYXksIC1jb2xuYW1lKSAlPiUgICMgRHJvcCB0aGVzZSB0byBhbGxvdyBjb2xsYXBzaW5nDQogICAgdGlkeXI6OnBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAicm93bmFtZSIsIHZhbHVlc19mcm9tID0gInZhbHVlIikgJT4lDQogICAgIyBDT05WRVJUIEJBUkNPREUgQ09MVU1OIFRPIFJPV05BTUVTDQogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoInByaW1hcnkiKQ0KICANCiAgcmV0dXJuKGRmKQ0KfQ0KYGBgDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMgUEFSVCBCIC0gQkFZRVNJQU4gTkVUV09SSyBJTkZFUkVOQ0UNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KIyMjIDEzLiBNQUlOIEZVTkNUSU9OICMjIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KYGBge3J9DQojJyBSVU4gQkFZRVNJQU4gTkVUV09SS1MNCiMnIEBwYXJhbSBkZl9yYXcgREFUQUZSQU1FDQojJyBAcGFyYW0gbl9yZXN0YXJ0cyBOVU1CRVIgT0YgUkFORE9NIFJFU1RBUlRTDQojJyBAcGFyYW0gYmwgQkxBQ0tMSVNUDQojJyBAcGFyYW0gcnVuX25hbWUgRk9MREVSIE5BTUUgT0YgT1VUUFVUDQojJyBAcGFyYW0gcmVmX2NvbG9yIFJFRkVSRU5DRSBDT0xPVVIgKEFJQykNCiMnIEBwYXJhbSBjb21wX2NvbG9yIENPTVBBUklTT04gQ09MT1VSIChNQVRDSEVTL01CKQ0KDQojJyBSVU4gQkFZRVNJQU4gTkVUV09SS1MgKFJFRkFDVE9SRUQgVjMpDQpydW5fbmV0d29ya3MgPC0gZnVuY3Rpb24oZGZfcmF3LCBuX3Jlc3RhcnRzID0gNTAsIGJsID0gTlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgcnVuX25hbWUgPSAiRmluYWxfVFA1M19BbmFseXNpcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcmVmX2NvbG9yID0gIm9yYW5nZTMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBfY29sb3IgPSAib3JhbmdlIikgew0KICANCiAgIyAtLS0gMTMuMSBSVU4gNiBCTnMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICBydW5fcGF0aCA8LSBmaWxlLnBhdGgob3V0cHV0X2RpciwgcnVuX25hbWUpDQogIGRpci5jcmVhdGUocnVuX3BhdGgsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KICANCiAgY2F0KCI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiIpDQogIGNhdCgiQk4gUElQRUxJTkUgUlVOOiAiLCBydW5fbmFtZSwgIlxuIikNCiAgY2F0KCJTQU1QTEUgU0laRSAoTik6IiwgbnJvdyhkZl9yYXcpLCAiXG4iKQ0KICBjYXQoIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuIikNCiAgDQogIGRpc2NfbGV2ZWxzICA8LSBjKDIsIDMpDQogIHNjb3JlcyAgICAgICA8LSBjKCJhaWMiLCAiYmljIiwgImJkZSIpDQogIGFsbF9yZXN1bHRzICA8LSBsaXN0KCkNCiAgcmVmX2lkICAgICAgIDwtICIyZGlzYy5haWMiDQogIHRhcmdldF9ub2RlICA8LSAiUklTSyINCiAgbWFzdGVyX25vZGVzIDwtIGNvbG5hbWVzKGRmX3JhdykNCg0KICBmb3IgKGQgaW4gZGlzY19sZXZlbHMpIHsNCiAgICBkZl9kaXNjIDwtIGJubGVhcm46OmRpc2NyZXRpemUoZGZfcmF3LCBtZXRob2QgPSAncXVhbnRpbGUnLCBicmVha3MgPSBkKQ0KICAgIGRmX2Rpc2MgPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoZGZfZGlzYywgZHJvcGxldmVscykpWywgbWFzdGVyX25vZGVzXQ0KDQogICAgZm9yIChzIGluIHNjb3Jlcykgew0KICAgICAgaWQgPC0gcGFzdGUwKGQsICJkaXNjLiIsIHMpDQogICAgICBjYXQoIlxuPj4+IFBST0NFU1NJTkcgQ09ORklHVVJBVElPTjoiLCBpZCwgIlxuIikNCiAgICAgIA0KICAgICAgc2V0LnNlZWQoMTIzKQ0KICAgICAgc3RhcnRzIDwtIGMobGlzdChlbXB0eS5ncmFwaChtYXN0ZXJfbm9kZXMpKSwNCiAgICAgICAgICAgICAgICAgIHJhbmRvbS5ncmFwaChub2RlcyA9IG1hc3Rlcl9ub2RlcywgbnVtID0gbl9yZXN0YXJ0cyAtIDEsIG1ldGhvZCA9ICJpYy1kYWciLCBtYXguZGVncmVlID0gMSkpDQogICAgICANCiAgICAgIG5ldF9saXN0IDwtIGxhcHBseShzdGFydHMsIGZ1bmN0aW9uKGcpIHsNCiAgICAgICAgdHJ5Q2F0Y2goeyBzdHJ1Y3R1cmFsLmVtKGRmX2Rpc2MsIG1heGltaXplID0gImhjIiwgc3RhcnQgPSBnLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGltaXplLmFyZ3MgPSBsaXN0KHNjb3JlID0gcywgYmxhY2tsaXN0ID0gYmwpKQ0KICAgICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHJldHVybihOVUxMKSkNCiAgICAgIH0pDQogICAgICANCiAgICAgIG5ldF9saXN0IDwtIG5ldF9saXN0WyFzYXBwbHkobmV0X2xpc3QsIGlzLm51bGwpXQ0KICAgICAgYXZnX2RhZyAgPC0gYXZlcmFnZWQubmV0d29yayhjdXN0b20uc3RyZW5ndGgobmV0X2xpc3QsIG5vZGVzID0gbWFzdGVyX25vZGVzKSkNCiAgICAgIA0KICAgICAgcHJpbnQoYXZnX2RhZykgDQogICAgICANCiAgICAgIG1iX25vZGVzIDwtIG1iKGF2Z19kYWcsIHRhcmdldF9ub2RlKQ0KICAgICAgY3Vycl9hbWF0IDwtIGFtYXQoYXZnX2RhZylbbWFzdGVyX25vZGVzLCBtYXN0ZXJfbm9kZXNdDQogICAgICANCiAgICAgIHJlc19vYmogPC0gbGlzdChkYWcgPSBhdmdfZGFnLCBhZGphY2VuY3kgPSBjdXJyX2FtYXQsIG5vZGVzID0gbWFzdGVyX25vZGVzLCBkYXRhID0gZGZfZGlzYywgbWIgPSBtYl9ub2RlcykNCiAgICAgIGFsbF9yZXN1bHRzW1tpZF1dIDwtIHJlc19vYmoNCiAgICAgIA0KICAgICAgc2F2ZVJEUyhyZXNfb2JqLCBmaWxlLnBhdGgocnVuX3BhdGgsIHBhc3RlMChpZCwgIi5yZHMiKSkpDQogICAgICBhc3NpZ24ocGFzdGUwKHJ1bl9uYW1lLCAiLiIsIGlkKSwgcmVzX29iaiwgZW52aXIgPSAuR2xvYmFsRW52KQ0KICAgICAgDQogICAgICAjIEhJR0ggUkVTIEJOIFBMT1RTDQogICAgICBwbmcoZmlsZS5wYXRoKHJ1bl9wYXRoLCBwYXN0ZTAoaWQsICIucG5nIikpLCB3aWR0aCA9IDI0MDAsIGhlaWdodCA9IDIxMDAsIHJlcyA9IDMwMCwgYmcgPSAidHJhbnNwYXJlbnQiLCB0eXBlID0gImNhaXJvIikNCiAgICAgIGlmIChsZW5ndGgobWJfbm9kZXMpID4gMCkgew0KICAgICAgICBncmFwaHZpei5wbG90KGF2Z19kYWcsIGhpZ2hsaWdodCA9IGxpc3Qobm9kZXMgPSBtYl9ub2RlcywgZmlsbCA9IGNvbXBfY29sb3IsIGNvbCA9ICJibGFjayIpLCANCiAgICAgICAgICAgICAgICAgICAgICBtYWluID0gcGFzdGUoaWQsICJ8IE1CIG9mIiwgdGFyZ2V0X25vZGUpKQ0KICAgICAgfSBlbHNlIHsgDQogICAgICAgIGdyYXBodml6LnBsb3QoYXZnX2RhZywgbWFpbiA9IHBhc3RlKGlkLCAifCBObyBNQiBmb3IiLCB0YXJnZXRfbm9kZSkpIA0KICAgICAgfQ0KICAgICAgZGV2Lm9mZigpDQogICAgfQ0KICB9DQoNCiAgIyAtLS0gMTMuMiBBREpBQ0VOQ1kgTUFUUklYIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyAoS2VlcGluZyB5b3VyIG9yaWdpbmFsIGFkamFjZW5jeSBncmlkIGxvZ2ljIGFzIHJlcXVlc3RlZCkNCiAgYWRqX21hdHMgPC0gbGFwcGx5KGFsbF9yZXN1bHRzLCBmdW5jdGlvbih4KSB4JGFkamFjZW5jeSkNCiAgc3VtbWVkX21hdCA8LSBSZWR1Y2UoIisiLCBhZGpfbWF0cykNCiAgdW5pdmVyc2FsX21hdCA8LSAoc3VtbWVkX21hdCA9PSBsZW5ndGgoYWRqX21hdHMpKSANCiAgcmVmX21hdCA8LSBhbGxfcmVzdWx0c1tbcmVmX2lkXV0kYWRqYWNlbmN5DQogIHBsb3RfbGlzdCA8LSBsaXN0KCkNCiAgY29uZmlnX25hbWVzIDwtIG5hbWVzKGFsbF9yZXN1bHRzKQ0KDQogIGZvciAoaSBpbiBzZXFfYWxvbmcoY29uZmlnX25hbWVzKSkgew0KICAgIGlkIDwtIGNvbmZpZ19uYW1lc1tpXQ0KICAgIGN1cnJfbWF0IDwtIGFsbF9yZXN1bHRzW1tpZF1dJGFkamFjZW5jeQ0KICAgIA0KICAgIHBsb3RfZGYgPC0gYXMuZGF0YS5mcmFtZShjdXJyX21hdCkgJT4lDQogICAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiZnJvbSIpICU+JQ0KICAgICAgdGlkeXI6OnBpdm90X2xvbmdlcigtZnJvbSwgbmFtZXNfdG8gPSAidG8iLCB2YWx1ZXNfdG8gPSAiZXhpc3RzIikgJT4lDQogICAgICByb3d3aXNlKCkgJT4lDQogICAgICBtdXRhdGUoDQogICAgICAgIGlzX3VuaXYgPSB1bml2ZXJzYWxfbWF0W2Zyb20sIHRvXSwNCiAgICAgICAgaXNfcmVmICA9IHJlZl9tYXRbZnJvbSwgdG9dLA0KICAgICAgICBjYXRlZ29yeSA9IGNhc2Vfd2hlbigNCiAgICAgICAgICBleGlzdHMgPT0gMSAgJiBpc191bml2ID09IFRSVUUgfiAiVW5pdmVyc2FsIiwNCiAgICAgICAgICBpZCA9PSByZWZfaWQgJiBleGlzdHMgPT0gMSAmIGlzX3VuaXYgPT0gRkFMU0UgfiAiUmVmZXJlbmNlIE9ubHkiLA0KICAgICAgICAgIGV4aXN0cyA9PSAxICAmIGlzX3JlZiA9PSAxICYgaXNfdW5pdiA9PSBGQUxTRSB+ICJNYXRjaCBSZWYiLA0KICAgICAgICAgIGV4aXN0cyA9PSAxICAmIGlzX3JlZiA9PSAwICYgaXNfdW5pdiA9PSBGQUxTRSB+ICJOZXcgRWRnZSIsDQogICAgICAgICAgVFJVRSB+ICJOb25lIg0KICAgICAgICApDQogICAgICApICU+JSB1bmdyb3VwKCkNCg0KICAgIGlzX2xlZnRfY29sICAgPC0gaSAlaW4lIGMoMSwgNCkNCiAgICBpc19ib3R0b21fcm93IDwtIGkgJWluJSBjKDQsIDUsIDYpDQoNCiAgICBwIDwtIGdncGxvdChwbG90X2RmLCBhZXMoeCA9IGZhY3Rvcih0bywgbGV2ZWxzID0gbWFzdGVyX25vZGVzKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBmYWN0b3IoZnJvbSwgbGV2ZWxzID0gcmV2KG1hc3Rlcl9ub2RlcykpKSkgKw0KICAgICAgZ2VvbV90aWxlKGFlcyhmaWxsID0gY2F0ZWdvcnkpLCBjb2xvciA9ICJncmF5ODUiLCBsaW5ld2lkdGggPSAwLjIpICsNCiAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKA0KICAgICAgICB2YWx1ZXMgPSBjKCJVbml2ZXJzYWwiID0gImJsYWNrIiwgIlJlZmVyZW5jZSBPbmx5IiA9IHJlZl9jb2xvciwgDQogICAgICAgICAgICAgICAgICAgIk1hdGNoIFJlZiIgPSBjb21wX2NvbG9yLCAiTmV3IEVkZ2UiID0gImxpZ2h0Z3JheSIsICJOb25lIiA9ICJ3aGl0ZSIpLA0KICAgICAgICBndWlkZSA9ICJub25lIg0KICAgICAgKSArDQogICAgICBsYWJzKHRpdGxlID0gaWQsIHggPSBOVUxMLCB5ID0gTlVMTCkgKw0KICAgICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICAgIHRoZW1lKA0KICAgICAgICBhc3BlY3QucmF0aW8gPSAxLA0KICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiYmxhY2siLCBmaWxsID0gTkEsIGxpbmV3aWR0aCA9IDAuOCksDQogICAgICAgIHBhbmVsLmdyaWQgICA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSAgID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCAgPSBpZihpc19ib3R0b21fcm93KSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSwgc2l6ZSA9IDcpIGVsc2UgZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBpZihpc19ib3R0b21fcm93KSBlbGVtZW50X2xpbmUoY29sb3IgPSAiYmxhY2siKSBlbHNlIGVsZW1lbnRfbGluZShjb2xvciA9IE5BKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgID0gaWYoaXNfbGVmdF9jb2wpIGVsZW1lbnRfdGV4dChzaXplID0gNykgZWxzZSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGlja3MueSA9IGlmKGlzX2xlZnRfY29sKSBlbGVtZW50X2xpbmUoY29sb3IgPSAiYmxhY2siKSBlbHNlIGVsZW1lbnRfbGluZShjb2xvciA9IE5BKSwNCiAgICAgICAgYXhpcy50aWNrcy5sZW5ndGggPSB1bml0KDMsICJwdCIpLA0KICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbig1LCA1LCA1LCA1KSwNCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvciA9IE5BKQ0KICAgICAgKQ0KICAgIHBsb3RfbGlzdFtbaV1dIDwtIHANCiAgfQ0KDQogIGZpbmFsX2dyaWQgPC0gcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDMsIG5yb3cgPSAyKSArIA0KICAgIHBsb3RfYW5ub3RhdGlvbigNCiAgICAgIHRpdGxlID0gcGFzdGUoIlN0cnVjdHVyYWwgU3RhYmlsaXR5IEFuYWx5c2lzOiIsIHJ1bl9uYW1lKSwNCiAgICAgIHN1YnRpdGxlID0gIlJvdyAxOiAyLWxldmVsIERpc2NyZXRpemF0aW9uIHwgUm93IDI6IDMtbGV2ZWwgRGlzY3JldGl6YXRpb24iLA0KICAgICAgdGhlbWUgPSB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgaGp1c3QgPSAwLjUpLA0KICAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG9yID0gTkEpKQ0KICAgICkNCiAgDQogIGdnc2F2ZShmaWxlLnBhdGgocnVuX3BhdGgsICJBZGphY2VuY3lfQ29tcGFyaXNvbl9HcmlkX0ZpbmFsLnBuZyIpLCANCiAgICAgICAgIHBsb3QgPSBmaW5hbF9ncmlkLCB3aWR0aCA9IDExLCBoZWlnaHQgPSA3LjUsIGJnID0gInRyYW5zcGFyZW50IiwgdHlwZSA9ICJjYWlybyIpDQoNCiAgIyAtLS0gMTMuMyBDUFQgKFJFRkVSRU5DRSBPTkxZKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQogIHJlZl9yZXMgIDwtIGFsbF9yZXN1bHRzW1tyZWZfaWRdXQ0KICBtYl9ub2RlcyA8LSByZWZfcmVzJG1iDQogIA0KICAjIElkZW50aWZ5IHZhcmlhYmxlcyBhbmQgb3JkZXIgdGhlbSAocHV0dGluZyBQQzMgYmVmb3JlIFJpc2sgaWYgcHJlc2VudCkNCiAgcGNfaW5fbWIgPC0gZ3JlcCgiXlBDWzEtNV0kIiwgbWJfbm9kZXMsIHZhbHVlID0gVFJVRSkNCiAgb3RoZXJfbWIgPC0gc2V0ZGlmZihtYl9ub2RlcywgcGNfaW5fbWIpDQogIA0KICBpZiAoIlBDMyIgJWluJSBwY19pbl9tYikgew0KICAgIG9yZGVyZWRfdmFycyA8LSBjKG90aGVyX21iLCBzZXRkaWZmKHBjX2luX21iLCAiUEMzIiksICJQQzMiLCB0YXJnZXRfbm9kZSkNCiAgfSBlbHNlIHsNCiAgICBvcmRlcmVkX3ZhcnMgPC0gYyhvdGhlcl9tYiwgcGNfaW5fbWIsIHRhcmdldF9ub2RlKQ0KICB9DQogIA0KICAjIDEuIEdlbmVyYXRlIEZyZXF1ZW5jeSBUYWJsZSAoTikgYW5kIFByb2JhYmlsaXR5IFRhYmxlDQogIHJhd19jb3VudHMgICA8LSB0YWJsZShyZWZfcmVzJGRhdGFbLCBvcmRlcmVkX3ZhcnNdKQ0KICBwcm9iX3RhYmxlICAgPC0gcHJvcC50YWJsZShyYXdfY291bnRzLCBtYXJnaW4gPSAxOihsZW5ndGgob3JkZXJlZF92YXJzKSAtIDEpKQ0KICANCiAgIyAyLiBDb252ZXJ0IHRvIERhdGFGcmFtZXMNCiAgZGZfcHJvYnMgIDwtIGFzLmRhdGEuZnJhbWUocHJvYl90YWJsZSkNCiAgZGZfY291bnRzIDwtIGFzLmRhdGEuZnJhbWUocmF3X2NvdW50cykNCiAgDQogICMgMy4gTWVyZ2UgUHJvYmFiaWxpdGllcyBhbmQgU2FtcGxlIE51bWJlcnMgKE4pDQogIGZpbmFsX2NwdF9vdXRwdXQgPC0gZGZfcHJvYnMgJT4lDQogICAgZHBseXI6OnJlbmFtZShQcm9iYWJpbGl0eSA9IEZyZXEpICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoTiA9IGRmX2NvdW50cyRGcmVxKQ0KDQogICMgNC4gU2F2ZSBDU1YNCiAgd3JpdGUuY3N2KGZpbmFsX2NwdF9vdXRwdXQsIGZpbGUucGF0aChydW5fcGF0aCwgIlJlZmVyZW5jZV9SaXNrX0NQVF93aXRoX04uY3N2IiksIHJvdy5uYW1lcyA9IEZBTFNFKQ0KDQogICMgLS0tIDEzLjQgTU9TQUlDIFBMT1QgKFJFRkVSRU5DRSBPTkxZKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQogIG5fY29uZmlncyA8LSBwcm9kKGRpbShjb3VudHNfdGFibGUpWy1sZW5ndGgoZGltKGNvdW50c190YWJsZSkpXSkNCiAgcF9oaWdoICAgIDwtIGFzLnZlY3RvcihjcHRfdGFibGUpWyhuX2NvbmZpZ3MgKyAxKTooMiAqIG5fY29uZmlncyldDQogIHBfaGlnaFtpcy5uYShwX2hpZ2gpXSA8LSAwDQogIA0KICBwX2NvbnRyYXN0IDwtIDEgLyAoMSArIGV4cCgtMTAgKiAocF9oaWdoIC0gMC41KSkpIA0KICBncmFkX3BhbCAgIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygiI0Y1RjVGNSIsIHJlZl9jb2xvcikpKDEwMCkNCiAgcmlza19jb2xvcnMgPC0gZ3JhZF9wYWxbcm91bmQocF9jb250cmFzdCAqIDk5KSArIDFdDQogIGNvbG9yX2FycmF5IDwtIGFycmF5KGMocmVwKCIjRkZGRkZGMDAiLCBuX2NvbmZpZ3MpLCByaXNrX2NvbG9ycyksIGRpbSA9IGRpbShjb3VudHNfdGFibGUpKQ0KDQogICMgTU9TQUlDIFBMT1QgV0lUSCBUSElOIEJMQUNLIE9VVExJTkVTDQogIHBuZyhmaWxlLnBhdGgocnVuX3BhdGgsICJSZWZlcmVuY2VfTW9zYWljX1Jpc2tfRmluYWwucG5nIiksIHdpZHRoID0gMzYwMCwgaGVpZ2h0ID0gMjcwMCwgcmVzID0gMzAwLCBiZyA9ICJ0cmFuc3BhcmVudCIsIHR5cGUgPSAiY2Fpcm8iKQ0KICBtb3NhaWMoY291bnRzX3RhYmxlLCANCiAgICAgICAgIGdwID0gZ3BhcihmaWxsID0gY29sb3JfYXJyYXksIGNvbCA9ICJibGFjayIsIGx3ZCA9IDAuNSksICMgY29sID0gImJsYWNrIiBmb3IgdGhpbiBibGFjayBvdXRsaW5lcw0KICAgICAgICAgbWFpbiA9IHBhc3RlKCJSZWZlcmVuY2UgUnVuOiIsIHJ1bl9uYW1lLCAiUHJvZ25vc3RpYyBSaXNrIEhpZXJhcmNoeSIpLA0KICAgICAgICAgbGFiZWxpbmcgPSBsYWJlbGluZ19ib3JkZXIoZ3BfbGFiZWxzID0gZ3Bhcihmb250c2l6ZSA9IDksIGZvbnRmYWNlID0gImJvbGQiKSwgcm90X2xhYmVscyA9IGMoMCwgOTAsIDAsIDkwKSkpDQogIGRldi5vZmYoKQ0KDQogIGNhdCgiXG5ET05FLiBBbGwgb3V0cHV0cyBzYXZlZCB0bzoiLCBydW5fcGF0aCwgIlxuIikNCiAgcmV0dXJuKGFsbF9yZXN1bHRzKQ0KfQ0KYGBgDQoNCiMjIyAxNC4gTU9GQSBGQUNUT1JTICMjIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmBgYHtyfQ0KIyMgMTQuMSBHRVQgREFUQQ0KIyBERUZJTkUgVkFSSUFCTEVTDQpzZWxlY3Rpb24gPC0gbGlzdCgNCiAgaXNvZm9ybV9wY2EgID0gVFJVRSwgDQogIG1vZmFfZmFjdG9ycyA9IHBhc3RlMCgiRmFjdG9yIiwgMTo4KSwgDQogIGhyZCAgICAgICAgICA9ICJIUkQiLA0KICBzdGVtbmVzcyAgICAgPSAiUk5Bc3MiDQopDQoNCiMgREVGSU5FIE1FVEFEQVRBDQptZXRhX3ZhcnMgPC0gYygib3Nfcmlza19zY29yZSIpDQoNCiMgUlVOIGdldF9kYXRhDQptb2ZhX2RhdGEgPC0gYXMuZGF0YS5mcmFtZShnZXRfZGF0YShtYWUsIHNlbGVjdGlvbiwgbWV0YV92YXJzKSkgJT4lDQogIHJlbmFtZShSSVNLID0gb3Nfcmlza19zY29yZSkNCg0KIyMgMTQuMiBCTEFDS0xJU1QNCm1vZmFfYmwgPC0gZGF0YS5mcmFtZSgNCiAgZnJvbSA9ICJSSVNLIiwgDQogIHRvICAgPSBzZXRkaWZmKGNvbG5hbWVzKG1vZmFfZGF0YSksICJSSVNLIikNCikNCg0KIyMgMTQuMyBSVU4gTUFJTiBGVU5DVElPTg0KbXlfbmV0cyA8LSBydW5fbmV0d29ya3MoDQogIGRmX3JhdyA9IG1vZmFfZGF0YSwNCiAgbl9yZXN0YXJ0cyA9IDEwMCwNCiAgYmwgPSBtb2ZhX2JsLA0KICBydW5fbmFtZSA9ICJtb2ZhIiwNCiAgcmVmX2NvbG9yID0gIiMwMDZFQUUiLA0KICBjb21wX2NvbG9yID0gIiM5QkNBRTkiDQopDQpgYGANCg0KIyMjIDE1LiBGSVQgRVZJREVOQ0UgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KYGBge3J9DQojIyAxNS4xIFBSRVBBUkUgVEhFIFRFTVBMQVRFIEFORCAgREFUQSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZ2xvYmFsX2RhZyA8LSBibmxlYXJuOjpjZXh0ZW5kKG1vZmEuMmRpc2MuYWljJGRhZykNCkRBR19OT0RFUyAgPC0gYm5sZWFybjo6bm9kZXMoZ2xvYmFsX2RhZykNCg0KIyBSRS1TWU5DIE1PRkEgQ0xVU1RFUlMgVE8gWU9VUiBEQVRBIEZSQU1FDQptb2ZhX2RhdGEkbW9mYV9jbHVzdGVyIDwtIGNvbERhdGEobWFlKVtyb3duYW1lcyhtb2ZhX2RhdGEpLCAibW9mYV9jbHVzdGVyIl0NCg0KIyBQRVJGT1JNIEdMT0JBTCBESVNDUkVUSVpBVElPTiBPTkxZIE9OIERBRyBOT0RFUw0KZnVsbF9kaXNjIDwtIGJubGVhcm46OmRpc2NyZXRpemUoDQogIG1vZmFfZGF0YVssIGludGVyc2VjdChjb2xuYW1lcyhtb2ZhX2RhdGEpLCBEQUdfTk9ERVMpXSwgDQogIG1ldGhvZCA9ICdxdWFudGlsZScsIA0KICBicmVha3MgPSAyDQopDQoNCiMgQUREIENMVVNURVIgQVNTSUdOTUVOVFMgVE8gVEhFIERJU0NSRVRJWkVEIERBVEENCmZ1bGxfZGlzYyRtb2ZhX2NsdXN0ZXIgPC0gbW9mYV9kYXRhJG1vZmFfY2x1c3Rlcg0KDQojIERFRklORSBUQVJHRVQgT1VUQ09NRSBBTkQgUFJJTUFSWSBEUklWRVIgTk9ERVMNCnRhcmdldF9ub2RlICAgICA8LSAiUklTSyINCmRyaXZlcl9ub2RlICAgICA8LSAiUEMzIg0KaGlnaF9yaXNrX2xhYmVsIDwtIGxldmVscyhmdWxsX2Rpc2NbW3RhcmdldF9ub2RlXV0pWzJdDQpsb3dfcGMzX2xhYmVsICAgPC0gbGV2ZWxzKGZ1bGxfZGlzY1tbZHJpdmVyX25vZGVdXSlbMV0NCmhpZ2hfcGMzX2xhYmVsICA8LSBsZXZlbHMoZnVsbF9kaXNjW1tkcml2ZXJfbm9kZV1dKVsyXQ0KDQojIDE1LjIgVEhFIFJFRklUIExPT1AgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBJREVOVElGWSBVTklRVUUgQ0xVU1RFUlMgV0hJTEUgSUdOT1JJTkcNCmNsdXN0ZXJzIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUobmEub21pdChmdWxsX2Rpc2MkbW9mYV9jbHVzdGVyKSkpDQpyZWZpdF9yZXN1bHRzIDwtIGxpc3QoKQ0KDQojIEVYVFJBQ1QgRVhBQ1QgTk9ERSBOQU1FUyBSRVFVSVJFRCBCWSBUSEUgR0xPQkFMIERBRw0KZGFnX25vZGVzIDwtIGJubGVhcm46Om5vZGVzKGdsb2JhbF9kYWcpDQoNCmZvciAoY2wgaW4gY2x1c3RlcnMpIHsNCiAgICBjYXQoIj4+PiBSRUZJVFRJTkcgR0xPQkFMIEdSQVBIIEZPUjoiLCBjbCwgIlxuIikNCiAgICANCiAgICAjIEEuIFNVQlNFVCBEQVRBIEZPUiBUSEUgQ1VSUkVOVCBDTFVTVEVSDQogICAgY2xfZGF0YSAgPC0gZnVsbF9kaXNjW2Z1bGxfZGlzYyRtb2ZhX2NsdXN0ZXIgPT0gY2wsIF0NCiAgICBjbF9uICAgICA8LSBucm93KGNsX2RhdGEpDQogICAgDQogICAgIyBCLiBFTlNVUkUgREFUQSBPTkxZIENPTlRBSU5TIERBRyBOT0RFUyBUTyBQUkVWRU5UIERJTUVOU0lPTiBFUlJPUlMNCiAgICBjbF9pbnB1dCA8LSBjbF9kYXRhWywgaW50ZXJzZWN0KGNvbG5hbWVzKGNsX2RhdGEpLCBkYWdfbm9kZXMpXQ0KICAgIA0KICAgICMgVkFMSURBVEUgVEhBVCBBTEwgUkVRVUlSRUQgTk9ERVMgQVJFIFBSRVNFTlQgSU4gVEhFIFNVQlNFVA0KICAgIG1pc3Npbmdfbm9kZXMgPC0gc2V0ZGlmZihkYWdfbm9kZXMsIGNvbG5hbWVzKGNsX2lucHV0KSkNCiAgICBpZihsZW5ndGgobWlzc2luZ19ub2RlcykgPiAwKSB7DQogICAgICAgIHN0b3AocGFzdGUoIkRBVEEgSVMgTUlTU0lORyBOT0RFUyBSRVFVSVJFRCBCWSBEQUc6IiwgcGFzdGUobWlzc2luZ19ub2RlcywgY29sbGFwc2U9IiwgIikpKQ0KICAgIH0NCg0KICAgICMgQy4gUkVGSVQgUEFSQU1FVEVSUyBVU0lORyBCQVlFU0lBTiBFU1RJTUFUSU9OIChJU1M9MSkNCiAgICBjbF9maXQgPC0gYm5sZWFybjo6Ym4uZml0KGdsb2JhbF9kYWcsIGNsX2lucHV0LCBtZXRob2QgPSAiYmF5ZXMiLCBpc3MgPSAxKQ0KICAgIA0KICAgICMgRC4gQ0FMQ1VMQVRFIE1BUkdJTkFMIEVGRkVDVFMgVklBIENPTkRJVElPTkFMIFBST0JBQklMSVRZIFRBQkxFIA0KICAgIGNwdF9yaXNrIDwtIGFzLmRhdGEuZnJhbWUoY2xfZml0W1t0YXJnZXRfbm9kZV1dJHByb2IpDQogICAgY3B0X2hpZ2ggPC0gY3B0X3Jpc2tbY3B0X3Jpc2tbW3RhcmdldF9ub2RlXV0gPT0gaGlnaF9yaXNrX2xhYmVsLCBdDQogICAgDQogICAgIyBQSVZPVCBUQUJMRSBUTyBDT01QQVJFIFBDMyBISUdIIFZTIExPVyBBQ1JPU1MgQUxMIFBBUkVOVCBTVEFURVMNCiAgICB3aWRlX2NwdCA8LSB0aWR5cjo6cGl2b3Rfd2lkZXIoDQogICAgICBjcHRfaGlnaCwgDQogICAgICBuYW1lc19mcm9tID0gISFzeW0oZHJpdmVyX25vZGUpLCANCiAgICAgIHZhbHVlc19mcm9tID0gRnJlcSwgDQogICAgICBuYW1lc19wcmVmaXggPSAiUEMzXyINCiAgICApDQogICAgDQogICAgY29sX2xvdyAgPC0gcGFzdGUwKCJQQzNfIiwgbG93X3BjM19sYWJlbCkNCiAgICBjb2xfaGlnaCA8LSBwYXN0ZTAoIlBDM18iLCBoaWdoX3BjM19sYWJlbCkNCiAgICANCiAgICBpZiAoY29sX2xvdyAlaW4lIGNvbG5hbWVzKHdpZGVfY3B0KSAmIGNvbF9oaWdoICVpbiUgY29sbmFtZXMod2lkZV9jcHQpKSB7DQogICAgICAgICMgQ09NUFVURSBESUZGRVJFTkNFIElOIFJJU0sgRk9SIEVWRVJZIEJBQ0tHUk9VTkQgQ09ORklHVVJBVElPTg0KICAgICAgICBhbGxfZGVsdGFzIDwtIHdpZGVfY3B0W1tjb2xfaGlnaF1dIC0gd2lkZV9jcHRbW2NvbF9sb3ddXQ0KICAgICAgICANCiAgICAgICAgIyAxLiBDQUxDVUxBVEUgTUVBTiBORVQgRUZGRUNUIChESVJFQ1RJT05BTCBJTVBBQ1QpDQogICAgICAgIG1lYW5fZGVsdGEgPC0gbWVhbihhbGxfZGVsdGFzLCBuYS5ybSA9IFRSVUUpDQogICAgICAgIA0KICAgICAgICAjIDIuIENBTENVTEFURSBNRUFOIEFCU09MVVRFIElORkxVRU5DRSAoVE9UQUwgQklPTE9HSUNBTCBXRUlHSFQpDQogICAgICAgIG1lYW5fYWJzX2RlbHRhIDwtIG1lYW4oYWJzKGFsbF9kZWx0YXMpLCBuYS5ybSA9IFRSVUUpDQogICAgICAgIA0KICAgICAgICBjYXQoIiAgICBNRUFOIERFTFRBIFJJU0s6Iiwgcm91bmQobWVhbl9kZWx0YSwgNCksICJcbiIpDQogICAgICAgIGNhdCgiICAgIE1FQU4gQUJTT0xVVEUgSU5GTFVFTkNFOiIsIHJvdW5kKG1lYW5fYWJzX2RlbHRhLCA0KSwgIlxuIikNCiAgICB9IGVsc2Ugew0KICAgICAgICBtZWFuX2RlbHRhIDwtIE5BOyBtZWFuX2Fic19kZWx0YSA8LSBOQTsgaW50ZXJhY3Rpb25faWR4IDwtIE5BDQogICAgfQ0KICAgIA0KICAgICMgRS4gU1RPUkUgUkVTVUxUUyBJTiBEQVRBRlJBTUUNCiAgICByZWZpdF9yZXN1bHRzW1tjbF1dIDwtIGRhdGEuZnJhbWUoDQogICAgICAgIENsdXN0ZXIgPSBjbCwNCiAgICAgICAgTiA9IGNsX24sDQogICAgICAgIE1lYW5fTmV0X0VmZmVjdCA9IG1lYW5fZGVsdGEsDQogICAgICAgIE1lYW5fQWJzb2x1dGVfSW5mbHVlbmNlID0gbWVhbl9hYnNfZGVsdGEsDQogICAgICAgIEludGVyYWN0aW9uX0luZGV4ID0gaW50ZXJhY3Rpb25faWR4DQogICAgKQ0KICAgIA0KICAgICMgU0FWRSBUSEUgQ0xVU1RFUi1TUEVDSUZJQyBGSVRURUQgT0JKRUNUDQogICAgc2F2ZVJEUyhjbF9maXQsIGZpbGUucGF0aChvdXRwdXRfZGlyLCAibW9mYSIsIHBhc3RlMCgiUmVmaXR0ZWRfR3JhcGhfIiwgY2wsICIucmRzIikpKQ0KfQ0KDQojIDMuIEZJTkFMIFNVTU1BUlkgQU5EIEVYUE9SVCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZmluYWxfZGVsdGFfZGYgPC0gZG8uY2FsbChyYmluZCwgcmVmaXRfcmVzdWx0cykNCnJvd25hbWVzKGZpbmFsX2RlbHRhX2RmKSA8LSBOVUxMDQpwcmludChmaW5hbF9kZWx0YV9kZikNCg0Kd3JpdGUuY3N2KGZpbmFsX2RlbHRhX2RmLCBmaWxlLnBhdGgob3V0cHV0X2RpciwgIm1vZmEiLCAiQ2x1c3Rlcl9QQzNfTWFyZ2luYWxfRWZmZWN0c19GdWxsLmNzdiIpLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQojIyMgMTYuIFZJT0xJTiBQTE9UUyAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMgMTYuMSBEQVRBIEVYVFJBQ1RJT04gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpTRUxFQ1RJT05fSEFMTE1BUktTIDwtIGxpc3QoDQogICBycHBhID0gYygiRVJBTFBIQSIsICJQUiIsICJIRVIyIiwgIkdBVEEzIiksDQogICBybmFzZXEgPSBjKCJTT1gxMCIsICJNS0k2NyIpIA0KKQ0KDQpIQUxMTUFSS19EQVRBIDwtIGFzLmRhdGEuZnJhbWUoZ2V0X2RhdGEobWFlLCBTRUxFQ1RJT05fSEFMTE1BUktTLCBjKCJtb2ZhX2NsdXN0ZXIiKSkpDQoNCiMgMTYuMiBMT05HIEZPUk1BVCBQUkVQIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpUQVJHRVRfRkVBVFVSRVMgPC0gYygiRVJBTFBIQSIsICJQUiIsICJIRVIyIiwgIkdBVEEzIiwgIlNPWDEwIiwgIk1LSTY3IikNCg0KSEFMTE1BUktfTE9ORyA8LSBIQUxMTUFSS19EQVRBICU+JQ0KICBzZWxlY3QobW9mYV9jbHVzdGVyLCBhbGxfb2YoVEFSR0VUX0ZFQVRVUkVTKSkgJT4lDQogIGZpbHRlcighaXMubmEobW9mYV9jbHVzdGVyKSkgJT4lDQogIG11dGF0ZShtb2ZhX2NsdXN0ZXIgPSBmYWN0b3IobW9mYV9jbHVzdGVyKSkgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gYWxsX29mKFRBUkdFVF9GRUFUVVJFUyksIA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiRmVhdHVyZSIsIA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIkxldmVsIikNCg0KIyAxNi4zIFNFVFRJTkdTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCk1ZX1BBTEVUVEUgPC0gYygiIzAwNkVBRSIsICIjQ0E5QjIzIiwgIiNDNTM3M0QiKQ0KTVlfQ09NUEFSSVNPTlMgPC0gY29tYm4obGV2ZWxzKEhBTExNQVJLX0xPTkckbW9mYV9jbHVzdGVyKSwgMiwgc2ltcGxpZnkgPSBGQUxTRSkNCg0KIyAxNi40IFBMT1QgR0VORVJBVElPTiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NClBfSEFMTE1BUktTX0ZJTkFMIDwtIGdncGxvdChIQUxMTUFSS19MT05HLCBhZXMoeCA9IG1vZmFfY2x1c3RlciwgeSA9IExldmVsLCBmaWxsID0gbW9mYV9jbHVzdGVyKSkgKw0KICBnZW9tX3Zpb2xpbih0cmltID0gRkFMU0UsIGFscGhhID0gMC43LCBjb2xvciA9ICJibGFjayIpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4xLCBmaWxsID0gIndoaXRlIiwgb3V0bGllci5zaGFwZSA9IE5BLCBjb2xvciA9ICJibGFjayIpICsNCiAgDQogIGZhY2V0X3dyYXAofkZlYXR1cmUsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDMpICsNCiAgDQogICMgU0lHTklGSUNBTkNFIFNUQVJTDQogIHN0YXRfY29tcGFyZV9tZWFucyhjb21wYXJpc29ucyA9IE1ZX0NPTVBBUklTT05TLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gInAuc2lnbmlmIiwgDQogICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAid2lsY294LnRlc3QiKSArDQogIA0KICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSArIA0KICANCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gTVlfUEFMRVRURSkgKw0KICB0aGVtZV9wdWJyKCkgKyANCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICMgVEhFTUUNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBsb3QuYmFja2dyb3VuZCAgPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgDQogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxMiksDQogICAgDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuc3BhY2luZy55ID0gdW5pdCgyLCAibGluZXMiKSwNCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbih0ID0gMzAsIHIgPSAxNSwgYiA9IDE1LCBsID0gMTUpDQogICkgKw0KICBsYWJzKHRpdGxlID0gIkNsaW5pY2FsIEhhbGxtYXJrIEV4cHJlc3Npb24gYnkgQ2x1c3RlciIsDQogICAgICAgeCA9IE5VTEwsIA0KICAgICAgIHkgPSAiUmVsYXRpdmUgTGV2ZWwiKQ0KDQojIDE2LjUgU0FWRSAgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZ2dzYXZlKGZpbGUucGF0aChvdXRwdXRfZGlyLCAibW9mYSIsICJIYWxsbWFya3NfRmluYWxfVHJhbnNwYXJlbnQucG5nIiksIA0KICAgICAgIFBfSEFMTE1BUktTX0ZJTkFMLCANCiAgICAgICB3aWR0aCA9IDEyLCBoZWlnaHQgPSAxMCwgDQogICAgICAgZHBpID0gMzAwLCANCiAgICAgICBiZyA9ICJ0cmFuc3BhcmVudCIpDQpgYGANCg0KIyMjIDE3LiBTVEVNTkVTUyAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KYGBge3J9DQojIyAxNy4xIEdFVCBEQVRBDQojIERFRklORSBWQVJJQUJMRVMNCnNlbGVjdGlvbiA8LSBsaXN0KA0KICBpc29mb3JtX3BjYSAgPSBUUlVFLCANCiAgc3RlbW5lc3MgICAgID0gIlJOQXNzIiwNCiAgbXV0YXRpb24gICAgID0gIlRQNTMiLA0KICBybmFzZXEgICAgICAgPSBjKCAgICANCiAgICANCiAgICAjIHA1MyBDT05URVhUDQogICAgIlRQNjMiLCAiVFA3MyIsIlBQUDFSMTNCIiwgIlRQNTNCUDIiLCAiVFA1M0JQMSIsICJQUFAxUjEzTCIsICJNRE0yIiwgDQogICAgDQogICAgIyBORVVST0VORE9DUklORSBNQVJLRVRTDQogICAgIkFTQ0wxIiwgIklOU00xIiwgIlNZUCIsICJDSEdBIiwgIkdBVEEzIiwgIlZHRiIsDQogICAgDQogICAgIyBTVEVNTkVTUw0KICAgICJTT1gxMCIsICJTT1gyIiwgIk1ZQyIsICJQUk9NMSIsDQogICAgDQogICAgIyBFTVQNCiAgICAiU05BSTEiLCAiWkVCMSIsICJDREgxIikNCikNCg0KIyBERUZJTkUgTUVUQURBVEENCm1ldGFfdmFycyA8LSBjKCJvc19yaXNrX3Njb3JlIiwgIm1vZmFfY2x1c3RlciIpDQoNCiMgUlVOIGdldF9kYXRhDQpzdGVtbmVzc19kYXRhIDwtIGFzLmRhdGEuZnJhbWUoZ2V0X2RhdGEobWFlLCBzZWxlY3Rpb24sIG1ldGFfdmFycykpICU+JQ0KICByZW5hbWUoUklTSyA9IG9zX3Jpc2tfc2NvcmUpICU+JQ0KICBmaWx0ZXIobW9mYV9jbHVzdGVyID09ICJDbHVzdGVyXzIiKSAlPiUNCiAgc2VsZWN0KC1tb2ZhX2NsdXN0ZXIpDQoNCiMjIDE3LjIgQkxBQ0tMSVNUDQpzdGVtbmVzc19ibCA8LSBkYXRhLmZyYW1lKA0KICBmcm9tID0gIlJJU0siLCANCiAgdG8gICA9IHNldGRpZmYoY29sbmFtZXMoc3RlbW5lc3NfZGF0YSksICJSSVNLIikNCikNCg0KIyMgMTcuMyBBUyBGQUNUT1INCnN0ZW1uZXNzX2RhdGEkVFA1MyA8LSBhcy5mYWN0b3Ioc3RlbW5lc3NfZGF0YSRUUDUzKQ0KDQojIyAxNy40IFJVTiBNQUlOIEZVTkNUSU9ODQpteV9uZXRzIDwtIHJ1bl9uZXR3b3JrcygNCiAgZGZfcmF3ID0gc3RlbW5lc3NfZGF0YSwNCiAgbl9yZXN0YXJ0cyA9IDEwMCwNCiAgYmwgPSBzdGVtbmVzc19ibCwNCiAgcnVuX25hbWUgPSAic3RlbW5lc3MiLA0KICByZWZfY29sb3IgPSAiI0NBOUIyMyIsDQogIGNvbXBfY29sb3IgPSAiI0Y2REM4NyINCikNCmBgYA0KDQojIyMgMTguIElNTVVORSAjIyM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpgYGB7cn0NCiMjIDE4LjEgR0VUIERBVEENCiMgREVGSU5FIFZBUklBQkxFUw0Kc2VsZWN0aW9uIDwtIGxpc3QoDQogIGlzb2Zvcm1fcGNhICA9IFRSVUUsIA0KICBtdXRhdGlvbiAgICAgPSAiVFA1MyIsDQogIHJuYXNlcSAgICAgICA9IGMoICAgIA0KICAgICAgICAiVFA2MyIsICJUUDczIiwiUFBQMVIxM0IiLCAiVFA1M0JQMiIsICJUUDUzQlAxIiwgIlBQUDFSMTNMIiwgIk1ETTIiDQogICAgDQogICAgKSwNCiAgaW1tdW5lID0gYygNCiAgICAgICAgIlRBTXN1cnJfc2NvcmUiLCAiVGNlbGxfcmVjZXB0b3JzX3Njb3JlIiwgIkJjZWxsX3JlY2VwdG9yc19zY29yZSIsICJQRDFfUERMMV9zY29yZSIsDQogICAgIklGTkdfc2NvcmVfMjEwNTA0NjciLCAiTUhDMl8yMTk3ODQ1NiIsICJUR0ZCX1BDQV8xNzM0OTU4MyIsICJUcm9lc3Rlcl9Xb3VuZFNpZ18xOTg4NzQ4NCIsDQogICAgIkNoZW1va2luZTEyX3Njb3JlIiwgIk1vZHVsZTExX1Byb2xpZl9zY29yZSINCiAgICApDQopDQoNCiMgREVGSU5FIE1FVEFEQVRBDQptZXRhX3ZhcnMgPC0gYygib3Nfcmlza19zY29yZSIsICJtb2ZhX2NsdXN0ZXIiKQ0KDQojIFJVTiBnZXRfZGF0YQ0KaW1tdW5lX2RhdGEgPC0gYXMuZGF0YS5mcmFtZShnZXRfZGF0YShtYWUsIHNlbGVjdGlvbiwgbWV0YV92YXJzKSkgJT4lDQogIHJlbmFtZSgNCiAgICAjIE5FV19OQU1FID0gT0xEX05BTUUNCiAgICBSSVNLICAgICAgICAgICA9IG9zX3Jpc2tfc2NvcmUsDQogICAgVEFNc3VyciAgICAgICAgPSBUQU1zdXJyX3Njb3JlLA0KICAgIFRDZWxsX1JlYyAgICAgID0gVGNlbGxfcmVjZXB0b3JzX3Njb3JlLA0KICAgIEJDZWxsX1JlYyAgICAgID0gQmNlbGxfcmVjZXB0b3JzX3Njb3JlLA0KICAgIFBEMV9QREwxICAgICAgID0gUEQxX1BETDFfc2NvcmUsDQogICAgSUZORyAgICAgICAgICAgPSBJRk5HX3Njb3JlXzIxMDUwNDY3LA0KICAgIE1IQzIgICAgICAgICAgID0gTUhDMl8yMTk3ODQ1NiwNCiAgICBUR0ZCICAgICAgICAgICA9IFRHRkJfUENBXzE3MzQ5NTgzLA0KICAgIFdvdW5kX0hlYWxpbmcgID0gVHJvZXN0ZXJfV291bmRTaWdfMTk4ODc0ODQsDQogICAgQ2hlbW9raW5lMTIgICAgPSBDaGVtb2tpbmUxMl9zY29yZSwNCiAgICBQcm9saWZlcmF0aW9uICA9IE1vZHVsZTExX1Byb2xpZl9zY29yZQ0KICApICU+JQ0KICBmaWx0ZXIobW9mYV9jbHVzdGVyID09ICJDbHVzdGVyXzIiKSAlPiUNCiAgc2VsZWN0KC1tb2ZhX2NsdXN0ZXIpDQoNCiMjIDE4LjIgQkxBQ0tMSVNUDQppbW11bmVfYmwgPC0gZGF0YS5mcmFtZSgNCiAgZnJvbSA9ICJSSVNLIiwgDQogIHRvICAgPSBzZXRkaWZmKGNvbG5hbWVzKGltbXVuZV9kYXRhKSwgIlJJU0siKQ0KKQ0KDQojIyAxOC4zIEFTIEZBQ1RPUg0KaW1tdW5lX2RhdGEkVFA1MyA8LSBhcy5mYWN0b3IoaW1tdW5lX2RhdGEkVFA1MykNCg0KIyMgMTguNCBSVU4gTUFJTiBGVU5DVElPTg0KbXlfbmV0cyA8LSBydW5fbmV0d29ya3MoDQogIGRmX3JhdyA9IGltbXVuZV9kYXRhLA0KICBuX3Jlc3RhcnRzID0gMTAwLA0KICBibCA9IGltbXVuZV9ibCwNCiAgcnVuX25hbWUgPSAiaW1tdW5lIiwNCiAgcmVmX2NvbG9yID0gIiNDNTM3M0QiLA0KICBjb21wX2NvbG9yID0gIiNFOUEwQTUiDQopDQpgYGANCg0KIyMjIDE5LiBSRlMgIyMjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KDQpgYGB7cn0NCiMjIDE5LjEgR0VUIERBVEEgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgREVGSU5FIFZBUklBQkxFUw0Kc2VsZWN0aW9uIDwtIGxpc3QoDQogIGlzb2Zvcm1fcGNhICA9IGMoIlBDMyIsICJQQzUiKSwNCiAgbW9mYV9mYWN0b3JzID0gYygiRmFjdG9yMiIsICJGYWN0b3IzIiwgIkZhY3RvcjgiKSwNCiAgc3RlbW5lc3MgICAgID0gIlJOQXNzIiwNCiAgcm5hc2VxICAgICAgID0gYygiU09YMTAiKSwNCiAgaW1tdW5lICAgICAgID0gYygiVHJvZXN0ZXJfV291bmRTaWdfMTk4ODc0ODQiKSwNCiAgbXV0YXRpb24gICAgID0gIlRQNTMiDQopDQoNCiMgREVGSU5FIE1FVEFEQVRBDQptZXRhX3ZhcnMgPC0gYygiT1MiLCAiT1MudGltZSIsICJtb2ZhX2NsdXN0ZXIiKQ0KDQojIFJVTiBnZXRfZGF0YQ0KcmZzX2RhdGEgPC0gYXMuZGF0YS5mcmFtZShnZXRfZGF0YShtYWUsIHNlbGVjdGlvbiwgbWV0YV92YXJzKSkgJT4lDQogIGZpbHRlcihtb2ZhX2NsdXN0ZXIgPT0gIkNsdXN0ZXJfMiIpICU+JQ0KICBzZWxlY3QoLW1vZmFfY2x1c3RlcikNCg0KIyMgMTkuMiBQUkUtRklMVEVSIERBVEEgKDctWUVBUlMgT05MWSkgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnJmc19jbGVhbiA8LSByZnNfZGF0YSAlPiUNCiAgZmlsdGVyKE9TLnRpbWUgPD0gMjU1NSkgJT4lDQogIGRyb3BfbmEoKQ0KDQpjYXQoc3ByaW50ZigiXG5TYW1wbGUgc2l6ZSBhZnRlciBmaWx0ZXJpbmc6IE4gPSAlZFxuIiwgbnJvdyhyZnNfY2xlYW4pKSkNCg0KIyMgMTkuMyBTRVQgU1VSVklWQUwgRElSIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0Kc3Vydml2YWxfZGlyIDwtIGZpbGUucGF0aChvdXRwdXRfZGlyLCAic3Vydml2YWwiKQ0KDQojIyAxOS40IFNBVkUgRlVOQ1RJT04gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpzYXZlX3BuZyA8LSBmdW5jdGlvbihwbG90LCBmaWxlbmFtZSwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNikgew0KICBnZ3NhdmUoDQogICAgZmlsZW5hbWUgPSBmaWxlLnBhdGgoc3Vydml2YWxfZGlyLCBmaWxlbmFtZSksDQogICAgcGxvdCAgICAgPSBwbG90LA0KICAgIHdpZHRoICAgID0gd2lkdGgsDQogICAgaGVpZ2h0ICAgPSBoZWlnaHQsDQogICAgZHBpICAgICAgPSAzMDAsDQogICAgYmcgICAgICAgPSAidHJhbnNwYXJlbnQiDQogICkNCiAgY2F0KHNwcmludGYoIlNhdmVkOiAlc1xuIiwgZmlsZW5hbWUpKQ0KfQ0KDQojIyAxOS41IERFRklORSBGRUFUVVJFIFNFVFMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQphbGxfZmVhdHVyZXMgICAgPC0gY29sbmFtZXMocmZzX2NsZWFuKVshY29sbmFtZXMocmZzX2NsZWFuKSAlaW4lIGMoIk9TIiwgIk9TLnRpbWUiLCAiUEMzIiwgIlBDNSIpXQ0KZmVhdHVyZXNfZnVsbCAgIDwtIGMoIlBDMyIsICJQQzUiLCBhbGxfZmVhdHVyZXMpDQpmZWF0dXJlc19ub19wNTMgPC0gYWxsX2ZlYXR1cmVzDQoNCiMjIDE5LjYgQ1JPU1MtVkFMSURBVElPTiAoMjAgw5cgNTA6NTAgU3BsaXQpIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQphbGxfbW9kZWxfcmVzdWx0cyA8LSBsaXN0KCkNCmFsbF9rbV9kYXRhICAgICAgIDwtIGxpc3QoKQ0KDQpmb3IgKG1vZGVsX3R5cGUgaW4gYygiRnVsbCIsICJOb19wNTMiKSkgew0KDQogIGN1cnJlbnRfZmVhdHVyZXMgPC0gaWYgKG1vZGVsX3R5cGUgPT0gIkZ1bGwiKSBmZWF0dXJlc19mdWxsIGVsc2UgZmVhdHVyZXNfbm9fcDUzDQogIHJmX2Zvcm11bGEgPC0gYXMuZm9ybXVsYSgNCiAgICBwYXN0ZSgiU3VydihPUy50aW1lLCBPUykgfiIsIHBhc3RlKGN1cnJlbnRfZmVhdHVyZXMsIGNvbGxhcHNlID0gIisiKSkNCiAgKQ0KDQogIHJlc3VsdHNfbGlzdCA8LSBsaXN0KCkNCiAga21fZGF0YV9saXN0IDwtIGxpc3QoKQ0KDQogIGZvciAoaSBpbiAxOjIwKSB7DQoNCiAgICBzZXQuc2VlZChpKQ0KDQogICAgIyBBLiA1MDo1MCBSQU5ET00gU1BMSVRTDQogICAgdHJhaW5faWR4IDwtIHNhbXBsZShzZXFfbGVuKG5yb3cocmZzX2NsZWFuKSksIHNpemUgPSBmbG9vcigwLjUwICogbnJvdyhyZnNfY2xlYW4pKSkNCiAgICB0cmFpbl9zZXQgPC0gcmZzX2NsZWFuW3RyYWluX2lkeCwgXQ0KICAgIHRlc3Rfc2V0ICA8LSByZnNfY2xlYW5bLXRyYWluX2lkeCwgXQ0KDQogICAgIyBCLiBUUkFJTiBSRlMNCiAgICByZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3RTUkM6OnJmc3JjKHJmX2Zvcm11bGEsIGRhdGEgPSB0cmFpbl9zZXQsIG50cmVlID0gMTAwMCkNCg0KICAgICMgQy4gUFJFRElDVCBPTiBURVNUIFNFVA0KICAgIHJmX3ByZWQgPC0gcmFuZG9tRm9yZXN0U1JDOjo6cHJlZGljdC5yZnNyYyhyZl9tb2RlbCwgdGVzdF9zZXQpDQogICAgdGVzdF9zZXQkUHJlZGljdGVkX1Jpc2sgPC0gcmZfcHJlZCRwcmVkaWN0ZWQNCg0KICAgICMgRC4gT1BUSU1BTCBDVVRQT0lOVA0KICAgIHJlc19jdXQgPC0gdHJ5KA0KICAgICAgc3Vydm1pbmVyOjpzdXJ2X2N1dHBvaW50KHRlc3Rfc2V0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWUgICAgICA9ICJPUy50aW1lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudCAgICAgPSAiT1MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlcyA9ICJQcmVkaWN0ZWRfUmlzayIpLA0KICAgICAgc2lsZW50ID0gVFJVRQ0KICAgICkNCg0KICAgIGlmICghaW5oZXJpdHMocmVzX2N1dCwgInRyeS1lcnJvciIpKSB7DQoNCiAgICAgIG9wdGltYWxfY3V0ICAgIDwtIHJlc19jdXQkY3V0cG9pbnQkY3V0cG9pbnQNCiAgICAgIHRlc3Rfc2V0JEdyb3VwIDwtIGZhY3RvcigNCiAgICAgICAgaWZlbHNlKHRlc3Rfc2V0JFByZWRpY3RlZF9SaXNrID4gb3B0aW1hbF9jdXQsICJIaWdoIiwgIkxvdyIpLA0KICAgICAgICBsZXZlbHMgPSBjKCJMb3ciLCAiSGlnaCIpDQogICAgICApDQoNCiAgICAgICMgRS4gRElTQ1JFVEUgQ09YIE1PREVMDQogICAgICBjb3hfbW9kIDwtIHN1cnZpdmFsOjpjb3hwaChTdXJ2KE9TLnRpbWUsIE9TKSB+IEdyb3VwLCBkYXRhID0gdGVzdF9zZXQpDQogICAgICBzdW1fY294IDwtIHN1bW1hcnkoY294X21vZCkNCg0KICAgICAgcmVzdWx0c19saXN0W1tpXV0gPC0gZGF0YS5mcmFtZSgNCiAgICAgICAgSXRlcmF0aW9uID0gaSwNCiAgICAgICAgTW9kZWwgICAgID0gbW9kZWxfdHlwZSwNCiAgICAgICAgUF9WYWx1ZSAgID0gc3VtX2NveCRsb2d0ZXN0WyJwdmFsdWUiXSwNCiAgICAgICAgSFIgICAgICAgID0gc3VtX2NveCRjb25mLmludFsxXSwNCiAgICAgICAgTG93ZXJfQ0kgID0gc3VtX2NveCRjb25mLmludFszXSwNCiAgICAgICAgVXBwZXJfQ0kgID0gc3VtX2NveCRjb25mLmludFs0XSwNCiAgICAgICAgQ19JbmRleCAgID0gMSAtIHJmX21vZGVsJGVyci5yYXRlW3JmX21vZGVsJG50cmVlXQ0KICAgICAgKQ0KDQogICAgICAjIEYuIFNUT1JFIFRFU1QgU0VUUyBGT1IgS00NCiAgICAgIGttX2RhdGFfbGlzdFtbaV1dIDwtIHRlc3Rfc2V0ICU+JQ0KICAgICAgICBzZWxlY3QoT1MudGltZSwgT1MsIEdyb3VwKSAlPiUNCiAgICAgICAgbXV0YXRlKEl0ZXJhdGlvbiA9IGkpDQogICAgfQ0KICB9DQoNCiAgYWxsX21vZGVsX3Jlc3VsdHNbW21vZGVsX3R5cGVdXSA8LSBkcGx5cjo6YmluZF9yb3dzKHJlc3VsdHNfbGlzdCkNCiAgYWxsX2ttX2RhdGFbW21vZGVsX3R5cGVdXSAgICAgICA8LSBkcGx5cjo6YmluZF9yb3dzKGttX2RhdGFfbGlzdCkNCn0NCg0KIyMgMTkuNyBJVEVSQVRJT04gVEFCTEUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KaXRlcmF0aW9uX3RhYmxlIDwtIGRwbHlyOjpiaW5kX3Jvd3MoYWxsX21vZGVsX3Jlc3VsdHMpICU+JQ0KICBmaWx0ZXIoSFIgPCAxMDAwKSAlPiUNCiAgc2VsZWN0KE1vZGVsLCBJdGVyYXRpb24sIEhSLCBMb3dlcl9DSSwgVXBwZXJfQ0ksIFBfVmFsdWUsIENfSW5kZXgpICU+JQ0KICBhcnJhbmdlKE1vZGVsLCBJdGVyYXRpb24pDQoNCmNhdCgiXG7ilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZBcbiIpDQpjYXQoIiAgUEVSLUlURVJBVElPTiBSRVNVTFRTIChhbGwgMjAgZm9sZHMsIGJvdGggbW9kZWxzKVxuIikNCmNhdCgi4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQXG4iKQ0KcHJpbnQoaXRlcmF0aW9uX3RhYmxlLCByb3cubmFtZXMgPSBGQUxTRSwgZGlnaXRzID0gMykNCg0Kc3VtbWFyeV90YWJsZSA8LSBpdGVyYXRpb25fdGFibGUgJT4lDQogIGdyb3VwX2J5KE1vZGVsKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIE4gICAgICAgICAgID0gbigpLA0KICAgIE1lYW5fSFIgICAgID0gbWVhbihIUiwgICAgICAgbmEucm0gPSBUUlVFKSwNCiAgICBTRF9IUiAgICAgICA9IHNkKEhSLCAgICAgICAgIG5hLnJtID0gVFJVRSksDQogICAgTWVhbl9MQ0kgICAgPSBtZWFuKExvd2VyX0NJLCBuYS5ybSA9IFRSVUUpLA0KICAgIFNEX0xDSSAgICAgID0gc2QoTG93ZXJfQ0ksICAgbmEucm0gPSBUUlVFKSwNCiAgICBNZWFuX1VDSSAgICA9IG1lYW4oVXBwZXJfQ0ksIG5hLnJtID0gVFJVRSksDQogICAgU0RfVUNJICAgICAgPSBzZChVcHBlcl9DSSwgICBuYS5ybSA9IFRSVUUpLA0KICAgIE1lYW5fUCAgICAgID0gbWVhbihQX1ZhbHVlLCAgbmEucm0gPSBUUlVFKSwNCiAgICBTRF9QICAgICAgICA9IHNkKFBfVmFsdWUsICAgIG5hLnJtID0gVFJVRSksDQogICAgTWVhbl9DSW5kZXggPSBtZWFuKENfSW5kZXgsICBuYS5ybSA9IFRSVUUpLA0KICAgIFNEX0NJbmRleCAgID0gc2QoQ19JbmRleCwgICAgbmEucm0gPSBUUlVFKSwNCiAgICAuZ3JvdXBzICAgICA9ICJkcm9wIg0KICApDQoNCmNhdCgiXG7ilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZBcbiIpDQpjYXQoIiAgU1VNTUFSWTogTUVBTiDCsSBTRCBBQ1JPU1MgMjAgSVRFUkFUSU9OU1xuIikNCmNhdCgi4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQXG4iKQ0KcHJpbnQoYXMuZGF0YS5mcmFtZShzdW1tYXJ5X3RhYmxlKSwgcm93Lm5hbWVzID0gRkFMU0UsIGRpZ2l0cyA9IDMpDQoNCiMgU0FWRSBUQUJMRVMgQVMgQ1NWDQoNCndyaXRlLmNzdihpdGVyYXRpb25fdGFibGUsDQogICAgICAgICAgZmlsZS5wYXRoKHN1cnZpdmFsX2RpciwgIml0ZXJhdGlvbnNfYWxsX2ZvbGRzLmNzdiIpLA0KICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQ0KY2F0KCJTYXZlZDogaXRlcmF0aW9uc19hbGxfZm9sZHMuY3N2XG4iKQ0KDQp3cml0ZS5jc3Yoc3VtbWFyeV90YWJsZSwNCiAgICAgICAgICBmaWxlLnBhdGgoc3Vydml2YWxfZGlyLCAiaXRlcmF0aW9uc19zdW1tYXJ5LmNzdiIpLA0KICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQ0KY2F0KCJTYXZlZDogaXRlcmF0aW9uc19zdW1tYXJ5LmNzdlxuIikNCg0KIyMgMTkuOCBLQVBMQU4tTUVJRVIgQ1VSVkVTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KdGltZV9ncmlkIDwtIHNlcSgwLCAyNTU1LCBieSA9IDUpDQoNCiMgRlVOQ1RJT04gVE8gTUFLRSBJTkRJVklEVUFMDQoNCmJ1aWxkX2l0ZXJfY3VydmVzIDwtIGZ1bmN0aW9uKGttX2RhdGFfbW9kZWwpIHsNCiAgbGFwcGx5KHNwbGl0KGttX2RhdGFfbW9kZWwsIGttX2RhdGFfbW9kZWwkSXRlcmF0aW9uKSwgZnVuY3Rpb24oaXRlcl9kZikgew0KICAgIGxhcHBseShjKCJMb3ciLCAiSGlnaCIpLCBmdW5jdGlvbihncnApIHsNCiAgICAgIHN1YiA8LSBpdGVyX2RmW2l0ZXJfZGYkR3JvdXAgPT0gZ3JwLCBdDQogICAgICBpZiAobnJvdyhzdWIpIDwgMikgcmV0dXJuKE5VTEwpDQogICAgICBmaXQgPC0gc3VydmZpdChTdXJ2KE9TLnRpbWUsIE9TKSB+IDEsIGRhdGEgPSBzdWIpDQogICAgICBzZiAgPC0gc3RlcGZ1bihmaXQkdGltZSwgYygxLCBmaXQkc3VydikpDQogICAgICBkYXRhLmZyYW1lKA0KICAgICAgICB0aW1lICAgICAgPSB0aW1lX2dyaWQsDQogICAgICAgIHN1cnYgICAgICA9IHNmKHRpbWVfZ3JpZCksDQogICAgICAgIEdyb3VwICAgICA9IGdycCwNCiAgICAgICAgSXRlcmF0aW9uID0gdW5pcXVlKGl0ZXJfZGYkSXRlcmF0aW9uKQ0KICAgICAgKQ0KICAgIH0pICU+JSBkcGx5cjo6YmluZF9yb3dzKCkNCiAgfSkgJT4lIGRwbHlyOjpiaW5kX3Jvd3MoKQ0KfQ0KDQojIDE5LjkgRlVOQ1RJT04gVE8gR0VUIEhSIFNUQVRTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpidWlsZF9tZWFuX2NpIDwtIGZ1bmN0aW9uKGN1cnZlc19kZikgew0KICBjdXJ2ZXNfZGYgJT4lDQogICAgZ3JvdXBfYnkoR3JvdXAsIHRpbWUpICU+JQ0KICAgIHN1bW1hcmlzZSgNCiAgICAgIG1lYW5fc3VydiA9IG1lYW4oc3VydiwgbmEucm0gPSBUUlVFKSwNCiAgICAgIHNkX3N1cnYgICA9IHNkKHN1cnYsICAgbmEucm0gPSBUUlVFKSwNCiAgICAgIG4gICAgICAgICA9IHN1bSghaXMubmEoc3VydikpLA0KICAgICAgc2Vfc3VydiAgID0gc2Rfc3VydiAvIHNxcnQobiksDQogICAgICBjaV9sbyAgICAgPSBwbWF4KG1lYW5fc3VydiAtIDEuOTYgKiBzZV9zdXJ2LCAwKSwNCiAgICAgIGNpX2hpICAgICA9IHBtaW4obWVhbl9zdXJ2ICsgMS45NiAqIHNlX3N1cnYsIDEpLA0KICAgICAgLmdyb3VwcyAgID0gImRyb3AiDQogICAgKQ0KfQ0KDQpjdXJ2ZXNfZnVsbCAgIDwtIGJ1aWxkX2l0ZXJfY3VydmVzKGFsbF9rbV9kYXRhW1siRnVsbCJdXSkNCmN1cnZlc19ub3A1MyAgPC0gYnVpbGRfaXRlcl9jdXJ2ZXMoYWxsX2ttX2RhdGFbWyJOb19wNTMiXV0pDQptZWFuX2NpX2Z1bGwgIDwtIGJ1aWxkX21lYW5fY2koY3VydmVzX2Z1bGwpDQptZWFuX2NpX25vcDUzIDwtIGJ1aWxkX21lYW5fY2koY3VydmVzX25vcDUzKQ0KDQojIDE5LjEwIFBMT1QgT1ZFUkxBSUQgQ1VSVkVTIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpwbG90XzIwX2ttIDwtIGZ1bmN0aW9uKGN1cnZlc19kZiwgbWVhbl9jaV9kZiwgbW9kZWxfcmVzdWx0c19kZiwgdGl0bGVfdGV4dCkgew0KDQogIGhyX3N1bW1hcnkgPC0gbW9kZWxfcmVzdWx0c19kZiAlPiUNCiAgICBmaWx0ZXIoSFIgPCAxMDAwKSAlPiUNCiAgICBzdW1tYXJpc2UoDQogICAgICBtSFIgID0gbWVhbihIUiwgICAgICAgbmEucm0gPSBUUlVFKSwNCiAgICAgIHNkSFIgPSBzZChIUiwgICAgICAgICBuYS5ybSA9IFRSVUUpLA0KICAgICAgbUxDSSA9IG1lYW4oTG93ZXJfQ0ksIG5hLnJtID0gVFJVRSksDQogICAgICBtVUNJID0gbWVhbihVcHBlcl9DSSwgbmEucm0gPSBUUlVFKSwNCiAgICAgIG1QICAgPSBtZWFuKFBfVmFsdWUsICBuYS5ybSA9IFRSVUUpLA0KICAgICAgbUNJICA9IG1lYW4oQ19JbmRleCwgIG5hLnJtID0gVFJVRSkNCiAgICApDQoNCiAgc3VidGl0bGVfdGV4dCA8LSBzcHJpbnRmKA0KICAgICJNZWFuIEhSID0gJS4yZiAoU0QgJS4yZikgIHwgIE1lYW4gOTUlJSBDSSBbJS4yZuKAkyUuMmZdICB8ICBNZWFuIHAgPSAlLjNmICB8ICBNZWFuIEMtaW5kZXggPSAlLjNmIiwNCiAgICBocl9zdW1tYXJ5JG1IUiwgaHJfc3VtbWFyeSRzZEhSLA0KICAgIGhyX3N1bW1hcnkkbUxDSSwgaHJfc3VtbWFyeSRtVUNJLA0KICAgIGhyX3N1bW1hcnkkbVAsICAgaHJfc3VtbWFyeSRtQ0kNCiAgKQ0KDQogIGdncGxvdCgpICsNCiAgICBnZW9tX3N0ZXAoZGF0YSA9IGN1cnZlc19kZiwNCiAgICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5ID0gc3VydiwgY29sb3VyID0gR3JvdXAsDQogICAgICAgICAgICAgICAgICBncm91cCA9IGludGVyYWN0aW9uKEdyb3VwLCBJdGVyYXRpb24pKSwNCiAgICAgICAgICAgICAgYWxwaGEgPSAwLjUwLCBsaW5ld2lkdGggPSAwLjQ1KSArDQogICAgZ2VvbV9yaWJib24oZGF0YSA9IG1lYW5fY2lfZGYsDQogICAgICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5bWluID0gY2lfbG8sIHltYXggPSBjaV9oaSwNCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEdyb3VwLCBncm91cCA9IEdyb3VwKSwNCiAgICAgICAgICAgICAgICBhbHBoYSA9IDAuMjApICsNCiAgICBnZW9tX3N0ZXAoZGF0YSA9IG1lYW5fY2lfZGYsDQogICAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IG1lYW5fc3VydiwgY29sb3VyID0gR3JvdXAsIGdyb3VwID0gR3JvdXApLA0KICAgICAgICAgICAgICBsaW5ld2lkdGggPSAxLjMpICsNCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKA0KICAgICAgdmFsdWVzID0gYyhIaWdoID0gIiNFODQ4NTUiLCBMb3cgPSAiIzJFODZBQiIpLA0KICAgICAgbGFiZWxzID0gYyhIaWdoID0gIkhpZ2ggUmlzayIsIExvdyA9ICJMb3cgUmlzayIpDQogICAgKSArDQogICAgc2NhbGVfZmlsbF9tYW51YWwoDQogICAgICB2YWx1ZXMgPSBjKEhpZ2ggPSAiI0U4NDg1NSIsIExvdyA9ICIjMkU4NkFCIiksDQogICAgICBsYWJlbHMgPSBjKEhpZ2ggPSAiSGlnaCBSaXNrIiwgTG93ID0gIkxvdyBSaXNrIikNCiAgICApICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoDQogICAgICBicmVha3MgPSBzZXEoMCwgMjU1NSwgYnkgPSAzNjUpLA0KICAgICAgbGFiZWxzID0gcGFzdGUwKDA6NywgInkiKQ0KICAgICkgKw0KICAgIHNjYWxlX3lfY29udGludW91cygNCiAgICAgIGxpbWl0cyA9IGMoMCwgMSksDQogICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkNCiAgICApICsNCiAgICBsYWJzKA0KICAgICAgdGl0bGUgICAgPSB0aXRsZV90ZXh0LA0KICAgICAgc3VidGl0bGUgPSBzdWJ0aXRsZV90ZXh0LA0KICAgICAgeCAgICAgICAgPSAiVGltZSIsDQogICAgICB5ICAgICAgICA9ICJPdmVyYWxsIFN1cnZpdmFsIFByb2JhYmlsaXR5IiwNCiAgICAgIGNvbG91ciAgID0gIlJpc2sgR3JvdXAiLA0KICAgICAgZmlsbCAgICAgPSAiUmlzayBHcm91cCIsDQogICAgICBjYXB0aW9uICA9ICJGYWludCBsaW5lcyA9IDIwIE1vbnRlIENhcmxvIGl0ZXJhdGlvbnMgKDUwOjUwIHNwbGl0KSB8IEJvbGQgPSBtZWFuIHwgUmliYm9uID0gbWVhbiA5NSUgQ0kiDQogICAgKSArDQogICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMykgKw0KICAgIHRoZW1lKA0KICAgICAgcGFuZWwuYmFja2dyb3VuZCAgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwNCiAgICAgIHBsb3QuYmFja2dyb3VuZCAgID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksDQogICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLA0KICAgICAgbGVnZW5kLmtleSAgICAgICAgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwNCiAgICAgIHBsb3QudGl0bGUgICAgICAgID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksDQogICAgICBwbG90LnN1YnRpdGxlICAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gOS41LCBjb2xvdXIgPSAiZ3JleTM1IiksDQogICAgICBwbG90LmNhcHRpb24gICAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gOCwgICBjb2xvdXIgPSAiZ3JleTU1IiksDQogICAgICBsZWdlbmQucG9zaXRpb24gICA9ICJib3R0b20iLA0KICAgICAgbGVnZW5kLnRpdGxlICAgICAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikNCiAgICApDQp9DQoNCnBfZnVsbCAgPC0gcGxvdF8yMF9rbShjdXJ2ZXNfZnVsbCwgIG1lYW5fY2lfZnVsbCwNCiAgICAgICAgICAgICAgICAgICAgICBhbGxfbW9kZWxfcmVzdWx0c1tbIkZ1bGwiXV0sDQogICAgICAgICAgICAgICAgICAgICAgIlJGUyBNb2RlbDogRnVsbCAod2l0aCBQQzMvUEM1KSIpDQoNCnBfbm9wNTMgPC0gcGxvdF8yMF9rbShjdXJ2ZXNfbm9wNTMsIG1lYW5fY2lfbm9wNTMsDQogICAgICAgICAgICAgICAgICAgICAgYWxsX21vZGVsX3Jlc3VsdHNbWyJOb19wNTMiXV0sDQogICAgICAgICAgICAgICAgICAgICAgIlJGUyBNb2RlbDogTm8gUEMzL1BDNSIpDQoNCnByaW50KHBfZnVsbCAvIHBfbm9wNTMpDQoNCnNhdmVfcG5nKHBfZnVsbCwgICAgICAgICAgICAia21fZnVsbF9tb2RlbC5wbmciLCAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2KQ0Kc2F2ZV9wbmcocF9ub3A1MywgICAgICAgICAgICJrbV9ub19wYzM1X21vZGVsLnBuZyIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpDQpzYXZlX3BuZyhwX2Z1bGwgLyBwX25vcDUzLCAgImttX2JvdGhfbW9kZWxzLnBuZyIsICAgd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTIpDQoNCiMjIDE5LjExIE1PREVMIE9OIEFMTCBEQVRBIChWSU1QICsgUERQcykgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCmNhdCgiXG5UcmFpbmluZyBmaW5hbCBtb2RlbCBvbiBmdWxsIGRhdGFzZXQgZm9yIFZJTVAgYW5kIFBEUC4uLlxuIikNCg0Kc2V0LnNlZWQoNDIpDQpyZl9mb3JtdWxhX2Z1bGwgPC0gYXMuZm9ybXVsYSgNCiAgcGFzdGUoIlN1cnYoT1MudGltZSwgT1MpIH4iLCBwYXN0ZShmZWF0dXJlc19mdWxsLCBjb2xsYXBzZSA9ICIrIikpDQopDQoNCmZpbmFsX21vZGVsIDwtIHJhbmRvbUZvcmVzdFNSQzo6cmZzcmMoDQogIHJmX2Zvcm11bGFfZnVsbCwNCiAgZGF0YSAgICAgICA9IHJmc19jbGVhbiwNCiAgbnRyZWUgICAgICA9IDEwMDAsDQogIGltcG9ydGFuY2UgPSBUUlVFDQopDQoNCiMjIDE5LjEyIFZJTVAgUExPVCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCnZpbXBfZGYgPC0gZGF0YS5mcmFtZSgNCiAgRmVhdHVyZSA9IG5hbWVzKGZpbmFsX21vZGVsJGltcG9ydGFuY2UpLA0KICBWSU1QICAgID0gYXMubnVtZXJpYyhmaW5hbF9tb2RlbCRpbXBvcnRhbmNlKQ0KKSAlPiUNCiAgYXJyYW5nZShkZXNjKFZJTVApKQ0KDQpjYXQoIlxu4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQ4pWQXG4iKQ0KY2F0KCIgIFZBUklBQkxFIElNUE9SVEFOQ0UgKFZJTVApIOKAlCBGdWxsIE1vZGVsXG4iKQ0KY2F0KCLilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZBcbiIpDQpwcmludCh2aW1wX2RmLCByb3cubmFtZXMgPSBGQUxTRSwgZGlnaXRzID0gNCkNCg0Kd3JpdGUuY3N2KHZpbXBfZGYsDQogICAgICAgICAgZmlsZS5wYXRoKHN1cnZpdmFsX2RpciwgInZpbXBfZnVsbF9tb2RlbC5jc3YiKSwNCiAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSkNCmNhdCgiU2F2ZWQ6IHZpbXBfZnVsbF9tb2RlbC5jc3ZcbiIpDQoNCiMgUExPVCBWSU1QIFBMT1QNCg0KcF92aW1wIDwtIGdncGxvdCh2aW1wX2RmLA0KICAgICAgICAgICAgICAgICBhZXMoeCA9IHJlb3JkZXIoRmVhdHVyZSwgVklNUCksIHkgPSBWSU1QLCBmaWxsID0gVklNUCA+IDApKSArDQogIGdlb21fY29sKHdpZHRoID0gMC43KSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG91ciA9ICJncmV5NTAiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKA0KICAgIHZhbHVlcyA9IGMoYFRSVUVgID0gIiMyRTg2QUIiLCBgRkFMU0VgID0gIiNFODQ4NTUiKSwNCiAgICBndWlkZSAgPSAibm9uZSINCiAgKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnMoDQogICAgdGl0bGUgICA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIChWSU1QKSDigJQgRnVsbCBSRlMgTW9kZWwiLA0KICAgIHggICAgICAgPSBOVUxMLA0KICAgIHkgICAgICAgPSAiVklNUCIsDQogICAgY2FwdGlvbiA9ICJCbHVlID0gcG9zaXRpdmUgaW1wb3J0YW5jZSAgfCAgUmVkID0gZmVhdHVyZSBtYXkgYWRkIG5vaXNlIg0KICApICsNCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMykgKw0KICB0aGVtZSgNCiAgICBwYW5lbC5iYWNrZ3JvdW5kICA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLA0KICAgIHBsb3QuYmFja2dyb3VuZCAgID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksDQogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwNCiAgICBsZWdlbmQua2V5ICAgICAgICA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLA0KICAgIHBsb3QudGl0bGUgICAgICAgID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpDQogICkNCg0KcHJpbnQocF92aW1wKQ0Kc2F2ZV9wbmcocF92aW1wLCAidmltcF9mdWxsX21vZGVsLnBuZyIsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNikNCg0KIyBTQVZFIFBEUCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KcG5nKGZpbGUucGF0aChzdXJ2aXZhbF9kaXIsICJwZHBfZnVsbF9tb2RlbC5wbmciKSwNCiAgICB3aWR0aCAgPSAxMCwgaGVpZ2h0ID0gOCwNCiAgICB1bml0cyAgPSAiaW4iLA0KICAgIHJlcyAgICA9IDMwMCwNCiAgICBiZyAgICAgPSAidHJhbnNwYXJlbnQiKQ0KICByYW5kb21Gb3Jlc3RTUkM6OnBsb3QudmFyaWFibGUoDQogICAgZmluYWxfbW9kZWwsDQogICAgcGFydGlhbCAgICAgICAgPSBUUlVFLA0KICAgIHNvcnRlZCAgICAgICAgID0gVFJVRSwNCiAgICBwbG90cy5wZXIucGFnZSA9IDMsDQogICAgbWFpbiAgICAgICAgICAgPSAiUGFydGlhbCBEZXBlbmRlbmNlIOKAlCBGdWxsIFJGUyBNb2RlbCAod2l0aCBQQzMvUEM1KSINCiAgKQ0KZGV2Lm9mZigpDQpjYXQoIlNhdmVkOiBwZHBfZnVsbF9tb2RlbC5wbmdcbiIpDQoNCnJhbmRvbUZvcmVzdFNSQzo6cGxvdC52YXJpYWJsZSgNCiAgZmluYWxfbW9kZWwsDQogIHBhcnRpYWwgICAgICAgID0gVFJVRSwNCiAgc29ydGVkICAgICAgICAgPSBUUlVFLA0KICBwbG90cy5wZXIucGFnZSA9IDMsDQogIG1haW4gICAgICAgICAgID0gIlBhcnRpYWwgRGVwZW5kZW5jZSDigJQgRnVsbCBSRlMgTW9kZWwgKHdpdGggUEMzL1BDNSkiDQopDQpgYGANCg0KDQoNCg0K