1. load libraries

2. Nice function to easily draw a graph:

3. Reading data

All_samples_Merged <- readRDS("../0-Seurat_RDS_OBJECT_FINAL/All_samples_Merged_with_STCAT_and_renamed_FINAL.rds")

##. Define some color palette



# Define some color palette
pal <- c(scales::hue_pal()(8), RColorBrewer::brewer.pal(9, "Set1"), RColorBrewer::brewer.pal(8, "Set2"))
set.seed(1)
pal <- rep(sample(pal, length(pal)), 200)

4. Trajectory inference using Slingshot

             used   (Mb) gc trigger    (Mb)   max used   (Mb)
Ncells    9223676  492.6   16857814   900.4   12210725  652.2
Vcells 1232790134 9405.5 1530466652 11676.6 1249962156 9536.5

Let’s visualize which clusters we have in our dataset:


vars <- c("Patient_origin", "orig.ident", "seurat_clusters", "Phase")
pl <- list()

for (i in vars) {
  pl[[i]] <- DimPlot(obj, group.by = i, label = T) + theme_void() + NoLegend()
}
wrap_plots(pl)


table(obj$seurat_clusters)

   0    1    2    3    4    5    6    7    8    9   10   11   12   13 
6789 5275 4663 4661 4086 3634 3536 3409 3338 3273 3212 1675 1063  691 

5. Exploring the data


vars <- c("TOX", "LAG3", "CTLA4", "TIGIT", "FOXP3", "IL2RA", "CTLA4", "IKZF2", "TIGIT", "CCR7", "SELL", "IL7R", "TCF7", "CCR7", "SELL", "IL7R", "TCF7", "LEF1", "GZMB", "PRF1", "IFNG", "KLRG1", "CD69", "CXCR6", "PRF1", "GZMB", "NKG7", "GNLY", "ISG15", "IFI6", "IFIT3", "MX1", "MKI67", "TOP2A", "STAT3", "AHR", "CCR6", "BATF", "PTPRC", "GZMB", "BCL6", "ICOS", "HSPA1A", "ATF3", "IL2RA", "CD69", "HLA-DRA", "CD34")
pl <- list()

pl <- list(DimPlot(obj, group.by = "seurat_clusters", label = T) + theme_void() + NoLegend())
for (i in vars) {
  pl[[i]] <- FeaturePlot(obj, features = i, order = T) + theme_void() + NoLegend()
}
Warning: The `slot` argument of `FetchData()` is deprecated as of SeuratObject 5.0.0.
Please use the `layer` argument instead.
wrap_plots(pl)

NA
NA

. Exploring the data-2


# Load necessary libraries
library(Seurat)
library(patchwork)
library(glue)

Attaching package: ‘glue’

The following object is masked from ‘package:SummarizedExperiment’:

    trim

The following object is masked from ‘package:GenomicRanges’:

    trim

The following object is masked from ‘package:IRanges’:

    trim
# Define marker groups
marker_groups <- list(
  "CD4 Tex (Exhausted)" = c("TOX", "LAG3", "CTLA4", "TIGIT"),
  "CD4 Treg" = c("FOXP3", "IL2RA", "CTLA4", "IKZF2", "TIGIT"),
  "CD4 Tcm (Central Memory)" = c("CCR7", "SELL", "IL7R", "TCF7"),
  "CD4 Tn (Naive)" = c("CCR7", "SELL", "IL7R", "TCF7", "LEF1"),
  "CD4 Tem (Effector Memory)" = c("GZMB", "PRF1", "IFNG", "KLRG1"),
  "CD4 Trm (Tissue Resident)" = c("CD69", "CXCR6"),
  "CD4 Tc (Cytotoxic)" = c("PRF1", "GZMB", "NKG7", "GNLY"),
  "CD4 Tisg (IFN Signature)" = c("ISG15", "IFI6", "IFIT3", "MX1"),
  "CD4 Proliferation" = c("MKI67", "TOP2A"),
  "CD4 Th17" = c("STAT3", "AHR", "CCR6", "BATF"),
  "CD4 Temra (Effector Memory RA+)" = c("PTPRC", "GZMB"),
  "CD4 Tfh (Follicular Helper)" = c("BCL6", "ICOS"),
  "CD4 Tstr (Stress)" = c("HSPA1A", "ATF3"),
  "CD4 Activated" = c("IL2RA", "CD69", "HLA-DRA")
)

# OPTIONAL: Uncomment this to save all plots in a single PDF
# pdf("CD4_MarkerGroups_FeaturePlots.pdf", width = 12, height = 10)

# Main plotting loop
for (group_name in names(marker_groups)) {
  genes <- unique(marker_groups[[group_name]])
  message(glue("Plotting: {group_name}"))

  # Cleaner titles and better theme
  pl <- lapply(genes, function(gene) {
    FeaturePlot(obj, features = gene, order = TRUE) +
      ggtitle(gene) +  # shorter title
      theme_minimal(base_size = 10) +  # use minimal for spacing
      NoLegend()
  })

  # Combine plots
  combined_plot <- wrap_plots(pl, ncol = 2) +  # fewer columns = more space
                   plot_annotation(title = group_name)

  print(combined_plot)
}
Plotting: CD4 Tex (Exhausted)

# OPTIONAL: Uncomment if using pdf()
# dev.off()

. Exploring the data-3


# Required markers
progenitor_markers <- c("CD34", "KIT", "GATA2", "MKI67", "PROM1", "FLT3")

# Feature plots for each marker
FeaturePlot(obj, features = progenitor_markers, order = TRUE, ncol = 3)
Warning: Could not find PROM1 in the default search locations, found in ‘RNA’ assay instead

6. compute the lineages on these dataset

# # Define lineage ends
# ENDS <- c("1", "8", "0" , "13")

set.seed(1)
lineages <- as.SlingshotDataSet(getLineages(
  data           = obj@reductions$umap@cell.embeddings,
  clusterLabels  = obj$seurat_clusters,
  dist.method    = "mnn", # It can be: "simple", "scaled.full", "scaled.diag", "slingshot" or "mnn"
 
)) # define where to START the trajectories


# IF NEEDED, ONE CAN ALSO MANULALLY EDIT THE LINEAGES, FOR EXAMPLE:
# sel <- sapply( lineages@lineages, function(x){rev(x)[1]} ) %in% ENDS
# lineages@lineages <- lineages@lineages[ sel ]
# names(lineages@lineages) <- paste0("Lineage",1:length(lineages@lineages))
# lineages


