Chi Yen Tseng1, Christine M. Custer2, Thomas W. Custer2, Paul M. Dummer2, Natalie Karouna‐Renier3 and Cole W. Matson1

  1. Department of Environmental Science, The Institute of Ecological, Earth, and Environmental Sciences (TIE3S), and the Center for Reservoir and Aquatic Systems Research (CRASR), Baylor University, Waco, Texas 76798, United States
  2. Upper Midwest Environmental Sciences Center, U.S. Geological Survey, La Crosse, Wisconsin 54603, United States
  3. U.S. Geological Survey, Eastern Ecological Science Center (EESC) at Patuxent, Beltsville, Maryland 20705, United States

Load packages

library(glmnet)
library(edgeR)
library(readr)
library(tidyverse)
library(foreach)
library(doParallel)
library(DESeq2)

1. For selecting top predictor genes to predcit PCBs (by individual nestlings)

Load all data

GLRI_coldata
chemistry data (“Dioxin” “MultiRedsiduePest” “PAHs” “PBDEs” “LRPCBs” “HRPCBs” “Pesticides” “PFCs” “PPCPs”)
data.contaminants.majorSubset.geo.bysite
geometric mean of each contaminant key by site
contaminants.coldata.subset
only includes the nestlings with genomic information and removes PAHs data
ID.convert
gene ID convertion table between tree swallow gene pseudo name and chicken gene name
siteMAP.all
site ID and propersite name convertion Table
dds.bothSex.adjusted or counts.tswallow
normalized count matrix for all nestlings, adjusted for batch difference
Cont.major.list
major contaminants (“Nonachlor, cis-_C”, “Heptachlor Epoxide_C”, “Nonachlor, trans-_C”, “PFDA_C”, “Chlordane, oxy-_C”, “Total Parent PAHs”, “Total PBDE”, “Total PAHs”,“Total PCBs”,“Total_PFCs”)
counts.tswallow.norm
vst transformed normalized counts
setwd("/home/chiyen/Documents/work/Tswallow_chem_GLRI_update/GLRI_MS2_all")
# load chemistry data and show class
GLRI_coldata <- readRDS("GLRI_coldata_to2020.rds")
print(c("chemistry data class:", unique(GLRI_coldata$class)))
# Determine min contamin value for each Key
GLRI_coldata <- GLRI_coldata$contaminants %>% filter(!is.na(Value))
# Load chemistry GeoMean by site 
data.contaminants.majorSubset.geo.bysite <- readRDS("data_contaminants_majorSubset_geo_bysite.rds")
# Determine minimum conc. for each chemical 
contaminants.min.adjusted <- GLRI_coldata %>% group_by(Key,class) %>% summarise(minValue = min(as.numeric(value.adjust),na.rm = TRUE)) %>% ungroup()
# Editing chmistry geoMean by substituting "." (below detection limit)  into t1/3 of min of that chemistry 
contaminants.coldata.subset <- read_csv("GLRI_TRES_contaminantsto2020_Transcriptome_subset_CT.csv")
contaminants.coldata.subset <- contaminants.coldata.subset %>% filter(!is.na(Value)) ## remove those with only NA
contaminants.coldata.subset <- contaminants.coldata.subset %>% left_join(contaminants.min.adjusted, by = c("Key","class"))
contaminants.coldata.subset <- contaminants.coldata.subset %>% filter(!minValue == "Inf") ## remove those with only "."
contaminants.coldata.subset$value.adjust[contaminants.coldata.subset$Value == "."] <- contaminants.coldata.subset$minValue[contaminants.coldata.subset$Value == "."]/3 # "." converts to 1/3 of loweast detetable value
# Load Gene ID MAP for GeneID to GeneName conversion
ID.convert <- readRDS("IDmap_final.rds") ## gene ID Convert table
siteMAP.all <- as_tibble(readRDS("siteMAP_all.rds")) ## site ID convert Table
siteMAP.all$SiteID[siteMAP.all$propersite == "StarLake"] <- "SL"
dds.bothSex.adjusted <- readRDS("dds.bothSex.adjusted.outlierRM.rds") ## normalized count matrix
dds.bothSex.adjusted <- dds.bothSex.adjusted[,colnames(dds.bothSex.adjusted) != "19HW576B"] ## remove "19HW576B" only use A chick for the analysis for regression analysis
colnames(dds.bothSex.adjusted) <- gsub("A$|B$", "", colnames(dds.bothSex.adjusted)) ## remove all the A or B tail 
dds.bothSex.adjusted$propersite2 <- as.character(siteMAP.all$propersite[match(dds.bothSex.adjusted$site, siteMAP.all$SiteID)]) 
counts.tswallow <- assay(dds.bothSex.adjusted)
Cont.major.list <- c("Nonachlor, cis-_C", "Heptachlor Epoxide_C",   "Nonachlor, trans-_C",  "PFDA_C", "Chlordane, oxy-_C", "Total Parent PAHs", "Total PBDE", "Total PAHs","Total PCBs","Total_PFCs")
## contaminants.coldata.subset2: There are some PCBs are LRPCBs, some are HRPCBs. Combining all of  "TOTAL PCBs_C", and "TOTAL PCBs" into "TOTAL PCBs" and if there are overlapping, pick LRPCBs first 
contaminants.coldata.subset <- contaminants.coldata.subset %>% filter(class != "PAHs")
contaminants.coldata.subset <- contaminants.coldata.subset %>% mutate(MergeSite=replace(MergeSite, MergeSite == "ref", "SL")) ## replace ref to SL 
contaminants.coldata.subset$MergeID <- gsub("-","", contaminants.coldata.subset$MergeID) ## remove all dash
contaminants.coldata.subset$MergeID <- gsub("A$","", contaminants.coldata.subset$MergeID) ## remove A tail
contaminants.coldata.subset <- contaminants.coldata.subset %>% mutate(Key=replace(Key, Key == "TOTAL PCBs_C" | Key == "TOTAL PCBs", "TOTAL PCBs"))
counts.tswallow.norm <- vst(dds.bothSex.adjusted)
counts.tswallow.norm <- assay(counts.tswallow.norm)

Load self-defined functions for PCB training

GetTopDEGs_byGeoMean.byNest(Con,Con_ind)
get DEGs (p < 0.05) and select top 1000 most differentiating genes using EdgeR against PCBs tissue concentrations
Run_lasso.onesiteout(n, Con, Con_ind, s, lam.m)
Run leave-one-out (by site) cross validation and train lasso regression against PCBs tissue concentrations; 31 sites, sample n out of 31*300 re-sampling
GetTopDEGs_byGeoMean.byNest <- function(Con,Con_ind) {
  nestid <- contaminants.coldata.subset2 %>% filter(Key == Con_ind) %>% dplyr::pull(MergeID)
  match1 <- colnames(counts.tswallow) %in% nestid
  counts.con  <-  counts.tswallow[,match1]
  counts.con.batch <- as.factor(as.character(dds.bothSex.adjusted$batch2)[match1])
  counts.con.sex <- as.factor(as.character(dds.bothSex.adjusted$sex)[match1])
  contamin.matrix <- contaminants.coldata.subset2 %>% dplyr::slice(match(colnames(counts.con), contaminants.coldata.subset2$MergeID)) %>% filter(Key == Con_ind)
  counts.con.contamin <- log10(contamin.matrix$value.adjust) ## get log10 value to suppress potential outlier
  ## EdgeR needs non-normalized counts
  y <- DGEList(counts=as.matrix(counts.con))
  y <- calcNormFactors(y)
  if (length(levels(counts.con.batch)) > 1) {
    design <- model.matrix(~counts.con.batch+counts.con.sex+counts.con.contamin) ## add batch as cofactor
    y <- estimateDisp(y,design)
    fit <- glmQLFit(y,design)
    qlf <- glmQLFTest(fit, coef=4)} else {
      design <- model.matrix(~counts.con.sex+counts.con.contamin) ## add batch as cofactor
      y <- estimateDisp(y,design)
      fit <- glmQLFit(y,design) 
      qlf <- glmQLFTest(fit, coef=3)
    }
  DEGs.Q <- sum(decideTests(qlf) != 0)
  print(DEGs.Q)
  match2 <- row.names(topTags(qlf, n=1000))
  match2.logFC <- topTags(qlf, n=1000)[[1]][,"logFC"]
  match2.table <- topTags(qlf, n=2000)
  match2.table.all <- topTags(qlf, n=nrow(qlf))
  match2.DEGs.table <- topTags(qlf, n=DEGs.Q)
  assign(paste0("DEGs.table_by_GeoMeanNest.",Con,Con_ind),match2.DEGs.table, envir = parent.frame())
  assign(paste0("top1000DEGs_by_GeoMeanNest.",Con,Con_ind),match2, envir = parent.frame())
  assign(paste0("top1000DEGs_by_GeoMeanNest.direction",Con,Con_ind),match2.logFC, envir = parent.frame())
  assign(paste0("DEGs.top2000.table_by_GeoMeanNest.",Con,Con_ind),match2.table, envir = parent.frame())
  assign(paste0("DEGs.allGene.table_by_GeoMeanNest.",Con_ind),match2.table.all, envir = parent.frame())
} # get DEGs (p < 0.05) and select top 1000 most differentiating genes using EdgeR against PCBs tissue concentrations

assessmargin <- function(accuracy.Q.accum){
  qt(0.975,df=length(accuracy.Q.accum)-1)*sd(accuracy.Q.accum)/sqrt(length(accuracy.Q.accum))
} # determine margin

lasso.by.nest.onesiteout.test <- function(Con,Con_ind,i) {
  
  nestid <- contaminants.coldata.subset2 %>% filter(Key == Con_ind) %>% dplyr::pull(MergeID)
  match1 <- colnames(counts.tswallow.norm) %in% nestid
  counts.con  <-  counts.tswallow.norm[,match1]
  counts.con.batch <- as.factor(as.character(dds.bothSex.adjusted$batch2)[match1])
  counts.con.sex <- as.factor(as.character(dds.bothSex.adjusted$sex)[match1])
  contamin.raw <- contaminants.coldata.subset2 %>% filter(Key == Con_ind) %>% dplyr::pull(value.adjust) %>% log10()
  contamin.matrix <- contaminants.coldata.subset2 %>% dplyr::slice(match(colnames(counts.con), contaminants.coldata.subset2$MergeID)) %>% filter(Key == Con_ind)
  counts.con.contamin <- log10(contamin.matrix$value.adjust) ## get log10 value to suppress potential outlier
  ## Building matrix 
  if (length(levels(counts.con.batch)) > 1) {
    counts.con.t <- as.data.frame(t(counts.con[get(paste0("top1000DEGs_by_GeoMeanNest.",Con,Con_ind)),]))
    counts.con.t <- cbind(counts.con.t, batch = counts.con.batch, sex = counts.con.sex, contaminant = counts.con.contamin)
    m <- model.matrix(contaminant ~ batch +., counts.con.t)
    m <- m[,-1] } else {
      counts.con.t <- as.data.frame(t(counts.con[get(paste0("top1000DEGs_by_GeoMeanNest.",Con,Con_ind)),]))
      counts.con.t <- cbind(counts.con.t, sex = counts.con.sex, contaminant = counts.con.contamin)
      m <- model.matrix(contaminant ~ ., counts.con.t)
      m <- m[,-1] }  
  ## make sure most of sites have testing samples, allsite: # of all genomic individuals, nestsite: # of genomic individuals with matching nest chemical info
  counts.site  <-  dds.bothSex.adjusted$propersite2[match1]
  matchsubset.train <- counts.site != unique(counts.site)[i] # train subset
  print(c("training #", sum(matchsubset.train)))
  matchsubset.test <- counts.site != unique(counts.site)[i]
  l.lse <- {}
  for (j in 1:20) {
    cvfit <- cv.glmnet(x=m[matchsubset.train,], y=counts.con.t$contaminant[matchsubset.train], nfold = 10, alpha = 1, lambda = 10^seq(0,-2,length=200), relax =FALSE)
    l.lse = c(l.lse, cvfit$lambda.1se)
  }
  l.lse = median(l.lse)
  cvfit1 <- cv.glmnet(x=m[matchsubset.train,], y=counts.con.t$contaminant[matchsubset.train], nfold = 10, alpha = 1, lambda = 10^seq(0,-2,length=600), relax =FALSE)
  ## only plot it 3 times in test runs
  pdf(paste0("~/Documents/work/Tswallow_chem_GLRI_update/Temp_plots/ByNest_",Con_ind, "_distribution",".pdf"))
  plot(sort(counts.con.contamin),main = paste0("ByNest.",Con_ind, ".distribution"), ylab = "log10(value)")
  dev.off()
  
  pdf(paste0("~/Documents/work/Tswallow_chem_GLRI_update/Temp_plots/lamdaPlot_byNest_onesiteout",Con_ind, ".pdf"))
  plot(cvfit1,main = paste0("R_lamdaPlot_leaveonesiteout.byNest.",Con_ind))
  
  dev.off()
  } # test Run 

