Read in necessary libraries, functions and look up tables

library(igraph)
library(qgraph)
library(casnet)
library(plyr)
library(dplyr)
library(tidyr)
library(caret)
library(psych)
library(lme4)
library(reshape2)
library(lubridate)
library(parallel)
library(wesanderson)
library(stringr)
library(matrixStats)
library(ggthemes)
library(gridExtra)
library(cowplot)

# save the original plotting space parameters
og_par <- par()
Show the session information
devtools::session_info()
─ Session info ────────────────────────────────────────────────────────────────────────────────────────────────────────

─ Packages ────────────────────────────────────────────────────────────────────────────────────────────────────────────
 package      * version    date       lib source                               
 abind          1.4-5      2016-07-21 [1] CRAN (R 3.5.1)                       
 acepack        1.4.1      2016-10-29 [1] CRAN (R 3.6.1)                       
 assertthat     0.2.1      2019-03-21 [1] CRAN (R 3.5.1)                       
 backports      1.1.4      2019-04-10 [1] CRAN (R 3.5.1)                       
 base64enc      0.1-3      2015-07-28 [1] CRAN (R 3.5.1)                       
 BDgraph        2.58       2019-04-15 [1] CRAN (R 3.5.1)                       
 boot           1.3-25     2020-04-26 [3] CRAN (R 3.6.3)                       
 broom          0.5.2      2019-04-07 [1] CRAN (R 3.5.1)                       
 callr          3.3.0      2019-07-04 [1] CRAN (R 3.5.1)                       
 caret        * 6.0-83     2019-04-18 [1] CRAN (R 3.5.1)                       
 casnet       * 0.1.4      2019-07-10 [1] Github (FredHasselman/casnet@2e6f024)
 checkmate      1.9.4      2019-07-04 [1] CRAN (R 3.6.1)                       
 class          7.3-17     2020-04-26 [3] CRAN (R 3.6.3)                       
 cli            1.1.0      2019-03-19 [1] CRAN (R 3.5.1)                       
 cluster        2.1.0      2019-06-19 [3] CRAN (R 3.6.1)                       
 codetools      0.2-16     2018-12-24 [3] CRAN (R 3.6.3)                       
 colorspace     1.4-1      2019-03-18 [1] CRAN (R 3.5.1)                       
 corpcor        1.6.9      2017-04-01 [1] CRAN (R 3.5.1)                       
 cowplot      * 0.9.4      2019-01-08 [1] CRAN (R 3.5.1)                       
 crayon         1.3.4      2017-09-16 [1] CRAN (R 3.5.1)                       
 d3Network      0.5.2.1    2015-01-31 [1] CRAN (R 3.5.1)                       
 data.table     1.12.2     2019-04-07 [1] CRAN (R 3.6.1)                       
 desc           1.2.0      2018-05-01 [1] CRAN (R 3.5.1)                       
 devtools       2.0.1      2018-10-26 [1] CRAN (R 3.5.1)                       
 digest         0.6.20     2019-07-04 [1] CRAN (R 3.5.1)                       
 dplyr        * 0.8.3      2019-07-04 [1] CRAN (R 3.5.1)                       
 fdrtool        1.2.15     2015-07-08 [1] CRAN (R 3.5.1)                       
 foreach        1.4.4      2017-12-12 [1] CRAN (R 3.5.1)                       
 foreign        0.8-76     2020-03-03 [3] CRAN (R 3.6.3)                       
 Formula        1.2-3      2018-05-03 [1] CRAN (R 3.6.1)                       
 fs             1.2.6      2018-08-23 [1] CRAN (R 3.5.1)                       
 generics       0.0.2      2018-11-29 [1] CRAN (R 3.5.1)                       
 ggm            2.3        2015-01-21 [1] CRAN (R 3.5.1)                       
 ggplot2      * 3.2.0      2019-06-16 [1] CRAN (R 3.5.1)                       
 ggthemes     * 4.2.0      2019-05-13 [1] CRAN (R 3.5.1)                       
 glasso         1.10       2018-07-13 [1] CRAN (R 3.5.1)                       
 glue           1.3.1      2019-03-12 [1] CRAN (R 3.5.1)                       
 gower          0.2.0      2019-03-07 [1] CRAN (R 3.5.1)                       
 gridExtra    * 2.3        2017-09-09 [1] CRAN (R 3.6.1)                       
 gtable         0.3.0      2019-03-25 [1] CRAN (R 3.5.1)                       
 gtools         3.8.1      2018-06-26 [1] CRAN (R 3.5.1)                       
 Hmisc          4.2-0      2019-01-26 [1] CRAN (R 3.5.1)                       
 htmlTable      1.13.1     2019-01-07 [1] CRAN (R 3.6.1)                       
 htmltools      0.3.6      2017-04-28 [1] CRAN (R 3.5.1)                       
 htmlwidgets    1.3        2018-09-30 [1] CRAN (R 3.5.1)                       
 huge           1.3.2      2019-04-08 [1] CRAN (R 3.5.1)                       
 igraph       * 1.2.4.1    2019-04-22 [1] CRAN (R 3.5.1)                       
 invctr         0.1.0      2019-03-07 [1] CRAN (R 3.5.1)                       
 ipred          0.9-8      2018-11-05 [1] CRAN (R 3.5.1)                       
 iterators      1.0.10     2018-07-13 [1] CRAN (R 3.5.1)                       
 jpeg           0.1-8      2014-01-23 [1] CRAN (R 3.5.1)                       
 knitr          1.23       2019-05-18 [1] CRAN (R 3.5.1)                       
 lattice      * 0.20-41    2020-04-02 [3] CRAN (R 3.6.3)                       
 latticeExtra   0.6-28     2016-02-09 [1] CRAN (R 3.6.1)                       
 lava           1.6.5      2019-02-12 [1] CRAN (R 3.5.1)                       
 lavaan         0.6-3      2018-09-22 [1] CRAN (R 3.5.1)                       
 lazyeval       0.2.2      2019-03-15 [1] CRAN (R 3.5.1)                       
 lme4         * 1.1-21     2019-03-05 [1] CRAN (R 3.5.1)                       
 lubridate    * 1.7.4      2018-04-11 [1] CRAN (R 3.5.1)                       
 magrittr       1.5        2014-11-22 [1] CRAN (R 3.5.1)                       
 MASS           7.3-51.3   2019-03-31 [1] CRAN (R 3.5.1)                       
 Matrix       * 1.2-17     2019-03-22 [1] CRAN (R 3.5.1)                       
 matrixStats  * 0.54.0     2018-07-23 [1] CRAN (R 3.5.1)                       
 memoise        1.1.0      2017-04-21 [1] CRAN (R 3.5.1)                       
 minqa          1.2.4      2014-10-09 [1] CRAN (R 3.5.1)                       
 mnormt         1.5-5      2016-10-15 [1] CRAN (R 3.5.1)                       
 ModelMetrics   1.2.2      2018-11-03 [1] CRAN (R 3.5.1)                       
 munsell        0.5.0      2018-06-12 [1] CRAN (R 3.5.1)                       
 nlme           3.1-147    2020-04-13 [3] CRAN (R 3.6.3)                       
 nloptr         1.2.1      2018-10-03 [1] CRAN (R 3.6.1)                       
 nnet           7.3-14     2020-04-26 [3] CRAN (R 3.6.3)                       
 pbapply        1.4-0      2019-02-05 [1] CRAN (R 3.5.1)                       
 pbivnorm       0.6.0      2015-01-23 [1] CRAN (R 3.6.1)                       
 pillar         1.4.2      2019-06-29 [1] CRAN (R 3.5.1)                       
 pkgbuild       1.0.2      2018-10-16 [1] CRAN (R 3.5.1)                       
 pkgconfig      2.0.2      2018-08-16 [1] CRAN (R 3.5.1)                       
 pkgload        1.0.2      2018-10-29 [1] CRAN (R 3.5.1)                       
 plyr         * 1.8.4      2016-06-08 [1] CRAN (R 3.5.1)                       
 png            0.1-7      2013-12-03 [1] CRAN (R 3.5.1)                       
 prettyunits    1.0.2      2015-07-13 [1] CRAN (R 3.5.1)                       
 processx       3.4.0      2019-07-03 [1] CRAN (R 3.5.1)                       
 prodlim        2018.04.18 2018-04-18 [1] CRAN (R 3.5.1)                       
 ps             1.3.0      2018-12-21 [1] CRAN (R 3.5.1)                       
 psych        * 1.8.12     2019-01-12 [1] CRAN (R 3.5.1)                       
 purrr          0.3.4      2020-04-17 [1] CRAN (R 3.6.3)                       
 qgraph       * 1.6.1      2019-02-13 [1] CRAN (R 3.5.1)                       
 R6             2.4.0      2019-02-14 [1] CRAN (R 3.5.1)                       
 RColorBrewer   1.1-2      2014-12-07 [1] CRAN (R 3.5.1)                       
 Rcpp           1.0.2      2019-07-25 [1] CRAN (R 3.6.1)                       
 recipes        0.1.5      2019-03-21 [1] CRAN (R 3.5.1)                       
 remotes        2.0.2      2018-10-30 [1] CRAN (R 3.5.1)                       
 reshape2     * 1.4.3      2017-12-11 [1] CRAN (R 3.5.1)                       
 rjson          0.2.20     2018-06-08 [1] CRAN (R 3.6.1)                       
 rlang          0.4.0      2019-06-25 [1] CRAN (R 3.5.1)                       
 rpart          4.1-15     2019-04-12 [3] CRAN (R 3.6.1)                       
 rprojroot      1.3-2      2018-01-03 [1] CRAN (R 3.5.1)                       
 rstudioapi     0.10       2019-03-19 [1] CRAN (R 3.5.1)                       
 scales         1.0.0      2018-08-09 [1] CRAN (R 3.5.1)                       
 sessioninfo    1.1.1      2018-11-05 [1] CRAN (R 3.5.1)                       
 stringi        1.4.3      2019-03-12 [1] CRAN (R 3.5.1)                       
 stringr      * 1.4.0      2019-02-10 [1] CRAN (R 3.5.1)                       
 survival       3.1-12     2020-04-10 [3] CRAN (R 3.6.3)                       
 testthat       2.1.1      2019-04-23 [1] CRAN (R 3.5.1)                       
 tibble         2.1.3      2019-06-06 [1] CRAN (R 3.5.1)                       
 tidyr        * 0.8.3      2019-03-01 [1] CRAN (R 3.5.1)                       
 tidyselect     0.2.5      2018-10-11 [1] CRAN (R 3.5.1)                       
 timeDate       3043.102   2018-02-21 [1] CRAN (R 3.5.1)                       
 usethis        1.4.0      2018-08-14 [1] CRAN (R 3.5.1)                       
 wesanderson  * 0.3.6      2018-04-20 [1] CRAN (R 3.6.3)                       
 whisker        0.3-2      2013-04-28 [1] CRAN (R 3.5.1)                       
 withr          2.1.2      2018-03-15 [1] CRAN (R 3.5.1)                       
 xfun           0.8        2019-06-25 [1] CRAN (R 3.5.1)                       

[1] /usr/local/lib/R/site-library
[2] /usr/lib/R/site-library
[3] /usr/lib/R/library

Read in data

Prepare the Outcome measures