# Change the reduction to our "fixed" UMAP2d (FOR VISUALISATION ONLY)
lineages@reducedDim <- obj@reductions$umap@cell.embeddings

{
  plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16)
  lines(lineages, lwd = 1, col = "black", cex = 2)
  text(centroids2d, labels = rownames(centroids2d), cex = 0.8, font = 2, col = "white")
}

Defining Principal Curves


# Define curves
curves <- as.SlingshotDataSet(getCurves(
  data          = lineages,
  thresh        = 1e-1,
  stretch       = 1e-1,
  allow.breaks  = F,
  approx_points = 1000
))

curves
class: SlingshotDataSet 

lineages: 4 
Lineage1: 5  3  10  1  9  4  0  7  11  
Lineage2: 5  3  10  1  9  4  0  12  
Lineage3: 5  3  10  1  9  2  6  13  
Lineage4: 5  3  10  1  9  2  6  8  

curves: 4 
Curve1: Length: 24.111  Samples: 32790.38
Curve2: Length: 23.614  Samples: 33116.73
Curve3: Length: 29.842  Samples: 31122.23
Curve4: Length: 19.677  Samples: 31168.18

Plot


# Plots
{
  plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], pch = 16)
  lines(curves, lwd = 2, col = "black")
  text(centroids2d, labels = levels(obj$seurat_clusters), cex = 1, font = 2)
}

compute the differentiation pseudotime


pseudotime <- slingPseudotime(curves, na = FALSE)
cellWeights <- slingCurveWeights(curves)

x <- rowMeans(pseudotime)
x <- x / max(x)
o <- order(x)

{
  plot(obj@reductions$umap@cell.embeddings[o, ],
    main = paste0("pseudotime"), pch = 16, cex = 0.4, axes = F, xlab = "", ylab = "",
    col = colorRampPalette(c("grey70", "orange3", "firebrick", "purple4"))(99)[x[o] * 98 + 1]
  )
  points(centroids2d, cex = 2.5, pch = 16, col = "#FFFFFF99")
  text(centroids2d, labels = levels(obj$seurat_clusters), cex = 1, font = 2)
}

7. Finding differentially expressed genes


sel_cells <- split(colnames(obj@assays$SCT@data), obj$seurat_clusters)
sel_cells <- unlist(lapply(sel_cells, function(x) {
  set.seed(1)
  return(sample(x, 20))
}))


# Filter genes: expressed in at least 10 cells with count > 1
expr_filter <- rowSums(obj@assays$SCT@counts[, sel_cells] > 1) >= 10
filtered_genes <- rownames(obj)[expr_filter]

# Compute gene variance on filtered genes using raw counts
gv <- as.data.frame(na.omit(scran::modelGeneVar(obj@assays$SCT@data[, sel_cells])))
Warning: collapsing to unique 'x' values
# Order by biological variance decreasing
gv <- gv[order(gv$bio, decreasing = T), ]
# Select top 500 highly variable genes
sel_genes <- sort(rownames(gv)[1:500])

path_file <- "/home/bioinfo/data/trajectory/trajectory_seurat_filtered.rds"

# fetch_data is defined at the top of this document
sceGAM <- fitGAM(
  counts = drop0(obj@assays$SCT@data[sel_genes, sel_cells]),
  pseudotime = pseudotime[sel_cells, ],
  cellWeights = cellWeights[sel_cells, ],
  nknots = 5, verbose = TRUE, parallel = TRUE, sce = TRUE,
  BPPARAM = BiocParallel::MulticoreParam()
)
Warning: Impossible to place a knot at all endpoints.Increase the number of knots to avoid this issue.

  |                                                                                                                        
  |                                                                                                                  |   0%
  |                                                                                                                        
  |===                                                                                                               |   3%
  |                                                                                                                        
  |======                                                                                                            |   5%
  |                                                                                                                        
  |=========                                                                                                         |   8%
  |                                                                                                                        
  |============                                                                                                      |  10%
  |                                                                                                                        
  |===============                                                                                                   |  13%
  |                                                                                                                        
  |==================                                                                                                |  16%
  |                                                                                                                        
  |=====================                                                                                             |  18%
  |                                                                                                                        
  |========================                                                                                          |  21%
  |                                                                                                                        
  |===========================                                                                                       |  23%
  |                                                                                                                        
  |==============================                                                                                    |  26%
  |                                                                                                                        
  |=================================                                                                                 |  29%
  |                                                                                                                        
  |====================================                                                                              |  31%
  |                                                                                                                        
  |=======================================                                                                           |  34%
  |                                                                                                                        
  |=========================================                                                                         |  36%
  |                                                                                                                        
  |============================================                                                                      |  39%
  |                                                                                                                        
  |===============================================                                                                   |  42%
  |                                                                                                                        
  |==================================================                                                                |  44%
  |                                                                                                                        
  |=====================================================                                                             |  47%
  |                                                                                                                        
  |========================================================                                                          |  49%
  |                                                                                                                        
  |===========================================================                                                       |  52%
  |                                                                                                                        
  |==============================================================                                                    |  55%
  |                                                                                                                        
  |=================================================================                                                 |  57%
  |                                                                                                                        
  |====================================================================                                              |  60%
  |                                                                                                                        
  |=======================================================================                                           |  62%
  |                                                                                                                        
  |==========================================================================                                        |  65%
  |                                                                                                                        
  |=============================================================================                                     |  68%
  |                                                                                                                        
  |================================================================================                                  |  70%
  |                                                                                                                        
  |===================================================================================                               |  73%
  |                                                                                                                        
  |======================================================================================                            |  75%
  |                                                                                                                        
  |=========================================================================================                         |  78%
  |                                                                                                                        
  |============================================================================================                      |  81%
  |                                                                                                                        
  |===============================================================================================                   |  83%
  |                                                                                                                        
  |==================================================================================================                |  86%
  |                                                                                                                        
  |=====================================================================================================             |  88%
  |                                                                                                                        
  |========================================================================================================          |  91%
  |                                                                                                                        
  |=========================================================================================================         |  92%
  |                                                                                                                        
  |============================================================================================================      |  95%
  |                                                                                                                        
  |===============================================================================================================   |  97%
  |                                                                                                                        
  |==================================================================================================================| 100%
plotGeneCount(curves, clusters = obj$seurat_clusters, models = sceGAM)



lineages
class: SlingshotDataSet 

lineages: 4 
Lineage1: 5  3  10  1  9  4  0  7  11  
Lineage2: 5  3  10  1  9  4  0  12  
Lineage3: 5  3  10  1  9  2  6  13  
Lineage4: 5  3  10  1  9  2  6  8  