## Run leave-one-out (by site) cross validation and train lasso regression against PCBs tissue concentrations 
lasso.by.nest.onesiteout <- function(Con,Con_ind, s, lam.m,i) {
  nestid <- contaminants.coldata.subset2 %>% filter(Key == Con_ind) %>% dplyr::pull(MergeID)
  match1 <- colnames(counts.tswallow.norm) %in% nestid
  counts.con  <-  counts.tswallow.norm[,match1]
  counts.con.batch <- as.factor(as.character(dds.bothSex.adjusted$batch2)[match1])
  counts.con.sex <- as.factor(as.character(dds.bothSex.adjusted$sex)[match1])
  contamin.raw <- contaminants.coldata.subset2 %>% filter(Key == Con_ind) %>% dplyr::pull(value.adjust) %>% log10()
  contamin.matrix <- contaminants.coldata.subset2 %>% dplyr::slice(match(colnames(counts.con), contaminants.coldata.subset2$MergeID)) %>% filter(Key == Con_ind)
  counts.con.contamin <- log10(contamin.matrix$value.adjust) ## get log10 value to suppress potential outlier
  ## Building matrix 
  if (length(levels(counts.con.batch)) > 1) {
    counts.con.t <- as.data.frame(t(counts.con[get(paste0("top1000DEGs_by_GeoMeanNest.",Con,Con_ind)),]))
    counts.con.t <- cbind(counts.con.t, batch = counts.con.batch, sex = counts.con.sex, contaminant = counts.con.contamin)
    m <- model.matrix(contaminant ~ batch +., counts.con.t)
    m <- m[,-1] } else {
      counts.con.t <- as.data.frame(t(counts.con[get(paste0("top1000DEGs_by_GeoMeanNest.",Con,Con_ind)),]))
      counts.con.t <- cbind(counts.con.t, sex = counts.con.sex, contaminant = counts.con.contamin)
      m <- model.matrix(contaminant ~ ., counts.con.t)
      m <- m[,-1] }  
  ## make sure most of sites have testing samples, allsite: # of all genomic individuals, nestsite: # of genomic individuals with matching nest chemical info
  counts.site  <-  dds.bothSex.adjusted$propersite2[match1]
  matchsubset.train <- counts.site != unique(counts.site)[i] # train subset
  ## sample 90% 
  matchsubset.train <- sample(which(matchsubset.train),(sum(matchsubset.train)*0.9))
  print(c("training #", length(matchsubset.train)))
  matchsubset.test <- counts.site == unique(counts.site)[i]
  l.1se <- {}
  l.min <- {}
  for (j in 1:20) {
    cvfit <- cv.glmnet(x=m[matchsubset.train,], y=counts.con.t$contaminant[matchsubset.train], nfold = 10, alpha = 1, lambda = 10^seq(0,-2,length=200), relax =FALSE)
    l.1se <- c(l.1se, cvfit$lambda.1se)
    l.min <- c(l.min, cvfit$lambda.min)
  }
  l.1se = median(l.1se)
  l.min <- median(l.min)
  l.median <- exp(mean(c(log(l.min),log(l.1se))))
  lamda.sequence <- c(l.1se,l.min,l.median)
  ByNestResult.withlamdaP <- {}
  
  fit <- glmnet(x=m[matchsubset.train,], y=counts.con.t$contaminant[matchsubset.train], alpha = 1, lambda = 10^seq(0,lam.m,length=600), relax = FALSE)
  Result <- assess.glmnet(fit, newx = m[matchsubset.train,], newy = counts.con.t$contaminant[matchsubset.train], s = lamda.sequence[s])
  
  Result.lasso.assessment <- {}
  Result.lasso.assessment$mse <- Result$mse[[1]]
  Result.lasso.assessment$mae <- Result$mae[[1]]
  
  variables <- coef(fit, s = lamda.sequence[s])
  Result.lasso.assessment$variables <- row.names(variables)[!(variables[,1] == 0)][-1] 
  pseudo_R2 <- fit$dev.ratio[which(sort(c(lamda.sequence[s],10^seq(0,lam.m,length=600)),decreasing = TRUE) == lamda.sequence[s])[1] -1]
  Result.lasso.assessment$pseudo_R2 <- pseudo_R2
  
  ## Building test matrix 
  predict.site.test <- predict(fit, newx = m[matchsubset.test,], s = lamda.sequence[s]) 
  result.list = list(lamda.sequence,Result.lasso.assessment,predict.site.test)
  names(result.list) <- c("lamda.sequence","Result.relax.lasso.assessment","predictions")
  return(result.list)
} 
Run_lasso.onesiteout <- function(n, Con, Con_ind, s, lam.m) {
    lassoNestResult.run <- foreach(i = sample(rep(1:31,300),n), .packages = c("dplyr","glmnet"), .export = c("lasso.by.nest.onesiteout","contaminants.coldata.subset2","counts.tswallow.norm","dds.bothSex.adjusted", paste0("top1000DEGs_by_GeoMeanNest.",Con,Con_ind), "data.contaminants.majorSubset.geo.bysite")) %dopar% {
    r_lasso_result <- Vectorize(lasso.by.nest.onesiteout)(Con,Con_ind, s, lam.m,i)
    return(r_lasso_result)
  }
  assign(paste0("lassoResult.onesiteout",sub(" ","",Con_ind)), lassoNestResult.run, envir = parent.frame())
  return(lassoNestResult.run)
} # 31 sites, sample n out of  31*300 re-sampling 

Lasso regression analysis between global geene expression and PCB tissue concentrations

Run lasso with leave one (site) cross validation and by selecting 1000 individuals from (31*300 resampling = 9300), using 1.1se

topgenelistPCBs.rds
selecting 91 genes which appeared more than 50 times (> 5%) in the cross-validation
## Total PCBs
Con="PCBs_data";Con_ind="TOTAL PCBs"
## refresh between chemicals 
lamda.sequence <- {}
Result.lasso.assessment <- {}
variables <- {}
predict.site.test <- {}
rlassoResult <- list()
## Set up PCBs chemistry data 
contaminants.coldata.subset2 <-contaminants.coldata.subset
## Individual chemicals 
contaminants.coldata.subset2 <- contaminants.coldata.subset2 %>% filter(Key == Con_ind)
contaminants.coldata.subset2 <- contaminants.coldata.subset2 %>% filter(!is.na(value.adjust)) ## remove those with value == "NR"
## combine all duplicated items using mean value because they have the same nest id 
contaminants.coldata.subset2 <- contaminants.coldata.subset2 %>% group_by(MergeID,Species,Matrix,AOC,Proper_Site,Key,type,class,MergeSite,proper_site2) %>% summarise(value.adjust2 = mean(value.adjust)) %>% ungroup()
colnames(contaminants.coldata.subset2)[colnames(contaminants.coldata.subset2) == "value.adjust2"] <- "value.adjust" ## change the name back
print(sum(duplicated(contaminants.coldata.subset2$MergeID))) ## check duplication again
TotalPCBs.mergeIDlist = contaminants.coldata.subset2$MergeID  
## Get top DEGs using DESeq2 against PCBs gradient  
GetTopDEGs_byGeoMean.byNest(Con=Con,Con_ind=Con_ind) #103 DEGs (p < 0.05)
# saveRDS(`DEGs.allGene.table_by_GeoMeanNest.TOTAL PCBs`, "~/Documents/work/Tswallow_chem_GLRI_update/GLRI_MS_figures/TOP.PCBs.allgene.table.bynest.rds")
## Train lasso regression model using glmnet; relax lasso was not included because there was no significant improvement  

## Register parallel 
cl <- parallel::makeCluster(12)
doParallel::registerDoParallel(cl)
lassoResult.totalPCBs = Run_lasso.onesiteout(n= 1000, Con=Con,Con_ind=Con_ind, s= 1, lam.m=-2) # lamda.sequence <- c(l.1se,l.min,l.median); run lasso with leave one (site) cross validation and by selecting 1000 individuals from (31*300 resampling = 9300), using 1.1se 
parallel::stopCluster(cl) # stop parallel
## selecting those genes which were selected more than 50 times (> 5%)
test.genelist.topPCB <- names(table(unlist(sapply(1:1000, function(x) lassoResult.totalPCBs[[x]][[2]]$variables))))[table(unlist(sapply(1:1000, function(x) lassoResult.totalPCBs[[x]][[2]]$variables))) > 50]
## save top gene 
saveRDS(test.genelist.topPCB, file ="topgenelistPCBs.rds") # 91 genes  

2. For selecting top predictor genes to predcit PAHs (by pooled stomach contents each site)

Process coldata by site

data.contaminants.majorSubset.geo.bysite$MergeSite[data.contaminants.majorSubset.geo.bysite$MergeSite == "ref"] <- "SL" ## convert ref to SL 
## combine all duplicated items by MergeSite
data.contaminants.majorSubset.geo.bysite <- data.contaminants.majorSubset.geo.bysite %>% group_by(proper_site2,MergeSite,Key) %>% summarise(value.geoMean2 = mean(value.geoMean)) %>% ungroup()
colnames(data.contaminants.majorSubset.geo.bysite)[colnames(data.contaminants.majorSubset.geo.bysite) == "value.geoMean2"] <- "value.geoMean" ## change the name back

Load self-defined functions for lasso regression training

GetTopDEGs_byGeoMean.bySiteGeoMean()
get top DEGs using edgeR linear regression against PAHs concentrations
lasso.by.site()
using top1000 DEGs for lasso regression analysis with 10-fold validation to determine lamda, leave one (site) out cross-validation, resample (sample 90%) of training data
Run_lasso_by_site
Run n runs of lasso.by.site()
GetTopDEGs_byGeoMean.bySiteGeoMean <- function(Con,Con_ind) {
  
  match1 <- str_sub(colnames(counts.tswallow),3,4) %in% contaminants.coldata.subset2$MergeSite
  counts.con  <-  counts.tswallow[,match1]
  counts.con.batch <- as.factor(as.character(dds.bothSex.adjusted$batch2)[match1])
  counts.con.sex <- as.factor(as.character(dds.bothSex.adjusted$sex)[match1])
  counts.con.contamin <- log10(contaminants.coldata.subset2$value.geoMean[match(str_sub(colnames(counts.con),3,4), contaminants.coldata.subset2$MergeSite)])
  
  ## EdgeR needs non-normalized counts
  y <- DGEList(counts=as.matrix(counts.con))
  y <- calcNormFactors(y)
  if (length(levels(counts.con.batch)) > 1) {
    design <- model.matrix(~counts.con.batch+counts.con.sex+counts.con.contamin) ## add batch as cofactor
    y <- estimateDisp(y,design)
    fit <- glmQLFit(y,design)
    qlf <- glmQLFTest(fit, coef=4)} else {
      design <- model.matrix(~counts.con.sex+counts.con.contamin) ## add batch as cofactor
      y <- estimateDisp(y,design)
      fit <- glmQLFit(y,design) 
      qlf <- glmQLFTest(fit, coef=3)
    }
  DEGs.Q <- sum(decideTests(qlf) != 0)
  print(DEGs.Q)
  match2 <- row.names(topTags(qlf, n=1000))
  match2.logFC <- topTags(qlf, n=1000)[[1]][,"logFC"]
  match2.table <- topTags(qlf, n=2000)
  match2.table.all <- topTags(qlf, n=nrow(qlf))
  match2.DEGs.table <- topTags(qlf, n=DEGs.Q)
  assign(paste0("DEGs.table_by_GeoMeanSite.",Con,Con_ind),match2.DEGs.table, envir = parent.frame())
  assign(paste0("top1000DEGs_by_GeoMeanSite.",Con,Con_ind),match2, envir = parent.frame())
  assign(paste0("top1000DEGs_by_GeoMeanSite.direction",Con,Con_ind),match2.logFC, envir = parent.frame())
  assign(paste0("DEGs.top2000.table_by_GeoMeanSite.",Con,Con_ind),match2.table, envir = parent.frame())
  assign(paste0("DEGs.allGene.table_by_GeoMeanSite.",Con_ind),match2.table.all, envir = parent.frame())
}

# Min_L: for how low the lamda cv goes 10^(-2,-3,-4); s: lamda.sequence: 1) 1se 2) min 3) log median 
lasso.by.site <- function(Con,Con_ind,i,Min_L,s) {
  BySiteResult.withlamdaP <- {} ## refresh
  variables.all <- list()
  predict.site.test.all <- list()
  match1 <- str_sub(colnames(counts.tswallow),3,4) %in% contaminants.coldata.subset2$MergeSite
  counts.con  <-  counts.tswallow.norm[,match1]
  counts.con.batch <- as.factor(as.character(dds.bothSex.adjusted$batch2)[match1])
  counts.con.sex <- as.factor(as.character(dds.bothSex.adjusted$sex)[match1])
  counts.con.contamin <- log10(contaminants.coldata.subset2$value.geoMean[match(str_sub(colnames(counts.con),3,4), contaminants.coldata.subset2$MergeSite)])
  ## Building matrix 
  if (length(levels(counts.con.batch)) > 1) {
    counts.con.t <- as.data.frame(t(counts.con[get(paste0("top1000DEGs_by_GeoMeanSite.",Con,Con_ind)),]))
    counts.con.t <- cbind(counts.con.t, batch = counts.con.batch, sex = counts.con.sex, contaminant = counts.con.contamin)
    m <- model.matrix(contaminant ~ batch +., counts.con.t)
    m <- m[,-1] } else {
      counts.con.t <- as.data.frame(t(counts.con[get(paste0("top1000DEGs_by_GeoMeanSite.",Con,Con_ind)),]))
      counts.con.t <- cbind(counts.con.t, sex = counts.con.sex, contaminant = counts.con.contamin)
      m <- model.matrix(contaminant ~ ., counts.con.t)
      m <- m[,-1] }  
  
  ## determinne training and testing set
  counts.site  <-  dds.bothSex.adjusted$propersite2[match1]
  match1.test <- counts.site == unique(counts.site)[i]
  match1.test.sample <- sample(which(match1.test), size = median(table(counts.site)), replace = TRUE)
  matchsubset.train <- counts.site != unique(counts.site)[i] # train subset
  ## sample 90% of training set
  matchsubset.train <- sample(which(matchsubset.train),(sum(matchsubset.train)*0.9))
  
  l.1se <- {} ## repeat 5 times to avoid outlier in cross validation 
  l.min <- {}
  # foldid<- as.numeric(as.factor(str_sub(colnames(counts.con[,matchsubset.train]),3,4))) # don't use foldid as batch effect for consistency

  cvfit <- cv.glmnet(x=m[matchsubset.train,], y=counts.con.t$contaminant[matchsubset.train], nfolds = 10, alpha = 1, lambda = 10^seq(0,Min_L,length=600))
  l.1se <- cvfit$lambda.1se
  l.min <- cvfit$lambda.min
  l.median <- exp(mean(c(log(l.1se),log(l.min))))
  lamda.sequence <- c(l.1se,l.min,l.median)  
  names(lamda.sequence) <- c("1se","min","median")
    fit <- glmnet(x=m[matchsubset.train,], y=counts.con.t$contaminant[matchsubset.train], alpha = 1, lambda = 10^seq(0,Min_L,length=600))
    Result1 <- assess.glmnet(fit, newx = m[match1.test.sample,], newy = counts.con.t$contaminant[match1.test.sample], s = lamda.sequence[s]) 
    BySiteResult.withlamdaP$mse <- Result1$mse
    BySiteResult.withlamdaP$mae <- Result1$mae
    variables <- coef(fit, s = lamda.sequence[s])  
    variables <- row.names(variables)[!(variables[,1] == 0)]
    variables <- variables[-1]
    
    pseudo_R2 <- fit$dev.ratio[which(sort(c(lamda.sequence[s],10^seq(0,Min_L,length=600)),decreasing = TRUE) == lamda.sequence[s])[1] -1]
    predict.site.test1 <- predict(fit, newx = m[match1.test.sample,], s = lamda.sequence[s]) 
    predict.site.test.all <- predict.site.test1
    
    result = list(lamda.sequence,BySiteResult.withlamdaP,variables,pseudo_R2,predict.site.test.all)
    names(result) <- c("lamda.sequence","mse_mae","variables","pseudo_R2","predict.site")
    return(result)
}
Run_lasso_by_site <- function(n,Con,Con_ind,Min_L,s) {
TIME1 <- Sys.time()
lassositeResult.run <- foreach(i = rep(1:28,n), .packages = c("dplyr","glmnet"), .export = c("lasso.by.site","contaminants.coldata.subset2","counts.tswallow.norm","dds.bothSex.adjusted", paste0("top1000DEGs_by_GeoMeanSite.",Con,Con_ind), "data.contaminants.majorSubset.geo.bysite")) %dopar% {
  cat(paste("Starting iteration",i,Sys.time(),"\n"), file = paste0("~/Documents/work/Tswallow_chem_GLRI_update/relax_lasso_result/",Con_ind,"lassoBySiteRun.log2.txt"), append = TRUE)
  lasso_result <- Vectorize(lasso.by.site)(Con,Con_ind,i,Min_L,s)
  return(lasso_result)
}
return(lassositeResult.run)
TIME2 <- Sys.time()
print(TIME2 - TIME1)
} 