for (timepoint in c('m00', 'm03', 'm06', 'm09', 'm12')) {
  aumc_df[, paste0(timepoint, "_NHPT_D_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_NHPT_D1"), paste0(timepoint,"_NHPT_D2"))], na.rm=T)
  aumc_df[, paste0(timepoint, "_NHPT_ND_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_NHPT_ND1"), paste0(timepoint,"_NHPT_ND2"))], na.rm=T)
  aumc_df[, paste0(timepoint, "_NHPT_BH_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_NHPT_D_avg"), paste0(timepoint,"_NHPT_ND_avg"))], na.rm=T)
  aumc_df[, paste0(timepoint, "_TWT_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_TWT_1"), paste0(timepoint,"_TWT_2"))], na.rm=T)
}

Get a list of all the outcome measures of interest at each time point

outcomes_of_interest_ls <- c()
for (outcome in outcomes_list) {
  outcomes_of_interest_ls <- append(outcomes_of_interest_ls, colnames(aumc_df %>% select(matches(outcome))))
}

Features used in the creation of the complexity measures (taken RQA python notebook)

# features with highest rqa measures (most interesting NLTSA wise)
feature_selection <- c('post_correction_slowing_MEAN_2CD',
 'pre_correction_slowing_MEAN_2CD',
 'hold_time_STD',
 'flight_time_MEDIAN',
 'flight_time_MEAN_ABS_CHANGE',
 'hold_time_SKEW',
 'correction_duration_MEAN_2CD',
 'post_correction_slowing_P_AUTO_CORR',
 'after_punctuation_pause_MEAN_CHANGE',
 'after_punctuation_pause_MEAN_2CD',
 'session_duration_MEAN_2CD',
 'after_punctuation_pause_MEDIAN',
 'flight_time_MAX')

feature_selection
 [1] "post_correction_slowing_MEAN_2CD"    "pre_correction_slowing_MEAN_2CD"     "hold_time_STD"                      
 [4] "flight_time_MEDIAN"                  "flight_time_MEAN_ABS_CHANGE"         "hold_time_SKEW"                     
 [7] "correction_duration_MEAN_2CD"        "post_correction_slowing_P_AUTO_CORR" "after_punctuation_pause_MEAN_CHANGE"
[10] "after_punctuation_pause_MEAN_2CD"    "session_duration_MEAN_2CD"           "after_punctuation_pause_MEDIAN"     
[13] "flight_time_MAX"                    

Identify the different groups (change in MRI, no change in MRI and HC users) and prep the data for the analysis

mri_cols <- colnames(aumc_df %>% select(matches("mri_enhancing")))

diff_df <- NA
for (i in seq(1, length(mri_cols)-1)) {
  diff_df <- cbind(diff_df, aumc_df[mri_cols[i+1]] - aumc_df[mri_cols[i]])
}
diff_df <- diff_df[, 2:ncol(diff_df)]
diff_df <- diff_df[rowSums(is.na(diff_df)) < 3, ]

## SPECIFY THE INDICES YOU WISH TO INVESTIGATE
row_amount <- 58
# user indices which have a change in mri
mri_change_index <- as.integer(names(which(rowSums(abs(diff_df), na.rm=TRUE) > 0)))
mri_change_ls <- aumc_df$user_id[mri_change_index]
short_mri_change_ls <- c()
for (user in mri_change_ls) {
  if (nrow(key_df[key_df$user_id == user, ]) > row_amount) {
    short_mri_change_ls <- c(short_mri_change_ls, user)
  }
}
mri_change_index <- which(aumc_df$user_id %in% short_mri_change_ls)
# user indices which don't have a change in mri
mri_no_change_index <- as.integer(names(which(rowSums(abs(diff_df), na.rm=TRUE) == 0)))
mri_no_change_ls <- aumc_df$user_id[mri_no_change_index]
short_mri_no_change_ls <- c()
for (user in mri_no_change_ls) {
  if (nrow(key_df[key_df$user_id == user, ]) > row_amount) {
    short_mri_no_change_ls <- c(short_mri_no_change_ls, user)
  }
}
mri_no_change_index <- which(aumc_df$user_id %in% short_mri_no_change_ls)
# healthy controls
hc_users_index <- which(aumc_df$diagnosis_ms == 0)
hc_users_ls <- aumc_df$user_id[hc_users_index]
short_hc_users_ls <- c()
for (user in hc_users_ls) {
  if (nrow(key_df[key_df$user_id == user, ]) > row_amount) {
    short_hc_users_ls <- c(short_hc_users_ls, user)
  }
}
hc_users_index <- which(aumc_df$user_id %in% short_hc_users_ls)

list_of_user_index_lists=c(data.frame(mri_change=mri_change_index),
           data.frame(mri_no_change=mri_no_change_index),
           data.frame(hc_users=hc_users_index))

prepped_dfs_list <- list()
for (users_list_number in 1:length(list_of_user_index_lists)){
  user_list_name<-names(list_of_user_index_lists[users_list_number])
  users_selection_index<-list_of_user_index_lists[[user_list_name]]
  
  print(user_list_name)
  
  # use the chosen index to get a list of users
  user_selection <- aumc_df$user_id[users_selection_index]
  
  print(user_selection)
  
  # create a subset of the aumc_df which includes only the users of interest
  missing_df <- data.frame("sample size" = colSums(!is.na(aumc_df[aumc_df$diagnosis_ms == 1, c("user_id", mri_cols, visitdate_cols)])))
  
  # subset relevant users' keystroke data
  key_sub_df <- key_df[key_df$user_id %in% user_selection, ]
  # order based on user id and timestamp
  key_sub_df <- key_sub_df[order(key_sub_df$user_id, key_sub_df$timestamp), ]
  
  
  ## Creating a data frame of the complexity measures per user

  ## Writing code which will parallelize the EWS calculations
  dfs_list <- df_user_list(df = key_sub_df, features = c("user_id", "timestamp", feature_selection), 
                           user_column_name = "user_id", users = user_selection)
  complexity_dfs_list <- mclapply(dfs_list, EWS_calc) # mclapply is the parallelized version of lapply
  
  # filter only the data frames in the list (drop where there was not enough data)
  complexity_dfs_list <- Filter(function(x) is.data.frame(x)[[1]] > 0, complexity_dfs_list)
  
  complex_df <- rbind.fill(complexity_dfs_list)

  # merge the complex df back to the keystroke df
  key_complex_df <- merge(key_sub_df[, c("user_id", "timestamp", feature_selection)], 
                          complex_df, by = c("user_id", "timestamp")) 
  # relabel the 10's to 1's in the complexity_peaks factor to avoid issues later
  key_complex_df$complexity_peaks[key_complex_df$complexity_peaks == 10] <- 1
  
  # aumc_outcome_selector <- mri_cols
  aumc_outcome_selector <- outcomes_of_interest_ls
  # create a melted version of the aumc_df with only the variables of interest
  aumc_df_m_1 <- melt(aumc_df[c("user_id", aumc_outcome_selector)], id.vars="user_id",
                      variable.name = "PRO_name", value.name = "PRO_value")
  aumc_df[c(date_cols)] <- apply(aumc_df[c(date_cols)], 2, as.character)
  aumc_df_m_2 <- melt(aumc_df[c("user_id", date_cols)], id.vars="user_id", 
                      variable.name = "visitdate_cat", value.name = "date")
  aumc_df_m_2$date <- as.Date(aumc_df_m_2$date)
  
  aumc_df_m_1 <- aumc_df_m_1 %>% mutate(time_code = substr(PRO_name,1L,4L))
  aumc_df_m_2 <- aumc_df_m_2 %>% mutate(time_code = substr(visitdate_cat,1L,4L))
  aumc_df_m <- merge(aumc_df_m_1, aumc_df_m_2, by = c("user_id", "time_code"))
  
  # select the outcome measures and their corresponding questionnaire/ visitdates then reassign
  aumc_df_m_vs <-  aumc_df_m %>% 
    filter(str_detect(aumc_df_m$PRO_name, paste(visitdate_outcomes, collapse = "|")) & 
             str_detect(aumc_df_m$visitdate_cat, paste(visitdate_cols, collapse = "|")))
  
  aumc_df_m_qs <-  aumc_df_m %>% 
    filter(str_detect(aumc_df_m$PRO_name, paste(questionnaire_date_outcomes, collapse = "|")) & 
             str_detect(aumc_df_m$visitdate_cat, paste(questionnare_date_cols, collapse = "|")))
  aumc_df_m <- rbind(aumc_df_m_vs, aumc_df_m_qs)
  
  # merge the melted aumc_df with the key_complex_df
  key_complex_df$date <- as.Date(key_complex_df$timestamp)
  df.mod <- merge(key_complex_df, aumc_df_m, by = c("user_id", "date"), all.x=TRUE, all.y=FALSE)
  
  prepped_dfs_list[[user_list_name]] <- df.mod
  
}
[1] "mri_change"
 [1] 368 377 380 383 389 393 398 409 410 424 433 438 444
[1] "mri_no_change"
 [1] 364 365 367 369 382 386 387 388 390 396 400 406 408 411 413 416 429 430 435 449
[1] "hc_users"
 [1] 372 373 376 381 384 391 402 403 404 405 412 415 421 423 425 427 431 432 437 439 448 463

Plot the Dynamic Complexity, Cumulative Complexity Peaks, and the corresponding data from the Gd enhancing lesions and Patient Reported Outcome Measures