curves: 0 
lc <- sapply(lineages@lineages, function(x) {
  rev(x)[1]
})
names(lc) <- gsub("Lineage", "L", names(lc))
lc.idx = match(lc, levels(obj$seurat_clusters))

{
  plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], pch = 16)
  lines(curves, lwd = 2, col = "black")
  points(centroids2d[lc.idx, ], col = "black", pch = 16, cex = 4)
  text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white")
}

NA
NA
NA
NA

Genes that change with pseudotime


set.seed(8)
res <- na.omit(associationTest(sceGAM, contrastType = "consecutive"))
res <- res[res$pvalue < 1e-3, ]
res <- res[res$waldStat > mean(res$waldStat), ]
res <- res[order(res$waldStat, decreasing = T), ]
res[1:10, ]
NA

We can plot their expression


par(mfrow = c(4, 4), mar = c(.1, .1, 2, 1))
{
  plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16, axes = F, xlab = "", ylab = "")
  lines(curves, lwd = 2, col = "black")
  points(centroids2d[lc.idx, ], col = "black", pch = 15, cex = 3, xpd = T)
  text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white", xpd = T)
}

vars <- rownames(res[1:15, ])
vars <- na.omit(vars[vars != "NA"])

for (i in vars) {
  x <- drop0(obj@assays$SCT@data)[i, ]
  x <- (x - min(x)) / (max(x) - min(x))
  o <- order(x)
  plot(obj@reductions$umap@cell.embeddings[o, ],
    main = paste0(i), pch = 16, cex = 0.5, axes = F, xlab = "", ylab = "",
    col = colorRampPalette(c("lightgray", "grey60", "navy"))(99)[x[o] * 98 + 1]
  )
}

Genes that change between two pseudotime points

res <- na.omit(startVsEndTest(sceGAM, pseudotimeValues = c(0, 1)))
res <- res[res$pvalue < 1e-3, ]
res <- res[res$waldStat > mean(res$waldStat), ]
res <- res[order(res$waldStat, decreasing = T), ]
res[1:10, 1:6]

identify which genes go up or down. Let’s check lineage 1:

# Get the top UP and Down regulated in lineage 1
res_lin1 <- sort(setNames(res$logFClineage1, rownames(res)))
vars <- names(c(rev(res_lin1)[1:7], res_lin1[1:8]))
vars <- na.omit(vars[vars != "NA"])

par(mfrow = c(4, 4), mar = c(.1, .1, 2, 1))

{
  plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16, axes = F, xlab = "", ylab = "")
  lines(curves, lwd = 2, col = "black")
  points(centroids2d[lc.idx, ], col = "black", pch = 15, cex = 3, xpd = T)
  text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white", xpd = T)
}

for (i in vars) {
  x <- drop0(obj@assays$SCT@data)[i, ]
  x <- (x - min(x)) / (max(x) - min(x))
  o <- order(x)
  plot(obj@reductions$umap@cell.embeddings[o, ],
    main = paste0(i), pch = 16, cex = 0.5, axes = F, xlab = "", ylab = "",
    col = colorRampPalette(c("lightgray", "grey60", "navy"))(99)[x[o] * 98 + 1]
  )
}

Genes that are different between lineages

res <- na.omit(diffEndTest(sceGAM))
res <- res[res$pvalue < 1e-3, ]
res <- res[res$waldStat > mean(res$waldStat), ]
res <- res[order(res$waldStat, decreasing = T), ]
res[1:10, ]
NA

pairwise comparison between each lineage. Let’s check lineage 1 vs lineage 2:

# Get the top UP and Down regulated in lineage 1 vs 2
res_lin1_2 <- sort(setNames(res$logFC1_2, rownames(res)))
vars <- names(c(rev(res_lin1_2)[1:7], res_lin1_2[1:8]))
vars <- na.omit(vars[vars != "NA"])

par(mfrow = c(4, 4), mar = c(.1, .1, 2, 1))
{
  plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16, axes = F, xlab = "", ylab = "")
  lines(curves, lwd = 2, col = "black")
  points(centroids2d[lc.idx, ], col = "black", pch = 15, cex = 3, xpd = T)
  text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white", xpd = T)
}

for (i in vars) {
  x <- drop0(obj@assays$SCT@data)[i, ]
  x <- (x - min(x)) / (max(x) - min(x))
  o <- order(x)
  plot(obj@reductions$umap@cell.embeddings[o, ],
    main = paste0(i), pch = 16, cex = 0.5, axes = F, xlab = "", ylab = "",
    col = colorRampPalette(c("lightgray", "grey60", "navy"))(99)[x[o] * 98 + 1]
  )
}