Lasso regression analysis between global geene expression and PAH concentrations

Run lasso with leave one (site) cross validation, lamda chose l.1se for selecting top predictor genes from top 1000 genes (EdgeR, linear regression against PAHs concentrations) lasso_topgene_bySite_PAHs.rds : selecting top 110 genes in the cross-validation (> 10% of cross-validation)

Con="PAHs_data";Con_ind="Total PAHs"
## edit subset for overlapping genomic samples with contaminant data 
contaminants.coldata.subset2 <-data.contaminants.majorSubset.geo.bysite
## Individual chemicals 
contaminants.coldata.subset2 <- contaminants.coldata.subset2 %>% filter(Key == Con_ind)
## combine all duplicated items using mean value because they have the same MergeSite ## only River Raisin will be affected 
contaminants.coldata.subset2 <- contaminants.coldata.subset2 %>% group_by(MergeSite, Key) %>% summarise(value.geoMean2 = mean(value.geoMean)) %>% ungroup()
colnames(contaminants.coldata.subset2)[colnames(contaminants.coldata.subset2) == "value.geoMean2"] <- "value.geoMean" ## change the name back to value.geoMean
print(sum(duplicated(contaminants.coldata.subset2$MergeSite))) ## check duplication again
GetTopDEGs_byGeoMean.bySiteGeoMean(Con = Con, Con_ind = Con_ind) ## 570 DEGs (p < 0..05) against PAHs tissue concentrations
# saveRDS(`DEGs.allGene.table_by_GeoMeanSite.Total PAHs`,"~/Documents/work/Tswallow_chem_GLRI_update/GLRI_MS_figures/TOP.PAHs.allgene.table.bysite.rds")

## Run lasso regression by site to predict PAHs tissue concentrations
registerDoParallel(cores = 12)
## Becasue the purpose 
lassoResult.bySite.TotalPAHs = Run_lasso_by_site(n=200,Con=Con,Con_ind = Con_ind, Min_L = -2, s = 1) # lamda.sequence <- c(l.1se,l.min,l.median)
parallel::stopCluster(cl) # stop parallel
lassoResult.bySite = lassoResult.bySite.TotalPAHs
# saveRDS(lassoResult.bySite.TotalPAHs,"~/Documents/work/Tswallow_chem_GLRI_update/GLRI_MS2_all/lasso_topgene_bySite_PAHs.rds")