user_failed_list <- c()
for (users_list_number in 1:length(list_of_user_index_lists)){
  user_list_name<-names(list_of_user_index_lists[users_list_number])
  users_selection_index<-list_of_user_index_lists[[user_list_name]]
  
  print(user_list_name)
  
  prepped_df <- prepped_dfs_list[[user_list_name]]
    
  # use the chosen index to get a list of users
  user_selection <- aumc_df$user_id[users_selection_index]
  
  for (user in user_selection) {

    user_dc_df <- prepped_df[prepped_df$user_id == user, ]
    sub_aumc_df_m <- aumc_df_m[aumc_df_m$user_id == user, ]
    temp <- aumc_df_m[aumc_df_m$user_id %in% users_selection_index, ]
    
    selected_outcomes_list <- c()
    for (i in 1:length(outcomes_list)) {
      diff_list <- c()
      current_outcome_vector <- na.omit(sub_aumc_df_m[grep(outcomes_list[i], sub_aumc_df_m$PRO_name), "PRO_value"])
      for (j in 2: length(current_outcome_vector)-1) {
        diff_list <- append(diff_list, current_outcome_vector[j + 1] - current_outcome_vector[j])
      }
      if (outcomes_list[i] %in% c("NHPT_ND_avg", "NHPT_D_avg", "TWT_avg")){
        if (any(abs(diff_list) > diff_list[1] * outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"], na.rm=T)){
          selected_outcomes_list <- append(selected_outcomes_list, outcomes_list[i])
        }
    } else{
          if (any(abs(diff_list) >= outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"], na.rm=T)){
          selected_outcomes_list <- append(selected_outcomes_list, outcomes_list[i])
        }
      }
    }
    
    clin_matches <- unique(grep(paste(selected_outcomes_list,collapse="|"),
                            sub_aumc_df_m$PRO_name, value=TRUE))
    
    clin_sub_aumc_df_m <- sub_aumc_df_m
    clin_sub_aumc_df_m[clin_sub_aumc_df_m$PRO_name %notin% clin_matches, "PRO_value"] <- NA
    
    clin_sub_aumc_df_m$m_date <- str_split_fixed(clin_sub_aumc_df_m$PRO_name, "_", 2)[,1]
    clin_sub_aumc_df_m$m_date <- factor(clin_sub_aumc_df_m$m_date)
    clin_sub_aumc_df_m$PRO_group <- str_split_fixed(clin_sub_aumc_df_m$PRO_name, "_", 2)[,2]
    
    # Two scaling options - 1) scale within each outcome measure
    # sort data frame on PRO_group and PRO_name otherwise merge of scaled column will be incorrect
    clin_sub_aumc_df_m <- clin_sub_aumc_df_m[order(clin_sub_aumc_df_m$PRO_group, clin_sub_aumc_df_m$PRO_name), ]
    tst <- split(clin_sub_aumc_df_m$PRO_value, clin_sub_aumc_df_m$PRO_group)
    tst_ls <- lapply(tst, elascer)
    clin_sub_aumc_df_m<-cbind(clin_sub_aumc_df_m,  data.frame(PRO_value_scaled=unlist(tst_ls)))
    
    # Two scaling options - 2) scale all outcome measures without grouping prior
    # clin_sub_aumc_df_m$PRO_value_scaled <- elascer(clin_sub_aumc_df_m$PRO_value)
    
    user_dc_df$CCP_dates <- user_dc_df$date
    if (all(is.na(user_dc_df$complexity_peaks))) {
      print(paste("For user ==", user, "there were ZERO complexity peaks"))
      user_failed_list <- c(user_failed_list, user)
    }else{
      user_dc_df[user_dc_df$complexity_peaks < 0.5, "CCP_dates"] <- NA
      }
      
      # make sure the outcomes_list and the user outcomes data frame are sorted the same way otherwise stuff will not match correctly
      clin_sub_aumc_df_m <- clin_sub_aumc_df_m[order(clin_sub_aumc_df_m$PRO_group, clin_sub_aumc_df_m$PRO_name), ]
      outcomes_list <- sort(outcomes_list)
      clinically_relevant_bool_col <- c()
      for (i in 1:length(outcomes_list)) {
        diff_list <- c()
        current_outcome_vector <- clin_sub_aumc_df_m[grep(outcomes_list[i], clin_sub_aumc_df_m$PRO_name), "PRO_value"]
        for (j in 2: length(current_outcome_vector)-1) {
          diff_list <- append(diff_list, current_outcome_vector[j + 1] - current_outcome_vector[j])
        }
        
        if (outcomes_list[i] %in% c("NHPT_ND_avg", "NHPT_D_avg", "TWT_avg")){
          clinically_relevant_bool <- abs(diff_list) > diff_list[1] * outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"]
          
          clinically_relevant_bool <- c(FALSE, clinically_relevant_bool)
          for (j in 2: length(clinically_relevant_bool)) {
            if (clinically_relevant_bool[j] & !is.na(clinically_relevant_bool[j])) {
              clinically_relevant_bool[j - 1] <- TRUE
            }
          }
          clinically_relevant_bool_col <- append(clinically_relevant_bool_col, clinically_relevant_bool)
          
      } else{
          clinically_relevant_bool <- abs(diff_list) >= outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"]
          
          clinically_relevant_bool <- c(FALSE, clinically_relevant_bool)
          for (j in 2: length(clinically_relevant_bool)) {
            if (clinically_relevant_bool[j] & !is.na(clinically_relevant_bool[j])) {
              clinically_relevant_bool[j - 1] <- TRUE
            }
          }
          clinically_relevant_bool_col <- append(clinically_relevant_bool_col, clinically_relevant_bool)
        }
      }
      
      clin_sub_aumc_df_m$clinically_relevant_boolean <- clinically_relevant_bool_col
      
      # tiff(paste0("/home/james/Data_Science/data-science-project/research-papers/2020-NLTSA/latex/Images/", user, "_main_results.tiff"), units="in", width=10, height=4, res=400)
      # png(paste0("/home/james/Data_Science/data-science-project/research-papers/2020-NLTSA/latex/Images/", user, "_main_results.png"), units="in", width=10, height=4, res=400)
      date_selection <- unique(clin_sub_aumc_df_m[as.character(clin_sub_aumc_df_m$visitdate_cat) %in% questionnare_date_cols, "date"])
      plot <- ggplot(clin_sub_aumc_df_m) +
        geom_point(data=clin_sub_aumc_df_m[clin_sub_aumc_df_m$clinically_relevant_boolean, ],
                   aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group)) +
        geom_line(data=clin_sub_aumc_df_m[clin_sub_aumc_df_m$clinically_relevant_boolean, ],
                   aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group#, size = PRO_group
                       )) +
        geom_point(data=na.omit(clin_sub_aumc_df_m[!clin_sub_aumc_df_m$clinically_relevant_boolean, ]),
                   aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group), alpha=0.1) +
        geom_line(data=na.omit(clin_sub_aumc_df_m[!clin_sub_aumc_df_m$clinically_relevant_boolean, ]),
                   aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group), alpha=0.1) +
        geom_line(data = user_dc_df, aes(date, dynamic_complexity_sum), size=0.5) +
        geom_point(data = user_dc_df, aes(date, dynamic_complexity_sum), shape = 16, size=2) +
  
        xlim(min(aumc_df_m[aumc_df_m$user_id == user, "date"], na.rm = TRUE),
        max(aumc_df_m[aumc_df_m$user_id == user, "date"], na.rm = TRUE)) +
        ylim(0, max(elascer(user_dc_df[user_dc_df$user_id == user, "PRO_value"]), na.rm = TRUE)) +
        geom_vline(xintercept = user_dc_df[user_dc_df$complexity_peaks > 0, "date"], col="red", lty=2) +
        scale_x_date(breaks = date_selection, 
                     labels = c("m00", "m002", "m03", "m06", "m09", "m12")[1:length(date_selection)]) +
  
  
        labs(x = "Date", y = "Measure Change (Scaled)") +
        scale_color_discrete(name="Outcome measure") +
        scale_color_manual("Outcome Measures",
                            breaks = names(custom_cmap),
                            values = custom_cmap,
                            ) +
        # scale_size_manual(values = c(rep(0.3, 4), 1.5, rep(0.3, 7))) +
        theme_minimal() +
        # theme(axis.text.x = element_text(angle = 0), legend.title = element_blank())
        theme(axis.text.x = element_text(angle = 60, hjust = 0.9, size=10),
              axis.text.y = element_text(size=10), legend.title = element_blank(),
              plot.title = element_text(hjust = 0.5)) +
        ggtitle(paste("Change in time series data for user ==", user))
      
      print(plot)
      
      ggsave(paste0("/home/james/Data_Science/data-science-project/research-papers/2020-NLTSA/latex/Images/", user, "_main_results.eps"))
      # dev.off()

  }
}
[1] "mri_change"
[1] "mri_no_change"
[1] "hc_users"
[1] "For user == 381 there were ZERO complexity peaks"
[1] "For user == 391 there were ZERO complexity peaks"
[1] "For user == 403 there were ZERO complexity peaks"
[1] "For user == 404 there were ZERO complexity peaks"
[1] "For user == 405 there were ZERO complexity peaks"
[1] "For user == 423 there were ZERO complexity peaks"
[1] "For user == 439 there were ZERO complexity peaks"
[1] "For user == 448 there were ZERO complexity peaks"

Show the missingness within each user and per group

for (users_list_number in 1:length(list_of_user_index_lists)) {
  user_list_name<-names(list_of_user_index_lists[users_list_number])
  users_selection_index<-list_of_user_index_lists[[user_list_name]]
  
  print(user_list_name)
  
  # use the chosen index to get a list of users
  user_selection <- aumc_df$user_id[users_selection_index]

  ## Show missingness percentages
  all_missing_per_user <- data.frame()
  for (user in user_selection) {
    current_user_missing <- data.frame(user_id = user, t(colMeans(is.na(key_df[key_df$user_id == user, feature_selection]))*100))
    all_missing_per_user <- rbind(all_missing_per_user, current_user_missing)
  }
  
  print("All percentage missingness per user per feature")
  print(all_missing_per_user)
  
  print("Median percentage missingness per user")
  median_percent_miss <- data.frame(user_id = user_selection, median_percentage_missing = rowMedians(as.matrix(all_missing_per_user[, feature_selection])))
  print(median_percent_miss)
  
  print("Median of the Median percentage missingness per user")
  print(median(median_percent_miss$median_percentage_missing, na.rm=TRUE))
  cat("\n")

}
[1] "mri_change"
[1] "All percentage missingness per user per feature"
[1] "Median percentage missingness per user"
[1] "Median of the Median percentage missingness per user"
[1] 0.536193

[1] "mri_no_change"
[1] "All percentage missingness per user per feature"
[1] "Median percentage missingness per user"
[1] "Median of the Median percentage missingness per user"
[1] 2.036246

[1] "hc_users"
[1] "All percentage missingness per user per feature"
[1] "Median percentage missingness per user"
[1] "Median of the Median percentage missingness per user"
[1] 1.011029

Plot the CRD and CCP of an example user