LS0tCnRpdGxlOiAiVHJhamVjdG9yeSBpbmZlcmVuY2UgdXNpbmcgU2xpbmdzaG90IgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogICNybWRmb3JtYXRzOjpyZWFkdGhlZG93bgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQotLS0KCiMgMS4gbG9hZCBsaWJyYXJpZXMKYGBge3IsIGluY2x1ZGU9RkFMU0V9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShwbG90bHkpCiAgb3B0aW9ucyhyZ2wucHJpbnRSZ2x3aWRnZXQgPSBUUlVFKQogIGxpYnJhcnkoTWF0cml4KQogIGxpYnJhcnkoc3BhcnNlTWF0cml4U3RhdHMpCiAgbGlicmFyeShzbGluZ3Nob3QpCiAgbGlicmFyeSh0cmFkZVNlcSkKICBsaWJyYXJ5KHBhdGNod29yaykKfSkKCgpgYGAKCiMgMi4gTmljZSBmdW5jdGlvbiB0byBlYXNpbHkgZHJhdyBhIGdyYXBoOgpgYGB7ciwgaW5jbHVkZT1GQUxTRX0KIyBBZGQgZ3JhcGggdG8gdGhlIGJhc2UgUiBncmFwaGljcyBwbG90CmRyYXdfZ3JhcGggPC0gZnVuY3Rpb24obGF5b3V0LCBncmFwaCwgbHdkID0gMC4yLCBjb2wgPSAiZ3JleSIpIHsKICByZXMgPC0gcmVwKHggPSAxOihsZW5ndGgoZ3JhcGhAcCkgLSAxKSwgdGltZXMgPSAoZ3JhcGhAcFstMV0gLSBncmFwaEBwWy1sZW5ndGgoZ3JhcGhAcCldKSkKICBzZWdtZW50cygKICAgIHgwID0gbGF5b3V0W2dyYXBoQGkgKyAxLCAxXSwgeDEgPSBsYXlvdXRbcmVzLCAxXSwKICAgIHkwID0gbGF5b3V0W2dyYXBoQGkgKyAxLCAyXSwgeTEgPSBsYXlvdXRbcmVzLCAyXSwgbHdkID0gbHdkLCBjb2wgPSBjb2wKICApCn0KYGBgCgojIDMuIFJlYWRpbmcgZGF0YQpgYGB7cn0KQWxsX3NhbXBsZXNfTWVyZ2VkIDwtIHJlYWRSRFMoIi4uLzAtU2V1cmF0X1JEU19PQkpFQ1RfRklOQUwvQWxsX3NhbXBsZXNfTWVyZ2VkX3dpdGhfU1RDQVRfYW5kX3JlbmFtZWRfRklOQUwucmRzIikKCgpgYGAKCgojIy4gRGVmaW5lIHNvbWUgY29sb3IgcGFsZXR0ZQpgYGB7cn0KCgojIERlZmluZSBzb21lIGNvbG9yIHBhbGV0dGUKcGFsIDwtIGMoc2NhbGVzOjpodWVfcGFsKCkoOCksIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg5LCAiU2V0MSIpLCBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoOCwgIlNldDIiKSkKc2V0LnNlZWQoMSkKcGFsIDwtIHJlcChzYW1wbGUocGFsLCBsZW5ndGgocGFsKSksIDIwMCkKCmBgYAoKIyA0LiBUcmFqZWN0b3J5IGluZmVyZW5jZSB1c2luZyBTbGluZ3Nob3QKYGBge3IsIGVjaG89RkFMU0V9Cm9iaiA8LUFsbF9zYW1wbGVzX01lcmdlZAoKcm0oQWxsX3NhbXBsZXNfTWVyZ2VkKQoKZ2MoKQoKIyBDYWxjdWxhdGUgY2x1c3RlciBjZW50cm9pZHMgKGZvciBwbG90dGluZyB0aGUgbGFiZWxzIGxhdGVyKQptbSA8LSBzcGFyc2UubW9kZWwubWF0cml4KH4gMCArIGZhY3RvcihvYmokc2V1cmF0X2NsdXN0ZXJzKSkKY29sbmFtZXMobW0pIDwtIGxldmVscyhmYWN0b3Iob2JqJHNldXJhdF9jbHVzdGVycykpCmNlbnRyb2lkczJkIDwtIGFzLm1hdHJpeCh0KHQob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MpICUqJSBtbSkgLyBNYXRyaXg6OmNvbFN1bXMobW0pKQoKYGBgCgoKIyMgTGV04oCZcyB2aXN1YWxpemUgd2hpY2ggY2x1c3RlcnMgd2UgaGF2ZSBpbiBvdXIgZGF0YXNldDoKYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQoKdmFycyA8LSBjKCJQYXRpZW50X29yaWdpbiIsICJvcmlnLmlkZW50IiwgInNldXJhdF9jbHVzdGVycyIsICJQaGFzZSIpCnBsIDwtIGxpc3QoKQoKZm9yIChpIGluIHZhcnMpIHsKICBwbFtbaV1dIDwtIERpbVBsb3Qob2JqLCBncm91cC5ieSA9IGksIGxhYmVsID0gVCkgKyB0aGVtZV92b2lkKCkgKyBOb0xlZ2VuZCgpCn0Kd3JhcF9wbG90cyhwbCkKCnRhYmxlKG9iaiRzZXVyYXRfY2x1c3RlcnMpCmBgYAoKIyA1LiBFeHBsb3JpbmcgdGhlIGRhdGEKYGBge3J9Cgp2YXJzIDwtIGMoIlRPWCIsICJMQUczIiwgIkNUTEE0IiwgIlRJR0lUIiwgIkZPWFAzIiwgIklMMlJBIiwgIkNUTEE0IiwgIklLWkYyIiwgIlRJR0lUIiwgIkNDUjciLCAiU0VMTCIsICJJTDdSIiwgIlRDRjciLCAiQ0NSNyIsICJTRUxMIiwgIklMN1IiLCAiVENGNyIsICJMRUYxIiwgIkdaTUIiLCAiUFJGMSIsICJJRk5HIiwgIktMUkcxIiwgIkNENjkiLCAiQ1hDUjYiLCAiUFJGMSIsICJHWk1CIiwgIk5LRzciLCAiR05MWSIsICJJU0cxNSIsICJJRkk2IiwgIklGSVQzIiwgIk1YMSIsICJNS0k2NyIsICJUT1AyQSIsICJTVEFUMyIsICJBSFIiLCAiQ0NSNiIsICJCQVRGIiwgIlBUUFJDIiwgIkdaTUIiLCAiQkNMNiIsICJJQ09TIiwgIkhTUEExQSIsICJBVEYzIiwgIklMMlJBIiwgIkNENjkiLCAiSExBLURSQSIsICJDRDM0IikKcGwgPC0gbGlzdCgpCgpwbCA8LSBsaXN0KERpbVBsb3Qob2JqLCBncm91cC5ieSA9ICJzZXVyYXRfY2x1c3RlcnMiLCBsYWJlbCA9IFQpICsgdGhlbWVfdm9pZCgpICsgTm9MZWdlbmQoKSkKZm9yIChpIGluIHZhcnMpIHsKICBwbFtbaV1dIDwtIEZlYXR1cmVQbG90KG9iaiwgZmVhdHVyZXMgPSBpLCBvcmRlciA9IFQpICsgdGhlbWVfdm9pZCgpICsgTm9MZWdlbmQoKQp9CndyYXBfcGxvdHMocGwpCgoKYGBgCiMjIC4gRXhwbG9yaW5nIHRoZSBkYXRhLTIKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQoKIyBMb2FkIG5lY2Vzc2FyeSBsaWJyYXJpZXMKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdsdWUpCgojIERlZmluZSBtYXJrZXIgZ3JvdXBzCm1hcmtlcl9ncm91cHMgPC0gbGlzdCgKICAiQ0Q0IFRleCAoRXhoYXVzdGVkKSIgPSBjKCJUT1giLCAiTEFHMyIsICJDVExBNCIsICJUSUdJVCIpLAogICJDRDQgVHJlZyIgPSBjKCJGT1hQMyIsICJJTDJSQSIsICJDVExBNCIsICJJS1pGMiIsICJUSUdJVCIpLAogICJDRDQgVGNtIChDZW50cmFsIE1lbW9yeSkiID0gYygiQ0NSNyIsICJTRUxMIiwgIklMN1IiLCAiVENGNyIpLAogICJDRDQgVG4gKE5haXZlKSIgPSBjKCJDQ1I3IiwgIlNFTEwiLCAiSUw3UiIsICJUQ0Y3IiwgIkxFRjEiKSwKICAiQ0Q0IFRlbSAoRWZmZWN0b3IgTWVtb3J5KSIgPSBjKCJHWk1CIiwgIlBSRjEiLCAiSUZORyIsICJLTFJHMSIpLAogICJDRDQgVHJtIChUaXNzdWUgUmVzaWRlbnQpIiA9IGMoIkNENjkiLCAiQ1hDUjYiKSwKICAiQ0Q0IFRjIChDeXRvdG94aWMpIiA9IGMoIlBSRjEiLCAiR1pNQiIsICJOS0c3IiwgIkdOTFkiKSwKICAiQ0Q0IFRpc2cgKElGTiBTaWduYXR1cmUpIiA9IGMoIklTRzE1IiwgIklGSTYiLCAiSUZJVDMiLCAiTVgxIiksCiAgIkNENCBQcm9saWZlcmF0aW9uIiA9IGMoIk1LSTY3IiwgIlRPUDJBIiksCiAgIkNENCBUaDE3IiA9IGMoIlNUQVQzIiwgIkFIUiIsICJDQ1I2IiwgIkJBVEYiKSwKICAiQ0Q0IFRlbXJhIChFZmZlY3RvciBNZW1vcnkgUkErKSIgPSBjKCJQVFBSQyIsICJHWk1CIiksCiAgIkNENCBUZmggKEZvbGxpY3VsYXIgSGVscGVyKSIgPSBjKCJCQ0w2IiwgIklDT1MiKSwKICAiQ0Q0IFRzdHIgKFN0cmVzcykiID0gYygiSFNQQTFBIiwgIkFURjMiKSwKICAiQ0Q0IEFjdGl2YXRlZCIgPSBjKCJJTDJSQSIsICJDRDY5IiwgIkhMQS1EUkEiKQopCgojIE9QVElPTkFMOiBVbmNvbW1lbnQgdGhpcyB0byBzYXZlIGFsbCBwbG90cyBpbiBhIHNpbmdsZSBQREYKIyBwZGYoIkNENF9NYXJrZXJHcm91cHNfRmVhdHVyZVBsb3RzLnBkZiIsIHdpZHRoID0gMTIsIGhlaWdodCA9IDEwKQoKIyBNYWluIHBsb3R0aW5nIGxvb3AKZm9yIChncm91cF9uYW1lIGluIG5hbWVzKG1hcmtlcl9ncm91cHMpKSB7CiAgZ2VuZXMgPC0gdW5pcXVlKG1hcmtlcl9ncm91cHNbW2dyb3VwX25hbWVdXSkKICBtZXNzYWdlKGdsdWUoIlBsb3R0aW5nOiB7Z3JvdXBfbmFtZX0iKSkKCiAgIyBDbGVhbmVyIHRpdGxlcyBhbmQgYmV0dGVyIHRoZW1lCiAgcGwgPC0gbGFwcGx5KGdlbmVzLCBmdW5jdGlvbihnZW5lKSB7CiAgICBGZWF0dXJlUGxvdChvYmosIGZlYXR1cmVzID0gZ2VuZSwgb3JkZXIgPSBUUlVFKSArCiAgICAgIGdndGl0bGUoZ2VuZSkgKyAgIyBzaG9ydGVyIHRpdGxlCiAgICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTApICsgICMgdXNlIG1pbmltYWwgZm9yIHNwYWNpbmcKICAgICAgTm9MZWdlbmQoKQogIH0pCgogICMgQ29tYmluZSBwbG90cwogIGNvbWJpbmVkX3Bsb3QgPC0gd3JhcF9wbG90cyhwbCwgbmNvbCA9IDIpICsgICMgZmV3ZXIgY29sdW1ucyA9IG1vcmUgc3BhY2UKICAgICAgICAgICAgICAgICAgIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9IGdyb3VwX25hbWUpCgogIHByaW50KGNvbWJpbmVkX3Bsb3QpCn0KIyBPUFRJT05BTDogVW5jb21tZW50IGlmIHVzaW5nIHBkZigpCiMgZGV2Lm9mZigpCgoKCgpgYGAKIyMgLiBFeHBsb3JpbmcgdGhlIGRhdGEtMwpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9CgojIFJlcXVpcmVkIG1hcmtlcnMKcHJvZ2VuaXRvcl9tYXJrZXJzIDwtIGMoIkNEMzQiLCAiS0lUIiwgIkdBVEEyIiwgIk1LSTY3IiwgIlBST00xIiwgIkZMVDMiKQoKIyBGZWF0dXJlIHBsb3RzIGZvciBlYWNoIG1hcmtlcgpGZWF0dXJlUGxvdChvYmosIGZlYXR1cmVzID0gcHJvZ2VuaXRvcl9tYXJrZXJzLCBvcmRlciA9IFRSVUUsIG5jb2wgPSAzKQoKCgoKYGBgCgojIDYuIGNvbXB1dGUgdGhlIGxpbmVhZ2VzIG9uIHRoZXNlIGRhdGFzZXQKYGBge3J9CiMgIyBEZWZpbmUgbGluZWFnZSBlbmRzCiMgRU5EUyA8LSBjKCIxIiwgIjgiLCAiMCIgLCAiMTMiKQoKc2V0LnNlZWQoMSkKbGluZWFnZXMgPC0gYXMuU2xpbmdzaG90RGF0YVNldChnZXRMaW5lYWdlcygKICBkYXRhICAgICAgICAgICA9IG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzLAogIGNsdXN0ZXJMYWJlbHMgID0gb2JqJHNldXJhdF9jbHVzdGVycywKICBkaXN0Lm1ldGhvZCAgICA9ICJtbm4iLCAjIEl0IGNhbiBiZTogInNpbXBsZSIsICJzY2FsZWQuZnVsbCIsICJzY2FsZWQuZGlhZyIsICJzbGluZ3Nob3QiIG9yICJtbm4iCiAKKSkgIyBkZWZpbmUgd2hlcmUgdG8gU1RBUlQgdGhlIHRyYWplY3RvcmllcwoKCiMgSUYgTkVFREVELCBPTkUgQ0FOIEFMU08gTUFOVUxBTExZIEVESVQgVEhFIExJTkVBR0VTLCBGT1IgRVhBTVBMRToKIyBzZWwgPC0gc2FwcGx5KCBsaW5lYWdlc0BsaW5lYWdlcywgZnVuY3Rpb24oeCl7cmV2KHgpWzFdfSApICVpbiUgRU5EUwojIGxpbmVhZ2VzQGxpbmVhZ2VzIDwtIGxpbmVhZ2VzQGxpbmVhZ2VzWyBzZWwgXQojIG5hbWVzKGxpbmVhZ2VzQGxpbmVhZ2VzKSA8LSBwYXN0ZTAoIkxpbmVhZ2UiLDE6bGVuZ3RoKGxpbmVhZ2VzQGxpbmVhZ2VzKSkKIyBsaW5lYWdlcwoKCiMgQ2hhbmdlIHRoZSByZWR1Y3Rpb24gdG8gb3VyICJmaXhlZCIgVU1BUDJkIChGT1IgVklTVUFMSVNBVElPTiBPTkxZKQpsaW5lYWdlc0ByZWR1Y2VkRGltIDwtIG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzCgp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncywgY29sID0gcGFsW29iaiRzZXVyYXRfY2x1c3RlcnNdLCBjZXggPSAuNSwgcGNoID0gMTYpCiAgbGluZXMobGluZWFnZXMsIGx3ZCA9IDEsIGNvbCA9ICJibGFjayIsIGNleCA9IDIpCiAgdGV4dChjZW50cm9pZHMyZCwgbGFiZWxzID0gcm93bmFtZXMoY2VudHJvaWRzMmQpLCBjZXggPSAwLjgsIGZvbnQgPSAyLCBjb2wgPSAid2hpdGUiKQp9CgpgYGAKCiMjICBEZWZpbmluZyBQcmluY2lwYWwgQ3VydmVzCmBgYHtyfQoKIyBEZWZpbmUgY3VydmVzCmN1cnZlcyA8LSBhcy5TbGluZ3Nob3REYXRhU2V0KGdldEN1cnZlcygKICBkYXRhICAgICAgICAgID0gbGluZWFnZXMsCiAgdGhyZXNoICAgICAgICA9IDFlLTEsCiAgc3RyZXRjaCAgICAgICA9IDFlLTEsCiAgYWxsb3cuYnJlYWtzICA9IEYsCiAgYXBwcm94X3BvaW50cyA9IDEwMDAKKSkKCmN1cnZlcwoKYGBgCiMjICBQbG90CmBgYHtyfQoKIyBQbG90cwp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncywgY29sID0gcGFsW29iaiRzZXVyYXRfY2x1c3RlcnNdLCBwY2ggPSAxNikKICBsaW5lcyhjdXJ2ZXMsIGx3ZCA9IDIsIGNvbCA9ICJibGFjayIpCiAgdGV4dChjZW50cm9pZHMyZCwgbGFiZWxzID0gbGV2ZWxzKG9iaiRzZXVyYXRfY2x1c3RlcnMpLCBjZXggPSAxLCBmb250ID0gMikKfQpgYGAKCgoKIyMgIGNvbXB1dGUgdGhlIGRpZmZlcmVudGlhdGlvbiBwc2V1ZG90aW1lCmBgYHtyfQoKcHNldWRvdGltZSA8LSBzbGluZ1BzZXVkb3RpbWUoY3VydmVzLCBuYSA9IEZBTFNFKQpjZWxsV2VpZ2h0cyA8LSBzbGluZ0N1cnZlV2VpZ2h0cyhjdXJ2ZXMpCgp4IDwtIHJvd01lYW5zKHBzZXVkb3RpbWUpCnggPC0geCAvIG1heCh4KQpvIDwtIG9yZGVyKHgpCgp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1tvLCBdLAogICAgbWFpbiA9IHBhc3RlMCgicHNldWRvdGltZSIpLCBwY2ggPSAxNiwgY2V4ID0gMC40LCBheGVzID0gRiwgeGxhYiA9ICIiLCB5bGFiID0gIiIsCiAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGMoImdyZXk3MCIsICJvcmFuZ2UzIiwgImZpcmVicmljayIsICJwdXJwbGU0IikpKDk5KVt4W29dICogOTggKyAxXQogICkKICBwb2ludHMoY2VudHJvaWRzMmQsIGNleCA9IDIuNSwgcGNoID0gMTYsIGNvbCA9ICIjRkZGRkZGOTkiKQogIHRleHQoY2VudHJvaWRzMmQsIGxhYmVscyA9IGxldmVscyhvYmokc2V1cmF0X2NsdXN0ZXJzKSwgY2V4ID0gMSwgZm9udCA9IDIpCn0KYGBgCiMgNy4gRmluZGluZyBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMKYGBge3J9CgpzZWxfY2VsbHMgPC0gc3BsaXQoY29sbmFtZXMob2JqQGFzc2F5cyRTQ1RAZGF0YSksIG9iaiRzZXVyYXRfY2x1c3RlcnMpCnNlbF9jZWxscyA8LSB1bmxpc3QobGFwcGx5KHNlbF9jZWxscywgZnVuY3Rpb24oeCkgewogIHNldC5zZWVkKDEpCiAgcmV0dXJuKHNhbXBsZSh4LCAyMCkpCn0pKQoKCiMgRmlsdGVyIGdlbmVzOiBleHByZXNzZWQgaW4gYXQgbGVhc3QgMTAgY2VsbHMgd2l0aCBjb3VudCA+IDEKZXhwcl9maWx0ZXIgPC0gcm93U3VtcyhvYmpAYXNzYXlzJFNDVEBjb3VudHNbLCBzZWxfY2VsbHNdID4gMSkgPj0gMTAKZmlsdGVyZWRfZ2VuZXMgPC0gcm93bmFtZXMob2JqKVtleHByX2ZpbHRlcl0KCiMgQ29tcHV0ZSBnZW5lIHZhcmlhbmNlIG9uIGZpbHRlcmVkIGdlbmVzIHVzaW5nIHJhdyBjb3VudHMKZ3YgPC0gYXMuZGF0YS5mcmFtZShuYS5vbWl0KHNjcmFuOjptb2RlbEdlbmVWYXIob2JqQGFzc2F5cyRTQ1RAZGF0YVssIHNlbF9jZWxsc10pKSkKIyBPcmRlciBieSBiaW9sb2dpY2FsIHZhcmlhbmNlIGRlY3JlYXNpbmcKZ3YgPC0gZ3Zbb3JkZXIoZ3YkYmlvLCBkZWNyZWFzaW5nID0gVCksIF0KIyBTZWxlY3QgdG9wIDUwMCBoaWdobHkgdmFyaWFibGUgZ2VuZXMKc2VsX2dlbmVzIDwtIHNvcnQocm93bmFtZXMoZ3YpWzE6NTAwXSkKCnBhdGhfZmlsZSA8LSAiL2hvbWUvYmlvaW5mby9kYXRhL3RyYWplY3RvcnkvdHJhamVjdG9yeV9zZXVyYXRfZmlsdGVyZWQucmRzIgoKIyBmZXRjaF9kYXRhIGlzIGRlZmluZWQgYXQgdGhlIHRvcCBvZiB0aGlzIGRvY3VtZW50CnNjZUdBTSA8LSBmaXRHQU0oCiAgY291bnRzID0gZHJvcDAob2JqQGFzc2F5cyRTQ1RAZGF0YVtzZWxfZ2VuZXMsIHNlbF9jZWxsc10pLAogIHBzZXVkb3RpbWUgPSBwc2V1ZG90aW1lW3NlbF9jZWxscywgXSwKICBjZWxsV2VpZ2h0cyA9IGNlbGxXZWlnaHRzW3NlbF9jZWxscywgXSwKICBua25vdHMgPSA1LCB2ZXJib3NlID0gVFJVRSwgcGFyYWxsZWwgPSBUUlVFLCBzY2UgPSBUUlVFLAogIEJQUEFSQU0gPSBCaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKCkKKQoKCgpwbG90R2VuZUNvdW50KGN1cnZlcywgY2x1c3RlcnMgPSBvYmokc2V1cmF0X2NsdXN0ZXJzLCBtb2RlbHMgPSBzY2VHQU0pCgoKbGluZWFnZXMKCmxjIDwtIHNhcHBseShsaW5lYWdlc0BsaW5lYWdlcywgZnVuY3Rpb24oeCkgewogIHJldih4KVsxXQp9KQpuYW1lcyhsYykgPC0gZ3N1YigiTGluZWFnZSIsICJMIiwgbmFtZXMobGMpKQpsYy5pZHggPSBtYXRjaChsYywgbGV2ZWxzKG9iaiRzZXVyYXRfY2x1c3RlcnMpKQoKewogIHBsb3Qob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MsIGNvbCA9IHBhbFtvYmokc2V1cmF0X2NsdXN0ZXJzXSwgcGNoID0gMTYpCiAgbGluZXMoY3VydmVzLCBsd2QgPSAyLCBjb2wgPSAiYmxhY2siKQogIHBvaW50cyhjZW50cm9pZHMyZFtsYy5pZHgsIF0sIGNvbCA9ICJibGFjayIsIHBjaCA9IDE2LCBjZXggPSA0KQogIHRleHQoY2VudHJvaWRzMmRbbGMuaWR4LCBdLCBsYWJlbHMgPSBuYW1lcyhsYyksIGNleCA9IDEsIGZvbnQgPSAyLCBjb2wgPSAid2hpdGUiKQp9CgoKCgpgYGAKCgoKCgoKCgojIyBHZW5lcyB0aGF0IGNoYW5nZSB3aXRoIHBzZXVkb3RpbWUKYGBge3J9CgpzZXQuc2VlZCg4KQpyZXMgPC0gbmEub21pdChhc3NvY2lhdGlvblRlc3Qoc2NlR0FNLCBjb250cmFzdFR5cGUgPSAiY29uc2VjdXRpdmUiKSkKcmVzIDwtIHJlc1tyZXMkcHZhbHVlIDwgMWUtMywgXQpyZXMgPC0gcmVzW3JlcyR3YWxkU3RhdCA+IG1lYW4ocmVzJHdhbGRTdGF0KSwgXQpyZXMgPC0gcmVzW29yZGVyKHJlcyR3YWxkU3RhdCwgZGVjcmVhc2luZyA9IFQpLCBdCnJlc1sxOjEwLCBdCgpgYGAKCgojIyBXZSBjYW4gcGxvdCB0aGVpciBleHByZXNzaW9uCmBgYHtyfQoKcGFyKG1mcm93ID0gYyg0LCA0KSwgbWFyID0gYyguMSwgLjEsIDIsIDEpKQp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncywgY29sID0gcGFsW29iaiRzZXVyYXRfY2x1c3RlcnNdLCBjZXggPSAuNSwgcGNoID0gMTYsIGF4ZXMgPSBGLCB4bGFiID0gIiIsIHlsYWIgPSAiIikKICBsaW5lcyhjdXJ2ZXMsIGx3ZCA9IDIsIGNvbCA9ICJibGFjayIpCiAgcG9pbnRzKGNlbnRyb2lkczJkW2xjLmlkeCwgXSwgY29sID0gImJsYWNrIiwgcGNoID0gMTUsIGNleCA9IDMsIHhwZCA9IFQpCiAgdGV4dChjZW50cm9pZHMyZFtsYy5pZHgsIF0sIGxhYmVscyA9IG5hbWVzKGxjKSwgY2V4ID0gMSwgZm9udCA9IDIsIGNvbCA9ICJ3aGl0ZSIsIHhwZCA9IFQpCn0KCnZhcnMgPC0gcm93bmFtZXMocmVzWzE6MTUsIF0pCnZhcnMgPC0gbmEub21pdCh2YXJzW3ZhcnMgIT0gIk5BIl0pCgpmb3IgKGkgaW4gdmFycykgewogIHggPC0gZHJvcDAob2JqQGFzc2F5cyRTQ1RAZGF0YSlbaSwgXQogIHggPC0gKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkKICBvIDwtIG9yZGVyKHgpCiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1tvLCBdLAogICAgbWFpbiA9IHBhc3RlMChpKSwgcGNoID0gMTYsIGNleCA9IDAuNSwgYXhlcyA9IEYsIHhsYWIgPSAiIiwgeWxhYiA9ICIiLAogICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGdyYXkiLCAiZ3JleTYwIiwgIm5hdnkiKSkoOTkpW3hbb10gKiA5OCArIDFdCiAgKQp9CgpgYGAKCgoKIyMgR2VuZXMgdGhhdCBjaGFuZ2UgYmV0d2VlbiB0d28gcHNldWRvdGltZSBwb2ludHMKYGBge3J9CnJlcyA8LSBuYS5vbWl0KHN0YXJ0VnNFbmRUZXN0KHNjZUdBTSwgcHNldWRvdGltZVZhbHVlcyA9IGMoMCwgMSkpKQpyZXMgPC0gcmVzW3JlcyRwdmFsdWUgPCAxZS0zLCBdCnJlcyA8LSByZXNbcmVzJHdhbGRTdGF0ID4gbWVhbihyZXMkd2FsZFN0YXQpLCBdCnJlcyA8LSByZXNbb3JkZXIocmVzJHdhbGRTdGF0LCBkZWNyZWFzaW5nID0gVCksIF0KcmVzWzE6MTAsIDE6Nl0KYGBgCiMjIGlkZW50aWZ5IHdoaWNoIGdlbmVzIGdvIHVwIG9yIGRvd24uIExldOKAmXMgY2hlY2sgbGluZWFnZSAxOgpgYGB7cn0KIyBHZXQgdGhlIHRvcCBVUCBhbmQgRG93biByZWd1bGF0ZWQgaW4gbGluZWFnZSAxCnJlc19saW4xIDwtIHNvcnQoc2V0TmFtZXMocmVzJGxvZ0ZDbGluZWFnZTEsIHJvd25hbWVzKHJlcykpKQp2YXJzIDwtIG5hbWVzKGMocmV2KHJlc19saW4xKVsxOjddLCByZXNfbGluMVsxOjhdKSkKdmFycyA8LSBuYS5vbWl0KHZhcnNbdmFycyAhPSAiTkEiXSkKCnBhcihtZnJvdyA9IGMoNCwgNCksIG1hciA9IGMoLjEsIC4xLCAyLCAxKSkKCnsKICBwbG90KG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzLCBjb2wgPSBwYWxbb2JqJHNldXJhdF9jbHVzdGVyc10sIGNleCA9IC41LCBwY2ggPSAxNiwgYXhlcyA9IEYsIHhsYWIgPSAiIiwgeWxhYiA9ICIiKQogIGxpbmVzKGN1cnZlcywgbHdkID0gMiwgY29sID0gImJsYWNrIikKICBwb2ludHMoY2VudHJvaWRzMmRbbGMuaWR4LCBdLCBjb2wgPSAiYmxhY2siLCBwY2ggPSAxNSwgY2V4ID0gMywgeHBkID0gVCkKICB0ZXh0KGNlbnRyb2lkczJkW2xjLmlkeCwgXSwgbGFiZWxzID0gbmFtZXMobGMpLCBjZXggPSAxLCBmb250ID0gMiwgY29sID0gIndoaXRlIiwgeHBkID0gVCkKfQoKZm9yIChpIGluIHZhcnMpIHsKICB4IDwtIGRyb3AwKG9iakBhc3NheXMkU0NUQGRhdGEpW2ksIF0KICB4IDwtICh4IC0gbWluKHgpKSAvIChtYXgoeCkgLSBtaW4oeCkpCiAgbyA8LSBvcmRlcih4KQogIHBsb3Qob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3NbbywgXSwKICAgIG1haW4gPSBwYXN0ZTAoaSksIHBjaCA9IDE2LCBjZXggPSAwLjUsIGF4ZXMgPSBGLCB4bGFiID0gIiIsIHlsYWIgPSAiIiwKICAgIGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUoYygibGlnaHRncmF5IiwgImdyZXk2MCIsICJuYXZ5IikpKDk5KVt4W29dICogOTggKyAxXQogICkKfQpgYGAKCiMjIEdlbmVzIHRoYXQgYXJlIGRpZmZlcmVudCBiZXR3ZWVuIGxpbmVhZ2VzCmBgYHtyfQpyZXMgPC0gbmEub21pdChkaWZmRW5kVGVzdChzY2VHQU0pKQpyZXMgPC0gcmVzW3JlcyRwdmFsdWUgPCAxZS0zLCBdCnJlcyA8LSByZXNbcmVzJHdhbGRTdGF0ID4gbWVhbihyZXMkd2FsZFN0YXQpLCBdCnJlcyA8LSByZXNbb3JkZXIocmVzJHdhbGRTdGF0LCBkZWNyZWFzaW5nID0gVCksIF0KcmVzWzE6MTAsIF0KCmBgYAoKIyMgcGFpcndpc2UgY29tcGFyaXNvbiBiZXR3ZWVuIGVhY2ggbGluZWFnZS4gTGV04oCZcyBjaGVjayBsaW5lYWdlIDEgdnMgbGluZWFnZSAyOgpgYGB7cn0KIyBHZXQgdGhlIHRvcCBVUCBhbmQgRG93biByZWd1bGF0ZWQgaW4gbGluZWFnZSAxIHZzIDIKcmVzX2xpbjFfMiA8LSBzb3J0KHNldE5hbWVzKHJlcyRsb2dGQzFfMiwgcm93bmFtZXMocmVzKSkpCnZhcnMgPC0gbmFtZXMoYyhyZXYocmVzX2xpbjFfMilbMTo3XSwgcmVzX2xpbjFfMlsxOjhdKSkKdmFycyA8LSBuYS5vbWl0KHZhcnNbdmFycyAhPSAiTkEiXSkKCnBhcihtZnJvdyA9IGMoNCwgNCksIG1hciA9IGMoLjEsIC4xLCAyLCAxKSkKewogIHBsb3Qob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MsIGNvbCA9IHBhbFtvYmokc2V1cmF0X2NsdXN0ZXJzXSwgY2V4ID0gLjUsIHBjaCA9IDE2LCBheGVzID0gRiwgeGxhYiA9ICIiLCB5bGFiID0gIiIpCiAgbGluZXMoY3VydmVzLCBsd2QgPSAyLCBjb2wgPSAiYmxhY2siKQogIHBvaW50cyhjZW50cm9pZHMyZFtsYy5pZHgsIF0sIGNvbCA9ICJibGFjayIsIHBjaCA9IDE1LCBjZXggPSAzLCB4cGQgPSBUKQogIHRleHQoY2VudHJvaWRzMmRbbGMuaWR4LCBdLCBsYWJlbHMgPSBuYW1lcyhsYyksIGNleCA9IDEsIGZvbnQgPSAyLCBjb2wgPSAid2hpdGUiLCB4cGQgPSBUKQp9Cgpmb3IgKGkgaW4gdmFycykgewogIHggPC0gZHJvcDAob2JqQGFzc2F5cyRTQ1RAZGF0YSlbaSwgXQogIHggPC0gKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkKICBvIDwtIG9yZGVyKHgpCiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1tvLCBdLAogICAgbWFpbiA9IHBhc3RlMChpKSwgcGNoID0gMTYsIGNleCA9IDAuNSwgYXhlcyA9IEYsIHhsYWIgPSAiIiwgeWxhYiA9ICIiLAogICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGdyYXkiLCAiZ3JleTYwIiwgIm5hdnkiKSkoOTkpW3hbb10gKiA5OCArIDFdCiAgKQp9CmBgYAoKCgoK