## 100% trials have less than 95 genes, go ahead and pick genes appears at 10% of trials 
lasso_selected_topgene <- names(sort(table(unlist(sapply(1:(28*200),function(x) lassoResult.bySite[[x]][[3]]))),decreasing = TRUE)[1:110])
# saveRDS(lasso_selected_topgene, "~/Documents/work/Tswallow_chem_GLRI_update/GLRI_MS2_all/lasso_topgene_bySite_PAHs.rds")
LS0tCnRpdGxlOiAiSW50ZWdyYXRlZCBhbmFseXNpcyBhbmQgbW9kZWxsaW5nIG9mIGNvbnRhbWluYW50IG1peHR1cmVzIGFuZCB0cmFuc2NyaXB0b21pYyByZXNwb25zZXMgaW4gVHJlZSBTd2FsbG93IChUYWNoeWNpbmV0YSBiaWNvbG9yKSBuZXN0bGluZ3MgaW4gdGhlIEdyZWF0IExha2VzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGNlcnVsZWFuCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKQ2hpIFllbiBUc2VuZ14xXiwgQ2hyaXN0aW5lIE0uIEN1c3Rlcl4yXiwgVGhvbWFzIFcuIEN1c3Rlcl4yXiwgUGF1bCBNLiBEdW1tZXJeMl4sIE5hdGFsaWUgS2Fyb3VuYeKAkFJlbmllcl4zXiBhbmQgQ29sZSBXLiBNYXRzb25eMV4KCjEuIERlcGFydG1lbnQgb2YgRW52aXJvbm1lbnRhbCBTY2llbmNlLCBUaGUgSW5zdGl0dXRlIG9mIEVjb2xvZ2ljYWwsIEVhcnRoLCBhbmQgRW52aXJvbm1lbnRhbCBTY2llbmNlcyAoVElFM1MpLCBhbmQgdGhlIENlbnRlciBmb3IgUmVzZXJ2b2lyIGFuZCBBcXVhdGljIFN5c3RlbXMgUmVzZWFyY2ggKENSQVNSKSwgQmF5bG9yIFVuaXZlcnNpdHksIFdhY28sIFRleGFzIDc2Nzk4LCBVbml0ZWQgU3RhdGVzIAoyLiBVcHBlciBNaWR3ZXN0IEVudmlyb25tZW50YWwgU2NpZW5jZXMgQ2VudGVyLCBVLlMuIEdlb2xvZ2ljYWwgU3VydmV5LCBMYSBDcm9zc2UsIFdpc2NvbnNpbiA1NDYwMywgVW5pdGVkIFN0YXRlcyAKMy4gVS5TLiBHZW9sb2dpY2FsIFN1cnZleSwgRWFzdGVybiBFY29sb2dpY2FsIFNjaWVuY2UgQ2VudGVyIChFRVNDKSBhdCBQYXR1eGVudCwgQmVsdHN2aWxsZSwgTWFyeWxhbmQgMjA3MDUsIFVuaXRlZCBTdGF0ZXMKCgojIyMgTG9hZCBwYWNrYWdlcyAKYGBge3IgTG9hZCBwYWNrYWdlcywgZXZhbD1GQUxTRX0KbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGZvcmVhY2gpCmxpYnJhcnkoZG9QYXJhbGxlbCkKbGlicmFyeShERVNlcTIpCmBgYAoKCmBgYHtjc3MsIGVjaG89RkFMU0V9CnByZSB7CiAgbWF4LWhlaWdodDogMzAwcHg7CiAgb3ZlcmZsb3cteTogYXV0bzsKfQoKcHJlW2NsYXNzXSB7CiAgbWF4LWhlaWdodDogMTUwcHg7Cn0KYGBgCgojIyAxLiBGb3Igc2VsZWN0aW5nIHRvcCBwcmVkaWN0b3IgZ2VuZXMgdG8gcHJlZGNpdCBQQ0JzIChieSBpbmRpdmlkdWFsIG5lc3RsaW5ncykgIAoKIyMjIExvYWQgYWxsIGRhdGEgCgpgR0xSSV9jb2xkYXRhYCAgCjogY2hlbWlzdHJ5IGRhdGEgKCJEaW94aW4iICJNdWx0aVJlZHNpZHVlUGVzdCIgIlBBSHMiICJQQkRFcyIgIkxSUENCcyIgIkhSUENCcyIgIlBlc3RpY2lkZXMiICJQRkNzIiAiUFBDUHMiKSAgCgpgZGF0YS5jb250YW1pbmFudHMubWFqb3JTdWJzZXQuZ2VvLmJ5c2l0ZWAKOiBnZW9tZXRyaWMgbWVhbiBvZiBlYWNoIGNvbnRhbWluYW50IGtleSBieSBzaXRlICAKCmBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXRgCjogb25seSBpbmNsdWRlcyB0aGUgbmVzdGxpbmdzIHdpdGggZ2Vub21pYyBpbmZvcm1hdGlvbiBhbmQgcmVtb3ZlcyBQQUhzIGRhdGEgIAoKYElELmNvbnZlcnRgCjogZ2VuZSBJRCBjb252ZXJ0aW9uIHRhYmxlIGJldHdlZW4gdHJlZSBzd2FsbG93IGdlbmUgcHNldWRvIG5hbWUgYW5kIGNoaWNrZW4gZ2VuZSBuYW1lICAKCmBzaXRlTUFQLmFsbGAKOiBzaXRlIElEIGFuZCBwcm9wZXJzaXRlIG5hbWUgY29udmVydGlvbiBUYWJsZSAgCgpgZGRzLmJvdGhTZXguYWRqdXN0ZWQgb3IgY291bnRzLnRzd2FsbG93YAo6IG5vcm1hbGl6ZWQgY291bnQgbWF0cml4IGZvciBhbGwgbmVzdGxpbmdzLCBhZGp1c3RlZCBmb3IgYmF0Y2ggZGlmZmVyZW5jZSAgCgpgQ29udC5tYWpvci5saXN0YAo6IG1ham9yIGNvbnRhbWluYW50cyAoIk5vbmFjaGxvciwgY2lzLV9DIiwgIkhlcHRhY2hsb3IgRXBveGlkZV9DIiwgICAiTm9uYWNobG9yLCB0cmFucy1fQyIsICAiUEZEQV9DIiwgIkNobG9yZGFuZSwgb3h5LV9DIiwgIlRvdGFsIFBhcmVudCBQQUhzIiwgIlRvdGFsIFBCREUiLCAiVG90YWwgUEFIcyIsIlRvdGFsIFBDQnMiLCJUb3RhbF9QRkNzIikKCmBjb3VudHMudHN3YWxsb3cubm9ybWAKOiB2c3QgdHJhbnNmb3JtZWQgbm9ybWFsaXplZCBjb3VudHMgICAKCmBgYHtyIExvYWQgZGF0YSwgZXZhbD1GQUxTRX0Kc2V0d2QoIi9ob21lL2NoaXllbi9Eb2N1bWVudHMvd29yay9Uc3dhbGxvd19jaGVtX0dMUklfdXBkYXRlL0dMUklfTVMyX2FsbCIpCiMgbG9hZCBjaGVtaXN0cnkgZGF0YSBhbmQgc2hvdyBjbGFzcwpHTFJJX2NvbGRhdGEgPC0gcmVhZFJEUygiR0xSSV9jb2xkYXRhX3RvMjAyMC5yZHMiKQpwcmludChjKCJjaGVtaXN0cnkgZGF0YSBjbGFzczoiLCB1bmlxdWUoR0xSSV9jb2xkYXRhJGNsYXNzKSkpCiMgRGV0ZXJtaW5lIG1pbiBjb250YW1pbiB2YWx1ZSBmb3IgZWFjaCBLZXkKR0xSSV9jb2xkYXRhIDwtIEdMUklfY29sZGF0YSRjb250YW1pbmFudHMgJT4lIGZpbHRlcighaXMubmEoVmFsdWUpKQojIExvYWQgY2hlbWlzdHJ5IEdlb01lYW4gYnkgc2l0ZSAKZGF0YS5jb250YW1pbmFudHMubWFqb3JTdWJzZXQuZ2VvLmJ5c2l0ZSA8LSByZWFkUkRTKCJkYXRhX2NvbnRhbWluYW50c19tYWpvclN1YnNldF9nZW9fYnlzaXRlLnJkcyIpCiMgRGV0ZXJtaW5lIG1pbmltdW0gY29uYy4gZm9yIGVhY2ggY2hlbWljYWwgCmNvbnRhbWluYW50cy5taW4uYWRqdXN0ZWQgPC0gR0xSSV9jb2xkYXRhICU+JSBncm91cF9ieShLZXksY2xhc3MpICU+JSBzdW1tYXJpc2UobWluVmFsdWUgPSBtaW4oYXMubnVtZXJpYyh2YWx1ZS5hZGp1c3QpLG5hLnJtID0gVFJVRSkpICU+JSB1bmdyb3VwKCkKIyBFZGl0aW5nIGNobWlzdHJ5IGdlb01lYW4gYnkgc3Vic3RpdHV0aW5nICIuIiAoYmVsb3cgZGV0ZWN0aW9uIGxpbWl0KSAgaW50byB0MS8zIG9mIG1pbiBvZiB0aGF0IGNoZW1pc3RyeSAKY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0IDwtIHJlYWRfY3N2KCJHTFJJX1RSRVNfY29udGFtaW5hbnRzdG8yMDIwX1RyYW5zY3JpcHRvbWVfc3Vic2V0X0NULmNzdiIpCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQgJT4lIGZpbHRlcighaXMubmEoVmFsdWUpKSAjIyByZW1vdmUgdGhvc2Ugd2l0aCBvbmx5IE5BCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQgJT4lIGxlZnRfam9pbihjb250YW1pbmFudHMubWluLmFkanVzdGVkLCBieSA9IGMoIktleSIsImNsYXNzIikpCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQgJT4lIGZpbHRlcighbWluVmFsdWUgPT0gIkluZiIpICMjIHJlbW92ZSB0aG9zZSB3aXRoIG9ubHkgIi4iCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCR2YWx1ZS5hZGp1c3RbY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0JFZhbHVlID09ICIuIl0gPC0gY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0JG1pblZhbHVlW2NvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCRWYWx1ZSA9PSAiLiJdLzMgIyAiLiIgY29udmVydHMgdG8gMS8zIG9mIGxvd2Vhc3QgZGV0ZXRhYmxlIHZhbHVlCiMgTG9hZCBHZW5lIElEIE1BUCBmb3IgR2VuZUlEIHRvIEdlbmVOYW1lIGNvbnZlcnNpb24KSUQuY29udmVydCA8LSByZWFkUkRTKCJJRG1hcF9maW5hbC5yZHMiKSAjIyBnZW5lIElEIENvbnZlcnQgdGFibGUKc2l0ZU1BUC5hbGwgPC0gYXNfdGliYmxlKHJlYWRSRFMoInNpdGVNQVBfYWxsLnJkcyIpKSAjIyBzaXRlIElEIGNvbnZlcnQgVGFibGUKc2l0ZU1BUC5hbGwkU2l0ZUlEW3NpdGVNQVAuYWxsJHByb3BlcnNpdGUgPT0gIlN0YXJMYWtlIl0gPC0gIlNMIgpkZHMuYm90aFNleC5hZGp1c3RlZCA8LSByZWFkUkRTKCJkZHMuYm90aFNleC5hZGp1c3RlZC5vdXRsaWVyUk0ucmRzIikgIyMgbm9ybWFsaXplZCBjb3VudCBtYXRyaXgKZGRzLmJvdGhTZXguYWRqdXN0ZWQgPC0gZGRzLmJvdGhTZXguYWRqdXN0ZWRbLGNvbG5hbWVzKGRkcy5ib3RoU2V4LmFkanVzdGVkKSAhPSAiMTlIVzU3NkIiXSAjIyByZW1vdmUgIjE5SFc1NzZCIiBvbmx5IHVzZSBBIGNoaWNrIGZvciB0aGUgYW5hbHlzaXMgZm9yIHJlZ3Jlc3Npb24gYW5hbHlzaXMKY29sbmFtZXMoZGRzLmJvdGhTZXguYWRqdXN0ZWQpIDwtIGdzdWIoIkEkfEIkIiwgIiIsIGNvbG5hbWVzKGRkcy5ib3RoU2V4LmFkanVzdGVkKSkgIyMgcmVtb3ZlIGFsbCB0aGUgQSBvciBCIHRhaWwgCmRkcy5ib3RoU2V4LmFkanVzdGVkJHByb3BlcnNpdGUyIDwtIGFzLmNoYXJhY3RlcihzaXRlTUFQLmFsbCRwcm9wZXJzaXRlW21hdGNoKGRkcy5ib3RoU2V4LmFkanVzdGVkJHNpdGUsIHNpdGVNQVAuYWxsJFNpdGVJRCldKSAKY291bnRzLnRzd2FsbG93IDwtIGFzc2F5KGRkcy5ib3RoU2V4LmFkanVzdGVkKQpDb250Lm1ham9yLmxpc3QgPC0gYygiTm9uYWNobG9yLCBjaXMtX0MiLCAiSGVwdGFjaGxvciBFcG94aWRlX0MiLCAgICJOb25hY2hsb3IsIHRyYW5zLV9DIiwgICJQRkRBX0MiLCAiQ2hsb3JkYW5lLCBveHktX0MiLCAiVG90YWwgUGFyZW50IFBBSHMiLCAiVG90YWwgUEJERSIsICJUb3RhbCBQQUhzIiwiVG90YWwgUENCcyIsIlRvdGFsX1BGQ3MiKQojIyBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyOiBUaGVyZSBhcmUgc29tZSBQQ0JzIGFyZSBMUlBDQnMsIHNvbWUgYXJlIEhSUENCcy4gQ29tYmluaW5nIGFsbCBvZiAgIlRPVEFMIFBDQnNfQyIsIGFuZCAiVE9UQUwgUENCcyIgaW50byAiVE9UQUwgUENCcyIgYW5kIGlmIHRoZXJlIGFyZSBvdmVybGFwcGluZywgcGljayBMUlBDQnMgZmlyc3QgCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQgJT4lIGZpbHRlcihjbGFzcyAhPSAiUEFIcyIpCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQgJT4lIG11dGF0ZShNZXJnZVNpdGU9cmVwbGFjZShNZXJnZVNpdGUsIE1lcmdlU2l0ZSA9PSAicmVmIiwgIlNMIikpICMjIHJlcGxhY2UgcmVmIHRvIFNMIApjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQkTWVyZ2VJRCA8LSBnc3ViKCItIiwiIiwgY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0JE1lcmdlSUQpICMjIHJlbW92ZSBhbGwgZGFzaApjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQkTWVyZ2VJRCA8LSBnc3ViKCJBJCIsIiIsIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCRNZXJnZUlEKSAjIyByZW1vdmUgQSB0YWlsCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQgJT4lIG11dGF0ZShLZXk9cmVwbGFjZShLZXksIEtleSA9PSAiVE9UQUwgUENCc19DIiB8IEtleSA9PSAiVE9UQUwgUENCcyIsICJUT1RBTCBQQ0JzIikpCmNvdW50cy50c3dhbGxvdy5ub3JtIDwtIHZzdChkZHMuYm90aFNleC5hZGp1c3RlZCkKY291bnRzLnRzd2FsbG93Lm5vcm0gPC0gYXNzYXkoY291bnRzLnRzd2FsbG93Lm5vcm0pCmBgYAojIyMjIExvYWQgc2VsZi1kZWZpbmVkIGZ1bmN0aW9ucyBmb3IgUENCIHRyYWluaW5nCgpHZXRUb3BERUdzX2J5R2VvTWVhbi5ieU5lc3QoQ29uLENvbl9pbmQpCjogZ2V0IERFR3MgKHAgPCAwLjA1KSBhbmQgc2VsZWN0IHRvcCAxMDAwIG1vc3QgZGlmZmVyZW50aWF0aW5nIGdlbmVzIHVzaW5nIEVkZ2VSIGFnYWluc3QgUENCcyB0aXNzdWUgY29uY2VudHJhdGlvbnMgIAoKUnVuX2xhc3NvLm9uZXNpdGVvdXQobiwgQ29uLCBDb25faW5kLCBzLCBsYW0ubSkKOiBSdW4gbGVhdmUtb25lLW91dCAoYnkgc2l0ZSkgY3Jvc3MgdmFsaWRhdGlvbiBhbmQgdHJhaW4gbGFzc28gcmVncmVzc2lvbiBhZ2FpbnN0IFBDQnMgdGlzc3VlIGNvbmNlbnRyYXRpb25zOyAzMSBzaXRlcywgc2FtcGxlIG4gb3V0IG9mICAzMSozMDAgcmUtc2FtcGxpbmcgIAoKCmBgYHtyIExvYWQgc2VsZi1kZWZpbmVkIGZ1bmN0aW9ucyBmb3IgUENCIHRyYWluaW5nLCBldmFsID0gRkFMU0V9CkdldFRvcERFR3NfYnlHZW9NZWFuLmJ5TmVzdCA8LSBmdW5jdGlvbihDb24sQ29uX2luZCkgewogIG5lc3RpZCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyICU+JSBmaWx0ZXIoS2V5ID09IENvbl9pbmQpICU+JSBkcGx5cjo6cHVsbChNZXJnZUlEKQogIG1hdGNoMSA8LSBjb2xuYW1lcyhjb3VudHMudHN3YWxsb3cpICVpbiUgbmVzdGlkCiAgY291bnRzLmNvbiAgPC0gIGNvdW50cy50c3dhbGxvd1ssbWF0Y2gxXQogIGNvdW50cy5jb24uYmF0Y2ggPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihkZHMuYm90aFNleC5hZGp1c3RlZCRiYXRjaDIpW21hdGNoMV0pCiAgY291bnRzLmNvbi5zZXggPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihkZHMuYm90aFNleC5hZGp1c3RlZCRzZXgpW21hdGNoMV0pCiAgY29udGFtaW4ubWF0cml4IDwtIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIgJT4lIGRwbHlyOjpzbGljZShtYXRjaChjb2xuYW1lcyhjb3VudHMuY29uKSwgY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiRNZXJnZUlEKSkgJT4lIGZpbHRlcihLZXkgPT0gQ29uX2luZCkKICBjb3VudHMuY29uLmNvbnRhbWluIDwtIGxvZzEwKGNvbnRhbWluLm1hdHJpeCR2YWx1ZS5hZGp1c3QpICMjIGdldCBsb2cxMCB2YWx1ZSB0byBzdXBwcmVzcyBwb3RlbnRpYWwgb3V0bGllcgogICMjIEVkZ2VSIG5lZWRzIG5vbi1ub3JtYWxpemVkIGNvdW50cwogIHkgPC0gREdFTGlzdChjb3VudHM9YXMubWF0cml4KGNvdW50cy5jb24pKQogIHkgPC0gY2FsY05vcm1GYWN0b3JzKHkpCiAgaWYgKGxlbmd0aChsZXZlbHMoY291bnRzLmNvbi5iYXRjaCkpID4gMSkgewogICAgZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+Y291bnRzLmNvbi5iYXRjaCtjb3VudHMuY29uLnNleCtjb3VudHMuY29uLmNvbnRhbWluKSAjIyBhZGQgYmF0Y2ggYXMgY29mYWN0b3IKICAgIHkgPC0gZXN0aW1hdGVEaXNwKHksZGVzaWduKQogICAgZml0IDwtIGdsbVFMRml0KHksZGVzaWduKQogICAgcWxmIDwtIGdsbVFMRlRlc3QoZml0LCBjb2VmPTQpfSBlbHNlIHsKICAgICAgZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+Y291bnRzLmNvbi5zZXgrY291bnRzLmNvbi5jb250YW1pbikgIyMgYWRkIGJhdGNoIGFzIGNvZmFjdG9yCiAgICAgIHkgPC0gZXN0aW1hdGVEaXNwKHksZGVzaWduKQogICAgICBmaXQgPC0gZ2xtUUxGaXQoeSxkZXNpZ24pIAogICAgICBxbGYgPC0gZ2xtUUxGVGVzdChmaXQsIGNvZWY9MykKICAgIH0KICBERUdzLlEgPC0gc3VtKGRlY2lkZVRlc3RzKHFsZikgIT0gMCkKICBwcmludChERUdzLlEpCiAgbWF0Y2gyIDwtIHJvdy5uYW1lcyh0b3BUYWdzKHFsZiwgbj0xMDAwKSkKICBtYXRjaDIubG9nRkMgPC0gdG9wVGFncyhxbGYsIG49MTAwMClbWzFdXVssImxvZ0ZDIl0KICBtYXRjaDIudGFibGUgPC0gdG9wVGFncyhxbGYsIG49MjAwMCkKICBtYXRjaDIudGFibGUuYWxsIDwtIHRvcFRhZ3MocWxmLCBuPW5yb3cocWxmKSkKICBtYXRjaDIuREVHcy50YWJsZSA8LSB0b3BUYWdzKHFsZiwgbj1ERUdzLlEpCiAgYXNzaWduKHBhc3RlMCgiREVHcy50YWJsZV9ieV9HZW9NZWFuTmVzdC4iLENvbixDb25faW5kKSxtYXRjaDIuREVHcy50YWJsZSwgZW52aXIgPSBwYXJlbnQuZnJhbWUoKSkKICBhc3NpZ24ocGFzdGUwKCJ0b3AxMDAwREVHc19ieV9HZW9NZWFuTmVzdC4iLENvbixDb25faW5kKSxtYXRjaDIsIGVudmlyID0gcGFyZW50LmZyYW1lKCkpCiAgYXNzaWduKHBhc3RlMCgidG9wMTAwMERFR3NfYnlfR2VvTWVhbk5lc3QuZGlyZWN0aW9uIixDb24sQ29uX2luZCksbWF0Y2gyLmxvZ0ZDLCBlbnZpciA9IHBhcmVudC5mcmFtZSgpKQogIGFzc2lnbihwYXN0ZTAoIkRFR3MudG9wMjAwMC50YWJsZV9ieV9HZW9NZWFuTmVzdC4iLENvbixDb25faW5kKSxtYXRjaDIudGFibGUsIGVudmlyID0gcGFyZW50LmZyYW1lKCkpCiAgYXNzaWduKHBhc3RlMCgiREVHcy5hbGxHZW5lLnRhYmxlX2J5X0dlb01lYW5OZXN0LiIsQ29uX2luZCksbWF0Y2gyLnRhYmxlLmFsbCwgZW52aXIgPSBwYXJlbnQuZnJhbWUoKSkKfSAjIGdldCBERUdzIChwIDwgMC4wNSkgYW5kIHNlbGVjdCB0b3AgMTAwMCBtb3N0IGRpZmZlcmVudGlhdGluZyBnZW5lcyB1c2luZyBFZGdlUiBhZ2FpbnN0IFBDQnMgdGlzc3VlIGNvbmNlbnRyYXRpb25zCgphc3Nlc3NtYXJnaW4gPC0gZnVuY3Rpb24oYWNjdXJhY3kuUS5hY2N1bSl7CiAgcXQoMC45NzUsZGY9bGVuZ3RoKGFjY3VyYWN5LlEuYWNjdW0pLTEpKnNkKGFjY3VyYWN5LlEuYWNjdW0pL3NxcnQobGVuZ3RoKGFjY3VyYWN5LlEuYWNjdW0pKQp9ICMgZGV0ZXJtaW5lIG1hcmdpbgoKbGFzc28uYnkubmVzdC5vbmVzaXRlb3V0LnRlc3QgPC0gZnVuY3Rpb24oQ29uLENvbl9pbmQsaSkgewogIAogIG5lc3RpZCA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyICU+JSBmaWx0ZXIoS2V5ID09IENvbl9pbmQpICU+JSBkcGx5cjo6cHVsbChNZXJnZUlEKQogIG1hdGNoMSA8LSBjb2xuYW1lcyhjb3VudHMudHN3YWxsb3cubm9ybSkgJWluJSBuZXN0aWQKICBjb3VudHMuY29uICA8LSAgY291bnRzLnRzd2FsbG93Lm5vcm1bLG1hdGNoMV0KICBjb3VudHMuY29uLmJhdGNoIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoZGRzLmJvdGhTZXguYWRqdXN0ZWQkYmF0Y2gyKVttYXRjaDFdKQogIGNvdW50cy5jb24uc2V4IDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoZGRzLmJvdGhTZXguYWRqdXN0ZWQkc2V4KVttYXRjaDFdKQogIGNvbnRhbWluLnJhdyA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyICU+JSBmaWx0ZXIoS2V5ID09IENvbl9pbmQpICU+JSBkcGx5cjo6cHVsbCh2YWx1ZS5hZGp1c3QpICU+JSBsb2cxMCgpCiAgY29udGFtaW4ubWF0cml4IDwtIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIgJT4lIGRwbHlyOjpzbGljZShtYXRjaChjb2xuYW1lcyhjb3VudHMuY29uKSwgY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiRNZXJnZUlEKSkgJT4lIGZpbHRlcihLZXkgPT0gQ29uX2luZCkKICBjb3VudHMuY29uLmNvbnRhbWluIDwtIGxvZzEwKGNvbnRhbWluLm1hdHJpeCR2YWx1ZS5hZGp1c3QpICMjIGdldCBsb2cxMCB2YWx1ZSB0byBzdXBwcmVzcyBwb3RlbnRpYWwgb3V0bGllcgogICMjIEJ1aWxkaW5nIG1hdHJpeCAKICBpZiAobGVuZ3RoKGxldmVscyhjb3VudHMuY29uLmJhdGNoKSkgPiAxKSB7CiAgICBjb3VudHMuY29uLnQgPC0gYXMuZGF0YS5mcmFtZSh0KGNvdW50cy5jb25bZ2V0KHBhc3RlMCgidG9wMTAwMERFR3NfYnlfR2VvTWVhbk5lc3QuIixDb24sQ29uX2luZCkpLF0pKQogICAgY291bnRzLmNvbi50IDwtIGNiaW5kKGNvdW50cy5jb24udCwgYmF0Y2ggPSBjb3VudHMuY29uLmJhdGNoLCBzZXggPSBjb3VudHMuY29uLnNleCwgY29udGFtaW5hbnQgPSBjb3VudHMuY29uLmNvbnRhbWluKQogICAgbSA8LSBtb2RlbC5tYXRyaXgoY29udGFtaW5hbnQgfiBiYXRjaCArLiwgY291bnRzLmNvbi50KQogICAgbSA8LSBtWywtMV0gfSBlbHNlIHsKICAgICAgY291bnRzLmNvbi50IDwtIGFzLmRhdGEuZnJhbWUodChjb3VudHMuY29uW2dldChwYXN0ZTAoInRvcDEwMDBERUdzX2J5X0dlb01lYW5OZXN0LiIsQ29uLENvbl9pbmQpKSxdKSkKICAgICAgY291bnRzLmNvbi50IDwtIGNiaW5kKGNvdW50cy5jb24udCwgc2V4ID0gY291bnRzLmNvbi5zZXgsIGNvbnRhbWluYW50ID0gY291bnRzLmNvbi5jb250YW1pbikKICAgICAgbSA8LSBtb2RlbC5tYXRyaXgoY29udGFtaW5hbnQgfiAuLCBjb3VudHMuY29uLnQpCiAgICAgIG0gPC0gbVssLTFdIH0gIAogICMjIG1ha2Ugc3VyZSBtb3N0IG9mIHNpdGVzIGhhdmUgdGVzdGluZyBzYW1wbGVzLCBhbGxzaXRlOiAjIG9mIGFsbCBnZW5vbWljIGluZGl2aWR1YWxzLCBuZXN0c2l0ZTogIyBvZiBnZW5vbWljIGluZGl2aWR1YWxzIHdpdGggbWF0Y2hpbmcgbmVzdCBjaGVtaWNhbCBpbmZvCiAgY291bnRzLnNpdGUgIDwtICBkZHMuYm90aFNleC5hZGp1c3RlZCRwcm9wZXJzaXRlMlttYXRjaDFdCiAgbWF0Y2hzdWJzZXQudHJhaW4gPC0gY291bnRzLnNpdGUgIT0gdW5pcXVlKGNvdW50cy5zaXRlKVtpXSAjIHRyYWluIHN1YnNldAogIHByaW50KGMoInRyYWluaW5nICMiLCBzdW0obWF0Y2hzdWJzZXQudHJhaW4pKSkKICBtYXRjaHN1YnNldC50ZXN0IDwtIGNvdW50cy5zaXRlICE9IHVuaXF1ZShjb3VudHMuc2l0ZSlbaV0KICBsLmxzZSA8LSB7fQogIGZvciAoaiBpbiAxOjIwKSB7CiAgICBjdmZpdCA8LSBjdi5nbG1uZXQoeD1tW21hdGNoc3Vic2V0LnRyYWluLF0sIHk9Y291bnRzLmNvbi50JGNvbnRhbWluYW50W21hdGNoc3Vic2V0LnRyYWluXSwgbmZvbGQgPSAxMCwgYWxwaGEgPSAxLCBsYW1iZGEgPSAxMF5zZXEoMCwtMixsZW5ndGg9MjAwKSwgcmVsYXggPUZBTFNFKQogICAgbC5sc2UgPSBjKGwubHNlLCBjdmZpdCRsYW1iZGEuMXNlKQogIH0KICBsLmxzZSA9IG1lZGlhbihsLmxzZSkKICBjdmZpdDEgPC0gY3YuZ2xtbmV0KHg9bVttYXRjaHN1YnNldC50cmFpbixdLCB5PWNvdW50cy5jb24udCRjb250YW1pbmFudFttYXRjaHN1YnNldC50cmFpbl0sIG5mb2xkID0gMTAsIGFscGhhID0gMSwgbGFtYmRhID0gMTBec2VxKDAsLTIsbGVuZ3RoPTYwMCksIHJlbGF4ID1GQUxTRSkKICAjIyBvbmx5IHBsb3QgaXQgMyB0aW1lcyBpbiB0ZXN0IHJ1bnMKICBwZGYocGFzdGUwKCJ+L0RvY3VtZW50cy93b3JrL1Rzd2FsbG93X2NoZW1fR0xSSV91cGRhdGUvVGVtcF9wbG90cy9CeU5lc3RfIixDb25faW5kLCAiX2Rpc3RyaWJ1dGlvbiIsIi5wZGYiKSkKICBwbG90KHNvcnQoY291bnRzLmNvbi5jb250YW1pbiksbWFpbiA9IHBhc3RlMCgiQnlOZXN0LiIsQ29uX2luZCwgIi5kaXN0cmlidXRpb24iKSwgeWxhYiA9ICJsb2cxMCh2YWx1ZSkiKQogIGRldi5vZmYoKQogIAogIHBkZihwYXN0ZTAoIn4vRG9jdW1lbnRzL3dvcmsvVHN3YWxsb3dfY2hlbV9HTFJJX3VwZGF0ZS9UZW1wX3Bsb3RzL2xhbWRhUGxvdF9ieU5lc3Rfb25lc2l0ZW91dCIsQ29uX2luZCwgIi5wZGYiKSkKICBwbG90KGN2Zml0MSxtYWluID0gcGFzdGUwKCJSX2xhbWRhUGxvdF9sZWF2ZW9uZXNpdGVvdXQuYnlOZXN0LiIsQ29uX2luZCkpCiAgCiAgZGV2Lm9mZigpCiAgfSAjIHRlc3QgUnVuIAoKIyMgUnVuIGxlYXZlLW9uZS1vdXQgKGJ5IHNpdGUpIGNyb3NzIHZhbGlkYXRpb24gYW5kIHRyYWluIGxhc3NvIHJlZ3Jlc3Npb24gYWdhaW5zdCBQQ0JzIHRpc3N1ZSBjb25jZW50cmF0aW9ucyAKbGFzc28uYnkubmVzdC5vbmVzaXRlb3V0IDwtIGZ1bmN0aW9uKENvbixDb25faW5kLCBzLCBsYW0ubSxpKSB7CiAgbmVzdGlkIDwtIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIgJT4lIGZpbHRlcihLZXkgPT0gQ29uX2luZCkgJT4lIGRwbHlyOjpwdWxsKE1lcmdlSUQpCiAgbWF0Y2gxIDwtIGNvbG5hbWVzKGNvdW50cy50c3dhbGxvdy5ub3JtKSAlaW4lIG5lc3RpZAogIGNvdW50cy5jb24gIDwtICBjb3VudHMudHN3YWxsb3cubm9ybVssbWF0Y2gxXQogIGNvdW50cy5jb24uYmF0Y2ggPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihkZHMuYm90aFNleC5hZGp1c3RlZCRiYXRjaDIpW21hdGNoMV0pCiAgY291bnRzLmNvbi5zZXggPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihkZHMuYm90aFNleC5hZGp1c3RlZCRzZXgpW21hdGNoMV0pCiAgY29udGFtaW4ucmF3IDwtIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIgJT4lIGZpbHRlcihLZXkgPT0gQ29uX2luZCkgJT4lIGRwbHlyOjpwdWxsKHZhbHVlLmFkanVzdCkgJT4lIGxvZzEwKCkKICBjb250YW1pbi5tYXRyaXggPC0gY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiAlPiUgZHBseXI6OnNsaWNlKG1hdGNoKGNvbG5hbWVzKGNvdW50cy5jb24pLCBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyJE1lcmdlSUQpKSAlPiUgZmlsdGVyKEtleSA9PSBDb25faW5kKQogIGNvdW50cy5jb24uY29udGFtaW4gPC0gbG9nMTAoY29udGFtaW4ubWF0cml4JHZhbHVlLmFkanVzdCkgIyMgZ2V0IGxvZzEwIHZhbHVlIHRvIHN1cHByZXNzIHBvdGVudGlhbCBvdXRsaWVyCiAgIyMgQnVpbGRpbmcgbWF0cml4IAogIGlmIChsZW5ndGgobGV2ZWxzKGNvdW50cy5jb24uYmF0Y2gpKSA+IDEpIHsKICAgIGNvdW50cy5jb24udCA8LSBhcy5kYXRhLmZyYW1lKHQoY291bnRzLmNvbltnZXQocGFzdGUwKCJ0b3AxMDAwREVHc19ieV9HZW9NZWFuTmVzdC4iLENvbixDb25faW5kKSksXSkpCiAgICBjb3VudHMuY29uLnQgPC0gY2JpbmQoY291bnRzLmNvbi50LCBiYXRjaCA9IGNvdW50cy5jb24uYmF0Y2gsIHNleCA9IGNvdW50cy5jb24uc2V4LCBjb250YW1pbmFudCA9IGNvdW50cy5jb24uY29udGFtaW4pCiAgICBtIDwtIG1vZGVsLm1hdHJpeChjb250YW1pbmFudCB+IGJhdGNoICsuLCBjb3VudHMuY29uLnQpCiAgICBtIDwtIG1bLC0xXSB9IGVsc2UgewogICAgICBjb3VudHMuY29uLnQgPC0gYXMuZGF0YS5mcmFtZSh0KGNvdW50cy5jb25bZ2V0KHBhc3RlMCgidG9wMTAwMERFR3NfYnlfR2VvTWVhbk5lc3QuIixDb24sQ29uX2luZCkpLF0pKQogICAgICBjb3VudHMuY29uLnQgPC0gY2JpbmQoY291bnRzLmNvbi50LCBzZXggPSBjb3VudHMuY29uLnNleCwgY29udGFtaW5hbnQgPSBjb3VudHMuY29uLmNvbnRhbWluKQogICAgICBtIDwtIG1vZGVsLm1hdHJpeChjb250YW1pbmFudCB+IC4sIGNvdW50cy5jb24udCkKICAgICAgbSA8LSBtWywtMV0gfSAgCiAgIyMgbWFrZSBzdXJlIG1vc3Qgb2Ygc2l0ZXMgaGF2ZSB0ZXN0aW5nIHNhbXBsZXMsIGFsbHNpdGU6ICMgb2YgYWxsIGdlbm9taWMgaW5kaXZpZHVhbHMsIG5lc3RzaXRlOiAjIG9mIGdlbm9taWMgaW5kaXZpZHVhbHMgd2l0aCBtYXRjaGluZyBuZXN0IGNoZW1pY2FsIGluZm8KICBjb3VudHMuc2l0ZSAgPC0gIGRkcy5ib3RoU2V4LmFkanVzdGVkJHByb3BlcnNpdGUyW21hdGNoMV0KICBtYXRjaHN1YnNldC50cmFpbiA8LSBjb3VudHMuc2l0ZSAhPSB1bmlxdWUoY291bnRzLnNpdGUpW2ldICMgdHJhaW4gc3Vic2V0CiAgIyMgc2FtcGxlIDkwJSAKICBtYXRjaHN1YnNldC50cmFpbiA8LSBzYW1wbGUod2hpY2gobWF0Y2hzdWJzZXQudHJhaW4pLChzdW0obWF0Y2hzdWJzZXQudHJhaW4pKjAuOSkpCiAgcHJpbnQoYygidHJhaW5pbmcgIyIsIGxlbmd0aChtYXRjaHN1YnNldC50cmFpbikpKQogIG1hdGNoc3Vic2V0LnRlc3QgPC0gY291bnRzLnNpdGUgPT0gdW5pcXVlKGNvdW50cy5zaXRlKVtpXQogIGwuMXNlIDwtIHt9CiAgbC5taW4gPC0ge30KICBmb3IgKGogaW4gMToyMCkgewogICAgY3ZmaXQgPC0gY3YuZ2xtbmV0KHg9bVttYXRjaHN1YnNldC50cmFpbixdLCB5PWNvdW50cy5jb24udCRjb250YW1pbmFudFttYXRjaHN1YnNldC50cmFpbl0sIG5mb2xkID0gMTAsIGFscGhhID0gMSwgbGFtYmRhID0gMTBec2VxKDAsLTIsbGVuZ3RoPTIwMCksIHJlbGF4ID1GQUxTRSkKICAgIGwuMXNlIDwtIGMobC4xc2UsIGN2Zml0JGxhbWJkYS4xc2UpCiAgICBsLm1pbiA8LSBjKGwubWluLCBjdmZpdCRsYW1iZGEubWluKQogIH0KICBsLjFzZSA9IG1lZGlhbihsLjFzZSkKICBsLm1pbiA8LSBtZWRpYW4obC5taW4pCiAgbC5tZWRpYW4gPC0gZXhwKG1lYW4oYyhsb2cobC5taW4pLGxvZyhsLjFzZSkpKSkKICBsYW1kYS5zZXF1ZW5jZSA8LSBjKGwuMXNlLGwubWluLGwubWVkaWFuKQogIEJ5TmVzdFJlc3VsdC53aXRobGFtZGFQIDwtIHt9CiAgCiAgZml0IDwtIGdsbW5ldCh4PW1bbWF0Y2hzdWJzZXQudHJhaW4sXSwgeT1jb3VudHMuY29uLnQkY29udGFtaW5hbnRbbWF0Y2hzdWJzZXQudHJhaW5dLCBhbHBoYSA9IDEsIGxhbWJkYSA9IDEwXnNlcSgwLGxhbS5tLGxlbmd0aD02MDApLCByZWxheCA9IEZBTFNFKQogIFJlc3VsdCA8LSBhc3Nlc3MuZ2xtbmV0KGZpdCwgbmV3eCA9IG1bbWF0Y2hzdWJzZXQudHJhaW4sXSwgbmV3eSA9IGNvdW50cy5jb24udCRjb250YW1pbmFudFttYXRjaHN1YnNldC50cmFpbl0sIHMgPSBsYW1kYS5zZXF1ZW5jZVtzXSkKICAKICBSZXN1bHQubGFzc28uYXNzZXNzbWVudCA8LSB7fQogIFJlc3VsdC5sYXNzby5hc3Nlc3NtZW50JG1zZSA8LSBSZXN1bHQkbXNlW1sxXV0KICBSZXN1bHQubGFzc28uYXNzZXNzbWVudCRtYWUgPC0gUmVzdWx0JG1hZVtbMV1dCiAgCiAgdmFyaWFibGVzIDwtIGNvZWYoZml0LCBzID0gbGFtZGEuc2VxdWVuY2Vbc10pCiAgUmVzdWx0Lmxhc3NvLmFzc2Vzc21lbnQkdmFyaWFibGVzIDwtIHJvdy5uYW1lcyh2YXJpYWJsZXMpWyEodmFyaWFibGVzWywxXSA9PSAwKV1bLTFdIAogIHBzZXVkb19SMiA8LSBmaXQkZGV2LnJhdGlvW3doaWNoKHNvcnQoYyhsYW1kYS5zZXF1ZW5jZVtzXSwxMF5zZXEoMCxsYW0ubSxsZW5ndGg9NjAwKSksZGVjcmVhc2luZyA9IFRSVUUpID09IGxhbWRhLnNlcXVlbmNlW3NdKVsxXSAtMV0KICBSZXN1bHQubGFzc28uYXNzZXNzbWVudCRwc2V1ZG9fUjIgPC0gcHNldWRvX1IyCiAgCiAgIyMgQnVpbGRpbmcgdGVzdCBtYXRyaXggCiAgcHJlZGljdC5zaXRlLnRlc3QgPC0gcHJlZGljdChmaXQsIG5ld3ggPSBtW21hdGNoc3Vic2V0LnRlc3QsXSwgcyA9IGxhbWRhLnNlcXVlbmNlW3NdKSAKICByZXN1bHQubGlzdCA9IGxpc3QobGFtZGEuc2VxdWVuY2UsUmVzdWx0Lmxhc3NvLmFzc2Vzc21lbnQscHJlZGljdC5zaXRlLnRlc3QpCiAgbmFtZXMocmVzdWx0Lmxpc3QpIDwtIGMoImxhbWRhLnNlcXVlbmNlIiwiUmVzdWx0LnJlbGF4Lmxhc3NvLmFzc2Vzc21lbnQiLCJwcmVkaWN0aW9ucyIpCiAgcmV0dXJuKHJlc3VsdC5saXN0KQp9IApSdW5fbGFzc28ub25lc2l0ZW91dCA8LSBmdW5jdGlvbihuLCBDb24sIENvbl9pbmQsIHMsIGxhbS5tKSB7CiAgICBsYXNzb05lc3RSZXN1bHQucnVuIDwtIGZvcmVhY2goaSA9IHNhbXBsZShyZXAoMTozMSwzMDApLG4pLCAucGFja2FnZXMgPSBjKCJkcGx5ciIsImdsbW5ldCIpLCAuZXhwb3J0ID0gYygibGFzc28uYnkubmVzdC5vbmVzaXRlb3V0IiwiY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiIsImNvdW50cy50c3dhbGxvdy5ub3JtIiwiZGRzLmJvdGhTZXguYWRqdXN0ZWQiLCBwYXN0ZTAoInRvcDEwMDBERUdzX2J5X0dlb01lYW5OZXN0LiIsQ29uLENvbl9pbmQpLCAiZGF0YS5jb250YW1pbmFudHMubWFqb3JTdWJzZXQuZ2VvLmJ5c2l0ZSIpKSAlZG9wYXIlIHsKICAgIHJfbGFzc29fcmVzdWx0IDwtIFZlY3Rvcml6ZShsYXNzby5ieS5uZXN0Lm9uZXNpdGVvdXQpKENvbixDb25faW5kLCBzLCBsYW0ubSxpKQogICAgcmV0dXJuKHJfbGFzc29fcmVzdWx0KQogIH0KICBhc3NpZ24ocGFzdGUwKCJsYXNzb1Jlc3VsdC5vbmVzaXRlb3V0IixzdWIoIiAiLCIiLENvbl9pbmQpKSwgbGFzc29OZXN0UmVzdWx0LnJ1biwgZW52aXIgPSBwYXJlbnQuZnJhbWUoKSkKICByZXR1cm4obGFzc29OZXN0UmVzdWx0LnJ1bikKfSAjIDMxIHNpdGVzLCBzYW1wbGUgbiBvdXQgb2YgIDMxKjMwMCByZS1zYW1wbGluZyAKYGBgCgojIyMjIExhc3NvIHJlZ3Jlc3Npb24gYW5hbHlzaXMgYmV0d2VlbiBnbG9iYWwgZ2VlbmUgZXhwcmVzc2lvbiBhbmQgUENCIHRpc3N1ZSBjb25jZW50cmF0aW9ucwpSdW4gbGFzc28gd2l0aCBsZWF2ZSBvbmUgKHNpdGUpIGNyb3NzIHZhbGlkYXRpb24gYW5kIGJ5IHNlbGVjdGluZyAxMDAwIGluZGl2aWR1YWxzIGZyb20gKDMxKjMwMCByZXNhbXBsaW5nID0gOTMwMCksIHVzaW5nIDEuMXNlICAgCgp0b3BnZW5lbGlzdFBDQnMucmRzCjogc2VsZWN0aW5nIDkxIGdlbmVzIHdoaWNoIGFwcGVhcmVkIG1vcmUgdGhhbiA1MCB0aW1lcyAoPiA1JSkgaW4gdGhlIGNyb3NzLXZhbGlkYXRpb24KCmBgYHtyIFRyYWluIFBDQiBsYXNzbyBtb2RlbCBhbmQgY3Jvc3MtdmFsaWRhaXRvbiwgZXZhbCA9IEZBTFNFfQojIyBUb3RhbCBQQ0JzCkNvbj0iUENCc19kYXRhIjtDb25faW5kPSJUT1RBTCBQQ0JzIgojIyByZWZyZXNoIGJldHdlZW4gY2hlbWljYWxzIApsYW1kYS5zZXF1ZW5jZSA8LSB7fQpSZXN1bHQubGFzc28uYXNzZXNzbWVudCA8LSB7fQp2YXJpYWJsZXMgPC0ge30KcHJlZGljdC5zaXRlLnRlc3QgPC0ge30Kcmxhc3NvUmVzdWx0IDwtIGxpc3QoKQojIyBTZXQgdXAgUENCcyBjaGVtaXN0cnkgZGF0YSAKY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiA8LWNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldAojIyBJbmRpdmlkdWFsIGNoZW1pY2FscyAKY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyICU+JSBmaWx0ZXIoS2V5ID09IENvbl9pbmQpCmNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIgPC0gY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiAlPiUgZmlsdGVyKCFpcy5uYSh2YWx1ZS5hZGp1c3QpKSAjIyByZW1vdmUgdGhvc2Ugd2l0aCB2YWx1ZSA9PSAiTlIiCiMjIGNvbWJpbmUgYWxsIGR1cGxpY2F0ZWQgaXRlbXMgdXNpbmcgbWVhbiB2YWx1ZSBiZWNhdXNlIHRoZXkgaGF2ZSB0aGUgc2FtZSBuZXN0IGlkIApjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyIDwtIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIgJT4lIGdyb3VwX2J5KE1lcmdlSUQsU3BlY2llcyxNYXRyaXgsQU9DLFByb3Blcl9TaXRlLEtleSx0eXBlLGNsYXNzLE1lcmdlU2l0ZSxwcm9wZXJfc2l0ZTIpICU+JSBzdW1tYXJpc2UodmFsdWUuYWRqdXN0MiA9IG1lYW4odmFsdWUuYWRqdXN0KSkgJT4lIHVuZ3JvdXAoKQpjb2xuYW1lcyhjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyKVtjb2xuYW1lcyhjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyKSA9PSAidmFsdWUuYWRqdXN0MiJdIDwtICJ2YWx1ZS5hZGp1c3QiICMjIGNoYW5nZSB0aGUgbmFtZSBiYWNrCnByaW50KHN1bShkdXBsaWNhdGVkKGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIkTWVyZ2VJRCkpKSAjIyBjaGVjayBkdXBsaWNhdGlvbiBhZ2FpbgpUb3RhbFBDQnMubWVyZ2VJRGxpc3QgPSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyJE1lcmdlSUQgIAojIyBHZXQgdG9wIERFR3MgdXNpbmcgREVTZXEyIGFnYWluc3QgUENCcyBncmFkaWVudCAgCkdldFRvcERFR3NfYnlHZW9NZWFuLmJ5TmVzdChDb249Q29uLENvbl9pbmQ9Q29uX2luZCkgIzEwMyBERUdzIChwIDwgMC4wNSkKIyBzYXZlUkRTKGBERUdzLmFsbEdlbmUudGFibGVfYnlfR2VvTWVhbk5lc3QuVE9UQUwgUENCc2AsICJ+L0RvY3VtZW50cy93b3JrL1Rzd2FsbG93X2NoZW1fR0xSSV91cGRhdGUvR0xSSV9NU19maWd1cmVzL1RPUC5QQ0JzLmFsbGdlbmUudGFibGUuYnluZXN0LnJkcyIpCiMjIFRyYWluIGxhc3NvIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgZ2xtbmV0OyByZWxheCBsYXNzbyB3YXMgbm90IGluY2x1ZGVkIGJlY2F1c2UgdGhlcmUgd2FzIG5vIHNpZ25pZmljYW50IGltcHJvdmVtZW50ICAKCiMjIFJlZ2lzdGVyIHBhcmFsbGVsIApjbCA8LSBwYXJhbGxlbDo6bWFrZUNsdXN0ZXIoMTIpCmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkKbGFzc29SZXN1bHQudG90YWxQQ0JzID0gUnVuX2xhc3NvLm9uZXNpdGVvdXQobj0gMTAwMCwgQ29uPUNvbixDb25faW5kPUNvbl9pbmQsIHM9IDEsIGxhbS5tPS0yKSAjIGxhbWRhLnNlcXVlbmNlIDwtIGMobC4xc2UsbC5taW4sbC5tZWRpYW4pOyBydW4gbGFzc28gd2l0aCBsZWF2ZSBvbmUgKHNpdGUpIGNyb3NzIHZhbGlkYXRpb24gYW5kIGJ5IHNlbGVjdGluZyAxMDAwIGluZGl2aWR1YWxzIGZyb20gKDMxKjMwMCByZXNhbXBsaW5nID0gOTMwMCksIHVzaW5nIDEuMXNlIApwYXJhbGxlbDo6c3RvcENsdXN0ZXIoY2wpICMgc3RvcCBwYXJhbGxlbAojIyBzZWxlY3RpbmcgdGhvc2UgZ2VuZXMgd2hpY2ggd2VyZSBzZWxlY3RlZCBtb3JlIHRoYW4gNTAgdGltZXMgKD4gNSUpCnRlc3QuZ2VuZWxpc3QudG9wUENCIDwtIG5hbWVzKHRhYmxlKHVubGlzdChzYXBwbHkoMToxMDAwLCBmdW5jdGlvbih4KSBsYXNzb1Jlc3VsdC50b3RhbFBDQnNbW3hdXVtbMl1dJHZhcmlhYmxlcykpKSlbdGFibGUodW5saXN0KHNhcHBseSgxOjEwMDAsIGZ1bmN0aW9uKHgpIGxhc3NvUmVzdWx0LnRvdGFsUENCc1tbeF1dW1syXV0kdmFyaWFibGVzKSkpID4gNTBdCiMjIHNhdmUgdG9wIGdlbmUgCnNhdmVSRFModGVzdC5nZW5lbGlzdC50b3BQQ0IsIGZpbGUgPSJ0b3BnZW5lbGlzdFBDQnMucmRzIikgIyA5MSBnZW5lcyAgCgpgYGAKCiMjIDIuIEZvciBzZWxlY3RpbmcgdG9wIHByZWRpY3RvciBnZW5lcyB0byBwcmVkY2l0IFBBSHMgKGJ5IHBvb2xlZCBzdG9tYWNoIGNvbnRlbnRzIGVhY2ggc2l0ZSkgIAoKIyMjIFByb2Nlc3MgY29sZGF0YSBieSBzaXRlIApgYGB7ciBQcm9jZXNzIGNoZW1pc3RyeSBieSBzaXRlLCBldmFsID0gRkFMU0V9CmRhdGEuY29udGFtaW5hbnRzLm1ham9yU3Vic2V0Lmdlby5ieXNpdGUkTWVyZ2VTaXRlW2RhdGEuY29udGFtaW5hbnRzLm1ham9yU3Vic2V0Lmdlby5ieXNpdGUkTWVyZ2VTaXRlID09ICJyZWYiXSA8LSAiU0wiICMjIGNvbnZlcnQgcmVmIHRvIFNMIAojIyBjb21iaW5lIGFsbCBkdXBsaWNhdGVkIGl0ZW1zIGJ5IE1lcmdlU2l0ZQpkYXRhLmNvbnRhbWluYW50cy5tYWpvclN1YnNldC5nZW8uYnlzaXRlIDwtIGRhdGEuY29udGFtaW5hbnRzLm1ham9yU3Vic2V0Lmdlby5ieXNpdGUgJT4lIGdyb3VwX2J5KHByb3Blcl9zaXRlMixNZXJnZVNpdGUsS2V5KSAlPiUgc3VtbWFyaXNlKHZhbHVlLmdlb01lYW4yID0gbWVhbih2YWx1ZS5nZW9NZWFuKSkgJT4lIHVuZ3JvdXAoKQpjb2xuYW1lcyhkYXRhLmNvbnRhbWluYW50cy5tYWpvclN1YnNldC5nZW8uYnlzaXRlKVtjb2xuYW1lcyhkYXRhLmNvbnRhbWluYW50cy5tYWpvclN1YnNldC5nZW8uYnlzaXRlKSA9PSAidmFsdWUuZ2VvTWVhbjIiXSA8LSAidmFsdWUuZ2VvTWVhbiIgIyMgY2hhbmdlIHRoZSBuYW1lIGJhY2sKYGBgCgojIyMgTG9hZCBzZWxmLWRlZmluZWQgZnVuY3Rpb25zIGZvciBsYXNzbyByZWdyZXNzaW9uIHRyYWluaW5nCkdldFRvcERFR3NfYnlHZW9NZWFuLmJ5U2l0ZUdlb01lYW4oKQo6IGdldCB0b3AgREVHcyB1c2luZyBlZGdlUiBsaW5lYXIgcmVncmVzc2lvbiBhZ2FpbnN0IFBBSHMgY29uY2VudHJhdGlvbnMgIAoKbGFzc28uYnkuc2l0ZSgpCjogdXNpbmcgdG9wMTAwMCBERUdzIGZvciBsYXNzbyByZWdyZXNzaW9uIGFuYWx5c2lzIHdpdGggMTAtZm9sZCB2YWxpZGF0aW9uIHRvIGRldGVybWluZSBsYW1kYSwgbGVhdmUgb25lIChzaXRlKSBvdXQgY3Jvc3MtdmFsaWRhdGlvbiwgcmVzYW1wbGUgKHNhbXBsZSA5MCUpIG9mIHRyYWluaW5nIGRhdGEgIAoKUnVuX2xhc3NvX2J5X3NpdGUKOiBSdW4gbiBydW5zIG9mIGxhc3NvLmJ5LnNpdGUoKSAgCgoKYGBge3Igc2VsZi1kZWZpbmVkIGZ1bmN0aW9ucyBmb3IgUEFIcyBsYXNzbyByZWdyZXNzaW9uIGFuYWx5c2lzLCBldmFsID0gRkFMU0V9CkdldFRvcERFR3NfYnlHZW9NZWFuLmJ5U2l0ZUdlb01lYW4gPC0gZnVuY3Rpb24oQ29uLENvbl9pbmQpIHsKICAKICBtYXRjaDEgPC0gc3RyX3N1Yihjb2xuYW1lcyhjb3VudHMudHN3YWxsb3cpLDMsNCkgJWluJSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyJE1lcmdlU2l0ZQogIGNvdW50cy5jb24gIDwtICBjb3VudHMudHN3YWxsb3dbLG1hdGNoMV0KICBjb3VudHMuY29uLmJhdGNoIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoZGRzLmJvdGhTZXguYWRqdXN0ZWQkYmF0Y2gyKVttYXRjaDFdKQogIGNvdW50cy5jb24uc2V4IDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoZGRzLmJvdGhTZXguYWRqdXN0ZWQkc2V4KVttYXRjaDFdKQogIGNvdW50cy5jb24uY29udGFtaW4gPC0gbG9nMTAoY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiR2YWx1ZS5nZW9NZWFuW21hdGNoKHN0cl9zdWIoY29sbmFtZXMoY291bnRzLmNvbiksMyw0KSwgY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiRNZXJnZVNpdGUpXSkKICAKICAjIyBFZGdlUiBuZWVkcyBub24tbm9ybWFsaXplZCBjb3VudHMKICB5IDwtIERHRUxpc3QoY291bnRzPWFzLm1hdHJpeChjb3VudHMuY29uKSkKICB5IDwtIGNhbGNOb3JtRmFjdG9ycyh5KQogIGlmIChsZW5ndGgobGV2ZWxzKGNvdW50cy5jb24uYmF0Y2gpKSA+IDEpIHsKICAgIGRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmNvdW50cy5jb24uYmF0Y2grY291bnRzLmNvbi5zZXgrY291bnRzLmNvbi5jb250YW1pbikgIyMgYWRkIGJhdGNoIGFzIGNvZmFjdG9yCiAgICB5IDwtIGVzdGltYXRlRGlzcCh5LGRlc2lnbikKICAgIGZpdCA8LSBnbG1RTEZpdCh5LGRlc2lnbikKICAgIHFsZiA8LSBnbG1RTEZUZXN0KGZpdCwgY29lZj00KX0gZWxzZSB7CiAgICAgIGRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmNvdW50cy5jb24uc2V4K2NvdW50cy5jb24uY29udGFtaW4pICMjIGFkZCBiYXRjaCBhcyBjb2ZhY3RvcgogICAgICB5IDwtIGVzdGltYXRlRGlzcCh5LGRlc2lnbikKICAgICAgZml0IDwtIGdsbVFMRml0KHksZGVzaWduKSAKICAgICAgcWxmIDwtIGdsbVFMRlRlc3QoZml0LCBjb2VmPTMpCiAgICB9CiAgREVHcy5RIDwtIHN1bShkZWNpZGVUZXN0cyhxbGYpICE9IDApCiAgcHJpbnQoREVHcy5RKQogIG1hdGNoMiA8LSByb3cubmFtZXModG9wVGFncyhxbGYsIG49MTAwMCkpCiAgbWF0Y2gyLmxvZ0ZDIDwtIHRvcFRhZ3MocWxmLCBuPTEwMDApW1sxXV1bLCJsb2dGQyJdCiAgbWF0Y2gyLnRhYmxlIDwtIHRvcFRhZ3MocWxmLCBuPTIwMDApCiAgbWF0Y2gyLnRhYmxlLmFsbCA8LSB0b3BUYWdzKHFsZiwgbj1ucm93KHFsZikpCiAgbWF0Y2gyLkRFR3MudGFibGUgPC0gdG9wVGFncyhxbGYsIG49REVHcy5RKQogIGFzc2lnbihwYXN0ZTAoIkRFR3MudGFibGVfYnlfR2VvTWVhblNpdGUuIixDb24sQ29uX2luZCksbWF0Y2gyLkRFR3MudGFibGUsIGVudmlyID0gcGFyZW50LmZyYW1lKCkpCiAgYXNzaWduKHBhc3RlMCgidG9wMTAwMERFR3NfYnlfR2VvTWVhblNpdGUuIixDb24sQ29uX2luZCksbWF0Y2gyLCBlbnZpciA9IHBhcmVudC5mcmFtZSgpKQogIGFzc2lnbihwYXN0ZTAoInRvcDEwMDBERUdzX2J5X0dlb01lYW5TaXRlLmRpcmVjdGlvbiIsQ29uLENvbl9pbmQpLG1hdGNoMi5sb2dGQywgZW52aXIgPSBwYXJlbnQuZnJhbWUoKSkKICBhc3NpZ24ocGFzdGUwKCJERUdzLnRvcDIwMDAudGFibGVfYnlfR2VvTWVhblNpdGUuIixDb24sQ29uX2luZCksbWF0Y2gyLnRhYmxlLCBlbnZpciA9IHBhcmVudC5mcmFtZSgpKQogIGFzc2lnbihwYXN0ZTAoIkRFR3MuYWxsR2VuZS50YWJsZV9ieV9HZW9NZWFuU2l0ZS4iLENvbl9pbmQpLG1hdGNoMi50YWJsZS5hbGwsIGVudmlyID0gcGFyZW50LmZyYW1lKCkpCn0KCiMgTWluX0w6IGZvciBob3cgbG93IHRoZSBsYW1kYSBjdiBnb2VzIDEwXigtMiwtMywtNCk7IHM6IGxhbWRhLnNlcXVlbmNlOiAxKSAxc2UgMikgbWluIDMpIGxvZyBtZWRpYW4gCmxhc3NvLmJ5LnNpdGUgPC0gZnVuY3Rpb24oQ29uLENvbl9pbmQsaSxNaW5fTCxzKSB7CiAgQnlTaXRlUmVzdWx0LndpdGhsYW1kYVAgPC0ge30gIyMgcmVmcmVzaAogIHZhcmlhYmxlcy5hbGwgPC0gbGlzdCgpCiAgcHJlZGljdC5zaXRlLnRlc3QuYWxsIDwtIGxpc3QoKQogIG1hdGNoMSA8LSBzdHJfc3ViKGNvbG5hbWVzKGNvdW50cy50c3dhbGxvdyksMyw0KSAlaW4lIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIkTWVyZ2VTaXRlCiAgY291bnRzLmNvbiAgPC0gIGNvdW50cy50c3dhbGxvdy5ub3JtWyxtYXRjaDFdCiAgY291bnRzLmNvbi5iYXRjaCA8LSBhcy5mYWN0b3IoYXMuY2hhcmFjdGVyKGRkcy5ib3RoU2V4LmFkanVzdGVkJGJhdGNoMilbbWF0Y2gxXSkKICBjb3VudHMuY29uLnNleCA8LSBhcy5mYWN0b3IoYXMuY2hhcmFjdGVyKGRkcy5ib3RoU2V4LmFkanVzdGVkJHNleClbbWF0Y2gxXSkKICBjb3VudHMuY29uLmNvbnRhbWluIDwtIGxvZzEwKGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIkdmFsdWUuZ2VvTWVhblttYXRjaChzdHJfc3ViKGNvbG5hbWVzKGNvdW50cy5jb24pLDMsNCksIGNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIkTWVyZ2VTaXRlKV0pCiAgIyMgQnVpbGRpbmcgbWF0cml4IAogIGlmIChsZW5ndGgobGV2ZWxzKGNvdW50cy5jb24uYmF0Y2gpKSA+IDEpIHsKICAgIGNvdW50cy5jb24udCA8LSBhcy5kYXRhLmZyYW1lKHQoY291bnRzLmNvbltnZXQocGFzdGUwKCJ0b3AxMDAwREVHc19ieV9HZW9NZWFuU2l0ZS4iLENvbixDb25faW5kKSksXSkpCiAgICBjb3VudHMuY29uLnQgPC0gY2JpbmQoY291bnRzLmNvbi50LCBiYXRjaCA9IGNvdW50cy5jb24uYmF0Y2gsIHNleCA9IGNvdW50cy5jb24uc2V4LCBjb250YW1pbmFudCA9IGNvdW50cy5jb24uY29udGFtaW4pCiAgICBtIDwtIG1vZGVsLm1hdHJpeChjb250YW1pbmFudCB+IGJhdGNoICsuLCBjb3VudHMuY29uLnQpCiAgICBtIDwtIG1bLC0xXSB9IGVsc2UgewogICAgICBjb3VudHMuY29uLnQgPC0gYXMuZGF0YS5mcmFtZSh0KGNvdW50cy5jb25bZ2V0KHBhc3RlMCgidG9wMTAwMERFR3NfYnlfR2VvTWVhblNpdGUuIixDb24sQ29uX2luZCkpLF0pKQogICAgICBjb3VudHMuY29uLnQgPC0gY2JpbmQoY291bnRzLmNvbi50LCBzZXggPSBjb3VudHMuY29uLnNleCwgY29udGFtaW5hbnQgPSBjb3VudHMuY29uLmNvbnRhbWluKQogICAgICBtIDwtIG1vZGVsLm1hdHJpeChjb250YW1pbmFudCB+IC4sIGNvdW50cy5jb24udCkKICAgICAgbSA8LSBtWywtMV0gfSAgCiAgCiAgIyMgZGV0ZXJtaW5uZSB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXQKICBjb3VudHMuc2l0ZSAgPC0gIGRkcy5ib3RoU2V4LmFkanVzdGVkJHByb3BlcnNpdGUyW21hdGNoMV0KICBtYXRjaDEudGVzdCA8LSBjb3VudHMuc2l0ZSA9PSB1bmlxdWUoY291bnRzLnNpdGUpW2ldCiAgbWF0Y2gxLnRlc3Quc2FtcGxlIDwtIHNhbXBsZSh3aGljaChtYXRjaDEudGVzdCksIHNpemUgPSBtZWRpYW4odGFibGUoY291bnRzLnNpdGUpKSwgcmVwbGFjZSA9IFRSVUUpCiAgbWF0Y2hzdWJzZXQudHJhaW4gPC0gY291bnRzLnNpdGUgIT0gdW5pcXVlKGNvdW50cy5zaXRlKVtpXSAjIHRyYWluIHN1YnNldAogICMjIHNhbXBsZSA5MCUgb2YgdHJhaW5pbmcgc2V0CiAgbWF0Y2hzdWJzZXQudHJhaW4gPC0gc2FtcGxlKHdoaWNoKG1hdGNoc3Vic2V0LnRyYWluKSwoc3VtKG1hdGNoc3Vic2V0LnRyYWluKSowLjkpKQogIAogIGwuMXNlIDwtIHt9ICMjIHJlcGVhdCA1IHRpbWVzIHRvIGF2b2lkIG91dGxpZXIgaW4gY3Jvc3MgdmFsaWRhdGlvbiAKICBsLm1pbiA8LSB7fQogICMgZm9sZGlkPC0gYXMubnVtZXJpYyhhcy5mYWN0b3Ioc3RyX3N1Yihjb2xuYW1lcyhjb3VudHMuY29uWyxtYXRjaHN1YnNldC50cmFpbl0pLDMsNCkpKSAjIGRvbid0IHVzZSBmb2xkaWQgYXMgYmF0Y2ggZWZmZWN0IGZvciBjb25zaXN0ZW5jeQoKICBjdmZpdCA8LSBjdi5nbG1uZXQoeD1tW21hdGNoc3Vic2V0LnRyYWluLF0sIHk9Y291bnRzLmNvbi50JGNvbnRhbWluYW50W21hdGNoc3Vic2V0LnRyYWluXSwgbmZvbGRzID0gMTAsIGFscGhhID0gMSwgbGFtYmRhID0gMTBec2VxKDAsTWluX0wsbGVuZ3RoPTYwMCkpCiAgbC4xc2UgPC0gY3ZmaXQkbGFtYmRhLjFzZQogIGwubWluIDwtIGN2Zml0JGxhbWJkYS5taW4KICBsLm1lZGlhbiA8LSBleHAobWVhbihjKGxvZyhsLjFzZSksbG9nKGwubWluKSkpKQogIGxhbWRhLnNlcXVlbmNlIDwtIGMobC4xc2UsbC5taW4sbC5tZWRpYW4pICAKICBuYW1lcyhsYW1kYS5zZXF1ZW5jZSkgPC0gYygiMXNlIiwibWluIiwibWVkaWFuIikKICAgIGZpdCA8LSBnbG1uZXQoeD1tW21hdGNoc3Vic2V0LnRyYWluLF0sIHk9Y291bnRzLmNvbi50JGNvbnRhbWluYW50W21hdGNoc3Vic2V0LnRyYWluXSwgYWxwaGEgPSAxLCBsYW1iZGEgPSAxMF5zZXEoMCxNaW5fTCxsZW5ndGg9NjAwKSkKICAgIFJlc3VsdDEgPC0gYXNzZXNzLmdsbW5ldChmaXQsIG5ld3ggPSBtW21hdGNoMS50ZXN0LnNhbXBsZSxdLCBuZXd5ID0gY291bnRzLmNvbi50JGNvbnRhbWluYW50W21hdGNoMS50ZXN0LnNhbXBsZV0sIHMgPSBsYW1kYS5zZXF1ZW5jZVtzXSkgCiAgICBCeVNpdGVSZXN1bHQud2l0aGxhbWRhUCRtc2UgPC0gUmVzdWx0MSRtc2UKICAgIEJ5U2l0ZVJlc3VsdC53aXRobGFtZGFQJG1hZSA8LSBSZXN1bHQxJG1hZQogICAgdmFyaWFibGVzIDwtIGNvZWYoZml0LCBzID0gbGFtZGEuc2VxdWVuY2Vbc10pICAKICAgIHZhcmlhYmxlcyA8LSByb3cubmFtZXModmFyaWFibGVzKVshKHZhcmlhYmxlc1ssMV0gPT0gMCldCiAgICB2YXJpYWJsZXMgPC0gdmFyaWFibGVzWy0xXQogICAgCiAgICBwc2V1ZG9fUjIgPC0gZml0JGRldi5yYXRpb1t3aGljaChzb3J0KGMobGFtZGEuc2VxdWVuY2Vbc10sMTBec2VxKDAsTWluX0wsbGVuZ3RoPTYwMCkpLGRlY3JlYXNpbmcgPSBUUlVFKSA9PSBsYW1kYS5zZXF1ZW5jZVtzXSlbMV0gLTFdCiAgICBwcmVkaWN0LnNpdGUudGVzdDEgPC0gcHJlZGljdChmaXQsIG5ld3ggPSBtW21hdGNoMS50ZXN0LnNhbXBsZSxdLCBzID0gbGFtZGEuc2VxdWVuY2Vbc10pIAogICAgcHJlZGljdC5zaXRlLnRlc3QuYWxsIDwtIHByZWRpY3Quc2l0ZS50ZXN0MQogICAgCiAgICByZXN1bHQgPSBsaXN0KGxhbWRhLnNlcXVlbmNlLEJ5U2l0ZVJlc3VsdC53aXRobGFtZGFQLHZhcmlhYmxlcyxwc2V1ZG9fUjIscHJlZGljdC5zaXRlLnRlc3QuYWxsKQogICAgbmFtZXMocmVzdWx0KSA8LSBjKCJsYW1kYS5zZXF1ZW5jZSIsIm1zZV9tYWUiLCJ2YXJpYWJsZXMiLCJwc2V1ZG9fUjIiLCJwcmVkaWN0LnNpdGUiKQogICAgcmV0dXJuKHJlc3VsdCkKfQpSdW5fbGFzc29fYnlfc2l0ZSA8LSBmdW5jdGlvbihuLENvbixDb25faW5kLE1pbl9MLHMpIHsKVElNRTEgPC0gU3lzLnRpbWUoKQpsYXNzb3NpdGVSZXN1bHQucnVuIDwtIGZvcmVhY2goaSA9IHJlcCgxOjI4LG4pLCAucGFja2FnZXMgPSBjKCJkcGx5ciIsImdsbW5ldCIpLCAuZXhwb3J0ID0gYygibGFzc28uYnkuc2l0ZSIsImNvbnRhbWluYW50cy5jb2xkYXRhLnN1YnNldDIiLCJjb3VudHMudHN3YWxsb3cubm9ybSIsImRkcy5ib3RoU2V4LmFkanVzdGVkIiwgcGFzdGUwKCJ0b3AxMDAwREVHc19ieV9HZW9NZWFuU2l0ZS4iLENvbixDb25faW5kKSwgImRhdGEuY29udGFtaW5hbnRzLm1ham9yU3Vic2V0Lmdlby5ieXNpdGUiKSkgJWRvcGFyJSB7CiAgY2F0KHBhc3RlKCJTdGFydGluZyBpdGVyYXRpb24iLGksU3lzLnRpbWUoKSwiXG4iKSwgZmlsZSA9IHBhc3RlMCgifi9Eb2N1bWVudHMvd29yay9Uc3dhbGxvd19jaGVtX0dMUklfdXBkYXRlL3JlbGF4X2xhc3NvX3Jlc3VsdC8iLENvbl9pbmQsImxhc3NvQnlTaXRlUnVuLmxvZzIudHh0IiksIGFwcGVuZCA9IFRSVUUpCiAgbGFzc29fcmVzdWx0IDwtIFZlY3Rvcml6ZShsYXNzby5ieS5zaXRlKShDb24sQ29uX2luZCxpLE1pbl9MLHMpCiAgcmV0dXJuKGxhc3NvX3Jlc3VsdCkKfQpyZXR1cm4obGFzc29zaXRlUmVzdWx0LnJ1bikKVElNRTIgPC0gU3lzLnRpbWUoKQpwcmludChUSU1FMiAtIFRJTUUxKQp9IApgYGAKCiMjIyBMYXNzbyByZWdyZXNzaW9uIGFuYWx5c2lzIGJldHdlZW4gZ2xvYmFsIGdlZW5lIGV4cHJlc3Npb24gYW5kIFBBSCBjb25jZW50cmF0aW9ucwpSdW4gbGFzc28gd2l0aCBsZWF2ZSBvbmUgKHNpdGUpIGNyb3NzIHZhbGlkYXRpb24sIGxhbWRhIGNob3NlIGwuMXNlIGZvciBzZWxlY3RpbmcgdG9wIHByZWRpY3RvciBnZW5lcyBmcm9tIHRvcCAxMDAwIGdlbmVzIChFZGdlUiwgbGluZWFyIHJlZ3Jlc3Npb24gYWdhaW5zdCBQQUhzIGNvbmNlbnRyYXRpb25zKQpsYXNzb190b3BnZW5lX2J5U2l0ZV9QQUhzLnJkcwo6IHNlbGVjdGluZyB0b3AgMTEwIGdlbmVzIGluIHRoZSBjcm9zcy12YWxpZGF0aW9uICg+IDEwJSBvZiBjcm9zcy12YWxpZGF0aW9uKSAgCmBgYHtyIFRyYWluIFBBSCBsYXNzbyBtb2RlbCBhbmQgY3Jvc3MtdmFsaWRhaXRvbiwgZXZhbCA9IEZBTFNFfQoKQ29uPSJQQUhzX2RhdGEiO0Nvbl9pbmQ9IlRvdGFsIFBBSHMiCiMjIGVkaXQgc3Vic2V0IGZvciBvdmVybGFwcGluZyBnZW5vbWljIHNhbXBsZXMgd2l0aCBjb250YW1pbmFudCBkYXRhIApjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyIDwtZGF0YS5jb250YW1pbmFudHMubWFqb3JTdWJzZXQuZ2VvLmJ5c2l0ZQojIyBJbmRpdmlkdWFsIGNoZW1pY2FscyAKY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyICU+JSBmaWx0ZXIoS2V5ID09IENvbl9pbmQpCiMjIGNvbWJpbmUgYWxsIGR1cGxpY2F0ZWQgaXRlbXMgdXNpbmcgbWVhbiB2YWx1ZSBiZWNhdXNlIHRoZXkgaGF2ZSB0aGUgc2FtZSBNZXJnZVNpdGUgIyMgb25seSBSaXZlciBSYWlzaW4gd2lsbCBiZSBhZmZlY3RlZCAKY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiA8LSBjb250YW1pbmFudHMuY29sZGF0YS5zdWJzZXQyICU+JSBncm91cF9ieShNZXJnZVNpdGUsIEtleSkgJT4lIHN1bW1hcmlzZSh2YWx1ZS5nZW9NZWFuMiA9IG1lYW4odmFsdWUuZ2VvTWVhbikpICU+JSB1bmdyb3VwKCkKY29sbmFtZXMoY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MilbY29sbmFtZXMoY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MikgPT0gInZhbHVlLmdlb01lYW4yIl0gPC0gInZhbHVlLmdlb01lYW4iICMjIGNoYW5nZSB0aGUgbmFtZSBiYWNrIHRvIHZhbHVlLmdlb01lYW4KcHJpbnQoc3VtKGR1cGxpY2F0ZWQoY29udGFtaW5hbnRzLmNvbGRhdGEuc3Vic2V0MiRNZXJnZVNpdGUpKSkgIyMgY2hlY2sgZHVwbGljYXRpb24gYWdhaW4KR2V0VG9wREVHc19ieUdlb01lYW4uYnlTaXRlR2VvTWVhbihDb24gPSBDb24sIENvbl9pbmQgPSBDb25faW5kKSAjIyA1NzAgREVHcyAocCA8IDAuLjA1KSBhZ2FpbnN0IFBBSHMgdGlzc3VlIGNvbmNlbnRyYXRpb25zCiMgc2F2ZVJEUyhgREVHcy5hbGxHZW5lLnRhYmxlX2J5X0dlb01lYW5TaXRlLlRvdGFsIFBBSHNgLCJ+L0RvY3VtZW50cy93b3JrL1Rzd2FsbG93X2NoZW1fR0xSSV91cGRhdGUvR0xSSV9NU19maWd1cmVzL1RPUC5QQUhzLmFsbGdlbmUudGFibGUuYnlzaXRlLnJkcyIpCgojIyBSdW4gbGFzc28gcmVncmVzc2lvbiBieSBzaXRlIHRvIHByZWRpY3QgUEFIcyB0aXNzdWUgY29uY2VudHJhdGlvbnMKcmVnaXN0ZXJEb1BhcmFsbGVsKGNvcmVzID0gMTIpCiMjIEJlY2FzdWUgdGhlIHB1cnBvc2UgCmxhc3NvUmVzdWx0LmJ5U2l0ZS5Ub3RhbFBBSHMgPSBSdW5fbGFzc29fYnlfc2l0ZShuPTIwMCxDb249Q29uLENvbl9pbmQgPSBDb25faW5kLCBNaW5fTCA9IC0yLCBzID0gMSkgIyBsYW1kYS5zZXF1ZW5jZSA8LSBjKGwuMXNlLGwubWluLGwubWVkaWFuKQpwYXJhbGxlbDo6c3RvcENsdXN0ZXIoY2wpICMgc3RvcCBwYXJhbGxlbApsYXNzb1Jlc3VsdC5ieVNpdGUgPSBsYXNzb1Jlc3VsdC5ieVNpdGUuVG90YWxQQUhzCiMgc2F2ZVJEUyhsYXNzb1Jlc3VsdC5ieVNpdGUuVG90YWxQQUhzLCJ+L0RvY3VtZW50cy93b3JrL1Rzd2FsbG93X2NoZW1fR0xSSV91cGRhdGUvR0xSSV9NUzJfYWxsL2xhc3NvX3RvcGdlbmVfYnlTaXRlX1BBSHMucmRzIikKCiMjIDEwMCUgdHJpYWxzIGhhdmUgbGVzcyB0aGFuIDk1IGdlbmVzLCBnbyBhaGVhZCBhbmQgcGljayBnZW5lcyBhcHBlYXJzIGF0IDEwJSBvZiB0cmlhbHMgCmxhc3NvX3NlbGVjdGVkX3RvcGdlbmUgPC0gbmFtZXMoc29ydCh0YWJsZSh1bmxpc3Qoc2FwcGx5KDE6KDI4KjIwMCksZnVuY3Rpb24oeCkgbGFzc29SZXN1bHQuYnlTaXRlW1t4XV1bWzNdXSkpKSxkZWNyZWFzaW5nID0gVFJVRSlbMToxMTBdKQojIHNhdmVSRFMobGFzc29fc2VsZWN0ZWRfdG9wZ2VuZSwgIn4vRG9jdW1lbnRzL3dvcmsvVHN3YWxsb3dfY2hlbV9HTFJJX3VwZGF0ZS9HTFJJX01TMl9hbGwvbGFzc29fdG9wZ2VuZV9ieVNpdGVfUEFIcy5yZHMiKQpgYGAKCg==