LS0tCnRpdGxlOiAiU3VwcGxlbWVudGFsIG1hdGVyaWFsOiBFYXJseS1XYXJuaW5nIFNpZ25hbHMgZm9yIERpc2Vhc2UgQWN0aXZpdHkgaW4gUGF0aWVudHMgRGlhZ25vc2VkIHdpdGggTXVsdGlwbGUgU2NsZXJvc2lzIGJhc2VkIG9uIEtleXN0cm9rZSBEeW5hbWljcyIKc3VidGl0bGU6ICJUaGUgZm9sbG93aW5nIG5vdGVib29rIGlzIHRoZSBjb25zdHJ1Y3Rpb24gb2YgY29tcGxleGl0eSBtZWFzdXJlcyBhbmQgdGhlIGZpbmFsIHJlc3VsdHMgcmVsYXRpdmUgdG8gX19zdWJzZWN0aW9uIElJSS5DX18gYW5kIF9fc2VjdGlvbiBJVl9fLiAiCmF1dGhvcjogCi0gIkphbWVzIFR3b3NlIgotICJqYW1lc0BuZXVyb2Nhc3QubmwiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKIyMjIyBSZWFkIGluIG5lY2Vzc2FyeSBsaWJyYXJpZXMsIGZ1bmN0aW9ucyBhbmQgbG9vayB1cCB0YWJsZXMKCmBgYHtyIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkocWdyYXBoKQpsaWJyYXJ5KGNhc25ldCkKbGlicmFyeShwbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KHBzeWNoKQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHBhcmFsbGVsKQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkobWF0cml4U3RhdHMpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGNvd3Bsb3QpCgojIHNhdmUgdGhlIG9yaWdpbmFsIHBsb3R0aW5nIHNwYWNlIHBhcmFtZXRlcnMKb2dfcGFyIDwtIHBhcigpCmBgYAoKIyMjIyMgU2hvdyB0aGUgc2Vzc2lvbiBpbmZvcm1hdGlvbgpgYGB7cn0KZGV2dG9vbHM6OnNlc3Npb25faW5mbygpCmBgYAoKCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgcmVhZCBpbiBoZWxwZnVsIGZ1bmN0aW9ucyBhbmQgbGlzdHMKc291cmNlKCJ+L0RhdGFfU2NpZW5jZS9kYXRhLXNjaWVuY2UtcHJvamVjdC9yZXNlYXJjaC1wYXBlcnMvMjAyMC1OTFRTQS9jb2RlL2F1bWNfZXh0cmFzLlIiKQpoaWdoX2Nvcl9mZWF0dXJlcyA8LSByZWFkLmNzdigifi9EYXRhX1NjaWVuY2UvZGF0YS1zY2llbmNlLXByb2plY3QvcmVzZWFyY2gtcGFwZXJzLzIwMjAtQVVtYy1sb25naXR1ZGluYWwtYW5hbHlzaXMvY29kZS91c2VmdWxfc2NyaXB0cy9zZWxlY3RlZF9mZWF0dXJlc193aXRoX291dGNvbWVfZGYuY3N2IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzPTEpCmBgYAoKIyMjIyBSZWFkIGluIGRhdGEKCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKI1JFQUQgSU4gREFUQT09PT0KQVdTX0tFWTwtc2V0X2Vudl9BV1MoIn4vRGF0YV9TY2llbmNlL2RhdGEtc2NpZW5jZS1wcm9qZWN0LyIpCiMgS2V5c3Ryb2tlIGRhdGEKQVdTX2RhdGE8LWdldF9kYXRhX0FXU19uZXVyb192bV9zaGFyZWQoeW91cl9BV1NfZm9sZGVyPSJKYW1lcy8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuX2RhdGFfc2V0PSJWdW1jX3VzZXJzXzM2M181MDBfZGF5XzIwMTgwODIxXzIwMjAwMzEzX05FV19BR0dzLmNzdiIpCmtleV9kZiA8LSBBV1NfZGF0YSRBV1NfZGF0YQoKIyBJRCBkYXRhCkFXU19kYXRhPC1nZXRfZGF0YV9BV1NfbmV1cm9fdm1fc2hhcmVkKHlvdXJfQVdTX2ZvbGRlcj0iVlVtYy8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuX2RhdGFfc2V0PSJ2dW1jX2NvZGVfbmV1cm9rZXlzX2NvZGVfdXNlcl9pZC5jc3YiKQppZF9kZiA8LSBBV1NfZGF0YSRBV1NfZGF0YQoKIyBBVW1jIGRhdGEKQVdTX2RhdGE8LWdldF9kYXRhX0FXU19uZXVyb192dW1jX2thX2hvb19wcml2YXRlKHlvdXJfQVdTX2ZvbGRlcj0iIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbl9kYXRhX3NldD0iMjAyMDA1MTNfQVBQU01TX2V4cG9ydF9EU19GcmllbmRseS5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwPSI7Iiwgcm93Lm5hbWVzPU5VTEwpCmF1bWNfZGYgPC0gQVdTX2RhdGEkQVdTX2RhdGEKCiMgbWVyZ2UgdXNlcl9pZCB3aXRoIHRoZSBhdW1jX2RmCmF1bWNfZGYgPC0gbWVyZ2UoaWRfZGZbLCAtYygxLCAyKV0sIGF1bWNfZGYsIGJ5Lng9IlZVbWMuY29kZSIsIGJ5Lnk9IlZVbWNfY29kZSIpWywgLWMoMSldCgojIHJlbmFtZSBhbmQgb3JkZXIgYXVtY19kZiBvbiB1c2VyX2lkCm5hbWVzKGF1bWNfZGYpW25hbWVzKGF1bWNfZGYpID09ICdOZXVyb2tleXMuSUQnXSA8LSAndXNlcl9pZCcKYXVtY19kZiA8LSBhdW1jX2RmW29yZGVyKGF1bWNfZGYkdXNlcl9pZCksIF0KCiMgbWVyZ2Ugc2V0cyB0aGUgbWVyZ2UgY29sdW1uIHRvIGJlIHJvdyBuYW1lcywgcmVzZXQgdGhpcyBmb2xsb3dpbmcgcmVvcmRlcgpyb3duYW1lcyhhdW1jX2RmKSA8LSAxOm5yb3coYXVtY19kZikKCiMgcmVuYW1lIHN0cmF5IHZpc2l0IGRhdGUgY29sdW1uCm5hbWVzKGF1bWNfZGYpW25hbWVzKGF1bWNfZGYpID09ICdtMDBfdmlzaXRfZGF0ZSddIDwtICdtMDBfdmlzaXRkYXRlJwoKIyByZXBsYWNlIGRlZmF1bHQgdmFsdWVzIHdpdGggTkEKYXVtY19kZlthdW1jX2RmID09ICIxLTEtMjk5OSJdIDwtIE5BCmF1bWNfZGZbYXVtY19kZiA8IC05NF0gPC0gTkEKCiMgY29udmVydCB0aGUgdGltZXN0YW1wIGFuZCBkYXRlIGNvbHVtbnMgdG8gYSBodW1hbiByZWFkYWJsZSBmb3JtYXQKIyBrZXlfZGYkdGltZXN0YW1wIDwtIGFzLlBPU0lYY3QoKGtleV9kZiR0aW1lc3RhbXAgKyBrZXlfZGYkdGltZVpvbmVPZmZzZXQpLzEwMDAsIGZvcm1hdCA9ICIlWS0lbS0lZCIsIHR6PSJ1dGMiLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpCmtleV9kZiR0aW1lc3RhbXAgPC0gYXMuUE9TSVhjdChrZXlfZGYkdGltZXN0YW1wLzEwMDAgKyBrZXlfZGYkdGltZVpvbmVPZmZzZXQsIGZvcm1hdCA9ICIlWS0lbS0lZCIsIHR6PSJ1dGMiLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpCiMga2V5X2RmJHRpbWVzdGFtcCA8LSBrZXlfZGYkdGltZXN0YW1wICsga2V5X2RmJHRpbWVab25lT2Zmc2V0CmRhdGVfY29scyA8LSBjb2xuYW1lcyhhdW1jX2RmICU+JSBzZWxlY3QobWF0Y2hlcygiZGF0ZSIpKSkKdmlzaXRkYXRlX2NvbHMgPC0gY29sbmFtZXMoYXVtY19kZiAlPiUgc2VsZWN0KG1hdGNoZXMoInZpc2l0ZGF0ZSIpKSkKcXVlc3Rpb25uYXJlX2RhdGVfY29scyA8LSBjb2xuYW1lcyhhdW1jX2RmICU+JSBzZWxlY3QobWF0Y2hlcygiUXVlc3Rpb25uYWlyZXNfY29tcCIpKSkKCmF1bWNfZGZbZGF0ZV9jb2xzXSA8LSBhcHBseShhdW1jX2RmW2RhdGVfY29sc10sIDIsIGFzLlBPU0lYbHQsIGZvcm1hdCA9ICIlZC0lbS0lWSIsIHR6PSJ1dGMiLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpCgoKYGBgCgojIyMjIFByZXBhcmUgdGhlIE91dGNvbWUgbWVhc3VyZXMKCmBgYHtyLCBlY2hvPVRSVUV9CmZvciAodGltZXBvaW50IGluIGMoJ20wMCcsICdtMDMnLCAnbTA2JywgJ20wOScsICdtMTInKSkgewogIGF1bWNfZGZbLCBwYXN0ZTAodGltZXBvaW50LCAiX05IUFRfRF9hdmciKV0gPC0gcm93TWVhbnMoYXVtY19kZlssIGMocGFzdGUwKHRpbWVwb2ludCwgIl9OSFBUX0QxIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9OSFBUX0QyIikpXSwgbmEucm09VCkKICBhdW1jX2RmWywgcGFzdGUwKHRpbWVwb2ludCwgIl9OSFBUX05EX2F2ZyIpXSA8LSByb3dNZWFucyhhdW1jX2RmWywgYyhwYXN0ZTAodGltZXBvaW50LCAiX05IUFRfTkQxIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9OSFBUX05EMiIpKV0sIG5hLnJtPVQpCiAgYXVtY19kZlssIHBhc3RlMCh0aW1lcG9pbnQsICJfTkhQVF9CSF9hdmciKV0gPC0gcm93TWVhbnMoYXVtY19kZlssIGMocGFzdGUwKHRpbWVwb2ludCwgIl9OSFBUX0RfYXZnIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9OSFBUX05EX2F2ZyIpKV0sIG5hLnJtPVQpCiAgYXVtY19kZlssIHBhc3RlMCh0aW1lcG9pbnQsICJfVFdUX2F2ZyIpXSA8LSByb3dNZWFucyhhdW1jX2RmWywgYyhwYXN0ZTAodGltZXBvaW50LCAiX1RXVF8xIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9UV1RfMiIpKV0sIG5hLnJtPVQpCn0KCmBgYAoKIyMjIyBHZXQgYSBsaXN0IG9mIGFsbCB0aGUgb3V0Y29tZSBtZWFzdXJlcyBvZiBpbnRlcmVzdCBhdCBlYWNoIHRpbWUgcG9pbnQKCmBgYHtyfQpvdXRjb21lc19vZl9pbnRlcmVzdF9scyA8LSBjKCkKZm9yIChvdXRjb21lIGluIG91dGNvbWVzX2xpc3QpIHsKICBvdXRjb21lc19vZl9pbnRlcmVzdF9scyA8LSBhcHBlbmQob3V0Y29tZXNfb2ZfaW50ZXJlc3RfbHMsIGNvbG5hbWVzKGF1bWNfZGYgJT4lIHNlbGVjdChtYXRjaGVzKG91dGNvbWUpKSkpCn0KCmBgYAoKIyMjIyBGZWF0dXJlcyB1c2VkIGluIHRoZSBjcmVhdGlvbiBvZiB0aGUgY29tcGxleGl0eSBtZWFzdXJlcyAodGFrZW4gUlFBIHB5dGhvbiBub3RlYm9vaykKCmBgYHtyfQojIGZlYXR1cmVzIHdpdGggaGlnaGVzdCBycWEgbWVhc3VyZXMgKG1vc3QgaW50ZXJlc3RpbmcgTkxUU0Egd2lzZSkKZmVhdHVyZV9zZWxlY3Rpb24gPC0gYygncG9zdF9jb3JyZWN0aW9uX3Nsb3dpbmdfTUVBTl8yQ0QnLAogJ3ByZV9jb3JyZWN0aW9uX3Nsb3dpbmdfTUVBTl8yQ0QnLAogJ2hvbGRfdGltZV9TVEQnLAogJ2ZsaWdodF90aW1lX01FRElBTicsCiAnZmxpZ2h0X3RpbWVfTUVBTl9BQlNfQ0hBTkdFJywKICdob2xkX3RpbWVfU0tFVycsCiAnY29ycmVjdGlvbl9kdXJhdGlvbl9NRUFOXzJDRCcsCiAncG9zdF9jb3JyZWN0aW9uX3Nsb3dpbmdfUF9BVVRPX0NPUlInLAogJ2FmdGVyX3B1bmN0dWF0aW9uX3BhdXNlX01FQU5fQ0hBTkdFJywKICdhZnRlcl9wdW5jdHVhdGlvbl9wYXVzZV9NRUFOXzJDRCcsCiAnc2Vzc2lvbl9kdXJhdGlvbl9NRUFOXzJDRCcsCiAnYWZ0ZXJfcHVuY3R1YXRpb25fcGF1c2VfTUVESUFOJywKICdmbGlnaHRfdGltZV9NQVgnKQoKZmVhdHVyZV9zZWxlY3Rpb24KCmBgYAoKIyMjIyBJZGVudGlmeSB0aGUgZGlmZmVyZW50IGdyb3VwcyAoY2hhbmdlIGluIE1SSSwgbm8gY2hhbmdlIGluIE1SSSBhbmQgSEMgdXNlcnMpIGFuZCBwcmVwIHRoZSBkYXRhIGZvciB0aGUgYW5hbHlzaXMKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQptcmlfY29scyA8LSBjb2xuYW1lcyhhdW1jX2RmICU+JSBzZWxlY3QobWF0Y2hlcygibXJpX2VuaGFuY2luZyIpKSkKCmRpZmZfZGYgPC0gTkEKZm9yIChpIGluIHNlcSgxLCBsZW5ndGgobXJpX2NvbHMpLTEpKSB7CiAgZGlmZl9kZiA8LSBjYmluZChkaWZmX2RmLCBhdW1jX2RmW21yaV9jb2xzW2krMV1dIC0gYXVtY19kZlttcmlfY29sc1tpXV0pCn0KZGlmZl9kZiA8LSBkaWZmX2RmWywgMjpuY29sKGRpZmZfZGYpXQpkaWZmX2RmIDwtIGRpZmZfZGZbcm93U3Vtcyhpcy5uYShkaWZmX2RmKSkgPCAzLCBdCgojIyBTUEVDSUZZIFRIRSBJTkRJQ0VTIFlPVSBXSVNIIFRPIElOVkVTVElHQVRFCnJvd19hbW91bnQgPC0gNTgKIyB1c2VyIGluZGljZXMgd2hpY2ggaGF2ZSBhIGNoYW5nZSBpbiBtcmkKbXJpX2NoYW5nZV9pbmRleCA8LSBhcy5pbnRlZ2VyKG5hbWVzKHdoaWNoKHJvd1N1bXMoYWJzKGRpZmZfZGYpLCBuYS5ybT1UUlVFKSA+IDApKSkKbXJpX2NoYW5nZV9scyA8LSBhdW1jX2RmJHVzZXJfaWRbbXJpX2NoYW5nZV9pbmRleF0Kc2hvcnRfbXJpX2NoYW5nZV9scyA8LSBjKCkKZm9yICh1c2VyIGluIG1yaV9jaGFuZ2VfbHMpIHsKICBpZiAobnJvdyhrZXlfZGZba2V5X2RmJHVzZXJfaWQgPT0gdXNlciwgXSkgPiByb3dfYW1vdW50KSB7CiAgICBzaG9ydF9tcmlfY2hhbmdlX2xzIDwtIGMoc2hvcnRfbXJpX2NoYW5nZV9scywgdXNlcikKICB9Cn0KbXJpX2NoYW5nZV9pbmRleCA8LSB3aGljaChhdW1jX2RmJHVzZXJfaWQgJWluJSBzaG9ydF9tcmlfY2hhbmdlX2xzKQojIHVzZXIgaW5kaWNlcyB3aGljaCBkb24ndCBoYXZlIGEgY2hhbmdlIGluIG1yaQptcmlfbm9fY2hhbmdlX2luZGV4IDwtIGFzLmludGVnZXIobmFtZXMod2hpY2gocm93U3VtcyhhYnMoZGlmZl9kZiksIG5hLnJtPVRSVUUpID09IDApKSkKbXJpX25vX2NoYW5nZV9scyA8LSBhdW1jX2RmJHVzZXJfaWRbbXJpX25vX2NoYW5nZV9pbmRleF0Kc2hvcnRfbXJpX25vX2NoYW5nZV9scyA8LSBjKCkKZm9yICh1c2VyIGluIG1yaV9ub19jaGFuZ2VfbHMpIHsKICBpZiAobnJvdyhrZXlfZGZba2V5X2RmJHVzZXJfaWQgPT0gdXNlciwgXSkgPiByb3dfYW1vdW50KSB7CiAgICBzaG9ydF9tcmlfbm9fY2hhbmdlX2xzIDwtIGMoc2hvcnRfbXJpX25vX2NoYW5nZV9scywgdXNlcikKICB9Cn0KbXJpX25vX2NoYW5nZV9pbmRleCA8LSB3aGljaChhdW1jX2RmJHVzZXJfaWQgJWluJSBzaG9ydF9tcmlfbm9fY2hhbmdlX2xzKQojIGhlYWx0aHkgY29udHJvbHMKaGNfdXNlcnNfaW5kZXggPC0gd2hpY2goYXVtY19kZiRkaWFnbm9zaXNfbXMgPT0gMCkKaGNfdXNlcnNfbHMgPC0gYXVtY19kZiR1c2VyX2lkW2hjX3VzZXJzX2luZGV4XQpzaG9ydF9oY191c2Vyc19scyA8LSBjKCkKZm9yICh1c2VyIGluIGhjX3VzZXJzX2xzKSB7CiAgaWYgKG5yb3coa2V5X2RmW2tleV9kZiR1c2VyX2lkID09IHVzZXIsIF0pID4gcm93X2Ftb3VudCkgewogICAgc2hvcnRfaGNfdXNlcnNfbHMgPC0gYyhzaG9ydF9oY191c2Vyc19scywgdXNlcikKICB9Cn0KaGNfdXNlcnNfaW5kZXggPC0gd2hpY2goYXVtY19kZiR1c2VyX2lkICVpbiUgc2hvcnRfaGNfdXNlcnNfbHMpCgpsaXN0X29mX3VzZXJfaW5kZXhfbGlzdHM9YyhkYXRhLmZyYW1lKG1yaV9jaGFuZ2U9bXJpX2NoYW5nZV9pbmRleCksCiAgICAgICAgICAgZGF0YS5mcmFtZShtcmlfbm9fY2hhbmdlPW1yaV9ub19jaGFuZ2VfaW5kZXgpLAogICAgICAgICAgIGRhdGEuZnJhbWUoaGNfdXNlcnM9aGNfdXNlcnNfaW5kZXgpKQoKcHJlcHBlZF9kZnNfbGlzdCA8LSBsaXN0KCkKZm9yICh1c2Vyc19saXN0X251bWJlciBpbiAxOmxlbmd0aChsaXN0X29mX3VzZXJfaW5kZXhfbGlzdHMpKXsKICB1c2VyX2xpc3RfbmFtZTwtbmFtZXMobGlzdF9vZl91c2VyX2luZGV4X2xpc3RzW3VzZXJzX2xpc3RfbnVtYmVyXSkKICB1c2Vyc19zZWxlY3Rpb25faW5kZXg8LWxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0c1tbdXNlcl9saXN0X25hbWVdXQogIAogIHByaW50KHVzZXJfbGlzdF9uYW1lKQogIAogICMgdXNlIHRoZSBjaG9zZW4gaW5kZXggdG8gZ2V0IGEgbGlzdCBvZiB1c2VycwogIHVzZXJfc2VsZWN0aW9uIDwtIGF1bWNfZGYkdXNlcl9pZFt1c2Vyc19zZWxlY3Rpb25faW5kZXhdCiAgCiAgcHJpbnQodXNlcl9zZWxlY3Rpb24pCiAgCiAgIyBjcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGF1bWNfZGYgd2hpY2ggaW5jbHVkZXMgb25seSB0aGUgdXNlcnMgb2YgaW50ZXJlc3QKICBtaXNzaW5nX2RmIDwtIGRhdGEuZnJhbWUoInNhbXBsZSBzaXplIiA9IGNvbFN1bXMoIWlzLm5hKGF1bWNfZGZbYXVtY19kZiRkaWFnbm9zaXNfbXMgPT0gMSwgYygidXNlcl9pZCIsIG1yaV9jb2xzLCB2aXNpdGRhdGVfY29scyldKSkpCiAgCiAgIyBzdWJzZXQgcmVsZXZhbnQgdXNlcnMnIGtleXN0cm9rZSBkYXRhCiAga2V5X3N1Yl9kZiA8LSBrZXlfZGZba2V5X2RmJHVzZXJfaWQgJWluJSB1c2VyX3NlbGVjdGlvbiwgXQogICMgb3JkZXIgYmFzZWQgb24gdXNlciBpZCBhbmQgdGltZXN0YW1wCiAga2V5X3N1Yl9kZiA8LSBrZXlfc3ViX2RmW29yZGVyKGtleV9zdWJfZGYkdXNlcl9pZCwga2V5X3N1Yl9kZiR0aW1lc3RhbXApLCBdCiAgCiAgCiAgIyMgQ3JlYXRpbmcgYSBkYXRhIGZyYW1lIG9mIHRoZSBjb21wbGV4aXR5IG1lYXN1cmVzIHBlciB1c2VyCgogICMjIFdyaXRpbmcgY29kZSB3aGljaCB3aWxsIHBhcmFsbGVsaXplIHRoZSBFV1MgY2FsY3VsYXRpb25zCiAgZGZzX2xpc3QgPC0gZGZfdXNlcl9saXN0KGRmID0ga2V5X3N1Yl9kZiwgZmVhdHVyZXMgPSBjKCJ1c2VyX2lkIiwgInRpbWVzdGFtcCIsIGZlYXR1cmVfc2VsZWN0aW9uKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXJfY29sdW1uX25hbWUgPSAidXNlcl9pZCIsIHVzZXJzID0gdXNlcl9zZWxlY3Rpb24pCiAgY29tcGxleGl0eV9kZnNfbGlzdCA8LSBtY2xhcHBseShkZnNfbGlzdCwgRVdTX2NhbGMpICMgbWNsYXBwbHkgaXMgdGhlIHBhcmFsbGVsaXplZCB2ZXJzaW9uIG9mIGxhcHBseQogIAogICMgZmlsdGVyIG9ubHkgdGhlIGRhdGEgZnJhbWVzIGluIHRoZSBsaXN0IChkcm9wIHdoZXJlIHRoZXJlIHdhcyBub3QgZW5vdWdoIGRhdGEpCiAgY29tcGxleGl0eV9kZnNfbGlzdCA8LSBGaWx0ZXIoZnVuY3Rpb24oeCkgaXMuZGF0YS5mcmFtZSh4KVtbMV1dID4gMCwgY29tcGxleGl0eV9kZnNfbGlzdCkKICAKICBjb21wbGV4X2RmIDwtIHJiaW5kLmZpbGwoY29tcGxleGl0eV9kZnNfbGlzdCkKCiAgIyBtZXJnZSB0aGUgY29tcGxleCBkZiBiYWNrIHRvIHRoZSBrZXlzdHJva2UgZGYKICBrZXlfY29tcGxleF9kZiA8LSBtZXJnZShrZXlfc3ViX2RmWywgYygidXNlcl9pZCIsICJ0aW1lc3RhbXAiLCBmZWF0dXJlX3NlbGVjdGlvbildLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBjb21wbGV4X2RmLCBieSA9IGMoInVzZXJfaWQiLCAidGltZXN0YW1wIikpIAogICMgcmVsYWJlbCB0aGUgMTAncyB0byAxJ3MgaW4gdGhlIGNvbXBsZXhpdHlfcGVha3MgZmFjdG9yIHRvIGF2b2lkIGlzc3VlcyBsYXRlcgogIGtleV9jb21wbGV4X2RmJGNvbXBsZXhpdHlfcGVha3Nba2V5X2NvbXBsZXhfZGYkY29tcGxleGl0eV9wZWFrcyA9PSAxMF0gPC0gMQogIAogICMgYXVtY19vdXRjb21lX3NlbGVjdG9yIDwtIG1yaV9jb2xzCiAgYXVtY19vdXRjb21lX3NlbGVjdG9yIDwtIG91dGNvbWVzX29mX2ludGVyZXN0X2xzCiAgIyBjcmVhdGUgYSBtZWx0ZWQgdmVyc2lvbiBvZiB0aGUgYXVtY19kZiB3aXRoIG9ubHkgdGhlIHZhcmlhYmxlcyBvZiBpbnRlcmVzdAogIGF1bWNfZGZfbV8xIDwtIG1lbHQoYXVtY19kZltjKCJ1c2VyX2lkIiwgYXVtY19vdXRjb21lX3NlbGVjdG9yKV0sIGlkLnZhcnM9InVzZXJfaWQiLAogICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJQUk9fbmFtZSIsIHZhbHVlLm5hbWUgPSAiUFJPX3ZhbHVlIikKICBhdW1jX2RmW2MoZGF0ZV9jb2xzKV0gPC0gYXBwbHkoYXVtY19kZltjKGRhdGVfY29scyldLCAyLCBhcy5jaGFyYWN0ZXIpCiAgYXVtY19kZl9tXzIgPC0gbWVsdChhdW1jX2RmW2MoInVzZXJfaWQiLCBkYXRlX2NvbHMpXSwgaWQudmFycz0idXNlcl9pZCIsIAogICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJ2aXNpdGRhdGVfY2F0IiwgdmFsdWUubmFtZSA9ICJkYXRlIikKICBhdW1jX2RmX21fMiRkYXRlIDwtIGFzLkRhdGUoYXVtY19kZl9tXzIkZGF0ZSkKICAKICBhdW1jX2RmX21fMSA8LSBhdW1jX2RmX21fMSAlPiUgbXV0YXRlKHRpbWVfY29kZSA9IHN1YnN0cihQUk9fbmFtZSwxTCw0TCkpCiAgYXVtY19kZl9tXzIgPC0gYXVtY19kZl9tXzIgJT4lIG11dGF0ZSh0aW1lX2NvZGUgPSBzdWJzdHIodmlzaXRkYXRlX2NhdCwxTCw0TCkpCiAgYXVtY19kZl9tIDwtIG1lcmdlKGF1bWNfZGZfbV8xLCBhdW1jX2RmX21fMiwgYnkgPSBjKCJ1c2VyX2lkIiwgInRpbWVfY29kZSIpKQogIAogICMgc2VsZWN0IHRoZSBvdXRjb21lIG1lYXN1cmVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIHF1ZXN0aW9ubmFpcmUvIHZpc2l0ZGF0ZXMgdGhlbiByZWFzc2lnbgogIGF1bWNfZGZfbV92cyA8LSAgYXVtY19kZl9tICU+JSAKICAgIGZpbHRlcihzdHJfZGV0ZWN0KGF1bWNfZGZfbSRQUk9fbmFtZSwgcGFzdGUodmlzaXRkYXRlX291dGNvbWVzLCBjb2xsYXBzZSA9ICJ8IikpICYgCiAgICAgICAgICAgICBzdHJfZGV0ZWN0KGF1bWNfZGZfbSR2aXNpdGRhdGVfY2F0LCBwYXN0ZSh2aXNpdGRhdGVfY29scywgY29sbGFwc2UgPSAifCIpKSkKICAKICBhdW1jX2RmX21fcXMgPC0gIGF1bWNfZGZfbSAlPiUgCiAgICBmaWx0ZXIoc3RyX2RldGVjdChhdW1jX2RmX20kUFJPX25hbWUsIHBhc3RlKHF1ZXN0aW9ubmFpcmVfZGF0ZV9vdXRjb21lcywgY29sbGFwc2UgPSAifCIpKSAmIAogICAgICAgICAgICAgc3RyX2RldGVjdChhdW1jX2RmX20kdmlzaXRkYXRlX2NhdCwgcGFzdGUocXVlc3Rpb25uYXJlX2RhdGVfY29scywgY29sbGFwc2UgPSAifCIpKSkKICBhdW1jX2RmX20gPC0gcmJpbmQoYXVtY19kZl9tX3ZzLCBhdW1jX2RmX21fcXMpCiAgCiAgIyBtZXJnZSB0aGUgbWVsdGVkIGF1bWNfZGYgd2l0aCB0aGUga2V5X2NvbXBsZXhfZGYKICBrZXlfY29tcGxleF9kZiRkYXRlIDwtIGFzLkRhdGUoa2V5X2NvbXBsZXhfZGYkdGltZXN0YW1wKQogIGRmLm1vZCA8LSBtZXJnZShrZXlfY29tcGxleF9kZiwgYXVtY19kZl9tLCBieSA9IGMoInVzZXJfaWQiLCAiZGF0ZSIpLCBhbGwueD1UUlVFLCBhbGwueT1GQUxTRSkKICAKICBwcmVwcGVkX2Rmc19saXN0W1t1c2VyX2xpc3RfbmFtZV1dIDwtIGRmLm1vZAogIAp9CgoKYGBgCgojIyMjIFBsb3QgdGhlIER5bmFtaWMgQ29tcGxleGl0eSwgQ3VtdWxhdGl2ZSBDb21wbGV4aXR5IFBlYWtzLCBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgZGF0YSBmcm9tIHRoZSBHZCBlbmhhbmNpbmcgbGVzaW9ucyBhbmQgUGF0aWVudCBSZXBvcnRlZCBPdXRjb21lIE1lYXN1cmVzCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnVzZXJfZmFpbGVkX2xpc3QgPC0gYygpCmZvciAodXNlcnNfbGlzdF9udW1iZXIgaW4gMTpsZW5ndGgobGlzdF9vZl91c2VyX2luZGV4X2xpc3RzKSl7CiAgdXNlcl9saXN0X25hbWU8LW5hbWVzKGxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0c1t1c2Vyc19saXN0X251bWJlcl0pCiAgdXNlcnNfc2VsZWN0aW9uX2luZGV4PC1saXN0X29mX3VzZXJfaW5kZXhfbGlzdHNbW3VzZXJfbGlzdF9uYW1lXV0KICAKICBwcmludCh1c2VyX2xpc3RfbmFtZSkKICAKICBwcmVwcGVkX2RmIDwtIHByZXBwZWRfZGZzX2xpc3RbW3VzZXJfbGlzdF9uYW1lXV0KICAgIAogICMgdXNlIHRoZSBjaG9zZW4gaW5kZXggdG8gZ2V0IGEgbGlzdCBvZiB1c2VycwogIHVzZXJfc2VsZWN0aW9uIDwtIGF1bWNfZGYkdXNlcl9pZFt1c2Vyc19zZWxlY3Rpb25faW5kZXhdCiAgCiAgZm9yICh1c2VyIGluIHVzZXJfc2VsZWN0aW9uKSB7CgogICAgdXNlcl9kY19kZiA8LSBwcmVwcGVkX2RmW3ByZXBwZWRfZGYkdXNlcl9pZCA9PSB1c2VyLCBdCiAgICBzdWJfYXVtY19kZl9tIDwtIGF1bWNfZGZfbVthdW1jX2RmX20kdXNlcl9pZCA9PSB1c2VyLCBdCiAgICB0ZW1wIDwtIGF1bWNfZGZfbVthdW1jX2RmX20kdXNlcl9pZCAlaW4lIHVzZXJzX3NlbGVjdGlvbl9pbmRleCwgXQogICAgCiAgICBzZWxlY3RlZF9vdXRjb21lc19saXN0IDwtIGMoKQogICAgZm9yIChpIGluIDE6bGVuZ3RoKG91dGNvbWVzX2xpc3QpKSB7CiAgICAgIGRpZmZfbGlzdCA8LSBjKCkKICAgICAgY3VycmVudF9vdXRjb21lX3ZlY3RvciA8LSBuYS5vbWl0KHN1Yl9hdW1jX2RmX21bZ3JlcChvdXRjb21lc19saXN0W2ldLCBzdWJfYXVtY19kZl9tJFBST19uYW1lKSwgIlBST192YWx1ZSJdKQogICAgICBmb3IgKGogaW4gMjogbGVuZ3RoKGN1cnJlbnRfb3V0Y29tZV92ZWN0b3IpLTEpIHsKICAgICAgICBkaWZmX2xpc3QgPC0gYXBwZW5kKGRpZmZfbGlzdCwgY3VycmVudF9vdXRjb21lX3ZlY3RvcltqICsgMV0gLSBjdXJyZW50X291dGNvbWVfdmVjdG9yW2pdKQogICAgICB9CiAgICAgIGlmIChvdXRjb21lc19saXN0W2ldICVpbiUgYygiTkhQVF9ORF9hdmciLCAiTkhQVF9EX2F2ZyIsICJUV1RfYXZnIikpewogICAgICAgIGlmIChhbnkoYWJzKGRpZmZfbGlzdCkgPiBkaWZmX2xpc3RbMV0gKiBvdXRjb21lX3BsdXNfY3V0b2Zmc19kZltvdXRjb21lX3BsdXNfY3V0b2Zmc19kZiRvdXRjb21lID09IG91dGNvbWVzX2xpc3RbaV0sICJjdXRvZmZzIl0sIG5hLnJtPVQpKXsKICAgICAgICAgIHNlbGVjdGVkX291dGNvbWVzX2xpc3QgPC0gYXBwZW5kKHNlbGVjdGVkX291dGNvbWVzX2xpc3QsIG91dGNvbWVzX2xpc3RbaV0pCiAgICAgICAgfQogICAgfSBlbHNlewogICAgICAgICAgaWYgKGFueShhYnMoZGlmZl9saXN0KSA+PSBvdXRjb21lX3BsdXNfY3V0b2Zmc19kZltvdXRjb21lX3BsdXNfY3V0b2Zmc19kZiRvdXRjb21lID09IG91dGNvbWVzX2xpc3RbaV0sICJjdXRvZmZzIl0sIG5hLnJtPVQpKXsKICAgICAgICAgIHNlbGVjdGVkX291dGNvbWVzX2xpc3QgPC0gYXBwZW5kKHNlbGVjdGVkX291dGNvbWVzX2xpc3QsIG91dGNvbWVzX2xpc3RbaV0pCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgICAKICAgIGNsaW5fbWF0Y2hlcyA8LSB1bmlxdWUoZ3JlcChwYXN0ZShzZWxlY3RlZF9vdXRjb21lc19saXN0LGNvbGxhcHNlPSJ8IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJfYXVtY19kZl9tJFBST19uYW1lLCB2YWx1ZT1UUlVFKSkKICAgIAogICAgY2xpbl9zdWJfYXVtY19kZl9tIDwtIHN1Yl9hdW1jX2RmX20KICAgIGNsaW5fc3ViX2F1bWNfZGZfbVtjbGluX3N1Yl9hdW1jX2RmX20kUFJPX25hbWUgJW5vdGluJSBjbGluX21hdGNoZXMsICJQUk9fdmFsdWUiXSA8LSBOQQogICAgCiAgICBjbGluX3N1Yl9hdW1jX2RmX20kbV9kYXRlIDwtIHN0cl9zcGxpdF9maXhlZChjbGluX3N1Yl9hdW1jX2RmX20kUFJPX25hbWUsICJfIiwgMilbLDFdCiAgICBjbGluX3N1Yl9hdW1jX2RmX20kbV9kYXRlIDwtIGZhY3RvcihjbGluX3N1Yl9hdW1jX2RmX20kbV9kYXRlKQogICAgY2xpbl9zdWJfYXVtY19kZl9tJFBST19ncm91cCA8LSBzdHJfc3BsaXRfZml4ZWQoY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lLCAiXyIsIDIpWywyXQogICAgCiAgICAjIFR3byBzY2FsaW5nIG9wdGlvbnMgLSAxKSBzY2FsZSB3aXRoaW4gZWFjaCBvdXRjb21lIG1lYXN1cmUKICAgICMgc29ydCBkYXRhIGZyYW1lIG9uIFBST19ncm91cCBhbmQgUFJPX25hbWUgb3RoZXJ3aXNlIG1lcmdlIG9mIHNjYWxlZCBjb2x1bW4gd2lsbCBiZSBpbmNvcnJlY3QKICAgIGNsaW5fc3ViX2F1bWNfZGZfbSA8LSBjbGluX3N1Yl9hdW1jX2RmX21bb3JkZXIoY2xpbl9zdWJfYXVtY19kZl9tJFBST19ncm91cCwgY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lKSwgXQogICAgdHN0IDwtIHNwbGl0KGNsaW5fc3ViX2F1bWNfZGZfbSRQUk9fdmFsdWUsIGNsaW5fc3ViX2F1bWNfZGZfbSRQUk9fZ3JvdXApCiAgICB0c3RfbHMgPC0gbGFwcGx5KHRzdCwgZWxhc2NlcikKICAgIGNsaW5fc3ViX2F1bWNfZGZfbTwtY2JpbmQoY2xpbl9zdWJfYXVtY19kZl9tLCAgZGF0YS5mcmFtZShQUk9fdmFsdWVfc2NhbGVkPXVubGlzdCh0c3RfbHMpKSkKICAgIAogICAgIyBUd28gc2NhbGluZyBvcHRpb25zIC0gMikgc2NhbGUgYWxsIG91dGNvbWUgbWVhc3VyZXMgd2l0aG91dCBncm91cGluZyBwcmlvcgogICAgIyBjbGluX3N1Yl9hdW1jX2RmX20kUFJPX3ZhbHVlX3NjYWxlZCA8LSBlbGFzY2VyKGNsaW5fc3ViX2F1bWNfZGZfbSRQUk9fdmFsdWUpCiAgICAKICAgIHVzZXJfZGNfZGYkQ0NQX2RhdGVzIDwtIHVzZXJfZGNfZGYkZGF0ZQogICAgaWYgKGFsbChpcy5uYSh1c2VyX2RjX2RmJGNvbXBsZXhpdHlfcGVha3MpKSkgewogICAgICBwcmludChwYXN0ZSgiRm9yIHVzZXIgPT0iLCB1c2VyLCAidGhlcmUgd2VyZSBaRVJPIGNvbXBsZXhpdHkgcGVha3MiKSkKICAgICAgdXNlcl9mYWlsZWRfbGlzdCA8LSBjKHVzZXJfZmFpbGVkX2xpc3QsIHVzZXIpCiAgICB9ZWxzZXsKICAgICAgdXNlcl9kY19kZlt1c2VyX2RjX2RmJGNvbXBsZXhpdHlfcGVha3MgPCAwLjUsICJDQ1BfZGF0ZXMiXSA8LSBOQQogICAgICB9CiAgICAgIAogICAgICAjIG1ha2Ugc3VyZSB0aGUgb3V0Y29tZXNfbGlzdCBhbmQgdGhlIHVzZXIgb3V0Y29tZXMgZGF0YSBmcmFtZSBhcmUgc29ydGVkIHRoZSBzYW1lIHdheSBvdGhlcndpc2Ugc3R1ZmYgd2lsbCBub3QgbWF0Y2ggY29ycmVjdGx5CiAgICAgIGNsaW5fc3ViX2F1bWNfZGZfbSA8LSBjbGluX3N1Yl9hdW1jX2RmX21bb3JkZXIoY2xpbl9zdWJfYXVtY19kZl9tJFBST19ncm91cCwgY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lKSwgXQogICAgICBvdXRjb21lc19saXN0IDwtIHNvcnQob3V0Y29tZXNfbGlzdCkKICAgICAgY2xpbmljYWxseV9yZWxldmFudF9ib29sX2NvbCA8LSBjKCkKICAgICAgZm9yIChpIGluIDE6bGVuZ3RoKG91dGNvbWVzX2xpc3QpKSB7CiAgICAgICAgZGlmZl9saXN0IDwtIGMoKQogICAgICAgIGN1cnJlbnRfb3V0Y29tZV92ZWN0b3IgPC0gY2xpbl9zdWJfYXVtY19kZl9tW2dyZXAob3V0Y29tZXNfbGlzdFtpXSwgY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lKSwgIlBST192YWx1ZSJdCiAgICAgICAgZm9yIChqIGluIDI6IGxlbmd0aChjdXJyZW50X291dGNvbWVfdmVjdG9yKS0xKSB7CiAgICAgICAgICBkaWZmX2xpc3QgPC0gYXBwZW5kKGRpZmZfbGlzdCwgY3VycmVudF9vdXRjb21lX3ZlY3RvcltqICsgMV0gLSBjdXJyZW50X291dGNvbWVfdmVjdG9yW2pdKQogICAgICAgIH0KICAgICAgICAKICAgICAgICBpZiAob3V0Y29tZXNfbGlzdFtpXSAlaW4lIGMoIk5IUFRfTkRfYXZnIiwgIk5IUFRfRF9hdmciLCAiVFdUX2F2ZyIpKXsKICAgICAgICAgIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbCA8LSBhYnMoZGlmZl9saXN0KSA+IGRpZmZfbGlzdFsxXSAqIG91dGNvbWVfcGx1c19jdXRvZmZzX2RmW291dGNvbWVfcGx1c19jdXRvZmZzX2RmJG91dGNvbWUgPT0gb3V0Y29tZXNfbGlzdFtpXSwgImN1dG9mZnMiXQogICAgICAgICAgCiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wgPC0gYyhGQUxTRSwgY2xpbmljYWxseV9yZWxldmFudF9ib29sKQogICAgICAgICAgZm9yIChqIGluIDI6IGxlbmd0aChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpKSB7CiAgICAgICAgICAgIGlmIChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xbal0gJiAhaXMubmEoY2xpbmljYWxseV9yZWxldmFudF9ib29sW2pdKSkgewogICAgICAgICAgICAgIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbFtqIC0gMV0gPC0gVFJVRQogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sIDwtIGFwcGVuZChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sLCBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpCiAgICAgICAgICAKICAgICAgfSBlbHNlewogICAgICAgICAgY2xpbmljYWxseV9yZWxldmFudF9ib29sIDwtIGFicyhkaWZmX2xpc3QpID49IG91dGNvbWVfcGx1c19jdXRvZmZzX2RmW291dGNvbWVfcGx1c19jdXRvZmZzX2RmJG91dGNvbWUgPT0gb3V0Y29tZXNfbGlzdFtpXSwgImN1dG9mZnMiXQogICAgICAgICAgCiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wgPC0gYyhGQUxTRSwgY2xpbmljYWxseV9yZWxldmFudF9ib29sKQogICAgICAgICAgZm9yIChqIGluIDI6IGxlbmd0aChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpKSB7CiAgICAgICAgICAgIGlmIChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xbal0gJiAhaXMubmEoY2xpbmljYWxseV9yZWxldmFudF9ib29sW2pdKSkgewogICAgICAgICAgICAgIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbFtqIC0gMV0gPC0gVFJVRQogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sIDwtIGFwcGVuZChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sLCBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpCiAgICAgICAgfQogICAgICB9CiAgICAgIAogICAgICBjbGluX3N1Yl9hdW1jX2RmX20kY2xpbmljYWxseV9yZWxldmFudF9ib29sZWFuIDwtIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbF9jb2wKICAgICAgCiAgICAgIGRhdGVfc2VsZWN0aW9uIDwtIHVuaXF1ZShjbGluX3N1Yl9hdW1jX2RmX21bYXMuY2hhcmFjdGVyKGNsaW5fc3ViX2F1bWNfZGZfbSR2aXNpdGRhdGVfY2F0KSAlaW4lIHF1ZXN0aW9ubmFyZV9kYXRlX2NvbHMsICJkYXRlIl0pCiAgICAgIHBsb3QgPC0gZ2dwbG90KGNsaW5fc3ViX2F1bWNfZGZfbSkgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YT1jbGluX3N1Yl9hdW1jX2RmX21bY2xpbl9zdWJfYXVtY19kZl9tJGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbGVhbiwgXSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCkpICsKICAgICAgICBnZW9tX2xpbmUoZGF0YT1jbGluX3N1Yl9hdW1jX2RmX21bY2xpbl9zdWJfYXVtY19kZl9tJGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbGVhbiwgXSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCMsIHNpemUgPSBQUk9fZ3JvdXAKICAgICAgICAgICAgICAgICAgICAgICApKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhPW5hLm9taXQoY2xpbl9zdWJfYXVtY19kZl9tWyFjbGluX3N1Yl9hdW1jX2RmX20kY2xpbmljYWxseV9yZWxldmFudF9ib29sZWFuLCBdKSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCksIGFscGhhPTAuMSkgKwogICAgICAgIGdlb21fbGluZShkYXRhPW5hLm9taXQoY2xpbl9zdWJfYXVtY19kZl9tWyFjbGluX3N1Yl9hdW1jX2RmX20kY2xpbmljYWxseV9yZWxldmFudF9ib29sZWFuLCBdKSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCksIGFscGhhPTAuMSkgKwogICAgICAgIGdlb21fbGluZShkYXRhID0gdXNlcl9kY19kZiwgYWVzKGRhdGUsIGR5bmFtaWNfY29tcGxleGl0eV9zdW0pLCBzaXplPTAuNSkgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHVzZXJfZGNfZGYsIGFlcyhkYXRlLCBkeW5hbWljX2NvbXBsZXhpdHlfc3VtKSwgc2hhcGUgPSAxNiwgc2l6ZT0yKSArCiAgCiAgICAgICAgeGxpbShtaW4oYXVtY19kZl9tW2F1bWNfZGZfbSR1c2VyX2lkID09IHVzZXIsICJkYXRlIl0sIG5hLnJtID0gVFJVRSksCiAgICAgICAgbWF4KGF1bWNfZGZfbVthdW1jX2RmX20kdXNlcl9pZCA9PSB1c2VyLCAiZGF0ZSJdLCBuYS5ybSA9IFRSVUUpKSArCiAgICAgICAgeWxpbSgwLCBtYXgoZWxhc2Nlcih1c2VyX2RjX2RmW3VzZXJfZGNfZGYkdXNlcl9pZCA9PSB1c2VyLCAiUFJPX3ZhbHVlIl0pLCBuYS5ybSA9IFRSVUUpKSArCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gdXNlcl9kY19kZlt1c2VyX2RjX2RmJGNvbXBsZXhpdHlfcGVha3MgPiAwLCAiZGF0ZSJdLCBjb2w9InJlZCIsIGx0eT0yKSArCiAgICAgICAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9IGRhdGVfc2VsZWN0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygibTAwIiwgIm0wMDIiLCAibTAzIiwgIm0wNiIsICJtMDkiLCAibTEyIilbMTpsZW5ndGgoZGF0ZV9zZWxlY3Rpb24pXSkgKwogIAogIAogICAgICAgIGxhYnMoeCA9ICJEYXRlIiwgeSA9ICJNZWFzdXJlIENoYW5nZSAoU2NhbGVkKSIpICsKICAgICAgICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lPSJPdXRjb21lIG1lYXN1cmUiKSArCiAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKCJPdXRjb21lIE1lYXN1cmVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGN1c3RvbV9jbWFwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGN1c3RvbV9jbWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgKSArCiAgICAgICAgdGhlbWVfbWluaW1hbCgpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDYwLCBoanVzdCA9IDAuOSwgc2l6ZT0xMCksCiAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgICAgIGdndGl0bGUocGFzdGUoIkNoYW5nZSBpbiB0aW1lIHNlcmllcyBkYXRhIGZvciB1c2VyID09IiwgdXNlcikpCiAgICAgIAogICAgICBwcmludChwbG90KQogICAgICAKICB9Cn0KCmBgYAoKIyMjIyBTaG93IHRoZSBtaXNzaW5nbmVzcyB3aXRoaW4gZWFjaCB1c2VyIGFuZCBwZXIgZ3JvdXAKCmBgYHtyfQpmb3IgKHVzZXJzX2xpc3RfbnVtYmVyIGluIDE6bGVuZ3RoKGxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0cykpIHsKICB1c2VyX2xpc3RfbmFtZTwtbmFtZXMobGlzdF9vZl91c2VyX2luZGV4X2xpc3RzW3VzZXJzX2xpc3RfbnVtYmVyXSkKICB1c2Vyc19zZWxlY3Rpb25faW5kZXg8LWxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0c1tbdXNlcl9saXN0X25hbWVdXQogIAogIHByaW50KHVzZXJfbGlzdF9uYW1lKQogIAogICMgdXNlIHRoZSBjaG9zZW4gaW5kZXggdG8gZ2V0IGEgbGlzdCBvZiB1c2VycwogIHVzZXJfc2VsZWN0aW9uIDwtIGF1bWNfZGYkdXNlcl9pZFt1c2Vyc19zZWxlY3Rpb25faW5kZXhdCgogICMjIFNob3cgbWlzc2luZ25lc3MgcGVyY2VudGFnZXMKICBhbGxfbWlzc2luZ19wZXJfdXNlciA8LSBkYXRhLmZyYW1lKCkKICBmb3IgKHVzZXIgaW4gdXNlcl9zZWxlY3Rpb24pIHsKICAgIGN1cnJlbnRfdXNlcl9taXNzaW5nIDwtIGRhdGEuZnJhbWUodXNlcl9pZCA9IHVzZXIsIHQoY29sTWVhbnMoaXMubmEoa2V5X2RmW2tleV9kZiR1c2VyX2lkID09IHVzZXIsIGZlYXR1cmVfc2VsZWN0aW9uXSkpKjEwMCkpCiAgICBhbGxfbWlzc2luZ19wZXJfdXNlciA8LSByYmluZChhbGxfbWlzc2luZ19wZXJfdXNlciwgY3VycmVudF91c2VyX21pc3NpbmcpCiAgfQogIAogIHByaW50KCJBbGwgcGVyY2VudGFnZSBtaXNzaW5nbmVzcyBwZXIgdXNlciBwZXIgZmVhdHVyZSIpCiAgcHJpbnQoYWxsX21pc3NpbmdfcGVyX3VzZXIpCiAgCiAgcHJpbnQoIk1lZGlhbiBwZXJjZW50YWdlIG1pc3NpbmduZXNzIHBlciB1c2VyIikKICBtZWRpYW5fcGVyY2VudF9taXNzIDwtIGRhdGEuZnJhbWUodXNlcl9pZCA9IHVzZXJfc2VsZWN0aW9uLCBtZWRpYW5fcGVyY2VudGFnZV9taXNzaW5nID0gcm93TWVkaWFucyhhcy5tYXRyaXgoYWxsX21pc3NpbmdfcGVyX3VzZXJbLCBmZWF0dXJlX3NlbGVjdGlvbl0pKSkKICBwcmludChtZWRpYW5fcGVyY2VudF9taXNzKQogIAogIHByaW50KCJNZWRpYW4gb2YgdGhlIE1lZGlhbiBwZXJjZW50YWdlIG1pc3NpbmduZXNzIHBlciB1c2VyIikKICBwcmludChtZWRpYW4obWVkaWFuX3BlcmNlbnRfbWlzcyRtZWRpYW5fcGVyY2VudGFnZV9taXNzaW5nLCBuYS5ybT1UUlVFKSkKICBjYXQoIlxuIikKCn0KYGBgCgojIyMjIFBsb3QgdGhlIENSRCBhbmQgQ0NQIG9mIGFuIGV4YW1wbGUgdXNlcgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgc3Vic2V0IHVzZXJzJyBrZXlzdHJva2UgZGF0YQprZXlfc3ViX2RmIDwtIGtleV9kZltrZXlfZGYkdXNlcl9pZCA9PSAzOTAsIF0KIyBvcmRlciBiYXNlZCBvbiB1c2VyIGlkIGFuZCB0aW1lc3RhbXAKa2V5X3N1Yl9kZiA8LSBrZXlfc3ViX2RmW29yZGVyKGtleV9zdWJfZGYkdXNlcl9pZCwga2V5X3N1Yl9kZiR0aW1lc3RhbXApLCBdCmRmIDwtIGRmX3VzZXJfbGlzdChkZiA9IGtleV9zdWJfZGYsIGZlYXR1cmVzID0gYygidXNlcl9pZCIsICJ0aW1lc3RhbXAiLCBmZWF0dXJlX3NlbGVjdGlvbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VyX2NvbHVtbl9uYW1lID0gInVzZXJfaWQiLCB1c2VycyA9IDM5MCkkSURfMzkwCgp1c2VyIDwtIGRmJHVzZXJfaWRbMV0KZGYgPC0gc3Vic2V0KGRmLCBzZWxlY3QgPSAtYyh1c2VyX2lkKSkKY2F0KCJDYWxjdWxhdGluZyBFYXJseSBXYXJuaW5nIFNpZ25hbHMgYW5kIEN1bXVsYXRpdmUgQ29tcGxleGl0eSBQZWFrcyBmb3IgdXNlciA9PSAiLCB1c2VyLCAiXG4iKQojIHJlbW92ZSBjb2x1bW5zIHdpdGggdG9vIG1hbnkgTkFzCmRmIDwtIGRmWywgY29sU3Vtcyhpcy5uYShkZikpIDwgbnJvdyhkZikvM10KdmFycyA8LSBjb2xuYW1lcyhkZilbMjpsZW5ndGgoY29sbmFtZXMoZGYpKV0KIyBJbXB1dGUgbWlzc2luZyB2YWx1ZXMgd2l0aCBDbGFzc2lmaWNhdGlvbiBBbmQgUmVncmVzc2lvbiBUcmVlcyAvIFJhbmRvbSBGb3Jlc3RzCiMgUkYgYW5kIENBUlQgcmV0dXJuIChpZGVudGljYWwpIGRpc2NyZXRlIG51bWJlcnMKaW1wLmNhcnQgIDwtIG1pY2U6Om1pY2UoZGZbdmFyc10sIG1ldGhvZCA9ICdjYXJ0JywgcmVtb3ZlLmNvbnN0YW50ID0gVFJVRSwgcmVtb3ZlLmNvbGxpbmVhciA9IFRSVUUsIHByaW50RmxhZyA9IEZBTFNFKQpkZl9pbXAgIDwtIG1pY2U6OmNvbXBsZXRlKGltcC5jYXJ0KQoKIyBwdXQgZWFjaCBjb2x1bW4gYmV0d2VlbiAwIGFuZCAxIHVzaW5nIGVsYXN0aWMgc2NhbGVyCmVsYXNjX2RmIDwtIGRhdGEuZnJhbWUoYXBwbHkoZGZfaW1wLCAyLCBlbGFzY2VyKSkKIyBkeW5hbWljIGNvbXBsZXhpdHkgb2YgdGhlIHZhcmlhYmxlcyB3aXRoIHRoZSBpbXB1dGVkIGRhdGEKd2luID0gMjgKZGMgPC0gZGNfd2luKGVsYXNjX2RmLCB3aW4gPSB3aW4sIHNjYWxlX21pbj0wLCBzY2FsZV9tYXg9MSwgZG9QbG90ID0gRkFMU0UsIGNvbE9yZGVyID0gTkEpCmRhdGVzSU1QIDwtIGRmJHRpbWVzdGFtcApjY3AuY2FzZUlNUCA8LSBkY19jY3AoZGZfd2luID0gZGMsIGFscGhhX2l0ZW0gPSAwLjAwMSwgYWxwaGFfdGltZSA9IDAuMDAxKQoKcGxvdF9wcmUgPSBUUlVFCgppZiAocGxvdF9wcmUgPT0gVFJVRSl7CiAgIyBQbG90IHRoZSBDb21wbGV4aXR5IFJlc29uYW5jZSBEaWFncmFtIFBsb3QKICBkYyA8LSBkcGx5cjo6c2VsZWN0KGRjLCBuYW1lcyhzb3J0KGNvbE1lYW5zKGRjLCBuYS5ybSA9IFRSVUUpKSkpCiAgcGxvdERDX3Jlc19qbXMoZGZfd2luID0gZGMsIHdpbiA9IHdpbiwgY29sT3JkZXIgPSBUUlVFLCAKICAgICAgICAgICAgIHVzZVRpbWVWZWN0b3IgPSBOQSwgI2RhdGVzSU1QLCAKICAgICAgICAgICAgIHRpbWVTdGFtcCA9ICIzMS0wMS05OSIsCiAgICAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJDb21wbGV4aXR5IFJlc29uYW5jZSBEaWFncmFtIHVzZXIgPT0gIiwgdXNlcikpCiAgIyBQbG90IHRoZSBDdW11bGF0aXZlIENvbXBsZXhpdHkgUGVhayBQbG90CiAgcGxvdERDX2NjcF9qbXMoZGZfY2NwID0gY2NwLmNhc2VJTVBbLCBjKGNvbG5hbWVzKGRjKSwgInNpZy5wZWFrcyIpXSwgd2luID0gd2luLCBjb2xPcmRlciA9IFRSVUUsIAogICAgICAgICAgICAgdXNlVGltZVZlY3RvciA9IE5BLCAjZGF0ZXNJTVAsIAogICAgICAgICAgICAgdGltZVN0YW1wID0gIjMxLTAxLTk5IiwKICAgICAgICAgICAgIHRpdGxlID0gcGFzdGUoIkN1bXVsYXRpdmUgQ29tcGxleGl0eSBQZWFrIFBsb3QgdXNlciA9PSAiLCB1c2VyKSkKfQoKYGBgCg==