#Notes on general structure:
#This R script contains all of the components necessary to generate a shiny app that allows the user to filter and generate plots based on an annotated microsatellite dataset. See "TRF-usat-annotations.Rmd" for code/more info on that dataset.
#The general components of a shiny app is the ui, server, and global environment.
#The global environment is where we load in any packages or datasets we will need to access in the app. These packages and datasets will be loaded in once when the app starts and cannot be re-loaded unless the app is restarted. We can also define R settings/options in the global environment (like where to cache data).
#The ui is the user interface. This defines the structure the user sees when the app loads - all of the buttons they will use for various inputs and all of the plots that are generated by these input. Some of the ui may be generated in the server if it reactive (dependent on other user inputs), or may be affected by events in the server.
#The server is the part of the app that carries out the calculations necessary to update the ui. These calculations may affect inputs (e.g. automatically updating input values or making inputs appear/disappear) or outputs (e.g. updating data for plots).

#Note on the documentation in this file:
#The documentation in this file consists of low-level "comments" on how the code works. For information on the the higher level structure of the app and how to edit/update it, see the companion benchling entry for more details.
#some elements documented in this file will only be described the first time they appear (as there is a lot of repeated structure with minor variations)

#load required packages
library(memoise)
library(shiny)
library(shinyjs)
library(shinyWidgets)
library(shinybusy)
library(shinyBS)
library(shinyalert)
library(readr)
library(dplyr)
library(data.table)
library(DT)
library(ggplot2)
library(GenomicRanges)

#define start-up cache locations
shinyOptions(cache = cachem::cache_disk("./cache"))
m_read_csv <- memoise(read_csv,cache = cachem::cache_mem(max_size = 1024 * 1024^2))

#load in data
usats <- m_read_csv('./hg-38.2.7.7.80.10.36.6.final.csv.zip',show_col_types=FALSE)

#make microsatellite granges for region filtering
usats.granges <- makeGRangesFromDataFrame(usats)

#list names of static region filters
regionfilters_static.name <- list.files(path="./regionfilters_static")

#list paths to static region filters
regionfilters_static.path <- lapply(regionfilters_static.name,function(x) paste0("./regionfilters_static/",x))

#list paths to static RE filters
RE.capture_filters.path <- list.files(path="./restriction-enzyme_filters",pattern=".hits_capture.csv",full.names=TRUE)
RE.internal.cuts_filters.path <- list.files(path="./restriction-enzyme_filters",pattern=".hits_internal.cuts.csv",full.names=TRUE)

#load static RE filters
RE.capture_filters.list <- lapply(RE.capture_filters.path, function(x) m_read_csv(x,show_col_types=FALSE))
RE.internal.cuts_filters.list <- lapply(RE.internal.cuts_filters.path, function(x) m_read_csv(x,show_col_types=FALSE))

#list names of static RE filters
RE.capture_filters.name <- lapply(RE.capture_filters.path, function(x) tail(unlist(strsplit(x,"[/]")),1))
RE.internal.cuts_filters.name <- lapply(RE.internal.cuts_filters.path, function(x) tail(unlist(strsplit(x,"[/]")),1))

#set names of elements in RE filter lists
names(RE.capture_filters.list) <- RE.capture_filters.name
names(RE.internal.cuts_filters.list) <- RE.internal.cuts_filters.name

#list annotation info for later use
columns <- 1:ncol(usats)
names(columns) <- colnames(usats)

#initialized ggplot to set initial y-axis values for user plots
gg_init <- ggplot(usats,aes_string(x = "width")) + 
  geom_histogram() +
  xlim(min(usats$width),max(usats$width))

# Define UI ----
ui <- function() {
  #set up fluidPage so that the ui scales automatically with the user's screen size/browser size.
  fluidPage(
  #allows us to use the shinyjs package
  useShinyjs(),
  #creates app title
  titlePanel("STRATIFY"),
  #creates a sidebar on the left side of the page for the input controls
  sidebarPanel(
    #access the app html/css to adjust the font size so that all tab labels are fully visible
    tags$head(
      tags$style(type='text/css', 
                 ".nav-tabs {font-size: 12px} ")),
    #create a set of tabs so that input controls can be divided categorically
    tabsetPanel(
      id = "control.panel",
      #this tab is where we put all of the microsatellite body specific buttons
      tabPanel("Microsatellite - Main",
               #add some padding above the buttons on the tab
               style = "padding-top: 10px;",
               #generate a row that scales to the user's screen/browser size.
               #within that row, generate two columns (column width is defined on a scale of 1-12 and the sum total of all column lengths may not exceed 12, the full width of the row)
               #in the smaller column, define an info button by using the "info" icon and style settings with the bsButton function (park of the shinyBS package)
               #in the larger column, define a numeric input that prompts the user to "Filter by Minimum Microsatellite Length (bp):" (label). This item will be accessed for server calculations later by its inputId ("minlength"). You must also set an initial value for the input ("value"), which has been set to the minimum length of the microsatellites in our initial microsatellite dataset (in the global environment), and you may define a step size (i.e. the interval at which the user may adjust the input value).
               fluidRow(column(1,bsButton("length-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(11,numericInput(inputId = "minlength",
                                               label = "Filter by Minimum Microsatellite Length (bp):",
                                               value = min(usats$width),
                                               step = 1))),
               #in another fluidRow, define a column that contains a numeric input that prompts the user to "Filter by Maximum Microsatellite Length (bp):". The column should be the same size as the minlength numeric input, and should be "offset" by the size of the info button (1), so that the two paired inputs (minimum and maximum microsatellite length) are aligned with one another on the page.
               fluidRow(column(11,numericInput(inputId = "maxlength",
                                               label = "Filter by Maximum Microsatellite Length (bp):",
                                               value = max(usats$width),
                                               step = 1), offset = 1)),
               #in another row, in which all elements should be aligned in the center of the row (rather than along the left side, as is the default setting), put an action button, which is a button type that simply records clicks. It requires an inputId ("length.reset") so that it can be accessed for later calculations in the server and a label ("Reset Microsatellite Length") to describe to the user what the button's function is.
               fluidRow(align = "center",
                        actionButton(inputId = "length.reset", label = "Reset Microsatellite Length")),
               #use the bsPopover function to target the "length-info" info button (the bsButton defined above), causing it to display the content of the bsPopover function when the user moves their mouse over the target bsButton. The placement option allows us to define where the popover appears relative to the target button (to the right of the button, in this case).
               bsPopover("length-info", placement = "right",options = list(container = "body"),"Total length of microsatellite sequence in bp. Note: the minimum length of microsatellites captured in this dataset is 18 because we set the minimum alignment score threshold for Tandem Repeats Finder to 36, and the match score to 2, meaning the minimum length of a microsatellite captured in this dataset is 18bp, assuming the microsatellite has no mismatches or indels. This was done to ensure a baseline level of quality for microsatellites included in this dataset, as setting this threshold allowed us to exclude many microsatellites that were both short and imperfect, and thus, undesirable for our purposes."),
               #places a horizontal rule below sections of the tab to divide inputs into discrete areas 
               hr(),
               fluidRow(column(1,bsButton("period-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        #define an input that allows the user to choose from a set of checkboxes to select or deselect. In this case, the user can choose from a set of 6 period sizes for microsatellites in the dataset. The default setting is that all period sizes are selected (see selected option).
                        column(11,checkboxGroupInput(inputId = "period",
                                                     label = "Filter Period Size (bp):",
                                                     choices = levels(factor(usats$motif.size)),
                                                     selected = levels(factor(usats$motif.size))))),
               bsPopover("period-info",placement = "right", options = list(container = "body"),"the length in bp of the repeating pattern (motif) of the microsatellite."),
               hr(),
               fluidRow(column(1,bsButton("motif-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        #define an input that presents the user with a dropdown menu of options to choose from. In this case, the dropdown menu contains all motifs that appear in the microsatellite dataset. The default setting is that all motifs are selected (see "selected" option). There are also additional "options" that may be added to the dropdown menu. In this case, we have an "action box" to allow the user to quickly "select all" or "deselect all", and a "live search" that allows the user to look up a specific motif of their choice. There's also an option that define whether or not the user is allowed to select multiple options from the dropdown menu, which is set to "TRUE" in this example.
                        column(11,pickerInput(inputId = "motif", 
                                              label = "Select Motif Families:",
                                              choices = unique(usats$motif.family[order(usats$motif.size,usats$motif.family)]),
                                              selected = unique(usats$motif.family[order(usats$motif.size,usats$motif.family)]),
                                              options = list(`actions-box` = T, `live-search` = T), 
                                              multiple = T))),
               bsPopover("motif-info",placement = "right", options = list(container = "body"),"a motif is a single unit of the repeating pattern that makes up a microsatellite. Motif families are the set of all equivalent variations of a given motif (e.g. reverse complements, circular permutations)."),
               hr(),
               #create input buttons for bioskryb coverage filtering
               fluidRow(column(1,bsButton("bioskryb-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(11,numericInput(inputId = "bioskryb.min",
                            label = "Min. Bioskryb Coverage of Microsatellite:",
                            value = min(usats$msat.bioskryb),
                            step = 0.01))),
               bsPopover("bioskryb-info", placement = "right", options = list(container = "body"), "Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra.Coverage normalized using average autosome genome coverage. For male samples: multiply chrX and chrY normalized coverage by 2, since the goal is to assess single-cell amplification relative to the original copy number (Otherwise, coverage filtering will tend to exclude chrX and chrY in males)."),
               fluidRow(column(11,numericInput(inputId = "bioskryb.max",
                            label = "Max. Bioskryb Coverage of Microsatellite:",
                            value = max(usats$msat.bioskryb),
                            step = 0.01), offset = 1)),
               fluidRow(align = "center",
                        actionButton(inputId = "bioskryb.reset", label = "Reset Bioskryb Coverage")),
               hr(),
               #create input buttons for estimated mutation rate filtering
               fluidRow(column(1,bsButton("mu-info", label = "", icon = icon("info"), 
                                 style = "info", size = "extra-small")),
                        column(11,numericInput(inputId = "mu.min",
                                               label = "Min. Mutation Rate:",
                                               value = min(usats$ml_mu),
                                               step = 0.01))),
               bsPopover("mu-info",placement = "right", options = list(container = "body"),"Mutation rates were determined by applying MUTEA model, developed by Gymrek et. al. (<em>Nat Genet.</em>,2017), to our microsatellite data. Rates are displayed as log10 mutations per generation. See &#39Annotation Definitions&#39 tab (accessible from the &#39View Data&#39 tab) for more info."),
               fluidRow(column(11,numericInput(inputId = "mu.max",
                            label = "Max. Mutation Rate:",
                            value = max(usats$ml_mu),
                            step = 0.01), offset = 1)),
               fluidRow(align = "center",
                        actionButton(inputId = "mu.reset", label = "Reset Mutation Rate")),
               hr(),
               #read the content of the htmlOutput function, which calls back to an output defined in the server that is tagged as "app.info", as html to generate a text output. There is a textOutput function in shiny as well, but it does not allow for the same complexity as text generated through an html output (like the ability to include links or format separate paragraphs)
               htmlOutput("app.info")),
      #define a new tab for more inputs so that the first page is not too overwhelming to the user
      tabPanel("Microsatellite - Other",
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        #the actionBttn function in the shinyWidgets package is essentially equivalent to the actionButton function in the shiny base package. The difference is that actionBttn allows for greater customization of button appearance.
                        #generate two action buttons. One that triggers a tab to open in the MainPanel (where we put plot/dataset outputs) and another to close the tab.
                        actionBttn(inputId = "tab.msat2.show",label = "Open Plots", 
                                   style = "minimal", color = "success", size = "xs"),
                        actionBttn(inputId = "tab.msat2.hide",label = "Close Plots",
                                   style = "minimal", color = "danger", size = "xs")),
               hr(),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        #create input buttons for copy number filtering
                        column(1,bsButton("copyNum-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5,numericInput(inputId = "copyNum.min",
                                     label = "Minimum Copy Number:",
                                     value = min(usats$copy.num),
                                     step = 0.1)),
                        column(5,numericInput(inputId = "copyNum.max",
                                     label = "Maximum Copy Number:",
                                     value = ceiling(max(usats$copy.num))+1,
                                     step = 0.1)), offset = 1),
               fluidRow(align = "center",
                        actionButton(inputId = "copy.num.reset", label = "Reset Copy Number")),
               bsPopover("copyNum-info",placement = "right", options = list(container = "body"),"the total number of repetitions of the base repeating pattern (motif) that make up the microsatellite."),
               hr(),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("perMatch-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        #generate a slider input that allows the user to filter along the percent match microsatellite annotation between a limited range of values (in this case 1-100). The user may drag the slider from the left side (filtering the lower values) and/or the right side (filtering the higher values), and is not forced to maintain the range size that is set by the initial values defined in the "value" option (dragRange = F). 
                        column(10,sliderInput(inputId = "perMatch",
                                              label = "Filter by Minimum and Maximum Percent Match:",
                                              min = 0,
                                              max = 100,
                                              value = c(0,100),
                                              step = 1,
                                              dragRange = F))),
               bsPopover("perMatch-info",placement = "right", options = list(container = "body"),"the percentage of bps that are called as &#39matches&#39 when the actual microsatellite sequence is aligned with a string of perfect repetitions of the consensus pattern in Tandem Repeats Finder."),
               hr(),
               #create input buttons for percent indel filtering
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("perIndel-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "perIndel",
                                              label = "Filter by Minimum and Maximum Percent Indel:",
                                              min = 0,
                                              max = 100,
                                              value = c(0,100),
                                              step = 1,
                                              dragRange = F))),
               bsPopover("perIndel-info", placement = "right", options = list(container = "body"),"the percentage of bps that are called as &#39indels&#39 when the actual microsatellite sequence is aligned with a string of perfect repetitions of the consensus pattern in Tandem Repeats Finder."),
               hr(),
               #create input buttons for score filtering
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("score-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5, numericInput(inputId = "scoreMin",
                                               label = "Minimum Score:",
                                               value = min(usats$score),
                                               step = 1)),
                        column(5, numericInput(inputId = "scoreMax",
                                               label = "Maximum Score:",
                                               value = max(usats$score),
                                               step = 1))),
               fluidRow(align = "center",
                        actionButton(inputId = "score.reset", label = "Reset Score")),
               bsPopover("score-info", placement = "right", options = list(container = "body"),"the alignment score given to a microsatellite based on the alignment parameters put into Tandem Repeats Finder. The alignment score is calculated by summing the &#39weight&#39 assigned to each base in the microsatellite based on whether that base is a &#39match&#39, &#39mismatch&#39, or &#39indel&#39 compared to a reference sequence made up of perfect repeats of the microsatellite motif. &#39Mismatch&#39 and &#39indel&#39 designations decrease the overall alignment score, while &#39match&#39 designations increase the overall alignment score. The program tries multiple value assignments for the bases in a given sequence and selects the one that maximizes the alignment score. We set the minimum alignment score to 36, with a match weight of +2, a mismatch weight of -2, and an indel weight of -7. Thus, a minimum alignment score of 36 ensures the smallest microsatellites called by TRF are 18bp microsatellites, where each base perfectly aligns to a perfect repetition of the motif. See &#39Annotation Definitions&#39 tab (accessible from the &#39View Data&#39 tab) for more info."),
               hr(),
               #create input buttons for entropy filtering
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("entropy-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "entropy",
                                              label = "Filter by Minimum and Maximum Entropy:",
                                              min = 0,
                                              max = 2,
                                              value = c(0,2),
                                              step = 0.01,
                                              dragRange = F))),
               bsPopover("entropy-info", placement = "right", options = list(container = "body"),"a measure of how much information needs to be stored in order to represent the microsatellite sequence."),
               hr(),
               #create input buttons for uninterrupted length filtering
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("ulength-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5, numericInput(inputId = "ulengthMin",
                                               label = "Minimum Uninterrupted Length (bp):",
                                               value = min(usats$length.uninterrupted),
                                               step = 1)),
                        column(5, numericInput(inputId = "ulengthMax",
                                               label = "Maximum Uninterrupted Length (bp):",
                                               value = max(usats$length.uninterrupted)),
                                               step = 1)),
               fluidRow(align = "center",
                        actionButton(inputId = "uninterrupted.length.reset", label = "Reset Uninterrupted Length")),
               bsPopover("ulength-info", placement = "right", options = list(container = "body"),"the maximum length of perfect repetitions of the microsatellite motif in the microsatellite."),
               hr(),
               #create input buttons for uninterrupted copy number filtering
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("uCopyNum-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5, numericInput(inputId = "uCopyNumMin",
                                               label = "Minimum Uninterrupted Copy Number:",
                                               value = min(usats$uninterrupted.copy.num),
                                               step = 1)),
                        column(5, numericInput(inputId = "uCopyNumMax",
                                               label = "Maximum Uninterrupted Copy Number:",
                                               value = max(usats$uninterrupted.copy.num),
                                               step = 1))),
               fluidRow(align = "center",
                        actionButton(inputId = "uninterrupted.copy.num.reset", label = "Reset Uninterrupted Copy Number")),
               bsPopover("uCopyNum-info", placement = "right", options = list(container = "body"),"the maximum number of copies of perfect repetitions of the microsatellite motif in the microsatellite."),
               hr(),
               #create input buttons for percent GC content filtering
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("GCmsat-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "GC.msat",
                                              label = "Filter by Minimum and Maximum Microsatellite GC Content:",
                                              min = 0,
                                              max = 1,
                                              value = c(0,1),
                                              step = 0.01,
                                              dragRange = F))),
               bsPopover("GCmsat-info",placement = "right", options = list(container = "body"),"the percentage of basepairs in the microsatellite that are either Guanine (G) or Cytosine (C)."),
               hr(),
               #create input buttons for filtering microsatellite overlap
               fluidRow(align = "center",
                        column(1,bsButton("overlapUsat-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5, numericInput(inputId = "overlapUsatMin",
                                               label = "Minimum Overlap Between Microsatellites:",
                                               value = 0.9001,
                                               step = 0.00001)),
                        column(5, numericInput(inputId = "overlapUsatMax",
                                               label = "Maximum Overlap Between Microsatellites",
                                               value = 1,
                                               step = 0.00001))),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(10,sliderInput("overlapUsat", label = "",
                                              min = 0,
                                              max = 1,
                                              value = c(0.9001,1),
                                              step = 0.00001), offset = 1)),
               bsPopover("overlapUsat-info",placement = "right", options = list(container = "body"),"Filter out microsatellites that overlap with another microsatellite by a certain percentage."),
               hr(),
               #create input buttons for filtering on distance to the next nearest microsatellite
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("nearestUsat-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5, numericInput(inputId = "nearestUsatMin.5",
                                               label = "Minimum Distance to Next 5' Microsatellite (bp):",
                                               value = min(usats$nearest.usat.5, na.rm = TRUE),
                                               step = 1)),
                        column(5, numericInput(inputId = "nearestUsatMax.5",
                                               label = "Maximum Distance to Next 5' Microsatellite (bp):",
                                               value = max(usats$nearest.usat.5, na.rm = TRUE),
                                               step = 1))),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(5, offset = 1, numericInput(inputId = "nearestUsatMin.3",
                                               label = "Minimum Distance to Next 3' Microsatellite (bp):",
                                               value = min(usats$nearest.usat.3, na.rm = TRUE),
                                               step = 1)),
                        column(5, numericInput(inputId = "nearestUsatMax.3",
                                               label = "Maximum Distance to Next 3' Microsatellite (bp):",
                                               value = max(usats$nearest.usat.3, na.rm = TRUE),
                                               step = 1))),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        actionButton(inputId = "nearest.usat.reset", label = "Reset Dist. to Next Nearest Microsatellite")),
               fluidRow(align = "center",
                        #creates a group of radio buttons with a set of choices and (an) optional default selection(s). In this case, the group of radio buttons determines whether microsatellites that pass the distance to the next nearest microsatellite filter in both flanks or in either flank. The default setting is that it must pass in both flanks.
                        radioGroupButtons(inputId = "nearest.usat.radio",
                                          label = "Pass microsatellites that meet 'Distance to Next Nearest Microsatellite' requirements in either flank or both flanks?",
                                          choices = c("either","both"),
                                          selected = "either")),
               fluidRow(align = "center",
                        column(1,bsButton("link-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        #define header to the following element ("LinkNearestUsatToMappability" prettyToggle), in a separate row because a descriptive label would not fit in the "label-on" and "label-off" options.
                        column(10,uiOutput("linkHeader"))),
               fluidRow(align = "center",
                        #define a single checkbox input using the prettyToggle function from the shinyWidgets package to render the checkbox as a thumbs up/thumbs down toggle. This checkbox is used to define whether the microsatellites that pass the nearest microsatellite, mappability, and region filters must pass all filters on the same flank or whether they can pass separate filters on separate flanks (default).
                        prettyToggle(
                          inputId = "LinkNearestUsatToMappability",
                          label_on = "Yes", 
                          label_off = "No", 
                          outline = TRUE,
                          plain = TRUE,
                          icon_on = icon("thumbs-up"), 
                          icon_off = icon("thumbs-down"),
                          value = FALSE
                        )),
               bsPopover("link-info", placement = "right", options = list(container = "body"),"Choose whether or not you require microsatellites to pass Distance to Next Nearest Microsatellite, any selected region filters, and any selected mappability filters on the same flank."),
               bsPopover("nearestUsat-info", placement = "right", options = list(container = "body"),"the distance in bps to the next nearest microsatellite in the dataset.")
               ),
      #--end of microsatellite body buttons--#
      #this tab is where we put all of the 60bp flank buttons
      tabPanel("60bp Flank",
               #generate two action buttons. One that triggers a tab to open in the MainPanel (where we put plot/dataset outputs) and another to close the tab.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        actionBttn(inputId = "tab60.show",label = "Open Flank Plots", 
                                   style = "minimal", color = "success", size = "xs"),
                        actionBttn(inputId = "tab60.hide",label = "Close Flank Plots",
                                   style = "minimal", color = "danger", size = "xs")),
               hr(),
               #this is where we put the 60bp mappability buttons
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("mappability-info-60", label = "", icon = icon("info"), 
                                          style = "info", size = "extra-small")),
                        column(10,pickerInput("umap60", label = "Select K-mer Size for UMAP Mappability:",
                               choices = c("k24","k36","k50","k100"),
                               selected = NULL,
                               multiple = TRUE,
                               options = list(`actions-box` = T)))),
               bsPopover("mappability-info-60", placement = "right", options = list(container = "body"), "Mappability describes the extent to which a given region can be uniquely mapped by sequence reads of a given length. The mappability annotations used here describe the average mappability of the flanking regions of the microsatellite, given sequencing reads of length k, where k is the length in bp of the selected k-mer. (hg38 genome-wide mappability data sourced from Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018). A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region."),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        uiOutput("sliderInput60_group_ui")),
               hr(),
               #--end of 60bp mappability buttons--#
               #this is where we put the 60bp overlap buttons
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("overlap-info-60", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "overlap60.5",
                                             label = "Filter by Microsatellite Overlap in the 5' Flank:",
                                             min = 0,
                                             max = 1,
                                             value = c(0,1),
                                             step = 0.01,
                                             dragRange = F))),
               fluidRow(align = "center",
                        column(10, offset = 1, sliderInput(inputId = "overlap60.3",
                                                           label = "Filter by Microsatellite Overlap in the 3' Flank:",
                                                           min = 0,
                                                           max = 1,
                                                           value = c(0,1),
                                                           step = 0.01,
                                                           dragRange = F))),
               bsPopover("overlap-info-60",placement = "right", options = list(container = "body"), "Microsatellite overlap describes the fraction of the microsatellite flanking region that contains another microsatellite."),
               hr(),
               #--end of 60bp overlap buttons--#
               #this is where we put the 60bp bioskryb buttons
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("bioskryb60-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5,numericInput(inputId = "bioskryb60.min5",
                                              label = "Min. Bioskryb Coverage in the 5' Flank:",
                                              value = min(usats$flank.5.60bp.bioskryb),
                                              step = 0.01)),
                        column(5,numericInput(inputId = "bioskryb60.max5",
                                              label = "Max. Bioskryb Coverage in the 5' Flank:",
                                              value = max(usats$flank.5.60bp.bioskryb),
                                              step = 0.01))),
               fluidRow(align = "center",
                        column(5, offset = 1, numericInput(inputId = "bioskryb60.min3",
                                                           label = "Min. Bioskryb Coverage in the 3' Flank:",
                                                           value = min(usats$flank.3.60bp.bioskryb),
                                                           step = 0.01)),
                        column(5, numericInput(inputId = "bioskryb60.max3",
                                               label = "Max. Bioskryb Coverage in the 3' Flank:",
                                               value = max(usats$flank.3.60bp.bioskryb),
                                               step = 0.01))),
               bsPopover("bioskryb60-info", placement = "right", options = list(container = "body"), "Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra. Coverage normalized using average autosome genome coverage. For male samples: multiply chrX and chrY normalized coverage by 2, since the goal is to assess single-cell amplification relative to the original copy number (Otherwise, coverage filtering will tend to exclude chrX and chrY in males)."),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        actionButton(inputId = "bioskryb60.reset", label = "Reset Flank Bioskryb")),
               hr(),
               #--end of 60bp bioskryb buttons--#
               #this is where we put the 60bp GC content buttons
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("GC60-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "GC60.flank5",
                                              label = "Filter by Minimum and Maximum GC Content in 5' Flank:",
                                              min = 0,
                                              max = 1,
                                              value = c(0,1),
                                              step = 0.01,
                                              dragRange = F))),
               bsPopover("GC60-info", placement = "right", options = list(container = "body"),"the percentage of basepairs in the 60bp flanking region of the microsatellite that are either Guanine (G) or Cytosine (C)."),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(10,sliderInput(inputId = "GC60.flank3",
                                              label = "Filter by Minimum and Maximum GC Content in 3' Flank:",
                                              min = 0,
                                              max = 1,
                                              value = c(0,1),
                                              step = 0.01,
                                              dragRange = F), offset = 1))
               #--end of 60bp GC content buttons--#
      ),
      #--end of 60bp flank buttons--#
      #this tab is where we put all of the 90bp flank buttons
      tabPanel("90bp Flank",
               #generate two action buttons. One that triggers a tab to open in the MainPanel (where we put plot/dataset outputs) and another to close the tab.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        actionBttn(inputId = "tab90.show",label = "Open Flank Plots", 
                                   style = "minimal", color = "success", size = "xs"),
                        actionBttn(inputId = "tab90.hide",label = "Close Flank Plots",
                                   style = "minimal", color = "danger", size = "xs")),
               hr(),
               #this is where we put the 90bp mappability buttons
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("mappability-info-90", label = "", icon = icon("info"), 
                                          style = "info", size = "extra-small")),
                        column(10,pickerInput("umap90", label = "Select K-mer Size for UMAP Mappability:",
                                              choices = c("k24","k36","k50","k100"),
                                              selected = NULL,
                                              multiple = TRUE,
                                              options = list(`actions-box` = T)))),
               bsPopover("mappability-info-90", placement = "right", options = list(container = "body"), "Mappability describes the extent to which a given region can be uniquely mapped by sequence reads of a given length. The mappability annotations used here describe the average mappability of the flanking regions of the microsatellite, given sequencing reads of length k, where k is the length in bp of the selected k-mer. (hg38 genome-wide mappability data sourced from Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018). A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region."),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        uiOutput("sliderInput90_group_ui")),
               hr(),
               #--end of 90bp mappability buttons--#
               #this is where we put the 90bp overlap buttons
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("overlap-info-90", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "overlap90.5",
                                             label = "Filter by Microsatellite Overlap in the 5' Flank:",
                                             min = 0,
                                             max = 1,
                                             value = c(0,1),
                                             step = 0.01,
                                             dragRange = F))),
               fluidRow(align = "center",
                        column(10, offset = 1,sliderInput(inputId = "overlap90.3",
                                                          label = "Filter by Microsatellite Overlap in the 3' Flank:",
                                                          min = 0,
                                                          max = 1,
                                                          value = c(0,1),
                                                          step = 0.01,
                                                          dragRange = F))),
               bsPopover("overlap-info-90",placement = "right", options = list(container = "body"), "Microsatellite overlap describes the fraction of the microsatellite flanking region that contains another microsatellite."),
               hr(),
               #--end of 90bp overlap buttons--#
               #this is where we put the 90bp bioskryb buttons
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("bioskryb90-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(5,numericInput(inputId = "bioskryb90.min5",
                                              label = "Min. Bioskryb Coverage in the 5' Flank:",
                                              value = min(usats$flank.5.90bp.bioskryb),
                                              step = 0.01)),
                        column(5,numericInput(inputId = "bioskryb90.max5",
                                              label = "Max. Bioskryb Coverage in the 5' Flank:",
                                              value = max(usats$flank.5.90bp.bioskryb),
                                              step = 0.01))),
               fluidRow(align = "center",
                        column(5, offset = 1, numericInput(inputId = "bioskryb90.min3",
                                                           label = "Min. Bioskryb Coverage in the 3' Flank:",
                                                           value = min(usats$flank.3.90bp.bioskryb),
                                                           step = 0.01)),
                        column(5, numericInput(inputId = "bioskryb90.max3",
                                               label = "Max. Bioskryb Coverage in the 3' Flank:",
                                               value = max(usats$flank.3.90bp.bioskryb),
                                               step = 0.01))),
               bsPopover("bioskryb90-info", placement = "right", options = list(container = "body"), "Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra. Coverage normalized using average autosome genome coverage. For male samples: multiply chrX and chrY normalized coverage by 2, since the goal is to assess single-cell amplification relative to the original copy number (Otherwise, coverage filtering will tend to exclude chrX and chrY in males)."),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        actionButton(inputId = "bioskryb90.reset", label = "Reset Flank Bioskryb")),
               hr(),
               #--end of 90bp bioskryb buttons--#
               #this is where we put 90bp GC content buttons
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(1,bsButton("GC90-info", label = "", icon = icon("info"),
                                          style = "info", size = "extra-small")),
                        column(10,sliderInput(inputId = "GC90.flank5",
                                              label = "Filter by Minimum and Maximum GC Content in 5' Flank:",
                                              min = 0,
                                              max = 1,
                                              value = c(0,1),
                                              step = 0.01,
                                              dragRange = F))),
               bsPopover("GC90-info", placement = "right", options = list(container = "body"),"the percentage of basepairs in the 90bp flanking region of the microsatellite that are either Guanine (G) or Cytosine (C)."),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(10,sliderInput(inputId = "GC90.flank3",
                                              label = "Filter by Minimum and Maximum GC Content in 3' Flank:",
                                              min = 0,
                                              max = 1,
                                              value = c(0,1),
                                              step = 0.01,
                                              dragRange = F), offset = 1))
               #--end of 90bp GC content buttons--#
      ),
      #--end of 90bp flank buttons--#
      #create new tab in sidebar panel for regionfilter options
      tabPanel("Regions",
               #html text output containing information on how regionfilters are defined.
               fluidRow(style = "padding-top:10px;",
                        htmlOutput("regionfilter.info")),
               #the uiOutput function allows us to display ui elements that are generated in the server. In this case, the regionfilter checkboxes are generated in the server because they are reactive to user inputs
               uiOutput("regionfilter.checkboxes_ui"),
               #generate two action buttons. One that triggers a tab to open in the MainPanel (where we put plot/dataset outputs) and another to close the tab.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        column(6,actionBttn(inputId = "filterDef.show",label = "View Default Region Filters Definitions", 
                                            style = "minimal", color = "success", size = "xs")),
                        column(6,actionBttn(inputId = "filterDef.hide",label = "Hide Default Region Filters Definitions",
                                            style = "minimal", color = "danger", size = "xs"))),
               hr(),
               #html text output containing information on how to generate and upload new regionfilters
               htmlOutput("regionfilter.upload.info"),
               hr(),
               #the fileInput function allows the user to upload files to the RAM to be used in server calculations. In this case, as long as file are formatted as described in the "regionfilter.upload.info" htmlOutput, the user can upload custom regionfilters. Some built-in checks on file formatting include the "accept" option which restricts file uploads to only files that have a ".csv" extension. The "multiple" option, however, allows the user to upload more than one custom regionfilter at once, if they choose to do so.
               fluidRow(fileInput(inputId = "import",
                                  label = "Import Region Filters:",
                                  accept = c(".csv",".zip"),
                                  multiple = TRUE))),
      #create new tab in sidebar panel for restriction enzyme filters
      tabPanel("Restriction Enzymes",
               #html text output containing information on how to apply restriction enzyme filters
               fluidRow(style = "padding-top: 10px;",
                        htmlOutput("RE.info")),
               #create a dropdown menu of the restriction enzymes the user may use to filter microsatellites. The user may select multiple microsatellites and there are action box and live search options that allow the user to quickly select/deselect all and search for the restiction enzyme they want by name.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        pickerInput(inputId = "selectREs", 
                                    label = "Select Restriction Enzyme(s) to Use:",
                                    choices = RE.capture_filters.name,
                                    selected = NULL,
                                    options = list(`actions-box` = T, `live-search` = T), 
                                    multiple = T)),
               #the uiOutput function allows us to display ui elements that are generated in the server. In this case, the restriction enzyme inputs are generated on the server based on which restriction enzymes the user selects from the dropdown menu above.
               fluidRow(style = "padding-top: 10px; padding-left: 5px",
                        uiOutput("numericInput_buttons_ui"))),
      #create a new tab in the sidebar panel for options on how to view data
      tabPanel("View Data",
               style = "padding-top: 10px;",
               #add a radio button that controls whether that data displayed in the data table (in an optional Main Panel tab) shows all microsatellites in the dataset, microsatellites that pass the applied filters or microsatellite that are excluded by those filters
               radioButtons("dataView","Select Data to Display [Data Table ONLY]:",
                            choices = c("All Microsatellites","Microsatellites that pass filter(s)","Microsatellites excluded by filter(s)"),
                            selected = "Microsatellites that pass filter(s)"),
               #add a dropdown menu that allows the user to select which columns to display in the data table
               pickerInput("annotation.select","Select Columns:",
                           choices = columns,
                           selected = columns[which(names(columns) %in% c("row.number","seqnames","start","end","width","motif","motif.family"))],
                           options = pickerOptions(actionsBox = T),
                           multiple = T),
               #generate two action buttons. One that triggers a tab to open in the MainPanel (where the dataset will appear) and another to close the tab.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        actionBttn(inputId = "dataTab.show",label = "View Data Table", 
                                   style = "minimal", color = "success", size = "xs"),
                        actionBttn(inputId = "dataTab.hide",label = "Hide Data Table",
                                   style = "minimal", color = "danger", size = "xs")),
               #generate two action buttons. One that triggers a tab to open in the MainPanel with definitions of all of the columns in the dataset and another to close the tab.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        actionBttn(inputId = "colDef.show",label = "View Annotation Definitions", 
                                   style = "minimal", color = "success", size = "xs"),
                        actionBttn(inputId = "colDef.hide",label = "Hide Annotation Definitions",
                                   style = "minimal", color = "danger", size = "xs")),
               hr(),
               #html text output that describes how to download filtered dataset
               htmlOutput("download.info"),
               #numeric input that defines the size of the randomly-selected subset of microsatellites to download if the user clickes the "Download Subset" download button
               fluidRow(numericInput(inputId = "subsetSize",
                                     label = "Subset Size:",
                                     value = nrow(usats))),
               #an html output that tells the user if they are exceeding the number of microsatellites in the dataset in their subset download
               fluidRow(style = "padding-bottom:10px;",
                        htmlOutput("downloadWarning")),
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        #create a download button that, when clicked, works with the download Handler in the server to download the specified subset of microsatellites.
                        downloadButton("downloadSubset","Download Subset"),
                        #create a download button that, when clicked, works with the download Handler in the server to download all microsatellites either a) in the dataset, b) that pass the applied filters, or c) that do not pass the applied filters.
                        downloadButton("downloadData","Download All"))
               ),
      #create a new tab in the sidebar panel for options that allow the user to generate their own plots in the a Main Panel tab
      tabPanel("User-Defined Plots",
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "left",
                        #allow the user select which dataset you would like to visualize in the user plots tab
                        radioButtons("dataPlot","Select Data to Display [User Plot ONLY]:",
                                     choices = c("All Microsatellites","Microsatellites that pass filter(s)","Microsatellites excluded by filter(s)"),
                                     selected = "Microsatellites that pass filter(s)")),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "left",
                        #allow the user to select which variable they would like to display on the x axis. Default = width
                        pickerInput("xAxis","Select Variable on X-axis:",
                                    choices = names(columns)[which(!(names(columns) %in% c("seqnames","start","end",
                                                                                    "strand","consensus","sequence",
                                                                                    "motif","motif.family","flank.5.prime.seq",
                                                                                    "flank.3.prime.seq")))],
                                    selected = "width")),
               #allow the user to select whether or not the x-axis should be on a log scale (default: not log scale)
               fluidRow(style = "padding-bottom: 10px;",
                        align = "left",
                        materialSwitch("xLog", label = "Log-scale X-axis:",
                                       status = "primary")),
               #allow the user to define the minimum and maximum x axis bounds
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(4, numericInput("min.Xaxis","Set Minimum Bound for X-Axis:",
                                               value = min(usats$width)), offset = 2),
                        column(4, numericInput("max.Xaxis", "Set Maximum Bound for X-Axis:",
                                               value = max(usats$width)))),
               fluidRow(style = "padding-bottom: 10px;",
                        align = "left",
                        #allow the user to select which variable they would like to display on the y axis, or "count" or "proportion" to display a histogram of the x axis variable. Default = count
                        pickerInput("yAxis","Select Variable on Y-axis:",
                                    choices = c("count","proportion",names(columns)[which(!(names(columns) %in% c("seqnames","start","end",
                                                                                    "strand","consensus","sequence",
                                                                                    "motif","motif.family","flank.5.prime.seq",
                                                                                    "flank.3.prime.seq")))]),
                                    selected = "count")),
               #allow the user to select whether or not the y-axis should be on a log scale (default: not log scale)
               fluidRow(style = "padding-bottom: 10px;",
                        align = "left",
                        materialSwitch("yLog", label = "Log-scale Y-axis:",
                                       status = "primary")),
               #allow the user to define the minimum and maximum y axis bounds
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        column(4, numericInput("min.Yaxis","Set Minimum Bound for Y-Axis:",
                                               value = 0), offset = 2),
                        column(4, numericInput("max.Yaxis", "Set Maximum Bound for Y-Axis:",
                                               value = round(max(ggplot_build(gg_init)$data[[1]]$count)*1.1)))),
               #generate two action buttons. One that triggers a tab to open in the MainPanel with definitions of all of the variables in the dataset and another to close the tab.
               fluidRow(style = "padding-top: 10px; padding-bottom: 10px;",
                        align = "center",
                        actionBttn(inputId = "varDef.show",label = "View Annotation Definitions", 
                                   style = "minimal", color = "success", size = "xs"),
                        actionBttn(inputId = "varDef.hide",label = "Hide Annotation Definitions",
                                   style = "minimal", color = "danger", size = "xs")),
               #generate two action buttons. One that triggers a tab to open in the MainPanel with the user defined plot and another to close the tab.
               fluidRow(style = "padding-bottom: 10px;",
                        align = "center",
                        actionBttn(inputId = "userPlots.show",label = "View Plot", 
                                   style = "minimal", color = "success", size = "sm"),
                        actionBttn(inputId = "userPlots.hide",label = "Hide Plot",
                                   style = "minimal", color = "danger", size = "sm"))
               )
    )
  ),
  #define an area of the page (on the right side) for outputs of the inputs in the sidepanel. This area is the mainPanel.
  mainPanel(
    #this is where we put global action buttons
    
    #these are inputs that go in the upper right-hand part of the page so that they are always easily accessible to the user so that they don't forget to implement the changes they made using the filters in the sidebar panel.
    fluidRow(align = "right",
             #this button applies any changes made in the sidebar panel to the microsatellite dataset and filters it based on the updated values. It also causes any plots or data tables that are displayed in the main panel to update.
             actionButton("goButton","Apply Changes", class = "btn-success"),
             #this button resets all changes made in the sidebar panel to their default status (when they are initially loaded) and resets and plots/datasets displayed in the main panel as well.
             actionButton("resetAll","Reset All"),
             #this button reverts all inputs back to the last saved values (the values that generated the plots/dataset displayed in the main panel)
             actionButton("revert","Revert"),
             #this is a hidden button that triggers the second part of the revert action (more details below)
             hidden(actionButton("revertGo","Revert Part 2")),
             #this is a hidden button that triggers the second part of the reset action (more details below)
             hidden(actionButton("resetGo","Apply Reset")),
             #these are hidden buttons that triggers the multi-part restoration of a previously bookmarked app state
             hidden(actionButton("restoreGoA","Restore Part A")),
             hidden(actionButton("restoreGoB","Restore Part B")),
             hidden(actionButton("restoreGoC","Restore Part C"))),
    fluidRow(align = "right",
             style = "padding-top: 10px; padding-bottom: 10px;",
             #this is a download button that, which clicked, downloads a .RDS file that contains the last saved state of the app
             downloadButton("bookmarkButton","Bookmark",icon = icon("bookmark")),
             #this is an action button that, when clicked, causes a window to open, prompting the user to upload a .RDS file to restore the app to a previously bookmarked state
             actionButton("restoreSession","Restore Session", icon = icon("window-restore"))),
    fluidRow(align = "right",
             #this is an html text output that automatically updates when any value in the sidebar panel is changed to alert the user to the fact that the plots/dataset displayed in the main panel are now out of date. Clicking the "Apply Changes","Reset","Revert", or "Restore Session" buttons will change the update message back to "up to date" to let the user know that the plots/dataset displayed in the main panel correspond to the data that passes the filters applied in the sidebar panel.
             htmlOutput("updateMessage")),
    #--end of global action buttons--#
    #this generates a set of tabs in the main panel for a variety of different plot/dataset outputs that the user may want to view for microsatellite analysis and selection
    tabsetPanel(id = "plot.tabs",
                #this tab is the main panel that is displayed when the app first loads. It displays the plots that correspond to the filters on the "Microsatellites - Main" sidebar panel tab
                tabPanel("Microsatellite Plots",
                         fluidRow(style = "padding-top: 10px;",
                                  plotOutput("width.hist")),
                         fluidRow(style = "padding-top: 10px;",
                                  plotOutput("motif.by.length.hist")),
                         fluidRow(style = "padding-top: 10px;",
                                  plotOutput("per.match.by.length")),
                         fluidRow(style = "padding-top: 10px;",
                                  plotOutput("per.indel.by.length")),
                         fluidRow(style = "padding-top:10px;",
                                  plotOutput("motif.size.hist")),
                         fluidRow(style = "padding-top: 10px;",
                                  plotOutput("bioskryb.usat.hist")),
                         fluidRow(style = "padding-top: 10px;",
                                  plotOutput("mu.usat.hist")))
    ),
    type = "pills"
  ),
  #this adds a loading screen  that takes up the full page and is displayed whenever the app is performing a calculation.
  add_busy_spinner(spin = "hollow-dots",
                   position = "full-page")
  )
  }

# Define server logic ----
server <- function(input, output, session) {
  
  #increase max file upload size to 1000 MB or 1 GB
  options(shiny.maxRequestSize=1000*1024^2)

  #the observeEvent function lets you trigger an action every time a user changes a particular input. In this case, when the user clicks the "length.reset" button, it will trigger the "minlength" and "maxlength" inputs to reset to their default values 
  observeEvent(input$length.reset,{
    reset("minlength")
    reset("maxlength")
  })
  
  #this resets the minimum and maximum bioskryb coverage inputs when the bioskryb.reset button is clicked
  observeEvent(input$bioskryb.reset,{
    reset("bioskryb.min")
    reset("bioskryb.max")
  })
  
  #this resets the minimum and maximum estimated mutation rate inputs when the bioskryb.reset button is clicked
  observeEvent(input$mu.reset,{
    reset("mu.min")
    reset("mu.max")
  })
  
  #this resets the minimum and maximum copy number inputs when the copy.num.reset button is clicked
  observeEvent(input$copy.num.reset,{
    reset("copyNum.min")
    reset("copyNum.max")
  })
  
  #this resets the minimum and maximum score inputs when the score.reset button is clicked
  observeEvent(input$score.reset,{
    reset("scoreMin")
    reset("scoreMax")
  })
  
  #this resets the minimum and maximum uninterrupted length inputs when the uninterrupted.length.reset button is clicked
  observeEvent(input$uninterrupted.length.reset,{
    reset("ulengthMin")
    reset("ulengthMax")
  })
  
  #this resets the minimum and maximum uninterrupted copy number inputs when the uninterrupted.copy.num.reset button is clicked
  observeEvent(input$uninterrupted.copy.num.reset,{
    reset("uCopyNumMin")
    reset("uCopyNumMax")
  })
  
  #this resets the minimum and maximum distance to the next nearest microsatellite inputs for both the 5' and 3' flanks when the nearest.usat.reset button is clicked
  observeEvent(input$nearest.usat.reset,{
    reset("nearestUsatMin.3")
    reset("nearestUsatMax.3")
    reset("nearestUsatMin.5")
    reset("nearestUsatMax.5")
  })
  
  #this resets the minimum and maximum bioskryb coverage inputs for both the 5' and 3' 90bp flanks when the bioskryb90.reset button is clicked
  observeEvent(input$bioskryb90.reset,{
    reset("bioskryb90.min5")
    reset("bioskryb90.max5")
    reset("bioskryb90.min3")
    reset("bioskryb90.max3")
  })
  
  #this resets the minimum and maximum bioskryb coverage inputs for both the 5' and 3' 60bp flanks when the bioskryb60.reset button is clicked
  observeEvent(input$bioskryb60.reset,{
    reset("bioskryb60.min5")
    reset("bioskryb60.max5")
    reset("bioskryb60.min3")
    reset("bioskryb60.max3")
  })
  
  #this resets all inputs when the resetAll button is clicked
  observeEvent(input$resetAll,{
    reset("minlength")
    reset("maxlength")
    reset("period")
    reset("motif")
    reset("bioskryb.min")
    reset("bioskryb.max")
    reset("mu.min")
    reset("mu.max")
    reset("umap60")
    reset("overlap60.5")
    reset("overlap60.3")
    reset("umap90")
    reset("overlap90.5")
    reset("overlap90.3")
    reset("bioskryb90.min5")
    reset("bioskryb90.max5")
    reset("bioskryb90.min3")
    reset("bioskryb90.max3")
    reset("bioskryb60.min5")
    reset("bioskryb60.max5")
    reset("bioskryb60.min3")
    reset("bioskryb60.max3")
    reset("selectREs")
    reset("dataView")
    reset("annotation.select")
    reset("copyNum.min")
    reset("copyNum.max")
    reset("perMatch")
    reset("perIndel")
    reset("scoreMin")
    reset("scoreMax")
    reset("entropy")
    reset("ulengthMin")
    reset("ulengthMax")
    reset("uCopyNumMin")
    reset("uCopyNumMax")
    reset("GC.msat")
    reset("overlapUsatMin")
    reset("overlapUsatMax")
    reset("overlapUsat")
    reset("nearestUsatMin.3")
    reset("nearestUsatMax.3")
    reset("nearestUsatMin.5")
    reset("nearestUsatMax.5")
    reset("GC60.flank5")
    reset("GC60.flank3")
    reset("GC90.flank5")
    reset("GC90.flank3")
    reset("xAxis")
    reset("yAxis")
    reset("min.Xaxis")
    reset("max.Xaxis")
    reset("min.Yaxis")
    reset("max.Yaxis")
    reset("xLog")
    reset("yLog")
    reset("dataPlot")
    updateRadioGroupButtons(session,"nearest.usat.radio", choices = c("either","both"), selected = "either")
    reset("LinkNearestUsatToMappability")
    rf.checkbox.options <- unlist(grep("rf_checkbox", names(input), value = TRUE))
    lapply(rf.checkbox.options, function(x) updateCheckboxInput(session,x, value = FALSE))
    click("resetGo")
  }, ignoreInit = TRUE)
  
  #resetting returns the input values to their default settings but it doesn't necessarily apply those filters to the dataset. For that to happen, you need to virtually click the Apply Changes button, but that has to happen only after all values have been changed, which is why there is this step to click a hidden "resetGo" button after all values have been reset, which only then triggers the Apply Changes button.
  observeEvent(input$resetGo,{
    click("goButton")
  }, ignoreInit = TRUE)
  
  #list all of the tabs that should appear in the order that they should appear in the main panel (the same order as their counterpart sidebar panel tabs)
  tab.id <- c("Microsatellite Plots","Microsatellite Plots - Other","60bp Flank Plots","90bp Flank Plots",
              "Defult Region Filters Definitions","View Data","Annotation Definitions","User Plots")
  
  #initialize a NON-REACTIVE variable for storing information about which tab was last open
  tab.history <- NULL
  
  #create a reactive variable that tracks which tabs are open using the tab counters (which ensure the "open tab" button only works when the tab is closed) associated with each tab
  #note: the tab counter numbers are not in order because they were numbered in the order in which they were created, which does not match the desired order of appearance
  open.tabs <- reactive({
    #note: this list of "tab.ctrls" starts with a "TRUE" hard-coded at the beginning because the first tab "Microsatellite Plots" is always open and can never be closed, and therefore doesn't have a tab counter associated with it to track its status
    tab.ctrls <- c(TRUE,
                   !(is.null(tab.counter.6())),
                   !(is.null(tab.counter.1())),
                   !(is.null(tab.counter.2())),
                   !(is.null(tab.counter.7())),
                   !(is.null(tab.counter.3())),
                   !(is.null(tab.counter.4())),
                   !(is.null(tab.counter.5())))
    open.tabs = which(tab.ctrls == TRUE)
    return(open.tabs)
    })
  
  #create an observer object in shiny which observes which tabs are open and saves that data OUTSIDE of the scope of the shiny reactive values to the non-reactive tab.history variable
  observe({
    t <- open.tabs()
    tab.history <<- t
  })
  
  #figure out which tab should be the target of the insertTab function used to insert a tab when an "open tab" button is clicked
  target <- reactive({
    #figure out which tab has been most recently selected by comparing the reactive open.tabs variable to the non-reactive tab.history variable. The tab that is in the open.tabs variable but not in the tab.history variable will be the most recent tab
    selected.tab <- open.tabs()[which(!(open.tabs() %in% tab.history))]
    #get the index of the target tab by getting the index number that just precedes the selected tab
    target.idx <- max(tab.history[tab.history < selected.tab])
    #get the name of the target tab by indexing the tab.id variable
    target <- tab.id[target.idx]
    #return target tab name
    return(target)
  })
  
  #create a reactive value to track when a tab specific tab is opened (in this case, the tab containing the plots that correspond to the filters in the 60bp flank tab of the sidebar panel). Set that value to NULL
  tab.counter.1 <- reactiveVal(NULL)
  #when the "open tab" button associated with this tab (tab60.show) is clicked, update the value of the tab counter to 1
  observeEvent(input$tab60.show,{
    tab.counter.1(tab.counter.1()+1)
  })
  
  #when the tab counter value is updated so that it is not null, insert a tab into the "plot.tabs" tabPanel after the target tab retreived by the "target" reactive value designated above to ensure that main panel tabs remain in the same order as the sidebar panel tabs, and navigate to that tab automatically
  observeEvent(tab.counter.1(),{
    insertTab(inputId = "plot.tabs", 
              tab = tabPanel("60bp Flank Plots",
                             fluidRow(style = "padding-top: 10px;",
                                      uiOutput("umap.60bp.hist.ui"),
                                      plotOutput("overlap.60bp.hist"),
                                      plotOutput("bioskryb.60bp.hist"),
                                      plotOutput("GC.60bp.hist"))),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  #if the "close tab" button associated with this tab (tab60.hide) is clicked, remove the associated tab and reset the corresponding tab counter back to NULL.
  observeEvent(input$tab60.hide,{
    removeTab(inputId = "plot.tabs",
              target = "60bp Flank Plots")
    tab.counter.1(NULL)
  })
  
  #make a tab that can be inserted into the main panel to display the plots associated with the 90bp flank filters in the corresponding tab in the sidebar panel
  tab.counter.2 <- reactiveVal(NULL)
  observeEvent(input$tab90.show,{
    tab.counter.2(tab.counter.2()+1)
  })
  
  observeEvent(tab.counter.2(),{
    insertTab(inputId = "plot.tabs", 
              tab = tabPanel("90bp Flank Plots",
                             fluidRow(style = "padding-top: 10px;",
                                      uiOutput("umap.90bp.hist.ui"),
                                      plotOutput("overlap.90bp.hist"),
                                      plotOutput("bioskryb.90bp.hist"),
                                      plotOutput("GC.90bp.hist"))),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  observeEvent(input$tab90.hide,{
    removeTab(inputId = "plot.tabs",
              target = "90bp Flank Plots")
    tab.counter.2(NULL)
  })
  
  #make a tab that can be inserted into the main panel to display the datatable based on the parameters defined in the corresponding tab in the sidebar panel
  tab.counter.3 <- reactiveVal(NULL)
  observeEvent(input$dataTab.show,{
    tab.counter.3(tab.counter.3()+1)
  })
  
  observeEvent(tab.counter.3(),{
    insertTab(inputId = "plot.tabs",
              tab = tabPanel("View Data",
                             DTOutput("viewData")),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  observeEvent(input$dataTab.hide,{
    removeTab(inputId = "plot.tabs",
              target = "View Data")
    tab.counter.3(NULL)
  })
  
  #make a tab that can be inserted into the main panel to display the definitions of the microsatellite annotations in the dataset
  tab.counter.4 <- reactiveVal(NULL)
  observeEvent(input$colDef.show | input$varDef.show,{
    tab.counter.4(tab.counter.4()+1)
  })
  
  observeEvent(tab.counter.4(),{
    insertTab(inputId = "plot.tabs",
              tab = tabPanel("Annotation Definitions",
                             htmlOutput("column.definitions")),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  observeEvent(input$colDef.hide | input$varDef.hide,{
    removeTab(inputId = "plot.tabs",
              target = "Annotation Definitions")
    tab.counter.4(NULL)
  })
  
  tab.counter.5 <- reactiveVal(NULL)
  observeEvent(input$userPlots.show,{
    tab.counter.5(tab.counter.5()+1)
  })
  
  #make a tab that can be inserted into the main panel to display the user plot based on the parameters selected in the corresponding tab in the sidebar panel
  observeEvent(tab.counter.5(),{
    insertTab(inputId = "plot.tabs",
              tab = tabPanel("User Plots",
                             plotOutput("userPlot"),
                             fluidRow(style = "padding-top:10px;",
                                      align = "center",
                                      actionButton("changePlot","Update Plot"))),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  observeEvent(input$userPlots.hide,{
    removeTab(inputId = "plot.tabs",
              target = "User Plots")
    tab.counter.5(NULL)
  })
  
  #make a tab that can be inserted into the main panel to display the plots associated with the second set of microsatellite filters in the corresponding tab in the sidebar panel
  tab.counter.6 <- reactiveVal(NULL)
  observeEvent(input$tab.msat2.show,{
    tab.counter.6(tab.counter.6()+1)
  })
  
  observeEvent(tab.counter.6(),{
    insertTab(inputId = "plot.tabs",
              tab = tabPanel("Microsatellite Plots - Other",
                             plotOutput("copy.num.hist"),
                             plotOutput("per.match.hist"),
                             plotOutput("per.indel.hist"),
                             plotOutput("score.hist"),
                             plotOutput("entropy.hist"),
                             plotOutput("length.uninterrupted.hist"),
                             plotOutput("uninterrupted.copy.num.hist"),
                             plotOutput("GC.msat.hist"),
                             plotOutput("nearest.usat.hist")),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  observeEvent(input$tab.msat2.hide,{
    removeTab(inputId = "plot.tabs",
              target = "Microsatellite Plots - Other")
    tab.counter.6(NULL)
  })
  
  #make a tab that can be inserted into the main panel to display the definitions of the static regionfilters associated with this app
  tab.counter.7 <- reactiveVal(NULL)
  observeEvent(input$filterDef.show,{
    tab.counter.7(tab.counter.7()+1)
  })
  
  observeEvent(tab.counter.7(),{
    insertTab(inputId = "plot.tabs",
              tab = tabPanel("Default Region Filters Definitions",
                             htmlOutput("defaultRegionFilters")),
              target = as.character(target()),
              position = "after",
              select = TRUE)
  })
  
  observeEvent(input$filterDef.hide,{
    removeTab(inputId = "plot.tabs",
              target = "Default Region Filters Definitions")
    tab.counter.7(NULL)
  })
  
  #this is where we put regionfilter reactivity
  file_counter <- reactiveValues(n=length(regionfilters_static.path))
  
  #keep a record of the path to the files that are either in the static regionfilters directory or uploaded by the user
  file_inputs <- reactiveValues(
    history = regionfilters_static.path
  )
  
  #keep a record of the names of the files that are either in the static regionfilters directory or uploaded by the user
  name_inputs <- reactiveValues(
    history = regionfilters_static.name
  )
  
  #if the user uploads a file using the import button, update the file counter to the new total number of files and update the lists of paths to and names of the regionfilter files
  observeEvent(input$import,{
    file_counter$n <- file_counter$n + 1
    file_inputs$history <- c(file_inputs$history, input$import$datapath)
    name_inputs$history <- c(name_inputs$history, input$import$name)
  })
  
  #this is where the user interface is generated for the regionfilters sidebar panel tab. 
  #This section of the code generates a single checkbox with two empty div elements that serve as targets for other ui elements for each regionfilter in the static regionfilters directory plus any user-uploaded regionfilters.
  regionfilter.checkboxes <- reactive({
    n <- file_counter$n
    if(n > 0){
      fluidPage(
        lapply(name_inputs$history,function(ii){
          fluidRow(checkboxInput(inputId = paste0("rf_checkbox",which(name_inputs$history == ii)),
                                 label = ii,
                                 value = FALSE), 
                   tags$div(id=paste0("rf_targetA",which(name_inputs$history == ii))),
                   tags$div(id=paste0("rf_targetB",which(name_inputs$history == ii))))})
      )
    }
  })
  
  #create a ui output object that can be displayed in the ui
  output$regionfilter.checkboxes_ui <- renderUI({regionfilter.checkboxes()})
  
  #for each regionfilter (either static or user-imported), use an observeEvent element to check for changes in the regionfilter's corresponding checkbox
  lapply(1:length(unlist(observe({name_inputs$history}))), function(x){
    observeEvent(eval(parse(text = paste0("input$rf_checkbox",x))),{
      #if the user clicks the checkbox, changing its value to "TRUE", then insert a radio button that allows the user to filter microsatellites to exclude microsatellites that overlap with the selected regionfilter in a) the microsatellite body only b) the flank region only or c) either the microsatellite body or the flank region
      if(eval(parse(text = paste0("input$rf_checkbox",x))) == TRUE){
        insertUI(
          selector = paste0("#rf_targetA",x),
          where = "beforeEnd",
          ui = tagList(
            fluidRow(
              column(1,bsButton(paste0("bs_radio",x), label = "", icon = icon("info"),
                                style = "info", size = "extra-small")),
              column(11,radioGroupButtons(
                inputId = paste0("rf_radio",x),
                label = "Apply filter to:",
                choices = c("microsatellite","flank","both"),
                selected = "microsatellite"))
            ),
            hidden(actionButton(paste0("removeUI",x),"Remove UI")),
            hidden(checkboxInput(paste0("alreadyActive",x),"Already Active?"))
        ), immediate = TRUE)
        addPopover(session,paste0("bs_radio",x), placement = "right",options = list(container = "body"),"Selecting &#34microsatellite&#34 option will apply region filter to the microsatellite body only. Selecting &#34flank&#34 option will apply region filter to only the specified microsatellite flanks. Selecting &#34both&#34 option will apply region filter to both the body of the microsatellite and the specified flanking regions.")
        #if the checkbox is unchecked, click the hidden "removeUI" button, which will trigger another chunk with a "removeUI" function to remove the targetted UI.
      }else if(eval(parse(text = paste0("input$rf_checkbox",x))) == FALSE){
        click(paste0("removeUI",x))
      }
    }, ignoreInit = TRUE)
  })
  
  #for each regionfilter (either static or user-imported), use an observeEvent element to check whether or not the removeUI button has been clicked (i.e. whether or not the checkbox has been unchecked)
  lapply(1:length(unlist(observe({name_inputs$history}))), function(x){
    observeEvent(eval(parse(text = paste0("input$removeUI",x))),{
      #if the checkbox has been unchecked and the "removeUI" button has been triggered, remove all UI elements that are below the unchecked checkbox button
      removeUI(
        selector = paste0("#rf_targetA",x," *"), 
        multiple = TRUE)
      removeUI(
        selector = paste0("#rf_targetB",x," *"), 
        multiple = TRUE)
    })
    
  })
  
  #for each regionfilter (either static or user-imported), use an observeEvent element to check for changes in the regionfilter's corresponding radio button
  lapply(1:length(unlist(observe({name_inputs$history}))), function(x){
    observeEvent(eval(parse(text = paste0("input$rf_radio",x))),{
      #if the radio button is changed to anything other than microsatellite, and this insertUI hasn't already been activated (meaning you're not changing from "both" to "flank" or from "flank" to "both"), insert the UI elements below (a set of numeric inputs for determining the bounds in the 5' and 3' flanking regions and a radio button that allows the user to determine whether microsatellites must pass the region filters in both flanks or in only one flank)
      if((eval(parse(text = paste0("input$rf_radio",x))) != "microsatellite") & (eval(parse(text = paste0("input$alreadyActive",x))) == FALSE)){
        insertUI(
          selector = paste0("#rf_targetB",x),
          where = "beforeEnd",
          ui = tagList(
            fluidRow(
              column(1,bsButton(paste0("bs_rf_flanks",x), label = "", icon = icon("info"),
                                style = "info", size = "extra-small")),
              column(5,numericInput(
                inputId = paste0("rf_numMin_flank5",x),
                label = "5' Flank Start:",
                value = 0)),
              column(5,numericInput(
                inputId = paste0("rf_numMax_flank5",x),
                label = "5' Flank End:",
                value = 150))
            ),
            fluidRow(
              column(5,numericInput(
                inputId = paste0("rf_numMin_flank3",x),
                label = "3' Flank Start:",
                value = 0), offset = 1),
              column(5,numericInput(
                inputId = paste0("rf_numMax_flank3",x),
                label = "3' Flank End:",
                value = 150))
            ),
            fluidRow(
              column(1,bsButton(paste0("bs_flank-choice",x), label = "", icon = icon("info"),
                                style = "info", size = "extra-small")),
              column(10,align = "center",
                     radioGroupButtons(inputId = paste0("flank_rf",x),
                                       label = "Exclude microsatellites that overlap a filtered region in one and only one flank or both flanks?",
                                       choices = c("one","both"),
                                       selected = "both"))),
            hidden(actionButton(paste0("removeNumeric",x),"Remove Numeric"))),
          immediate = TRUE)
        addPopover(session,paste0("bs_rf_flanks",x),placement = "right",options = list(container = "body"),"Use this feature to define the bounds of the microsatellite flanks. The start input defines the distance from the microsatellite body at which the 3&#39 and 5&#39 flank regions should start. The end input defines the distance from the microsatellite body at which the 3&#39 and 5&#39 flank regions should end. Once these bounds have been determined, any microsatellites that have region filters that overlap with one or both of these flanks (depending on the user&#39s preference), even if they overlap by a single bp, will be excluded from the returned list of filtered microsatellites.")
        addPopover(session,paste0("bs_flank-choice",x), placement = "right",options = list(container = "body"),"Selecting &#34one&#34 will exclude all microsatellites that have even one flank that overlaps the selected region filter. Selecting &#34both&#34 will exclude only microsatellites for which both flanks overlap the selected region filter. Note: this means that the &#34both&#34 option is less stringent than the &#34one&#34 option.")
        #change "alreadyActive" button value to TRUE to indicate that this radio button has already triggered the necessary insertUI action, and does not need to be triggered again
        updateCheckboxInput(session,paste0("alreadyActive",x), value = TRUE)
        #if the radio button is changed back to "microsatellite", click the hidden "removeNumeric" button, which will trigger another chunk with a "removeUI" function to remove the targeted UI.
        }else if(eval(parse(text = paste0("input$rf_radio",x))) == "microsatellite"){
          click(paste0("removeNumeric",x))
          updateCheckboxInput(session,paste0("alreadyActive",x), value = FALSE)
      }
    })
  })
  
  #for each regionfilter (either static or user-imported), use an observeEvent element to check whether or not the removeNumeric button has been clicked (i.e. whether or not the radio button has been changed to "microsatellite")
  lapply(1:length(unlist(observe({name_inputs$history}))), function(x){
    observeEvent(eval(parse(text = paste0("input$removeNumeric",x))),{
      #if the radio button has been changed to "microsatellite" and the "removeNumeric" button has been triggered, remove all UI elements that are below the radio button
      removeUI(
        selector = paste0("#rf_targetB",x," *"), 
        multiple = TRUE)
    })
  })
  
  #create a reactive list to store which regionfilters have been selected (i.e. which ones have their checkbox value set to TRUE)
  regions_select <- reactive({
    n <- file_counter$n
    options <- name_inputs$history
    dynInputList <- c(NULL)
    for(ii in seq_len(n)){
      dynInput <- paste0("rf_checkbox",ii)
      if(input[[dynInput]] == TRUE){
        dynInputList <- c(dynInputList,options[ii])
      }
    }
    return(dynInputList)
  })
  
  #create a reactive object that stores the exact selections the user has made regarding which regionfilters will only apply to the microsatellite body, which regionfilters will only apply to the microsatellite flanks, and which regionfilters will apply to both.
  myChoices2 <- reactive({
    options <- name_inputs$history
    usatFilters <- list()
    flankFilters <- list()
    bothFilters <- list()
    #for each regionfilter that has been selected by the user, determine whether it's corresponding radio button is set to "flank", "both", or "microsatellite and sort them into 3 lists accordingly
    for(ii in regions_select()){
        if(eval(parse(text = paste0("input$rf_radio",which(options == ii)))) == "flank"){
          flankFilters[[ii]] <- as.character(file_inputs$history[which(name_inputs$history %in% ii)])
        }else if(eval(parse(text = paste0("input$rf_radio",which(options == ii)))) == "both"){
          bothFilters[[ii]] <- as.character(file_inputs$history[which(name_inputs$history %in% ii)])
        }else{
        usatFilters[[ii]] <- as.character(file_inputs$history[which(name_inputs$history %in% ii)])
      }
    }
    #create a "selection string" that stores all of the regionfilter sorting information in one convenient object so that the final output looks something like this "usatFilters=regionfilter1&regionfilter2+flankFilters=regionfilter3&regionfilter4+bothFilters=regionfilter5&regionfilter6"
    selection_string <- paste(c(paste(c("usatFilters",paste(usatFilters,collapse = "&")),collapse = "="),paste(c("flankFilters",paste(flankFilters,collapse = "&")),collapse = "="),paste(c("bothFilters",paste(bothFilters,collapse = "&")), collapse = "=")), collapse = "+")
    return(selection_string)
  })
  
  #this is a reactive object that loads in and processes regionfilter data
  regionfilters <- reactive({
    #initialize 4 variables to use to breakdown the selection string into a format that is compatible with reading data into R
    split_choices <- c(NULL)
    usat_choices <- c(NULL)
    flank_choices <- c(NULL)
    both_choices <- c(NULL)
    
    #list all options for regionfilters that could be listed in selection string
    options <- unlist(name_inputs$history)
    #read in selection string into this reactive environment
    selection_string <- myChoices2()
    #breakdown selection string on the "+" character, which separates usatFilters from flankFilters and bothFilters
    split_choices <- unlist(strsplit(selection_string,split = "[+]"))
    #then breakdown each separated set of "split_choices" (usatFilters, flankFilters, and bothFilters), into individual regionfilter names by breaking down first the split between the "type indicator" (e.g. usatFilter) and the regionfilter names (the "=" character), and then between the individual regionfilter names that belong to that group (along the "&" character)
    usat_choices <- unlist(strsplit(tail(unlist(strsplit(grep("usatFilters",split_choices, value = TRUE),split = "[=]")),1),split = "[&]"))
    flank_choices <- unlist(strsplit(tail(unlist(strsplit(grep("flankFilters",split_choices, value = TRUE),split = "[=]")),1),split = "[&]"))
    both_choices <- unlist(strsplit(tail(unlist(strsplit(grep("bothFilters",split_choices, value = TRUE),split = "[=]")),1),split = "[&]"))

    #initialize a dataframe for microsatellite filters with 3 columns named "seqnames","start", and "end."
    usatFilters <- data.frame(matrix(nrow = 0, ncol = 3))
    colnames(usatFilters) <- c("seqnames","start","end")
    usatFilters$seqnames <- as.character(usatFilters$seqnames)
    usatFilters$start <- as.integer(usatFilters$start)
    usatFilters$end <- as.integer(usatFilters$end)

    #if usat_choices is not empty (i.e. if the usat_choices object doesn't only contain the "usatFilters" character from the type indicator that is, by default, included as part of the set of usat_choices), read in the files in the usat_choices set
    if(all(usat_choices != "usatFilters")){
      #be sure to exclude the "usatFilters" character from the usat_choices set before reading in the files, as that "filename" will not be found
      usat_choices <- usat_choices[which(usat_choices != "usatFilters")]
      usat_choices.csv <- grep(".csv",usat_choices, value = TRUE, fixed = TRUE)
      usat_choices.zip <- grep(".zip",usat_choices, value = TRUE, fixed = TRUE)
      if(!isEmpty(usat_choices.csv)){
        listFilters.csv <- lapply(usat_choices.csv, function(x) m_read_csv(x,show_col_types=FALSE))
        usatFilters.csv <- bind_rows(Filter(function(x) dim(x)[1] > 0, listFilters.csv))
      }
      if(!isEmpty(usat_choices.zip)){
        listFilters.zip <- lapply(usat_choices.zip, function(x) m_read_csv(x,show_col_types=FALSE))
        usatFilters.zip <- bind_rows(Filter(function(x) dim(x)[1] > 0, listFilters.zip))
      }
      if(!isEmpty(usat_choices.csv) & !isEmpty(usat_choices.zip)){
        usatFilters <- bind_rows(usatFilters.csv,usatFilters.zip)
      }else if(!isEmpty(usat_choices.csv) & isEmpty(usat_choices.zip)){
        usatFilters <- usatFilters.csv
      }else if (isEmpty(usat_choices.csv) & !isEmpty(usat_choices.zip)){
        usatFilters <- usatFilters.zip
      }
      #if a particular region appears in 2 or more files, consolidate it into one entry, since we don't need to note the same region multiple times
      usatFilters <- unique(usatFilters[,c("seqnames","start","end")])
      #remove any regions that might contain "NA" values for one or more of the coordinates
      usatFilters <- na.omit(usatFilters)
      #convert the usatFilters dataframe into a GRanges object
      usatFilters <- makeGRangesFromDataFrame(usatFilters)
      #use the countOverlaps function to identify microsatellites that overlap with the usatFilters regions by at least one basepair
      hits <- countOverlaps(usats.granges,usatFilters, minoverlap = 1)
      #get microsatellite regions that overlap with the usatFilters regions by at least one basepair.
      usatFilters <- usats.granges[hits > 0]
      #covert the data back into a dataframe
      usatFilters <- as.data.frame(usatFilters)
    }

    #initialize a dataframe for flank filters with 3 columns named "seqnames","start", and "end."
    flankFilters <- data.frame(matrix(nrow = 0, ncol = 3))
    colnames(flankFilters) <- c("seqnames","start","end")
    flankFilters$seqnames <- as.character(flankFilters$seqnames)
    flankFilters$start <- as.integer(flankFilters$start)
    flankFilters$end <- as.integer(flankFilters$end)

    #if flank_choices is not empty (i.e. if the flank_choices object doesn't only contain the "flankFilters" character from the type indicator that is, by default, included as part of the set of flank_choices), read in the files in the flank_choices set
    if(all(flank_choices != "flankFilters")){
      #be sure to exclude the "flankFilters" character from the flank_choices set before reading in the files, as that "filename" will not be found
      flank_choices <- flank_choices[which(flank_choices != "flankFilters")]
      #initialize a list object to hold the processed dataframes as regionfilter files are loaded in and processed
      flankFilters <- list()
      #note: we will be using a for loop rather than an lapply function here to loop over each file and read and format it as necessary because the formating of each filter may need to meet different specifications, depending on the options the user selects
      for(ii in flank_choices){
        #read in file
        file <- m_read_csv(ii,show_col_types=FALSE)
        if(!isEmpty(file)){
          #make sure there are no duplicate entries in the file
          file <- unique(file[,c("seqnames","start","end")])
          #remove any entries that have "NA" listed for any of the coordinates
          file <- na.omit(file)
          #convert dataframe to granges object
          file <- makeGRangesFromDataFrame(file)
          #make a dataframe for the 5' flank from the microsatellite granges object (loaded into the global space upon app startup)
          flank.5 <- as.data.frame(usats.granges)
          #change the end position of the region based on user input to the rf_numMin_flank5 input
          flank.5$end <- flank.5$start - eval(parse(text = paste0("input$rf_numMin_flank5",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)])))))
          #change the start position of the region based the user input to the rf_numMax_flank5 input
          flank.5$start <- flank.5$start - eval(parse(text = paste0("input$rf_numMax_flank5",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)])))))
          #convert adjusted dataframe to granges object
          flank.5 <- makeGRangesFromDataFrame(flank.5)
          #use the countOverlaps function to identify microsatellites that that overlap with the flankFilter regions by at least one basepair in the 5' flank region, as determined by the user.
          hits.5 <- countOverlaps(flank.5,file, minoverlap = 1)
          #repeat the same process for the 3' flank
          flank.3 <- as.data.frame(usats.granges)
          flank.3$start <- flank.3$end + eval(parse(text = paste0("input$rf_numMin_flank3",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)])))))
          flank.3$end <- flank.3$end + eval(parse(text = paste0("input$rf_numMax_flank3",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)])))))
          flank.3 <- makeGRangesFromDataFrame(flank.3)
          hits.3 <- countOverlaps(flank.3,file, minoverlap = 1)
          #get microsatellite 5' and 3' flanking regions that overlap with the flankFilter regions by at least one basepair.
          regions.5 <- usats.granges[hits.5 > 0]
          regions.3 <- usats.granges[hits.3 > 0]
          #if the user selects "one" for the flank_rf input, first determine whether or not the user has selected to link the regionfilter, nearest microsatellite, and mappability annotations or not
          if(eval(parse(text = paste0("input$flank_rf",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)]))))) == "one"){
            #if they did choose to link these annotations, then create two dataframes (subsets of the original usats dataframe that is loaded into the global environment upon app startup) for checking which regions pass the two other filters assessed by this linker button (i.e. the next nearest microsatellite filter and the mappability filters)
            if(input$LinkNearestUsatToMappability == TRUE){
              check3 <- usats[,c("seqnames","start","end","nearest.usat.3","umap.k24.60bp.flank.3","umap.k36.60bp.flank.3","umap.k50.60bp.flank.3","umap.k100.60bp.flank.3","umap.k24.90bp.flank.3","umap.k36.90bp.flank.3","umap.k50.90bp.flank.3","umap.k100.90bp.flank.3")] %>%
                {if(!("k50" %in% input$umap60)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k50" %in% input$umap60) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k50.60bp.flank.3 %between% input$umap60.3.k50)} %>%
                {if(!("k100" %in% input$umap60)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k100" %in% input$umap60) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k100.60bp.flank.3 %between% input$umap60.3.k100)} %>%
                {if(!("k24" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k24" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k24.90bp.flank.3 %between% input$umap90.3.k24)} %>%
                {if(!("k36" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k36" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k36.90bp.flank.3 %between% input$umap90.3.k36)} %>%
                {if(!("k50" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k50" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k50.90bp.flank.3 %between% input$umap90.3.k50)} %>%
                {if(!("k100" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k100" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k100.90bp.flank.3 %between% input$umap90.3.k100)}
              check5 <- usats[,c("seqnames","start","end","nearest.usat.5","umap.k24.60bp.flank.5","umap.k36.60bp.flank.5","umap.k50.60bp.flank.5","umap.k100.60bp.flank.5","umap.k24.90bp.flank.5","umap.k36.90bp.flank.5","umap.k50.90bp.flank.5","umap.k100.90bp.flank.5")] %>%
                {if(!("k24" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k24" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k24.60bp.flank.5 %between% input$umap60.5.k24)} %>%
                {if(!("k36" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k36" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k36.60bp.flank.5 %between% input$umap60.5.k36)} %>%
                {if(!("k50" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k50" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k50.60bp.flank.5 %between% input$umap60.5.k50)} %>%
                {if(!("k100" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k100" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k100.60bp.flank.5 %between% input$umap60.5.k100)} %>%
                {if(!("k24" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k24" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k24.90bp.flank.5 %between% input$umap90.5.k24)} %>%
                {if(!("k36" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k36" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k36.90bp.flank.5 %between% input$umap90.5.k36)} %>%
                {if(!("k50" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k50" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k50.90bp.flank.5 %between% input$umap90.5.k50)} %>%
                {if(!("k100" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k100" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k100.90bp.flank.5 %between% input$umap90.5.k100)}
              #once the regions that pass the other two filters have been found, intersect them with the regionfilter regions identified using the countOverlap function so that the "region.3_linked" and "region.5_linked" objects contain only the regionfilters in each flank that fall in regions where the microsatellites pass the criteria for the other linked filters
              regions.3_linked <- inner_join(as.data.frame(regions.3),check3, by = c("seqnames", "start", "end"))[,c("seqnames", "start", "end")]
              regions.5_linked <- inner_join(as.data.frame(regions.5),check5, by = c("seqnames", "start", "end"))[,c("seqnames", "start", "end")]
              #bind both linked flank dataframes into a single dataframe
              regions <- rbind(as.data.frame(regions.5_linked),as.data.frame(regions.3_linked))
            }else{
              #if the user chooses not to link the regionfilters to the mappability and next nearest microsatellite annotations, but does choose to filter regions that overlap with a regionfilter in only one flank, rather than both flanks, simply bind the two separate "region" dataframes into a single dataframe with no further filtering/processing
              regions <- rbind(as.data.frame(regions.5),as.data.frame(regions.3))
            }
          }else{
            #if the user selects "both" for the flank_rf input, then use the function "inner_join" to find microsatellites that would be excluded by the 5' flank filter and the 3' flank filter and create a dataframe containing only those microsatellites that would be filtered out by applying the regionfilters to both flanks.
            regions <- inner_join(as.data.frame(regions.5),as.data.frame(regions.3), by = c("seqnames","start","end"))[,c("seqnames", "start", "end")]
          }
          #once finished processing the regions dataframe based on the user inputs, bind the dataframe to the "flankFilters" list and loop back to the next regionfilter, until all selected regionfilters applied to the flank regions have been properly addressed.
          flankFilters <- bind_rows(flankFilters,regions)
        }else{
          next
        }
      }
      #once regionfilters applied to the flank regions have been properly addressed, find any duplicate entries and collapse them into a single entry for convenience, then convert the flankFilters list into a dataframe
      flankFilters <- as.data.frame(unique(flankFilters))
    }

    #initialize a dataframe for "both" filters (i.e. filters that apply to both the microsatellite flanks and the microsatellite body) with 3 columns named "seqnames","start", and "end."
    bothFilters <- data.frame(matrix(nrow = 0, ncol = 3))
    colnames(bothFilters) <- c("seqnames","start","end")
    bothFilters$seqnames <- as.character(bothFilters$seqnames)
    bothFilters$start <- as.integer(bothFilters$start)
    bothFilters$end <- as.integer(bothFilters$end)

    if(all(both_choices != "bothFilters")){
      #be sure to exclude the "bothFilters" character from the flank_choices set before reading in the files, as that "filename" will not be found
      both_choices <- both_choices[which(both_choices != "bothFilters")]
      #initialize a list object to hold the processed dataframes as regionfilter files are loaded in and processed
      bothFilters <- list()
      #note: we will be using a for loop rather than an lapply function here to loop over each file and read and format it as necessary because the formating of each filter may need to meet different specifications, depending on the options the user selects
      for(ii in both_choices){
        #read in file
        file <- m_read_csv(ii, col_names = TRUE, show_col_types=FALSE)
        if(!isEmpty(file)){
          colnames(file)[1] <- "row.number"
          #make sure there are no duplicate entries in the file
          file <- unique(file[,c("seqnames","start","end")])
          #remove any entries that have "NA" listed for any of the coordinates
          file <- na.omit(file)
          #convert dataframe to granges object
          file <- makeGRangesFromDataFrame(file)
          #make a dataframe for the 5' flank from the microsatellite granges object (loaded into the global space upon app startup)
          region.5 <- as.data.frame(usats.granges)
          #change the start position and ONLY the start position of the region based on user input to the rf_numMin_flank5 input (the end position will remain the same because we are going to assess the region spanning the 5' flank and the microsatellite body)
          region.5$start <- region.5$start - eval(parse(text = paste0("input$rf_numMax_flank5",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)])))))
          #convert adjusted dataframe to granges object
          region.5 <- makeGRangesFromDataFrame(region.5)
          #use the countOverlaps function to identify microsatellites that that overlap with the bothFilter regions by at least one basepair in the 5' flank region, as determined by the user, or the microsatellite body.
          hits.5 <- countOverlaps(region.5,file, minoverlap = 1)
          #get microsatellitethat overlap with the bothFilter regions by at least one basepair in either the 5' flank or the microsatellite body.
          regions.5 <- usats.granges[hits.5 > 0]
          #repeat the same process for the 3' flank, except this time, we are going to change the end position of the microsatellite granges object and only the end position, as the start position will remain the same because we will be assessing the region spanning the microsatellite body and the 3' flank.
          region.3 <- as.data.frame(usats.granges)
          region.3$end <- region.3$end + eval(parse(text = paste0("input$rf_numMax_flank3",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)])))))
          region.3 <- makeGRangesFromDataFrame(region.3)
          hits.3 <- countOverlaps(region.3,file, minoverlap = 1)
          regions.3 <- usats.granges[hits.3 > 0]
          #once the regions.5 and regions.3 object for the bothFilter regions are created, run the same calculations as we did for the flankFilter regions above, taking into account user selections in the flank_rf and LinkNearestUsatToMappability inputs for the final processing of the bothFilter regions.
          if(eval(parse(text = paste0("input$flank_rf",which(options == as.character(name_inputs$history[which(file_inputs$history == ii)]))))) == "one"){
            if(input$LinkNearestUsatToMappability == TRUE){
              check3 <- usats[,c("seqnames","start","end","nearest.usat.3","umap.k24.60bp.flank.3","umap.k36.60bp.flank.3","umap.k50.60bp.flank.3","umap.k100.60bp.flank.3","umap.k24.90bp.flank.3","umap.k36.90bp.flank.3","umap.k50.90bp.flank.3","umap.k100.90bp.flank.3")] %>%
                {if(!("k24" %in% input$umap60)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k24" %in% input$umap60) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k24.60bp.flank.3 %between% input$umap60.3.k24)} %>%
                {if(!("k36" %in% input$umap60)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k36" %in% input$umap60) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k36.60bp.flank.3 %between% input$umap60.3.k36)} %>%
                {if(!("k50" %in% input$umap60)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k50" %in% input$umap60) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k50.60bp.flank.3 %between% input$umap60.3.k50)} %>%
                {if(!("k100" %in% input$umap60)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k100" %in% input$umap60) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k100.60bp.flank.3 %between% input$umap60.3.k100)} %>%
                {if(!("k24" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k24" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k24.90bp.flank.3 %between% input$umap90.3.k24)} %>%
                {if(!("k36" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k36" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k36.90bp.flank.3 %between% input$umap90.3.k36)} %>%
                {if(!("k50" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k50" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k50.90bp.flank.3 %between% input$umap90.3.k50)} %>%
                {if(!("k100" %in% input$umap90)) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)) else if("k100" %in% input$umap90) filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),umap.k100.90bp.flank.3 %between% input$umap90.3.k100)}
              check5 <- usats[,c("seqnames","start","end","nearest.usat.5","umap.k24.60bp.flank.5","umap.k36.60bp.flank.5","umap.k50.60bp.flank.5","umap.k100.60bp.flank.5","umap.k24.90bp.flank.5","umap.k36.90bp.flank.5","umap.k50.90bp.flank.5","umap.k100.90bp.flank.5")] %>%
                {if(!("k24" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k24" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k24.60bp.flank.5 %between% input$umap60.5.k24)} %>%
                {if(!("k36" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k36" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k36.60bp.flank.5 %between% input$umap60.5.k36)} %>%
                {if(!("k50" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k50" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k50.60bp.flank.5 %between% input$umap60.5.k50)} %>%
                {if(!("k100" %in% input$umap60)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k100" %in% input$umap60) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k100.60bp.flank.5 %between% input$umap60.5.k100)} %>%
                {if(!("k24" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k24" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k24.90bp.flank.5 %between% input$umap90.5.k24)} %>%
                {if(!("k36" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k36" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k36.90bp.flank.5 %between% input$umap90.5.k36)} %>%
                {if(!("k50" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k50" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k50.90bp.flank.5 %between% input$umap90.5.k50)} %>%
                {if(!("k100" %in% input$umap90)) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if("k100" %in% input$umap90) filter(.,nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5),umap.k100.90bp.flank.5 %between% input$umap90.5.k100)}
              regions.3_linked <- inner_join(as.data.frame(regions.3),check3, by = c("seqnames", "start", "end"))[,c("seqnames", "start", "end")]
              regions.5_linked <- inner_join(as.data.frame(regions.5),check5, by = c("seqnames", "start", "end"))[,c("seqnames", "start", "end")]
              regions <- rbind(as.data.frame(regions.5_linked),as.data.frame(regions.3_linked))
            }else{
              regions <- rbind(as.data.frame(regions.5),as.data.frame(regions.3))
            }
          }else{
            regions <- inner_join(as.data.frame(regions.5),as.data.frame(regions.3), by = c("seqnames","start","end"))
          }
          #once finished processing the regions dataframe based on the user inputs, bind the dataframe to the "bothFilters" list and loop back to the next regionfilter, until all selected regionfilters applied to both the microsatellite body and its flanking regions regions have been properly addressed.
          bothFilters <- bind_rows(bothFilters,regions)
        }else{
          next
        }
      }
      #once these regionfilters have been properly addressed, find any duplicate entries and collapse them into a single entry for convenience, then convert the bothFilters list into a dataframe
      bothFilters <- as.data.frame(bothFilters)
    }

    #as long as there are options for the user to select regionfilters (even if none are selected), we will then create the "regionfilters" object which will combine all of the filters made in each subsection of regionfilter (usatFilters,flankFilters, and bothFilters)
    if(length(options)>0){
      regionfilters <- bind_rows(usatFilters,flankFilters,bothFilters)
      #if that combined regionfilters object is empty, reformat it as a dataframe with no rows, and make sure to name and define the class of each of the 3 columns so that no formatting errors occur later
      if(nrow(regionfilters) <= 1){
        regionfilters <- data.frame(matrix(nrow = 0, ncol = 3))
        colnames(regionfilters) <- c("seqnames","start","end")
        regionfilters$seqnames <- as.character(regionfilters$seqnames)
        regionfilters$start <- as.integer(regionfilters$start)
        regionfilters$end <- as.integer(regionfilters$end)
        #if the combined regionfilters object is not empty, reform the object as a dataframe, and keep only the columns relevant to filtering (i.e. "seqnames","start", and "end")
      }else{
        regionfilters <- as.data.frame(regionfilters)
        regionfilters <- regionfilters[,c("seqnames","start","end")]
      }
    }
    #return the regionfilters objext when all of these calculations have been completed for later microsatellite filtering
    return(regionfilters)
  })
  
  #--end regionfilter reactivity--#
  
  #this is where we put RE filter reactivity
  
  #initialize a reactive object that keeps track of how many restriction enzyme "capture" filters have been uploaded to the static restriction enzyme filters directory associated with this app
  RE.capture_counter <- reactiveValues(n=length(RE.capture_filters.path))

  #generate the ui for the restriction enzyme filters as a reactive object that generates inputs based on user selections in the selectREs input
  numericInput_group <- reactive({

    n <- length(input$selectREs)

    #if there are any restriction enzyme filters in the restriction enzymes directory associated with this app, generate the ui as follows
    if(n > 0){
      #determine which restriction enzyme filters have been selected
      selected.REs.idx <- which(RE.capture_filters.name %in% input$selectREs)
      #loop over selected restriction enzymes using the lapply function
      lapply(selected.REs.idx, function(ii){
        #generate a "header" that identifies the restriction enzyme filter to which the following ui elements belong
        headerID <- paste0("header",ii)
        output[[headerID]] = renderUI({HTML({paste0("<p style='color:blue;'><b>",RE.capture_filters.name[[ii]],"</b></p>")})})
        #render this header, a checkbox input to determine whether or not to remove microsatellites with internal cutsites, slider inputs to determine how far along the 150bp microsatellite flanking regions (which are annotated in the usats dataset) the program should look for restriction enzyme cut sites, and a set of numeric inputs to filter based on the final size of the fragment that would be generated if one were to try to capture microsatellites using this particular restriction enzyme
        fluidPage(
          fluidRow(htmlOutput(headerID)),
          fluidRow(checkboxInput(inputId = paste0("checkbox",ii),
                                 label = "Remove Microsatellites with Internal Cut Sites",
                                 value = FALSE)),
          fluidRow(sliderInput(inputId = paste0("slider5",ii),
                               label = "Filter by 5' Flank Size (bp):",
                               min = 0,
                               max = 150,
                               value = c(0,150),
                               step = 1,
                               dragRange = F)),
          fluidRow(sliderInput(inputId = paste0("slider3",ii),
                               label = "Filter by 3' Flank Size (bp):",
                               min = 0,
                               max = 150,
                               value = c(0,150),
                               step = 1,
                               dragRange = F)),
        fluidRow(column(6,numericInput(inputId = paste0("numericMin",ii),
                              label = "Minimum Fragment Size",
                              value = 0)),
                 column(6,numericInput(inputId = paste0("numericMax",ii),
                              label = "Maximum Fragment Size",
                              value = 200))))
      })
    }
  })

  #render the reactive object containing the restriction enzyme filters on the ui
  output$numericInput_buttons_ui <- renderUI({ numericInput_group() })
  
  #find microsatellites that pass the applied restriction enzyme "capture" filters as follows
  RE.capture_filters <- reactive({
    #keep track of how many restriction enzyme filters there are in the restriction enzyme filters directory associated with this app
    n <- RE.capture_counter$n
    #initialize a list to store the regions identified in this capture
    RE.capture.list <- list()
    #for each restriction enzyme filter listed in the restriction enzyme filters directory, create an entry in the "RE.capture.list" object that filters the RE.capture_filters.list object (which was generated in the global environment upon app startup and which loads in all restriction enzyme filter data into a list with individual dataframes for each restriction enzyme up-front) based on the user inputs that apply to the microsatellite capture (i.e. the flank region sliders and the min/max fragment size numeric inputs)
    for(ii in seq_len(n)){
      RE.capture.list[[ii]] <- RE.capture_filters.list[[ii]][which(RE.capture_filters.list[[ii]]$flank.5 < max(eval(parse(text = paste0("input$slider5",ii)))) & 
                                                                     RE.capture_filters.list[[ii]]$flank.5 > min(eval(parse(text = paste0("input$slider5",ii)))) &
                                                                     RE.capture_filters.list[[ii]]$flank.3 < max(eval(parse(text = paste0("input$slider3",ii)))) &
                                                                     RE.capture_filters.list[[ii]]$flank.3 > min(eval(parse(text = paste0("input$slider3",ii)))) &
                                                                     RE.capture_filters.list[[ii]]$total.width > eval(parse(text = paste0("input$numericMin",ii))) &
                                                                     RE.capture_filters.list[[ii]]$total.width < eval(parse(text = paste0("input$numericMax",ii)))),]
    }
    #name each datafrane ub tge RE.capture.list after the associated restriction enzyme
    names(RE.capture.list) <- RE.capture_filters.name
    #collapse that list into a single dataframe, with a new "filter_ID" column to keep track of which dataframe (and therefore which restriction enzyme) each entry came from
    RE.capture.df <- bind_rows(RE.capture.list, .id = "filter_ID")
    #keep only the rows in the RE.capture.df dataframe that have a selected restriction enzyme (based on user input to the selectREs input) as their "filter_ID" tag
    RE.capture.df <- RE.capture.df[which(RE.capture.df$filter_ID %in% input$selectREs),]
    #return the final RE.capture.df object for later microsatellite filtering
    return(RE.capture.df)
  })
  
  #separately, initialize a reactive object that keeps track of how many restriction enzyme "internal cut site" filters have been uploaded to the static restriction enzyme filters directory associated with this app
  RE.internal.cuts_counter <- reactiveValues(n=length(RE.internal.cuts_filters.path))
  
  #find microsatellites that have internal cut sites from the selected restriction enzyme as follows
  RE.internal.cuts_filters <- reactive({
    #keep track of how many internal cut site files there are in the static restriction enzyme filters directory associated with this app
    n <- RE.internal.cuts_counter$n
    
    #initialize a list to store the regions identified with internal cut sites
    RE.internal.cuts.list <- list()
    
    #first use this test to determine whether the individual checkbox inputs for internal cut sites are set to "Remove Microsatellites with Internal Cut Sites" or not, and code each response for each internal cut sites file as either "TRUE" or "FALSE" in the "test.results" object
    test.results <- NULL
    for(ii in seq_len(n)){
      test <- eval(parse(text=paste0("input$checkbox",ii))) == "Remove Microsatellites with Internal Cut Sites"
      if(isTRUE(test)){
        test.results <- c(test.results,"TRUE")
      }else{
        test.results <- c(test.results,"FALSE")
      }
    }
    
    #for the test results that come back "TRUE", create an entry in the "RE.internal.cuts.list" that will later be used to filter microsatellites
    RE.internal.cuts.list <- lapply(which(test.results==TRUE), function(x) RE.internal.cuts_filters.list[[x]])
    #name each element in the RE.internal.cuts.list object after its associated restriction enzyme
    names(RE.internal.cuts.list) <- lapply(which(test.results==TRUE), function(x) RE.internal.cuts_filters.name[[x]])
    #collapse that list into a single dataframe, with a new "filter_ID" column to keep track of which dataframe (and therefore which restriction enzyme) each entry came from
    RE.internal.cuts.df <- bind_rows(RE.internal.cuts.list, .id = "filter_ID")
    #if the internal cuts dataframe is empty, create a dataframe with a single entry that is impossible to overlap with any entry in the usats dataset as a placeholder so the program doesn't end up looking for an object that doesn't exist at any point.
    if(nrow(RE.internal.cuts.df) == 0){
      RE.internal.cuts.df <- data.frame(seqnames = "chr0", start = 0, end = 0)
    }else{
      RE.internal.cuts.df <- RE.internal.cuts.df
    }
    #return the final RE.internal.cuts.df object for later microsatellite filtering
    return(RE.internal.cuts.df)
  })
  
  #--end RE filter reactivity--#
  
  #--flank mappability filter reactivity--#
  #this section of code simply generates all mappability filter as reactive ui elements, rather than static ui elements so that they respond to the user inputs to the umap60 and umap90 inputs, and thus does not generate any unnecessary ui and does not perform any unnecessary filtering that would slow the app on start-up.
  sliderInput60_group <- reactive({
    n <- length(input$umap60)
    if(n > 0){
      lapply(input$umap60, function(ii){
        fluidPage(fluidRow(align = "center",
                           column(10,offset = 1,sliderInput(inputId = paste(c("umap60.5",ii), collapse = "."),
                                                            label = paste(c("Filter by UMAP ",ii," Mappability of the 5' Flank:"), collapse = ""),
                                                            min = 0,
                                                            max = 1,
                                                            value = c(0,1),
                                                            step = 0.01,
                                                            dragRange = F))),
                  fluidRow(align = "center",
                           column(10, offset = 1,sliderInput(inputId = paste(c("umap60.3",ii), collapse = "."),
                                                             label = paste(c("Filter by UMAP ",ii," Mappability of the 3' Flank:"), collapse = ""),
                                                             min = 0,
                                                             max = 1,
                                                             value = c(0,1),
                                                             step = 0.01,
                                                             dragRange = F))),
                  fluidRow(align = "center",
                           radioGroupButtons(inputId = paste(c("umap60.radio",ii), collapse = "."),
                                        label = paste(c("Pass microsatellites that meet ",ii," mappability requirements in either flank or both flanks?"), collapse = ""),
                                        choices = c("either","both"),
                                        selected = "both")))
      })
    }
  })
  
  output$sliderInput60_group_ui <- renderUI({ sliderInput60_group() })
  
  sliderInput90_group <- reactive({
    n <- length(input$umap90)
    if(n > 0){
      lapply(input$umap90, function(ii){
        fluidPage(fluidRow(align = "center",
                           column(10,offset = 1,sliderInput(inputId = paste(c("umap90.5",ii), collapse = "."),
                                                            label = paste(c("Filter by UMAP ",ii," Mappability of the 5' Flank:"), collapse = ""),
                                                            min = 0,
                                                            max = 1,
                                                            value = c(0,1),
                                                            step = 0.01,
                                                            dragRange = F))),
                  fluidRow(align = "center",
                           column(10, offset = 1,sliderInput(inputId = paste(c("umap90.3",ii), collapse = "."),
                                                             label = paste(c("Filter by UMAP ",ii," Mappability of the 3' Flank:"), collapse = ""),
                                                             min = 0,
                                                             max = 1,
                                                             value = c(0,1),
                                                             step = 0.01,
                                                             dragRange = F))),
                  fluidRow(align = "center",
                           radioGroupButtons(inputId = paste(c("umap90.radio",ii), collapse = "."),
                                             label = paste(c("Pass microsatellites that meet ",ii," mappability requirements in either flank or both flanks?"), collapse = ""),
                                             choices = c("either","both"),
                                             selected = "both")))
      })
    }
  })
  
  output$sliderInput90_group_ui <- renderUI({ sliderInput90_group() })
  #--end flank mappability filter reactivity--#
  
  #This is where we put overlap button reactivity
  observe({
    min <- input$overlapUsatMin
    max <- input$overlapUsatMax
    updateSliderInput(session,"overlapUsat", value = c(min, max))
  })
  
  observe({
    val <- min(input$overlapUsat)
    updateNumericInput(session,"overlapUsatMin", value = val)
  })
  
  observe({
    val <- max(input$overlapUsat)
    updateNumericInput(session,"overlapUsatMax", value = val)
  })
  
  #--end overlap button reactivity--#
  
  #this is where we filter the microsatellites
  #here we use an eventReactive shiny element that allows us to generate a reactive object (in this case the "filtered_usats" object), but only after a certain input is observed (in this case, when the "Apply Changes" button is clicked). This allows us to control when the app updates so that the app never updates until the user has made all of their filter selections
  filtered_usats <- eventReactive(input$goButton,{
    #the filtering done here uses a standard dplyr filter function and the %>% (pipe) operator to connect between more complex filtering steps
    usats %>%
      #this filtering step uses the anti_join function from the dplyr package to find the regions in the usats dataframe that do NOT match any of the regions in the regionfilters object, but this is only applied after the app has already started up because otherwise the app would crash trying to handle regionfilters that have not had time to load in yet
      {if(input$goButton < 1) . else anti_join(x = ., y = regionfilters(), by = c("seqnames","start","end"))} %>%
      #this filtering step uses the semi_join function to return any regions that passed the previous step of filtering that are also in the RE.capture_filters object (as long as that object already exists, if not, it just passes along the microsatellites from the previous filtering step)
      {if(isEmpty(input$selectREs)) . else semi_join(x = ., y = RE.capture_filters(), by = c("seqnames","start","end"))} %>%
      #this filtering step uses the anti_join function from the dplyr package to find regions in the set of microsatellites that have been passed from the previous filtering steps that do NOT match any of the regions in the RE.internal.cuts_filters object
      anti_join(y = RE.internal.cuts_filters(), by = c("seqnames","start","end")) %>%
      #the following set of filtering steps work out all of the boolean logic for filtering based on mappability filters (that may or may not exist) and the next nearest microsatellite filters, as well as their associated radio buttons to select whether either flank or both flanks need to pass each filter in order to be passed on and whether or not those flanks should be linked between the separate annotations
      #using the pipe operator between each of these steps also allows the user to filter on multiple mappability tracks at the same time
      {if(!("k24" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k24" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24, umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24, umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24 | umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24 | umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k24 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k24.60bp.flank.5 %between% input$umap60.5.k24 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k24.60bp.flank.3 %between% input$umap60.3.k24 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k36" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k36" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36, umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36, umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36 | umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36 | umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k36 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k36.60bp.flank.5 %between% input$umap60.5.k36 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k36.60bp.flank.3 %between% input$umap60.3.k36 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k50" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k50" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50, umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50, umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50 | umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50 | umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k50 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k50.60bp.flank.5 %between% input$umap60.5.k50 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k50.60bp.flank.3 %between% input$umap60.3.k50 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k100" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k100" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100, umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100, umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100 | umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100 | umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k100 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k100.60bp.flank.5 %between% input$umap60.5.k100 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k100.60bp.flank.3 %between% input$umap60.3.k100 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k24" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k24" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24, umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24, umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24 | umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k24 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k24.60bp.flank.5 %between% input$umap60.5.k24 | umap.k24.60bp.flank.3 %between% input$umap60.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k24 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k24.60bp.flank.5 %between% input$umap60.5.k24 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k24.60bp.flank.3 %between% input$umap60.3.k24 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k36" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k36" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36, umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36, umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36 | umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k36 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k36.60bp.flank.5 %between% input$umap60.5.k36 | umap.k36.60bp.flank.3 %between% input$umap60.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k36 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k36.60bp.flank.5 %between% input$umap60.5.k36 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k36.60bp.flank.3 %between% input$umap60.3.k36 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k50" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k50" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50, umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50, umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50 | umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k50 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k50.60bp.flank.5 %between% input$umap60.5.k50 | umap.k50.60bp.flank.3 %between% input$umap60.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k50 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k50.60bp.flank.5 %between% input$umap60.5.k50 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k50.60bp.flank.3 %between% input$umap60.3.k50 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k100" %in% input$umap60) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k100" %in% input$umap60) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100, umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100, umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100 | umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap60.radio.k100 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k100.60bp.flank.5 %between% input$umap60.5.k100 | umap.k100.60bp.flank.3 %between% input$umap60.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap60.radio.k100 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k100.60bp.flank.5 %between% input$umap60.5.k100 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k100.60bp.flank.3 %between% input$umap60.3.k100 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k24" %in% input$umap90) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k24" %in% input$umap90) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k24 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k24.90bp.flank.5 %between% input$umap90.5.k24, umap.k24.90bp.flank.3 %between% input$umap90.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k24 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k24.90bp.flank.5 %between% input$umap90.5.k24, umap.k24.90bp.flank.3 %between% input$umap90.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k24 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k24.90bp.flank.5 %between% input$umap90.5.k24 | umap.k24.90bp.flank.3 %between% input$umap90.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k24 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k24.90bp.flank.5 %between% input$umap90.5.k24 | umap.k24.90bp.flank.3 %between% input$umap90.3.k24,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap90.radio.k24 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k24.90bp.flank.5 %between% input$umap90.5.k24 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k24.90bp.flank.3 %between% input$umap90.3.k24 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k36" %in% input$umap90) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k36" %in% input$umap90) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k36 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k36.90bp.flank.5 %between% input$umap90.5.k36, umap.k36.90bp.flank.3 %between% input$umap90.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k36 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k36.90bp.flank.5 %between% input$umap90.5.k36, umap.k36.90bp.flank.3 %between% input$umap90.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k36 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k36.90bp.flank.5 %between% input$umap90.5.k36 | umap.k36.90bp.flank.3 %between% input$umap90.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k36 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k36.90bp.flank.5 %between% input$umap90.5.k36 | umap.k36.90bp.flank.3 %between% input$umap90.3.k36,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap90.radio.k36 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k36.90bp.flank.5 %between% input$umap90.5.k36 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k36.90bp.flank.3 %between% input$umap90.3.k36 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k50" %in% input$umap90) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k50" %in% input$umap90) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k50 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k50.90bp.flank.5 %between% input$umap90.5.k50, umap.k50.90bp.flank.3 %between% input$umap90.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k50 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k50.90bp.flank.5 %between% input$umap90.5.k50, umap.k50.90bp.flank.3 %between% input$umap90.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k50 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k50.90bp.flank.5 %between% input$umap90.5.k50 | umap.k50.90bp.flank.3 %between% input$umap90.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k50 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k50.90bp.flank.5 %between% input$umap90.5.k50 | umap.k50.90bp.flank.3 %between% input$umap90.3.k50,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap90.radio.k50 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k50.90bp.flank.5 %between% input$umap90.5.k50 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k50.90bp.flank.3 %between% input$umap90.3.k50 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      {if(!("k100" %in% input$umap90) & input$nearest.usat.radio == "both") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(!("k100" %in% input$umap90) & input$nearest.usat.radio == "either") filter(.,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k100 == "both" & input$nearest.usat.radio == "both") filter(.,umap.k100.90bp.flank.5 %between% input$umap90.5.k100, umap.k100.90bp.flank.3 %between% input$umap90.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3),nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k100 == "both" & input$nearest.usat.radio == "either")filter(.,umap.k100.90bp.flank.5 %between% input$umap90.5.k100, umap.k100.90bp.flank.3 %between% input$umap90.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)  | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k100 == "either" & input$nearest.usat.radio == "both")filter(.,umap.k100.90bp.flank.5 %between% input$umap90.5.k100 | umap.k100.90bp.flank.3 %between% input$umap90.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3), nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) else if(input$umap90.radio.k100 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability == FALSE)filter(.,umap.k100.90bp.flank.5 %between% input$umap90.5.k100 | umap.k100.90bp.flank.3 %between% input$umap90.3.k100,nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3) | nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5))else if(input$umap90.radio.k100 == "either" & input$nearest.usat.radio == "either" & input$LinkNearestUsatToMappability ==TRUE)filter(.,(umap.k100.90bp.flank.5 %between% input$umap90.5.k100 & nearest.usat.5 %between% c(input$nearestUsatMin.5, input$nearestUsatMax.5)) |(umap.k100.90bp.flank.3 %between% input$umap90.3.k100 & nearest.usat.3 %between% c(input$nearestUsatMin.3, input$nearestUsatMax.3)))} %>%
      #this filtering step applies all other filters that simply filter based on the user inputs to the specified ui elements and do not need any special processing
      filter(width %between% c(input$minlength,input$maxlength),
             motif.size %in% input$period,
             motif.family %in% input$motif,
             !(msat.overlap %between% input$overlapUsat),
             overlap.5.prime.60bp %between% input$overlap60.5,
             overlap.3.prime.60bp %between% input$overlap60.3,
             overlap.5.prime.90bp %between% input$overlap90.5,
             overlap.3.prime.90bp %between% input$overlap90.3,
             GC.60bp.flank.5 %between% input$GC60.flank5,
             GC.60bp.flank.3 %between% input$GC60.flank3,
             GC.90bp.flank.5 %between% input$GC90.flank5,
             GC.90bp.flank.3 %between% input$GC90.flank3,
             msat.bioskryb %between% c(input$bioskryb.min,input$bioskryb.max),
             flank.5.90bp.bioskryb %between% c(input$bioskryb90.min5,input$bioskryb90.max5),
             flank.3.90bp.bioskryb %between% c(input$bioskryb90.min3,input$bioskryb90.max3),
             flank.5.60bp.bioskryb %between% c(input$bioskryb60.min5,input$bioskryb60.max5),
             flank.3.60bp.bioskryb %between% c(input$bioskryb60.min3,input$bioskryb60.max3),
             ml_mu %between% c(input$mu.min,input$mu.max),
             copy.num %between% c(input$copyNum.min, input$copyNum.max),
             per.match %between% input$perMatch,
             per.indel %between% input$perIndel,
             score %between% c(input$scoreMin, input$scoreMax),
             entropy %between% input$entropy,
             length.uninterrupted %between% c(input$ulengthMin, input$ulengthMax),
             uninterrupted.copy.num %between% c(input$uCopyNumMin, input$uCopyNumMax),
             GC.msat %between% input$GC.msat)
  }, ignoreNULL = FALSE)
  
  #--end of microsatellite filters--# 
  
  #this is where we put settings history
  
  #create a reactive object that simply returns a string that collapses all of the restriction enzyme inputIDs into a single string with split operators that can then be used to store and retrieve input information later
  RE_inputs_observer <- reactive({
    selected.REs.idx <- which(RE.capture_filters.name %in% input$selectREs)
    inputStr <- c("checkbox","slider5","slider3","numericMin","numericMax")
    inputIDs <- lapply(inputStr,function(x) paste0(rep(x,length(selected.REs.idx)),selected.REs.idx))
    inputIDs <- unlist(inputIDs)
    
    RE_inputs <- NULL
    for(ii in inputIDs){
      RE_inputs <- c(RE_inputs,paste(c(ii,input[[ii]]), collapse = "-"))
    }
    RE_inputs <- paste(RE_inputs, collapse = ".")
    return(RE_inputs)
  })
  
  #initialize a reactive object for storing a history of all user inputs
  settings_history <- reactiveValues(
    numeric_history = data.frame(minlength = min(usats$width),
                         maxlength = max(usats$width),
                         umap60.min5.k24 = 0,
                         umap60.max5.k24 = 1,
                         umap60.min3.k24 = 0,
                         umap60.max3.k24 = 1,
                         umap90.min5.k24 = 0,
                         umap90.max5.k24 = 1,
                         umap90.min3.k24 = 0,
                         umap90.max3.k24 = 1,
                         umap60.min5.k36 = 0,
                         umap60.max5.k36 = 1,
                         umap60.min3.k36 = 0,
                         umap60.max3.k36 = 1,
                         umap90.min5.k36 = 0,
                         umap90.max5.k36 = 1,
                         umap90.min3.k36 = 0,
                         umap90.max3.k36 = 1,
                         umap60.min5.k50 = 0,
                         umap60.max5.k50 = 1,
                         umap60.min3.k50 = 0,
                         umap60.max3.k50 = 1,
                         umap90.min5.k50 = 0,
                         umap90.max5.k50 = 1,
                         umap90.min3.k50 = 0,
                         umap90.max3.k50 = 1,
                         umap60.min5.k100 = 0,
                         umap60.max5.k100 = 1,
                         umap60.min3.k100 = 0,
                         umap60.max3.k100 = 1,
                         umap90.min5.k100 = 0,
                         umap90.max5.k100 = 1,
                         umap90.min3.k100 = 0,
                         umap90.max3.k100 = 1,
                         overlap60.min5 = 0,
                         overlap60.max5 = 1,
                         overlap60.min3 = 0,
                         overlap60.max3 = 1,
                         overlap90.min5 = 0,
                         overlap90.max5 = 1,
                         overlap90.min3 = 0,
                         overlap90.max3 = 1,
                         bioskryb.min = min(usats$msat.bioskryb),
                         bioskryb.max = max(usats$msat.bioskryb),
                         bioskryb90.min5 = min(usats$flank.5.90bp.bioskryb),
                         bioskryb90.max5 = max(usats$flank.5.90bp.bioskryb),
                         bioskryb90.min3 = min(usats$flank.3.90bp.bioskryb),
                         bioskryb90.max3 = max(usats$flank.3.90bp.bioskryb),
                         bioskryb60.min5 = min(usats$flank.5.60bp.bioskryb),
                         bioskryb60.max5 = max(usats$flank.5.60bp.bioskryb),
                         bioskryb60.min3 = min(usats$flank.3.60bp.bioskryb),
                         bioskryb60.max3 = max(usats$flank.3.60bp.bioskryb),
                         GC60.flank5.min = 0,
                         GC60.flank5.max = 1,
                         GC60.flank3.min = 0,
                         GC60.flank3.max = 1,
                         GC90.flank5.min = 0,
                         GC90.flank5.max = 1,
                         GC90.flank3.min = 0,
                         GC90.flank3.max = 1,
                         mu.min = min(usats$ml_mu),
                         mu.max = max(usats$ml_mu),
                         copyNum.min = min(usats$copy.num),
                         copyNum.max = max(usats$copy.num),
                         perMatch.min = 0,
                         perMatch.max = 100,
                         perIndel.min = 0,
                         perIndel.max = 100,
                         score.min = min(usats$score),
                         score.max = max(usats$score),
                         entropy.min = 0,
                         entropy.max = 2,
                         length.uninterrupted.min = min(usats$length.uninterrupted),
                         length.uninterrupted.max = max(usats$length.uninterrupted),
                         uninterrupted.copy.num.min = min(usats$uninterrupted.copy.num),
                         uninterrupted.copy.num.max = max(usats$uninterrupted.copy.num),
                         GC.msat.min = 0,
                         GC.msat.max = 1,
                         overlapUsat.min = 0.9001,
                         overlapUsat.max = 1,
                         nearest.usat.min.5 = min(usats$nearest.usat.5),
                         nearest.usat.max.5 = max(usats$nearest.usat.5),
                         nearest.usat.min.3 = min(usats$nearest.usat.3),
                         nearest.usat.max.3 = max(usats$nearest.usat.3),
                         min.Xaxis = min(usats$width),
                         max.Xaxis = max(usats$width),
                         min.Yaxis = 0,
                         max.Yaxis = 10
                         ),
    period.size_history = paste(levels(factor(usats$motif.size)), collapse = "."),
    motif_history = paste(unique(usats$motif.family[order(usats$motif.size,usats$motif.family)]), collapse = "."),
    regionfilter_checkboxes = "",
    regionfilter_region_radio = "",
    regionfilter_which_flank = "",
    regionfilter_numMin5 = "",
    regionfilter_numMax5 = "",
    regionfilter_numMin3 = "",
    regionfilter_numMax3 = "",
    REselect_history = "",
    RE_observer_history = "",
    dataView_history = "Microsatellites that pass filter(s)",
    annotation.select_history = paste(columns[which(names(columns) %in% c("seqnames","start","end","width","motif","motif.family"))], collapse = "/"),
    xAxis_history = paste("width"),
    yAxis_history = paste("count"),
    xLog_history = FALSE,
    yLog_history = FALSE,
    dataPlot_history = "Microsatellites that pass filter(s)",
    umap60_history = "",
    umap90_history = "",
    umap60.radio.k24_history = "both",
    umap60.radio.k36_history = "both",
    umap60.radio.k50_history = "both",
    umap60.radio.k100_history = "both",
    umap90.radio.k24_history = "both",
    umap90.radio.k36_history = "both",
    umap90.radio.k50_history = "both",
    umap90.radio.k100_history = "both",
    nearest.usat.radio_history = "both",
    LinkNearestUsatToMappability_history = FALSE
    )
  
  #whenever the user updates the display (either by clicking the "Apply Changes" button or by clicking the "Update Plot" button on the user plots page), update the values in the settings_history object with the new input values
  observeEvent(input$goButton | input$changePlot,{
    minlength_new <- as.numeric(input$minlength)
    maxlength_new <- as.numeric(input$maxlength)
    umap60.min5.k24_new <- {if("k24" %in% input$umap60) min(as.numeric(input$umap60.5.k24)) else 0}
    umap60.max5.k24_new <- {if("k24" %in% input$umap60) max(as.numeric(input$umap60.5.k24)) else 1}
    umap60.min3.k24_new <- {if("k24" %in% input$umap60) min(as.numeric(input$umap60.3.k24)) else 0}
    umap60.max3.k24_new <- {if("k24" %in% input$umap60) max(as.numeric(input$umap60.3.k24)) else 1}
    umap60.min5.k36_new <- {if("k36" %in% input$umap60) min(as.numeric(input$umap60.5.k36)) else 0}
    umap60.max5.k36_new <- {if("k36" %in% input$umap60) max(as.numeric(input$umap60.5.k36)) else 1}
    umap60.min3.k36_new <- {if("k36" %in% input$umap60) min(as.numeric(input$umap60.3.k36)) else 0}
    umap60.max3.k36_new <- {if("k36" %in% input$umap60) max(as.numeric(input$umap60.3.k36)) else 1}
    umap60.min5.k50_new <- {if("k50" %in% input$umap60) min(as.numeric(input$umap60.5.k50)) else 0}
    umap60.max5.k50_new <- {if("k50" %in% input$umap60) max(as.numeric(input$umap60.5.k50)) else 1}
    umap60.min3.k50_new <- {if("k50" %in% input$umap60) min(as.numeric(input$umap60.3.k50)) else 0}
    umap60.max3.k50_new <- {if("k50" %in% input$umap60) max(as.numeric(input$umap60.3.k50)) else 1}
    umap60.min5.k100_new <- {if("k100" %in% input$umap60) min(as.numeric(input$umap60.5.k100)) else 0}
    umap60.max5.k100_new <- {if("k100" %in% input$umap60) max(as.numeric(input$umap60.5.k100)) else 1}
    umap60.min3.k100_new <- {if("k100" %in% input$umap60) min(as.numeric(input$umap60.3.k100)) else 0}
    umap60.max3.k100_new <- {if("k100" %in% input$umap60) max(as.numeric(input$umap60.3.k100)) else 1}
    umap90.min5.k24_new <- {if("k24" %in% input$umap90) min(as.numeric(input$umap90.5.k24)) else 0}
    umap90.max5.k24_new <- {if("k24" %in% input$umap90) max(as.numeric(input$umap90.5.k24)) else 1}
    umap90.min3.k24_new <- {if("k24" %in% input$umap90) min(as.numeric(input$umap90.3.k24)) else 0}
    umap90.max3.k24_new <- {if("k24" %in% input$umap90) max(as.numeric(input$umap90.3.k24)) else 1}
    umap90.min5.k36_new <- {if("k36" %in% input$umap90) min(as.numeric(input$umap90.5.k36)) else 0}
    umap90.max5.k36_new <- {if("k36" %in% input$umap90) max(as.numeric(input$umap90.5.k36)) else 1}
    umap90.min3.k36_new <- {if("k36" %in% input$umap90) min(as.numeric(input$umap90.3.k36)) else 0}
    umap90.max3.k36_new <- {if("k36" %in% input$umap90) max(as.numeric(input$umap90.3.k36)) else 1}
    umap90.min5.k50_new <- {if("k50" %in% input$umap90) min(as.numeric(input$umap90.5.k50)) else 0}
    umap90.max5.k50_new <- {if("k50" %in% input$umap90) max(as.numeric(input$umap90.5.k50)) else 1}
    umap90.min3.k50_new <- {if("k50" %in% input$umap90) min(as.numeric(input$umap90.3.k50)) else 0}
    umap90.max3.k50_new <- {if("k50" %in% input$umap90) max(as.numeric(input$umap90.3.k50)) else 1}
    umap90.min5.k100_new <- {if("k100" %in% input$umap90) min(as.numeric(input$umap90.5.k100)) else 0}
    umap90.max5.k100_new <- {if("k100" %in% input$umap90) max(as.numeric(input$umap90.5.k100)) else 1}
    umap90.min3.k100_new <- {if("k100" %in% input$umap90) min(as.numeric(input$umap90.3.k100)) else 0}
    umap90.max3.k100_new <- {if("k100" %in% input$umap90) max(as.numeric(input$umap90.3.k100)) else 1}
    overlap60.min5_new <- min(as.numeric(input$overlap60.5))
    overlap60.max5_new <- max(as.numeric(input$overlap60.5))
    overlap60.min3_new <- min(as.numeric(input$overlap60.3))
    overlap60.max3_new <- max(as.numeric(input$overlap60.3))
    overlap90.min5_new <- min(as.numeric(input$overlap90.5))
    overlap90.max5_new <- max(as.numeric(input$overlap90.5))
    overlap90.min3_new <- min(as.numeric(input$overlap90.3))
    overlap90.max3_new <- max(as.numeric(input$overlap90.3))
    bioskryb.min_new <- as.numeric(input$bioskryb.min)
    bioskryb.max_new <- as.numeric(input$bioskryb.max)
    bioskryb90.min5_new <- as.numeric(input$bioskryb90.min5)
    bioskryb90.max5_new <- as.numeric(input$bioskryb90.max5)
    bioskryb90.min3_new <- as.numeric(input$bioskryb90.min3)
    bioskryb90.max3_new <- as.numeric(input$bioskryb90.max3)
    bioskryb60.min5_new <- as.numeric(input$bioskryb60.min5)
    bioskryb60.max5_new <- as.numeric(input$bioskryb60.max5)
    bioskryb60.min3_new <- as.numeric(input$bioskryb60.min3)
    bioskryb60.max3_new <- as.numeric(input$bioskryb60.max3)
    GC60.flank5.min_new = min(as.numeric(input$GC60.flank5))
    GC60.flank5.max_new = max(as.numeric(input$GC60.flank5))
    GC60.flank3.min_new = min(as.numeric(input$GC60.flank3))
    GC60.flank3.max_new = max(as.numeric(input$GC60.flank3))
    GC90.flank5.min_new = min(as.numeric(input$GC90.flank5))
    GC90.flank5.max_new = max(as.numeric(input$GC90.flank5))
    GC90.flank3.min_new = min(as.numeric(input$GC90.flank3))
    GC90.flank3.max_new = max(as.numeric(input$GC90.flank3))
    mu.min_new <- as.numeric(input$mu.min)
    mu.max_new <- as.numeric(input$mu.max)
    copyNum.min_new <- as.numeric(input$copyNum.min)
    copyNum.max_new <- as.numeric(input$copyNum.max)
    perMatch.min_new <- as.numeric(min(input$perMatch))
    perMatch.max_new <- as.numeric(max(input$perMatch))
    perIndel.min_new <- as.numeric(min(input$perIndel))
    perIndel.max_new <- as.numeric(max(input$perIndel))
    score.min_new <- as.numeric(input$scoreMin)
    score.max_new <- as.numeric(input$scoreMax)
    entropy.min_new <- as.numeric(min(input$entropy))
    entropy.max_new <- as.numeric(max(input$entropy))
    length.uninterrupted.min_new <- as.numeric(input$ulengthMin)
    length.uninterrupted.max_new <- as.numeric(input$ulengthMax)
    uninterrupted.copy.num.min_new <- as.numeric(input$uCopyNumMin)
    uninterrupted.copy.num.max_new <- as.numeric(input$uCopyNumMax)
    GC.msat.min_new <- as.numeric(min(input$GC.msat))
    GC.msat.max_new <- as.numeric(max(input$GC.msat))
    overlapUsat.min_new <- as.numeric(input$overlapUsatMin)
    overlapUsat.max_new <- as.numeric(input$overlapUsatMax)
    nearest.usat.min.5_new <- as.numeric(input$nearestUsatMin.5)
    nearest.usat.max.5_new <- as.numeric(input$nearestUsatMax.5)
    nearest.usat.min.3_new <- as.numeric(input$nearestUsatMin.3)
    nearest.usat.max.3_new <- as.numeric(input$nearestUsatMax.3)
    min.Xaxis_new <- as.numeric(input$min.Xaxis)
    max.Xaxis_new <- as.numeric(input$max.Xaxis)
    min.Yaxis_new <- as.numeric(input$min.Yaxis)
    max.Yaxis_new <- as.numeric(input$max.Yaxis)
    xAxis_history_new <- as.character(input$xAxis)
    yAxis_history_new <- as.character(input$yAxis)
    xLog_history_new <- input$xLog
    yLog_history_new <- input$yLog
    dataPlot_new <- as.character(input$dataPlot)
    umap60_new <- as.character(paste(input$umap60, collapse = "/"))
    umap90_new <- as.character(paste(input$umap90, collapse = "/"))
    umap60.radio.k24_new <- {if("k24" %in% input$umap60) as.character(input$umap60.radio.k24) else "both"}
    umap60.radio.k36_new <- {if("k36" %in% input$umap60) as.character(input$umap60.radio.k36) else "both"}
    umap60.radio.k50_new <- {if("k50" %in% input$umap60) as.character(input$umap60.radio.k50) else "both"}
    umap60.radio.k100_new <- {if("k100" %in% input$umap60) as.character(input$umap60.radio.k100) else "both"}
    umap90.radio.k24_new <- {if("k24" %in% input$umap90) as.character(input$umap90.radio.k24) else "both"}
    umap90.radio.k36_new <- {if("k36" %in% input$umap90) as.character(input$umap90.radio.k36) else "both"}
    umap90.radio.k50_new <- {if("k50" %in% input$umap90) as.character(input$umap90.radio.k50) else "both"}
    umap90.radio.k100_new <- {if("k100" %in% input$umap90) as.character(input$umap90.radio.k100) else "both"}
    nearest.usat.radio_new <- as.character(input$nearest.usat.radio)
    LinkNearestUsatToMappability_new <- input$LinkNearestUsatToMappability
    settings_history$numeric_history <- data.frame(minlength = minlength_new,
                                                   maxlength = maxlength_new,
                                                   umap60.min5.k24 = umap60.min5.k24_new,
                                                   umap60.max5.k24 = umap60.max5.k24_new,
                                                   umap60.min3.k24 = umap60.min3.k24_new,
                                                   umap60.max3.k24 = umap60.max3.k24_new,
                                                   umap90.min5.k24 = umap90.min5.k24_new,
                                                   umap90.max5.k24 = umap90.max5.k24_new,
                                                   umap90.min3.k24 = umap90.min3.k24_new,
                                                   umap90.max3.k24 = umap90.max3.k24_new,
                                                   umap60.min5.k36 = umap60.min5.k36_new,
                                                   umap60.max5.k36 = umap60.max5.k36_new,
                                                   umap60.min3.k36 = umap60.min3.k36_new,
                                                   umap60.max3.k36 = umap60.max3.k36_new,
                                                   umap90.min5.k36 = umap90.min5.k36_new,
                                                   umap90.max5.k36 = umap90.max5.k36_new,
                                                   umap90.min3.k36 = umap90.min3.k36_new,
                                                   umap90.max3.k36 = umap90.max3.k36_new,
                                                   umap60.min5.k50 = umap60.min5.k50_new,
                                                   umap60.max5.k50 = umap60.max5.k50_new,
                                                   umap60.min3.k50 = umap60.min3.k50_new,
                                                   umap60.max3.k50 = umap60.max3.k50_new,
                                                   umap90.min5.k50 = umap90.min5.k50_new,
                                                   umap90.max5.k50 = umap90.max5.k50_new,
                                                   umap90.min3.k50 = umap90.min3.k50_new,
                                                   umap90.max3.k50 = umap90.max3.k50_new,
                                                   umap60.min5.k100 = umap60.min5.k100_new,
                                                   umap60.max5.k100 = umap60.max5.k100_new,
                                                   umap60.min3.k100 = umap60.min3.k100_new,
                                                   umap60.max3.k100 = umap60.max3.k100_new,
                                                   umap90.min5.k100 = umap90.min5.k100_new,
                                                   umap90.max5.k100 = umap90.max5.k100_new,
                                                   umap90.min3.k100 = umap90.min3.k100_new,
                                                   umap90.max3.k100 = umap90.max3.k100_new,
                                                   overlap60.min5 = overlap60.min5_new,
                                                   overlap60.max5 = overlap60.max5_new,
                                                   overlap60.min3 = overlap60.min3_new,
                                                   overlap60.max3 = overlap60.max3_new,
                                                   overlap90.min5 = overlap90.min5_new,
                                                   overlap90.max5 = overlap90.max5_new,
                                                   overlap90.min3 = overlap90.min3_new,
                                                   overlap90.max3 = overlap90.max3_new,
                                                   bioskryb.min = bioskryb.min_new,
                                                   bioskryb.max = bioskryb.max_new,
                                                   bioskryb90.min5 = bioskryb90.min5_new,
                                                   bioskryb90.max5 = bioskryb90.max5_new,
                                                   bioskryb90.min3 = bioskryb90.min3_new,
                                                   bioskryb90.max3 = bioskryb90.max3_new,
                                                   bioskryb60.min5 = bioskryb60.min5_new,
                                                   bioskryb60.max5 = bioskryb60.max5_new,
                                                   bioskryb60.min3 = bioskryb60.min3_new,
                                                   bioskryb60.max3 = bioskryb60.max3_new,
                                                   GC60.flank5.min = GC60.flank5.min_new,
                                                   GC60.flank5.max = GC60.flank5.max_new,
                                                   GC60.flank3.min = GC60.flank3.min_new,
                                                   GC60.flank3.max = GC60.flank3.max_new,
                                                   GC90.flank5.min = GC90.flank5.min_new,
                                                   GC90.flank5.max = GC90.flank5.max_new,
                                                   GC90.flank3.min = GC90.flank3.min_new,
                                                   GC90.flank3.max = GC90.flank3.max_new,
                                                   mu.min = mu.min_new,
                                                   mu.max = mu.max_new,
                                                   copyNum.min = copyNum.min_new,
                                                   copyNum.max = copyNum.max_new,
                                                   perMatch.min = perMatch.min_new,
                                                   perMatch.max = perMatch.max_new,
                                                   perIndel.min = perIndel.min_new,
                                                   perIndel.max = perIndel.max_new,
                                                   score.min = score.min_new,
                                                   score.max = score.max_new,
                                                   entropy.min = entropy.min_new,
                                                   entropy.max = entropy.max_new,
                                                   length.uninterrupted.min = length.uninterrupted.min_new,
                                                   length.uninterrupted.max = length.uninterrupted.max_new,
                                                   uninterrupted.copy.num.min = uninterrupted.copy.num.min_new,
                                                   uninterrupted.copy.num.max = uninterrupted.copy.num.max_new,
                                                   GC.msat.min = GC.msat.min_new,
                                                   GC.msat.max = GC.msat.max_new,
                                                   overlapUsat.min = overlapUsat.min_new,
                                                   overlapUsat.max = overlapUsat.max_new,
                                                   nearest.usat.min.5 = nearest.usat.min.5_new,
                                                   nearest.usat.max.5 = nearest.usat.max.5_new,
                                                   nearest.usat.min.3 = nearest.usat.min.3_new,
                                                   nearest.usat.max.3 = nearest.usat.max.3_new,
                                                   min.Xaxis = min.Xaxis_new,
                                                   max.Xaxis = max.Xaxis_new,
                                                   min.Yaxis = min.Yaxis_new,
                                                   max.Yaxis = max.Yaxis_new)
    period_new <- as.character(paste(input$period, collapse = "."))
    motif_new <- as.character(paste(input$motif, collapse = "."))
    regionfilter_checkboxes_new <- {if(settings_history$regionfilter_checkboxes == "") paste(rep("FALSE",isolate(file_counter$n)),collapse = "&") else as.character(paste(unlist(lapply(grep("rf_checkbox",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))}
    regionfilter_region_radio_new <- as.character(paste(unlist(lapply(grep("rf_radio",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))
    regionfilter_which_flank_new <- as.character(paste(unlist(lapply(grep("flank_rf",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))
    regionfilter_numMin5_new <- as.character(paste(unlist(lapply(grep("rf_numMin_flank5",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))
    regionfilter_numMax5_new <- as.character(paste(unlist(lapply(grep("rf_numMax_flank5",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))
    regionfilter_numMin3_new <- as.character(paste(unlist(lapply(grep("rf_numMin_flank3",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))
    regionfilter_numMax3_new <- as.character(paste(unlist(lapply(grep("rf_numMax_flank3",names(input),value = TRUE),function(x){input[[x]]})), collapse = "&"))
    dataView_new <- as.character(input$dataView)
    annotation.select_new <- as.character(paste(input$annotation.select, collapse = "/"))
    REselect_new <- as.character(paste(input$selectREs, collapse = "/"))
    settings_history$period.size_history <- period_new
    settings_history$motif_history <- motif_new
    settings_history$regionfilter_checkboxes <- regionfilter_checkboxes_new
    settings_history$regionfilter_region_radio <- regionfilter_region_radio_new
    settings_history$regionfilter_which_flank <- regionfilter_which_flank_new
    settings_history$regionfilter_numMin5 <- regionfilter_numMin5_new
    settings_history$regionfilter_numMax5 <- regionfilter_numMax5_new
    settings_history$regionfilter_numMin3 <- regionfilter_numMin3_new
    settings_history$regionfilter_numMax3 <- regionfilter_numMax3_new
    settings_history$dataView_history <- dataView_new
    settings_history$annotation.select_history <- annotation.select_new
    settings_history$REselect_history <- REselect_new
    selected.REs.idx <- which(RE.capture_filters.name %in% input$selectREs)
    inputStr <- c("checkbox","slider5","slider3","numericMin","numericMax")
    inputIDs <- lapply(inputStr,function(x) paste0(rep(x,length(selected.REs.idx)),selected.REs.idx))
    inputIDs <- unlist(inputIDs)
    
    RE_inputs_new <- NULL
    for(ii in inputIDs){
      RE_inputs_new <- c(RE_inputs_new,paste(c(ii,input[[ii]]), collapse = "-"))
    }
    settings_history$RE_observer_history <- paste(as.character(RE_inputs_new), collapse = ".")
    settings_history$xAxis_history <- xAxis_history_new
    settings_history$yAxis_history <- yAxis_history_new
    settings_history$xLog_history <- xLog_history_new
    settings_history$yLog_history <- yLog_history_new
    settings_history$dataPlot_history <- dataPlot_new
    settings_history$umap60_history <- umap60_new
    settings_history$umap90_history <- umap90_new
    settings_history$umap60.radio.k24_history <- umap60.radio.k24_new
    settings_history$umap60.radio.k36_history <- umap60.radio.k36_new
    settings_history$umap60.radio.k50_history <- umap60.radio.k50_new
    settings_history$umap60.radio.k100_history <- umap60.radio.k100_new
    settings_history$umap90.radio.k24_history <- umap90.radio.k24_new
    settings_history$umap90.radio.k36_history <- umap90.radio.k36_new
    settings_history$umap90.radio.k50_history <- umap90.radio.k50_new
    settings_history$umap90.radio.k100_history <- umap90.radio.k100_new
    settings_history$nearest.usat.radio_history <- nearest.usat.radio_new
    settings_history$LinkNearestUsatToMappability_history <- LinkNearestUsatToMappability_new
  })
  
  #initiate a "messageTracer" object that identifies whether or not the display is up-to-date to control the disabling/enabling of the revert button
  messageTracer = reactiveValues(t = 0)
  
  #generate an "update message" that informs the user whether any of the inputs have been changed since they last saved the app state so that they know whether or not the plots/datatable displayed is consistent with the values they are seeing in the sidebar panel
  #this message is generated by simply using a reactive object to compare the actual state of each input to the state of each input at the time the app state was last updated (as recorded in the settings_history object)
  output$updateMessage <- reactive({
    options <- unlist(name_inputs$history)
    if(input$minlength == settings_history$numeric_history$minlength &
       input$maxlength == settings_history$numeric_history$maxlength &
       paste(input$umap60, collapse = "/") == settings_history$umap60_history &
       paste(input$umap90, collapse = "/") == settings_history$umap90_history &
       {if("k24" %in% input$umap60) min(input$umap60.5.k24) == settings_history$numeric_history$umap60.min5.k24 else TRUE} &
       {if("k24" %in% input$umap60) max(input$umap60.5.k24) == settings_history$numeric_history$umap60.max5.k24 else TRUE} &
       {if("k24" %in% input$umap60) min(input$umap60.3.k24) == settings_history$numeric_history$umap60.min3.k24 else TRUE} &
       {if("k24" %in% input$umap60) max(input$umap60.3.k24) == settings_history$numeric_history$umap60.max3.k24 else TRUE} &
       {if("k24" %in% input$umap60) input$umap60.radio.k24 == settings_history$umap60.radio.k24_history else TRUE} &
       {if("k24" %in% input$umap90) min(input$umap90.5.k24) == settings_history$numeric_history$umap90.min5.k24 else TRUE} &
       {if("k24" %in% input$umap90) max(input$umap90.5.k24) == settings_history$numeric_history$umap90.max5.k24 else TRUE} &
       {if("k24" %in% input$umap90) min(input$umap90.3.k24) == settings_history$numeric_history$umap90.min3.k24 else TRUE} &
       {if("k24" %in% input$umap90) max(input$umap90.3.k24) == settings_history$numeric_history$umap90.max3.k24 else TRUE} &
       {if("k24" %in% input$umap90) input$umap90.radio.k24 == settings_history$umap90.radio.k24_history else TRUE} &
       {if("k36" %in% input$umap60) min(input$umap60.5.k36) == settings_history$numeric_history$umap60.min5.k36 else TRUE} &
       {if("k36" %in% input$umap60) max(input$umap60.5.k36) == settings_history$numeric_history$umap60.max5.k36 else TRUE} &
       {if("k36" %in% input$umap60) min(input$umap60.3.k36) == settings_history$numeric_history$umap60.min3.k36 else TRUE} &
       {if("k36" %in% input$umap60) max(input$umap60.3.k36) == settings_history$numeric_history$umap60.max3.k36 else TRUE} &
       {if("k36" %in% input$umap60) input$umap60.radio.k36 == settings_history$umap60.radio.k36_history else TRUE} &
       {if("k36" %in% input$umap90) min(input$umap90.5.k36) == settings_history$numeric_history$umap90.min5.k36 else TRUE} &
       {if("k36" %in% input$umap90) max(input$umap90.5.k36) == settings_history$numeric_history$umap90.max5.k36 else TRUE} &
       {if("k36" %in% input$umap90) min(input$umap90.3.k36) == settings_history$numeric_history$umap90.min3.k36 else TRUE} &
       {if("k36" %in% input$umap90) max(input$umap90.3.k36) == settings_history$numeric_history$umap90.max3.k36 else TRUE} &
       {if("k36" %in% input$umap90) input$umap90.radio.k36 == settings_history$umap90.radio.k36_history else TRUE} &
       {if("k50" %in% input$umap60) min(input$umap60.5.k50) == settings_history$numeric_history$umap60.min5.k50 else TRUE} &
       {if("k50" %in% input$umap60) max(input$umap60.5.k50) == settings_history$numeric_history$umap60.max5.k50 else TRUE} &
       {if("k50" %in% input$umap60) min(input$umap60.3.k50) == settings_history$numeric_history$umap60.min3.k50 else TRUE} &
       {if("k50" %in% input$umap60) max(input$umap60.3.k50) == settings_history$numeric_history$umap60.max3.k50 else TRUE} &
       {if("k50" %in% input$umap60) input$umap60.radio.k50 == settings_history$umap60.radio.k50_history else TRUE} &
       {if("k50" %in% input$umap90) min(input$umap90.5.k50) == settings_history$numeric_history$umap90.min5.k50 else TRUE} &
       {if("k50" %in% input$umap90) max(input$umap90.5.k50) == settings_history$numeric_history$umap90.max5.k50 else TRUE} &
       {if("k50" %in% input$umap90) min(input$umap90.3.k50) == settings_history$numeric_history$umap90.min3.k50 else TRUE} &
       {if("k50" %in% input$umap90) max(input$umap90.3.k50) == settings_history$numeric_history$umap90.max3.k50 else TRUE} &
       {if("k50" %in% input$umap90) input$umap90.radio.k50 == settings_history$umap90.radio.k50_history else TRUE} &
       {if("k100" %in% input$umap60) min(input$umap60.5.k100) == settings_history$numeric_history$umap60.min5.k100 else TRUE} &
       {if("k100" %in% input$umap60) max(input$umap60.5.k100) == settings_history$numeric_history$umap60.max5.k100 else TRUE} &
       {if("k100" %in% input$umap60) min(input$umap60.3.k100) == settings_history$numeric_history$umap60.min3.k100 else TRUE} &
       {if("k100" %in% input$umap60) max(input$umap60.3.k100) == settings_history$numeric_history$umap60.max3.k100 else TRUE} &
       {if("k100" %in% input$umap60) input$umap60.radio.k100 == settings_history$umap60.radio.k100_history else TRUE} &
       {if("k100" %in% input$umap90) min(input$umap90.5.k100) == settings_history$numeric_history$umap90.min5.k100 else TRUE} &
       {if("k100" %in% input$umap90) max(input$umap90.5.k100) == settings_history$numeric_history$umap90.max5.k100 else TRUE} &
       {if("k100" %in% input$umap90) min(input$umap90.3.k100) == settings_history$numeric_history$umap90.min3.k100 else TRUE} &
       {if("k100" %in% input$umap90) max(input$umap90.3.k100) == settings_history$numeric_history$umap90.max3.k100 else TRUE} &
       {if("k100" %in% input$umap90) input$umap90.radio.k100 == settings_history$umap90.radio.k100_history else TRUE} &
       min(input$overlap60.5) == settings_history$numeric_history$overlap60.min5 &
       max(input$overlap60.5) == settings_history$numeric_history$overlap60.max5 &
       min(input$overlap60.3) == settings_history$numeric_history$overlap60.min3 &
       max(input$overlap60.3) == settings_history$numeric_history$overlap60.max3 &
       min(input$overlap90.5) == settings_history$numeric_history$overlap90.min5 &
       max(input$overlap90.5) == settings_history$numeric_history$overlap90.max5 &
       min(input$overlap90.3) == settings_history$numeric_history$overlap90.min3 &
       max(input$overlap90.3) == settings_history$numeric_history$overlap90.max3 &
       input$bioskryb.min == settings_history$numeric_history$bioskryb.min &
       input$bioskryb.max == settings_history$numeric_history$bioskryb.max &
       input$bioskryb90.min5 == settings_history$numeric_history$bioskryb90.min5 &
       input$bioskryb90.max5 == settings_history$numeric_history$bioskryb90.max5 &
       input$bioskryb90.min3 == settings_history$numeric_history$bioskryb90.min3 &
       input$bioskryb90.max3 == settings_history$numeric_history$bioskryb90.max3 &
       input$bioskryb60.min5 == settings_history$numeric_history$bioskryb60.min5 &
       input$bioskryb60.max5 == settings_history$numeric_history$bioskryb60.max5 &
       input$bioskryb60.min3 == settings_history$numeric_history$bioskryb60.min3 &
       input$bioskryb60.max3 == settings_history$numeric_history$bioskryb60.max3 &
       min(input$GC60.flank5) == settings_history$numeric_history$GC60.flank5.min &
       max(input$GC60.flank5) == settings_history$numeric_history$GC60.flank5.max &
       min(input$GC60.flank3) == settings_history$numeric_history$GC60.flank3.min &
       max(input$GC60.flank3) == settings_history$numeric_history$GC60.flank3.max &
       min(input$GC90.flank5) == settings_history$numeric_history$GC90.flank5.min &
       max(input$GC90.flank5) == settings_history$numeric_history$GC90.flank5.max &
       min(input$GC90.flank3) == settings_history$numeric_history$GC90.flank3.min &
       max(input$GC90.flank3) == settings_history$numeric_history$GC90.flank3.max &
       input$min.Xaxis == settings_history$numeric_history$min.Xaxis &
       input$max.Xaxis == settings_history$numeric_history$max.Xaxis &
       input$min.Yaxis == settings_history$numeric_history$min.Yaxis &
       input$max.Yaxis == settings_history$numeric_history$max.Yaxis &
       input$mu.min == settings_history$numeric_history$mu.min &
       input$mu.max == settings_history$numeric_history$mu.max &
       input$copyNum.min == settings_history$numeric_history$copyNum.min &
       input$copyNum.max == settings_history$numeric_history$copyNum.max &
       min(input$perMatch) == settings_history$numeric_history$perMatch.min &
       max(input$perMatch) == settings_history$numeric_history$perMatch.max &
       min(input$perIndel) == settings_history$numeric_history$perIndel.min &
       max(input$perIndel) == settings_history$numeric_history$perIndel.max &
       input$scoreMin == settings_history$numeric_history$score.min &
       input$scoreMax == settings_history$numeric_history$score.max &
       min(input$entropy) == settings_history$numeric_history$entropy.min & 
       max(input$entropy) == settings_history$numeric_history$entropy.max &
       input$ulengthMin == settings_history$numeric_history$length.uninterrupted.min &
       input$ulengthMax == settings_history$numeric_history$length.uninterrupted.max &
       input$uCopyNumMin == settings_history$numeric_history$uninterrupted.copy.num.min &
       input$uCopyNumMax == settings_history$numeric_history$uninterrupted.copy.num.max &
       min(input$GC.msat) == settings_history$numeric_history$GC.msat.min &
       max(input$GC.msat) == settings_history$numeric_history$GC.msat.max &
       input$overlapUsatMin == settings_history$numeric_history$overlapUsat.min &
       input$overlapUsatMax == settings_history$numeric_history$overlapUsat.max &
       input$nearestUsatMin.5 == settings_history$numeric_history$nearest.usat.min.5 &
       input$nearestUsatMax.5 == settings_history$numeric_history$nearest.usat.max.5 &
       input$nearestUsatMin.3 == settings_history$numeric_history$nearest.usat.min.3 &
       input$nearestUsatMax.3 == settings_history$numeric_history$nearest.usat.max.3 &
       paste(input$period, collapse = ".") == settings_history$period.size_history &
       paste(input$motif, collapse = ".") == settings_history$motif_history &
       paste(input$selectREs, collapse = "/") == settings_history$REselect_history &
       as.character(RE_inputs_observer()) == settings_history$RE_observer_history &
       as.character(paste(unlist(lapply(grep("rf_checkbox",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_checkboxes &
       {if(isEmpty(settings_history$regionfilter_region_radio)) as.character(paste(unlist(lapply(grep("rf_radio",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_region_radio else TRUE} &
       {if(isEmpty(settings_history$regionfilter_which_flank)) as.character(paste(unlist(lapply(grep("flank_rf",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_which_flank else TRUE} &
       {if(isEmpty(settings_history$regionfilter_numMin5)) as.character(paste(unlist(lapply(grep("rf_numMin_flank5",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMin5 else TRUE} &
       {if(isEmpty(settings_history$regionfilter_numMax5)) as.character(paste(unlist(lapply(grep("rf_numMax_flank5",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMax5 else TRUE} &
       {if(isEmpty(settings_history$regionfilter_numMin3)) as.character(paste(unlist(lapply(grep("rf_numMin_flank3",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMin3 else TRUE} &
       {if(isEmpty(settings_history$regionfilter_numMax3)) as.character(paste(unlist(lapply(grep("rf_numMax_flank3",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMax3 else TRUE} &
       input$dataView == settings_history$dataView_history &
       paste(input$annotation.select, collapse = "/") == settings_history$annotation.select_history &
       input$xAxis == settings_history$xAxis_history &
       input$yAxis == settings_history$yAxis_history &
       input$xLog == settings_history$xLog_history &
       input$yLog == settings_history$yLog_history &
       input$dataPlot == settings_history$dataPlot_history &
       input$nearest.usat.radio == settings_history$nearest.usat.radio_history &
       input$LinkNearestUsatToMappability == settings_history$LinkNearestUsatToMappability_history){
      message <- HTML(paste("<p style='color:green;'>Display is up to date</p>"))
      messageTracer$t <- 1
    } else {
      message <- HTML(paste("<p style='color:red;'>Display is out of date</p>"))
      messageTracer$t <- 0
    }
    return(message)
  })
  
  #--end of settings history --#
  
  #update button reactivity
  
  #this observeEvent updates the values of each input to the input's last saved value, as recorded in the settings_history object
  observeEvent(input$revert,{
    updateNumericInput(session,"minlength", value = settings_history$numeric_history$minlength)
    updateNumericInput(session,"maxlength", value = settings_history$numeric_history$maxlength)
    umap60_string <- settings_history$umap60_history
    umap60_set <- unlist(strsplit(umap60_string, split = "[/]"))
    updatePickerInput(session,"umap60",choices =  c("k24","k36","k50","k100"), selected = umap60_set)
    umap90_string <- settings_history$umap90_history
    umap90_set <- unlist(strsplit(umap90_string, split = "[/]"))
    updatePickerInput(session,"umap90",choices =  c("k24","k36","k50","k100"), selected = umap90_set)
    {if("k24" %in% input$umap60) updateNumericInput(session,"umap60.5.k24", value = c(settings_history$numeric_history$umap60.min5.k24, settings_history$numeric_history$umap60.max5.k24)) else NULL}
    {if("k24" %in% input$umap60) updateNumericInput(session,"umap60.3.k24", value = c(settings_history$numeric_history$umap60.min3.k24, settings_history$numeric_history$umap60.max3.k24)) else NULL}
    {if("k24" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k24", choices = c("either","both"), selected = settings_history$umap60.radio.k24_history) else NULL}
    {if("k24" %in% input$umap90) updateNumericInput(session,"umap90.5.k24", value = c(settings_history$numeric_history$umap90.min5.k24, settings_history$numeric_history$umap90.max5.k24)) else NULL}
    {if("k24" %in% input$umap90) updateNumericInput(session,"umap90.3.k24", value = c(settings_history$numeric_history$umap90.min3.k24, settings_history$numeric_history$umap90.max3.k24)) else NULL}
    {if("k24" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k24", choices = c("either","both"), selected = settings_history$umap90.radio.k24_history) else NULL}
    {if("k36" %in% input$umap60) updateNumericInput(session,"umap60.5.k36", value = c(settings_history$numeric_history$umap60.min5.k36, settings_history$numeric_history$umap60.max5.k36)) else NULL}
    {if("k36" %in% input$umap60) updateNumericInput(session,"umap60.3.k36", value = c(settings_history$numeric_history$umap60.min3.k36, settings_history$numeric_history$umap60.max3.k36)) else NULL}
    {if("k36" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k36", choices = c("either","both"), selected = settings_history$umap60.radio.k36_history) else NULL}
    {if("k36" %in% input$umap90) updateNumericInput(session,"umap90.5.k36", value = c(settings_history$numeric_history$umap90.min5.k36, settings_history$numeric_history$umap90.max5.k36)) else NULL}
    {if("k36" %in% input$umap90) updateNumericInput(session,"umap90.3.k36", value = c(settings_history$numeric_history$umap90.min3.k36, settings_history$numeric_history$umap90.max3.k36)) else NULL}
    {if("k36" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k36", choices = c("either","both"), selected = settings_history$umap90.radio.k36_history) else NULL}
    {if("k50" %in% input$umap60) updateNumericInput(session,"umap60.5.k50", value = c(settings_history$numeric_history$umap60.min5.k50, settings_history$numeric_history$umap60.max5.k50)) else NULL}
    {if("k50" %in% input$umap60) updateNumericInput(session,"umap60.3.k50", value = c(settings_history$numeric_history$umap60.min3.k50, settings_history$numeric_history$umap60.max3.k50)) else NULL}
    {if("k50" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k50", choices = c("either","both"), selected = settings_history$umap60.radio.k50_history) else NULL}
    {if("k50" %in% input$umap90) updateNumericInput(session,"umap90.5.k50", value = c(settings_history$numeric_history$umap90.min5.k50, settings_history$numeric_history$umap90.max5.k50)) else NULL}
    {if("k50" %in% input$umap90) updateNumericInput(session,"umap90.3.k50", value = c(settings_history$numeric_history$umap90.min3.k50, settings_history$numeric_history$umap90.max3.k50)) else NULL}
    {if("k50" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k50", choices = c("either","both"), selected = settings_history$umap90.radio.k50_history) else NULL}
    {if("k100" %in% input$umap60) updateNumericInput(session,"umap60.5.k100", value = c(settings_history$numeric_history$umap60.min5.k100, settings_history$numeric_history$umap60.max5.k100)) else NULL}
    {if("k100" %in% input$umap60) updateNumericInput(session,"umap60.3.k100", value = c(settings_history$numeric_history$umap60.min3.k100, settings_history$numeric_history$umap60.max3.k100)) else NULL}
    {if("k100" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k100", choices = c("either","both"), selected = settings_history$umap60.radio.k100_history) else NULL}
    {if("k100" %in% input$umap90) updateNumericInput(session,"umap90.5.k100", value = c(settings_history$numeric_history$umap90.min5.k100, settings_history$numeric_history$umap90.max5.k100)) else NULL}
    {if("k100" %in% input$umap90) updateNumericInput(session,"umap90.3.k100", value = c(settings_history$numeric_history$umap90.min3.k100, settings_history$numeric_history$umap90.max3.k100)) else NULL}
    {if("k100" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k100", choices = c("either","both"), selected = settings_history$umap90.radio.k100_history) else NULL}
    updateRadioGroupButtons(session,"nearest.usat.radio", choices = c("either","both"), selected = settings_history$nearest.usat.radio_history)
    updatePrettyToggle(session,"LinkNearestUsatToMappability", value = settings_history$LinkNearestUsatToMappability_history)
    updateNumericInput(session,"overlap60.5", value = c(settings_history$numeric_history$overlap60.min5, settings_history$numeric_history$overlap60.max5))
    updateNumericInput(session,"overlap60.3", value = c(settings_history$numeric_history$overlap60.min3, settings_history$numeric_history$overlap60.max3))
    updateNumericInput(session,"overlap90.5", value = c(settings_history$numeric_history$overlap90.min5, settings_history$numeric_history$overlap90.max5))
    updateNumericInput(session,"overlap90.3", value = c(settings_history$numeric_history$overlap90.min3, settings_history$numeric_history$overlap90.max3))
    updateNumericInput(session,"bioskryb.min", value = settings_history$numeric_history$bioskryb.min)
    updateNumericInput(session,"bioskryb.max", value = settings_history$numeric_history$bioskryb.max)
    updateNumericInput(session,"bioskryb90.min5", value = settings_history$numeric_history$bioskryb90.min5)
    updateNumericInput(session,"bioskryb90.max5", value = settings_history$numeric_history$bioskryb90.max5)
    updateNumericInput(session,"bioskryb90.min3", value = settings_history$numeric_history$bioskryb90.min3)
    updateNumericInput(session,"bioskryb90.max3", value = settings_history$numeric_history$bioskryb90.max3)
    updateNumericInput(session,"bioskryb60.min5", value = settings_history$numeric_history$bioskryb60.min5)
    updateNumericInput(session,"bioskryb60.max5", value = settings_history$numeric_history$bioskryb60.max5)
    updateNumericInput(session,"bioskryb60.min3", value = settings_history$numeric_history$bioskryb60.min3)
    updateNumericInput(session,"bioskryb60.max3", value = settings_history$numeric_history$bioskryb60.max3)
    updateNumericInput(session,"GC60.flank5", value = c(settings_history$numeric_history$GC60.flank5.min,settings_history$numeric_history$GC60.flank5.max))
    updateNumericInput(session,"GC60.flank3", value = c(settings_history$numeric_history$GC60.flank3.min,settings_history$numeric_history$GC60.flank3.max))
    updateNumericInput(session,"GC90.flank5", value = c(settings_history$numeric_history$GC90.flank5.min,settings_history$numeric_history$GC90.flank5.max))
    updateNumericInput(session,"GC90.flank3", value = c(settings_history$numeric_history$GC90.flank3.min,settings_history$numeric_history$GC90.flank3.max))
    updateNumericInput(session,"mu.min", value = settings_history$numeric_history$mu.min)
    updateNumericInput(session,"mu.max", value = settings_history$numeric_history$mu.max)
    updateNumericInput(session,"copyNum.min", value = settings_history$numeric_history$copyNum.min)
    updateNumericInput(session,"copyNum.max", value = settings_history$numeric_history$copyNum.max)
    updateNumericInput(session,"perMatch", value = c(settings_history$numeric_history$perMatch.min,settings_history$numeric_history$perMatch.max))
    updateNumericInput(session,"perIndel", value = c(settings_history$numeric_history$perIndel.min,settings_history$numeric_history$perIndel.max))
    updateNumericInput(session,"scoreMin", value = settings_history$numeric_history$score.min)
    updateNumericInput(session,"scoreMax", value = settings_history$numeric_history$score.max)
    updateNumericInput(session,"entropy", value = c(settings_history$numeric_history$entropy.min,settings_history$numeric_history$entropy.max))
    updateNumericInput(session, "ulengthMin", value = settings_history$numeric_history$length.uninterrupted.min)
    updateNumericInput(session, "ulengthMax", value = settings_history$numeric_history$length.uninterrupted.max)
    updateNumericInput(session, "uCopyNumMin", value = settings_history$numeric_history$uninterrupted.copy.num.min)
    updateNumericInput(session, "uCopyNumMax", value = settings_history$numeric_history$uninterrupted.copy.num.max)
    updateNumericInput(session, "GC.msat", value = c(settings_history$numeric_history$GC.msat.min,settings_history$numeric_history$GC.msat.max))
    updateNumericInput(session, "overlapUsatMin", value = settings_history$numeric_history$overlapUsat.min)
    updateNumericInput(session, "overlapUsatMax", value = settings_history$numeric_history$overlapUsat.max)
    updateNumericInput(session, "nearestUsatMin.5", value = settings_history$numeric_history$nearest.usat.min.5)
    updateNumericInput(session, "nearestUsatMax.5", value = settings_history$numeric_history$nearest.usat.max.5)
    updateNumericInput(session, "nearestUsatMin.3", value = settings_history$numeric_history$nearest.usat.min.3)
    updateNumericInput(session, "nearestUsatMax.3", value = settings_history$numeric_history$nearest.usat.max.3)
    updateNumericInput(session,"min.Xaxis", value = settings_history$numeric_history$min.Xaxis)
    updateNumericInput(session,"max.Xaxis", value = settings_history$numeric_history$max.Xaxis)
    updateNumericInput(session,"min.Yaxis", value = settings_history$numeric_history$min.Yaxis)
    updateNumericInput(session,"max.Yaxis", value = settings_history$numeric_history$max.Yaxis)
    period_string <- settings_history$period.size_history
    period_set <- unlist(strsplit(period_string, split = "[.]"))
    updateCheckboxGroupInput(session,"period",
                             choices = levels(factor(usats$motif.size)),
                             selected = period_set)
    motif_string <- settings_history$motif_history
    motif_set <- unlist(strsplit(motif_string, split = "[.]"))
    updatePickerInput(session,"motif",
                      choices = unique(usats$motif.family[order(usats$motif.size,usats$motif.family)]),
                      selected = motif_set)
    rf.checkbox.options <- unlist(grep("rf_checkbox", names(input), value = TRUE))
    region_checkbox_string <- settings_history$regionfilter_checkboxes
    region_checkbox_set <- unlist(strsplit(region_checkbox_string, split = "[&]"))
    lapply(rf.checkbox.options, function(x) updateCheckboxInput(session,x, value = as.logical(region_checkbox_set[which(rf.checkbox.options == x)])))
    rf.radio.options <- unlist(grep("rf_radio", names(input), value = TRUE))
    region_radio_string <- settings_history$regionfilter_region_radio
    region_radio_set <- unlist(strsplit(region_radio_string, split = "[&]"))
    lapply(rf.radio.options, function(x) updateRadioGroupButtons(session,x, choices = c("microsatellite","flank","both"), selected = region_radio_set[which(rf.radio.options == x)]))
    flank.options <- unlist(grep("flank_rf", names(input), value = TRUE))
    regionfilter_which_flank_string <- settings_history$regionfilter_which_flank
    regionfilter_which_flank_set <- unlist(strsplit(regionfilter_which_flank_string, split = "[&]"))
    lapply(flank.options, function(x) updateRadioGroupButtons(session,x, choices = c("one","both"), selected = regionfilter_which_flank_set[which(flank.options == x)]))
    numMin5.options <- unlist(grep("rf_numMin_flank5", names(input), value = TRUE))
    regionfilter_numMin5_string <- settings_history$regionfilter_numMin5
    regionfilter_numMin5_set <- unlist(strsplit(regionfilter_numMin5_string, split = "[&]"))
    lapply(numMin5.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMin5_set[which(numMin5.options == x)])))
    numMax5.options <- unlist(grep("rf_numMax_flank5", names(input), value = TRUE))
    regionfilter_numMax5_string <- settings_history$regionfilter_numMax5
    regionfilter_numMax5_set <- unlist(strsplit(regionfilter_numMax5_string, split = "[&]"))
    lapply(numMax5.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMax5_set[which(numMax5.options == x)])))
    numMin3.options <- unlist(grep("rf_numMin_flank3", names(input), value = TRUE))
    regionfilter_numMin3_string <- settings_history$regionfilter_numMin3
    regionfilter_numMin3_set <- unlist(strsplit(regionfilter_numMin3_string, split = "[&]"))
    lapply(numMin3.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMin3_set[which(numMin3.options == x)])))
    numMax3.options <- unlist(grep("rf_numMax_flank3", names(input), value = TRUE))
    regionfilter_numMax3_string <- settings_history$regionfilter_numMax3
    regionfilter_numMax3_set <- unlist(strsplit(regionfilter_numMax3_string, split = "[&]"))
    lapply(numMax3.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMax3_set[which(numMax3.options == x)])))
    REselect_string <- settings_history$REselect_history
    REselect_set <- unlist(strsplit(REselect_string, split = "[/]"))
    updatePickerInput(session, "selectREs",
                      choices = RE.capture_filters.name,
                      selected = REselect_set)
    RE.settings_string <- settings_history$RE_observer_history
    RE.settings_sets <- unlist(strsplit(RE.settings_string,split = "[.]"))
    RE.settings_list <- lapply(RE.settings_sets, function(x) unlist(strsplit(x, split = "[-]")))
    RE.settings_checkbox <- grep("checkbox", RE.settings_list)
    lapply(RE.settings_checkbox, function(x) updateCheckboxInput(session,RE.settings_list[[x]][1],value = eval(parse(text=RE.settings_list[[x]][2]))))
    RE.settings_slider <- grep("slider", RE.settings_list)
    lapply(RE.settings_slider, function(x) updateSliderInput(session, RE.settings_list[[x]][1], value = c(eval(parse(text=RE.settings_list[[x]][2])),eval(parse(text=RE.settings_list[[x]][3])))))
    RE.settings_numeric <- grep("numeric", RE.settings_list)
    lapply(RE.settings_numeric, function(x) updateNumericInput(session, RE.settings_list[[x]][1], value = eval(parse(text=RE.settings_list[[x]][2]))))
    updateRadioButtons(session,"dataView",
                       choices = c("All Microsatellites","Microsatellites that pass filter(s)","Microsatellites excluded by filter(s)"),
                       selected = settings_history$dataView_history)
    annotations.select_string <- settings_history$annotation.select_history
    annotations.select_set <- unlist(strsplit(annotations.select_string, split = "[/]"))
    updatePickerInput(session, "annotation.select",
                      choices = columns,
                      selected = annotations.select_set)
    updateRadioButtons(session,"dataPlot",
                      choices = c("All Microsatellites","Microsatellites that pass filter(s)","Microsatellites excluded by filter(s)"),
                      selected = settings_history$dataPlot_history)
    updatePickerInput(session,"xAxis",
                      choices = names(columns)[which(!(names(columns) %in% c("seqnames","start","end",
                                                                             "strand","consensus","sequence",
                                                                             "motif","motif.family","flank.5.prime.seq",
                                                                             "flank.3.prime.seq")))],
                      selected = settings_history$xAxis_history)
    updatePickerInput(session,"yAxis",
                      choices = c("count","proportion",names(columns)[which(!(names(columns) %in% c("seqnames","start","end",
                                                                                                    "strand","consensus","sequence",
                                                                                                    "motif","motif.family","flank.5.prime.seq",
                                                                                                    "flank.3.prime.seq")))]),
                      selected = settings_history$yAxis_history)
    updateMaterialSwitch(session,"xLog",value = settings_history$xLog_history)
    updateMaterialSwitch(session,"yLog",value = settings_history$yLog_history)
    #once all the values are changed, the revert button needs to be clicked again in order to apply the changes to the plots/datatable displayed in the app ui, to do this, at the end of each "revert" action, a hidden "revertGo" button is triggered
    click("revertGo")
  })
  
  #initialize revertDelay button that controls how many times the revert button is clicked after values are updated
  revertDelay <- reactiveValues(n=0)
  
  #once the revert button is clicked, add 1 to the revertDelay counter
  observeEvent(input$revertGo,{
    revertDelay$n = revertDelay$n + 1
    #observe the value of the revert delay counter
    n = revertDelay$n
    #if the value of the counter is exactly 1, click the revert button again
    if(n == 1){
      click("revert")
      #if the value of the revert counter is greater than 1, update the revertDelay counter back to 0 to ready it for the next use of the revert button, but do not click the revert button again
    }else if(n > 1){
      revertDelay$n <- 0
    }
  })
  
  #use the messageTracer object, initialized above, to determine whether or not the app display is reflective of the values currently displayed in the sidebar panel section of the ui
  observeEvent(messageTracer$t,{
    t <- messageTracer$t
    #if the display is up-to-date, disable the revert button so that the user cannot trigger this action
    if(t == 1){
      shinyjs::disable("revert")
      #if the display is NOT up-to-date, enable the revert button so that the user can trigger this action, if they would like to
    }else{
      shinyjs::enable("revert")
    }
  })
  
  #--end of update button reactivity--#
  
  #this is where we put text outputs
  output$app.info <- renderUI({
    str1 <- "<p>Note: the &#34Revert&#34 button in the top right corner of this page allows you to revert the state of all inputs to all filters back to the last saved state of the app.</p>"
    str2 <- "<p>This app was created by Danielle Shields, a research associate in the Evrony Lab in 2021. It was created for the purpose of filtering microsatellites based on a variety of features.</p>"
    str3 <- "<p>The microsatellites selected for this app were identified by running the human reference genome hg38 through the Tandem Repeats Finder (TRF) program, developed by Dr. Gary Benson (<em>Nucleic Acids Res.</em>,1999).</p>"
    str4 <- "<p>The parameters we selected to call microsatellites using TRF are as follows:</p>"
    str5 <- "<ul><li>Alignment Parameters:<ul><li>Match Weight: 2</li><li>Mismatch Weight: -7</li><li>Indel Weight: -7</li></ul></li><li>Match Probability: 80</li><li>Indel Probability: 10</li><li>Minimum Alignment Score: 36</li><li>Maximum Period Size: 6</li></ul>"
    str6 <- "<p>For more information about how these parameters are defined, please <a target='_blank' rel='noopener noreferrer' href = 'https://tandem.bu.edu/trf/trfdesc.html'>click here</a>.</p>"
    HTML(paste(str1,str2,str3,paste0(str4,str5),str6,sep="<br/>"))
  })
  
  output$download.info <- renderUI({
    str1 <- "<p>To download a randomly selected subset of microsatellites from the selected data table, enter your desired subset size into 'Subset Size' and select 'Download Subset'.</p>"
    str2 <- "<p>To download all microsatellites in the selected data table, select 'Download All'.</p>"
    HTML(paste(str1,str2))
  })
  
  output$regionfilter.info <- renderUI({
    str1 <- "<p>Region Filters are regions of the genome you may be interested in excluding from your microsatellite analysis. Selecting one or more of the region filters below will exclude microsatellites that fall in the selected region(s) from the dataset.</p>"
    HTML(paste(str1))
  })
  
  output$regionfilter.upload.info <- renderUI({
    str1 <- "<p>All region filter files must be formatted as R GRanges objects exported as .csv files using the region_filter function prior to being uploaded to this app.</p>"
    str2 <- "<p>To make new region filters, bed and bigwig files, containing information on regions of the genome that we would like to exclude from microsatellite analysis, may be passed to the region_filter function, which will properly format and export region filter files that will be compatible with this app.</p>"
    HTML(paste(str1,str2))
  })
  
  output$defaultRegionFilters <- renderUI({
    header = "<header><h3>Definitions of Default Region Filters</h3></header>"
    str1 <- "<p><b>20160622.allChr.mask.filterregions</b> = regions of the genome that are less accessible to next generation sequencing methods using short reads, as called by the 1000 Genomes Project. This particular track uses a strict mask definition that requires that total coverage should be within 50% of the average, that no more than 0.1% of reads have mapping quality of zero, and that the average mapping quality for the position should be 56 or greater. This definition is quite stringent and focuses on the most unique regions of the genome. <u>Select 20160622.allChr.mask.filterregions.gte_score.threshold-0_bin.size-NA to exclude microsatellites that <b>did not pass</b> 1000 genomes strict filter analysis. Selecting 20160622.allChr.mask.filterregions.lt_score.threshold-0_bin.size-NA will do nothing.</u></p>"
    str2 <- "<p><b>20160622.allChr.pilot_mask.filterregions/b> = regions of the genome where the average depth of coverage (summed across all samples) was higher or lower than the average depth by a factor of 2-fold and/or where >20% of overlapping reads had a mapping quality score of zero. <u>Select 20160622.allChr.pilot_mask.filterregions.gte_score.threshold-0_bin.size-NA to exclude microsatellites that <b>did not pass</b> 1000 genomes pilot mask filter analysis. Selecting 20160622.allChr.pilot_mask.filterregions.lt_score.threshold-0_bin.size-NA will do nothing.</u></p>"
    str3 <- "<p><b>ENCFF356LFX</b> = the DAC Exclusion List Regions from the ENCODE project, which is &#39a comprehensive set of regions in the human genome that have anomalous, unstructured, high signal/read counts in next gen sequencing experiments independent of cell line and type of experiment...These regions tend to have a very high ratio of multi-mapping to unique mapping reads and high variance in mappability...[I]t is recommended to use this exclusion list alongside mappability filters.&#39<u> Select ENCFF356LFX.gte_score.threshold-0_bin.size-NA.csv to exclude microsatellites in these regions. Selecting ENCFF356LFX.lt_score.threshold-0_bin.size-NA.csv will do nothing.</u></p>"
    str4 <- "<p><b>k24.umap.sorted</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 24bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region. One might be interested in excluding all regions with a k24 mappability below a threshold score of 0.1 because of extremely low mappability.<u> Select k24.umap.sorted.gte_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score greater than or equal to 0.1. Select k24.umap.sorted.lt_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score less than 0.1.</u></p>"
    str5 <- "<p><b>k36.umap.sorted</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 36bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region. One might be interested in excluding all regions with a k36 mappability below a threshold score of 0.1 because of extremely low mappability.<u> Select k36.umap.sorted.gte_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score greater than or equal to 0.1. Select k36.umap.sorted.lt_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score less than 0.1.</u></p>"
    str6 <- "<p><b>k50.umap.sorted</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 50bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region. One might be interested in excluding all regions with a k50 mappability below a threshold score of 0.1 because of extremely low mappability.<u> Select k50.umap.sorted.gte_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score greater than or equal to 0.1. Select k50.umap.sorted.lt_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score less than 0.1.</u></p>"
    str7 <- "<p><b>k100.umap.sorted</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 100bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region. One might be interested in excluding all regions with a k100 mappability below a threshold score of 0.1 because of extremely low mappability.<u> Select k100.umap.sorted.gte_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score greater than or equal to 0.1. Select k100.umap.sorted.lt_score.threshold-0.1_bin.size-10.csv to exclude microsatellites with an average mappability score less than 0.1.</u></p>"
    str8 <- "<p><b>segdups</b> = segmental duplications (i.e. large regions of the genome that are duplicated). <u>Select segdups.gte_score.threshold-0_bin.size-NA.csv to exclude microsatellites in these regions. Selecting segdups.lt_score.threshold-0_bin.size-NA.csv will do nothing.</u></p>"
    header2 = "<header><h3>Other Terminology</h3></header>"
    str9 <- "<p><b>gte/lt</b> = these terms in the filename of each region filter signal whether that filter includes regions that have a score that is greater than or equal to the score threshold (gte) or less than the score threshold (lt). See &#39Score Threshold&#39 below for more details.</p>"
    str10 <- "<p><b>Score Threshold</b> = this term in the filename of each region filter indicates the pre-determined threshold value for a given region filter. For some region filters, you may want to exclude all regions with a score above this threshold value; for other region filters, you may want to exclude all values below the threshold value. For files with no score column, the threshold was automatically set to 0."
    str11 <- "<p><b>Bin Size</b> = this term in the filename of each region filter signals the bin size (in bp) used to calculate the mean threshold value for each bin across the genome in a wiggle file. For bed files, bin size is not-applicable, and is indicated as &#39NA&#39.</p>"
    HTML(paste(header,str1,str2,str3,str4,str5,str6,str7,str8,"<br/>",header2,str9,str10,str11))
  })
  
  output$RE.info <- renderUI({
    str1 <- "<p>Select restriction enzyme filters to filter data to include only microsatellites that have cut site(s) for the selected restriction enzyme(s) in their 5' and 3' flanking regions (up to 150bp away from the microsatellite). Minimum and maximum flank sizes and total width of the fragment remaining after restriction enzyme digestion may be adjusted. You may also choose to exclude microsatellites with internal cut sites for selected restriction enzyme(s).</p>"
    HTML(paste(str1))
  })
  
  output$column.definitions <- renderUI({
    header = "<header><h3>Definitions of Annotations in Dataset</h3></header>"
    str1 <- "<p><b>seqnames</b> = chromosome index for microsatellite.</p>"
    str2 <- "<p><b>start</b> = start position of microsatellite.</p>"
    str3 <- "<p><b>end</b> = end position of microsatellite.</p>"
    str4 <- "<p><b>width</b> = length of microsatellite.</p>"
    str5 <- "<p><b>strand</b> = which strand microsatellite is on ('*' means strand information is unavailable).</p>"
    str6 <- "<p><b>period.size</b> = the motif (repeating unit of the microsatellite) size used in Tandem Repeats Finder to call microsatellites.</p>"
    str7 <- "<p><b>copy.num</b> = the number of copies of the motif (repeating unit of the microsatellite) that align with the consensus pattern defined by Tandem Repeats Finder.</p>"
    str8 <- "<p><b>consensus.size</b> = the size of the consensus pattern used by Tandem Repeats finder to call microsatellites. Note: this may not be the same as the period size because TRF will look for the 'best' matching sequence for the microsatellite to use as the consensus pattern, which may not be the original pattern identified using the input period size.</p>"
    str9 <- "<p><b>per.match</b> = the percentage of bps that are called as 'matches' when the actual microsatellite sequence is aligned with a string of perfect repetitions of the consensus pattern in Tandem Repeats Finder.</p>"
    str10 <- "<p><b>per.indel</b> = the percentage of bps that are called as 'indels' when the actual microsatellite sequence is aligned with a string of perfect repetitions of the consensus pattern in Tandem Repeats Finder.</p>"
    str11 <- "<p><b>score</b> = the alignment score given to a given microsatellite based on the alignment parameters put into Tandem Repeats Finder. Shorter microsatellites with high alignment scores tend to be more 'perfect' (i.e. more 'match' positions to the consensus pattern and fewer indels or mismatches), while larger microsatellites with the same 'high' alignment scores may be less 'perfect' because a large number of 'match' positions might be able to 'cancel out' a large number of indels and mismatches in the alignment score calculation.</p>"
    str12 <- "<p><b>A</b> = percentage of the microsatellite that is made up of Adenine (A).</p>"
    str13 <- "<p><b>C</b> = percentage of the microsatellite that is made up of Cytosine (C).</p>"
    str14 <- "<p><b>G</b> = percentage of the microsatellite that is made up of Guanine (G).</p>"
    str15 <- "<p><b>T</b> = percentage of the microsatellite that is made up of Thymine (T).</p>"
    str16 <- "<p><b>entropy</b> = a measure of how much information needs to be stored in order to represent the microsatellite sequence. More complex sequences (i.e. sequences with longer motifs or more indels/mismatches) tend to have higher entropy than less complex sequences.</p>"
    str17 <- "<p><b>consensus</b> = the consensus pattern called by Tandem Repeats Finder.</p>"
    str18 <- "<p><b>sequence</b> = the full sequence of the microsatellite.</p>"
    str19 <- "<p><b>motif</b> = the repeating unit of the microsatellite based on a strict limit that the motif size must be less than or equal to 6. This resulted in the reduction of consensus pattens that were 7bp long to a 6bp motif, and the exclusion of any microsatellites that were called with a consensus size of 8 or greater from the dataset.</p>"
    str20 <- "<p><b>motif.family</b> = the group to which the microsatellite belongs when all possible reverse complements and circular permutations of the microsatellite motif are reduced to a single motif. E.g. the ATG motif would belong to the ACT motif family because it is the reverse complement of a circular permutation of the ACT motif: one possible circular permutation of ACT = CAT, the reverse complement of CAT = ATG.</p>"
    str21 <- "<p><b>length.uninterrupted</b> = the maximum length of perfect repetitions of the microsatellite motif (as we define it under the 'motif' annotation) in the microsatellite.</p>"
    str22 <- "<p><b>uninterrupted.copy.num</b> = the maximum number of copies of perfect repetitions of the microsatellite motif (as we define it under the 'motif' annotation) in the microsatellite.</p>"
    str23 <- "<p><b>flank.5.prime.seq</b> = the full sequence of the 150bp 5' flanking region of the microsatellite.</p>"
    str24 <- "<p><b>flank.3.prime.seq</b> = the full sequuence of the 150bp 3' flanking region of the microsatellite.</p>"
    str25 <- "<p><b>msat.overlap</b> = the percentage of the microsatellite length that overlaps with another microsatellite in the dataset.</p>"
    str26 <- "<p><b>overlap.5.prime.90bp</b> = the percentage of the 90bp 5' flanking region of the microsatellite that overlaps with another microsatellite in the dataset.</p>"
    str27 <- "<p><b>overlap.5.prime.60bp</b> = the percentage of the 60bp 5' flanking region of the microsatellite that overlaps with another microsatellite in the dataset.</p>"
    str28 <- "<p><b>overlap.3.prime.90bp</b> = the percentage of the 90bp 3' flanking region of the microsatellite that overlaps with another microsatellite in the dataset.</p>"
    str29 <- "<p><b>overlap.3.prime.60bp</b> = the percentage of the 60bp 3' flanking region of the microsatellite that overlaps with another microsatellite in the dataset.</p>"
    str30 <- "<p><b>GC.motif</b> = % GC content of the microsatellite motif (as we define it under the 'motif' annotation).</p>"
    str31 <- "<p><b>GC.msat</b> = % GC content of the microsatellite sequence.</p>"
    str32 <- "<p><b>GC.90bp.flank.5</b> = % GC content of the 90bp 5' flanking region of the microsatellite.</p>"
    str33 <- "<p><b>GC.90bp.flank.3</b> = % GC content of the 90bp 3' flanking region of the microsatellite.</p>"
    str34 <- "<p><b>GC.60bp.flank.5</b> = % GC content of the 60bp 5' flanking region of the microsatellite.</p>"
    str35 <- "<p><b>GC.60bp.flank.3</b> = % GC content of the 60bp 3' flanking region of the microsatellite.</p>"
    str36 <- "<p><b>umap.k100.msat</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 100bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str37 <- "<p><b>umap.k100.90bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 100bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str38 <- "<p><b>umap.k100.60bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 100bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str39 <- "<p><b>umap.k100.90bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 100bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str40 <- "<p><b>umap.k100.60bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 100bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str41 <- "<p><b>umap.k50.msat</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 50bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str42 <- "<p><b>umap.k50.90bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 50bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str43 <- "<p><b>umap.k50.60bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 50bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str44 <- "<p><b>umap.k50.90bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 50bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str45 <- "<p><b>umap.k50.60bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 50bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str46 <- "<p><b>umap.k36.msat</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 36bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str47 <- "<p><b>umap.k36.90bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 36bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str48 <- "<p><b>umap.k36.60bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 36bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str49 <- "<p><b>umap.k36.90bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 36bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str50 <- "<p><b>umap.k36.60bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 36bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str51 <- "<p><b>umap.k24.msat</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 24bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str52 <- "<p><b>umap.k24.90bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 24bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str53 <- "<p><b>umap.k24.60bp.flank.5</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 5' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 24bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str54 <- "<p><b>umap.k24.90bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 90bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 24bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str55 <- "<p><b>umap.k24.60bp.flank.3</b> = the mappability (i.e. the extent to which a given region can be uniquely mapped by sequence reads of a given length) of the 60bp 3' flanking region of the microsatellite, using hg38 data from the Umap program developed by Karimzadeh et al., <em>Nucleic Acids Research</em>, 2018, with a k-mer size of 24bp. A score closer to 1 indicated a more uniquely mappable region, whereas a score closer to 0 indicates a more repetitive region.</p>"
    str56 <- "<p><b>reptiming.4DN</b> = the average replication timing across the microsatellite, based on hg38 replication timing data in the lymphoblastoid cell line GM12878 from the 4D Nucleome project (Dekker et al., <em>Nature</em>, 2017).</p>"
    str57 <- "<p><b>reptiming.NPC.ENCFF469TYS</b> = the average replication timing across the microsatellite, based on the hg38 replication timing data in the neural progenitor cell line H9 from the ENCODE project (Ryba et al., <em>Nature Protoc.</em>, 2011). The data in this annotation is specifically from one of two technical replicates: ENCFF469TYS.</p>"
    str58 <- "<p><b>reptiming.NPC.ENCFF923TOC</b> = the average replication timing across the microsatellite, based on the hg38 replication timing data in the neural progenitor cell line H9 from the ENCODE project (Ryba et al., <em>Nature Protoc.</em>, 2011). The data in this annotation is specifically from one of two technical replicates: ENCFF923TOC.</p>"
    str59 <- "<p><b>msat.bioskryb</b> = bioskryb coverage data of the microsatellite, normalized to 1 across the genome. More information will be added when new coverage data is added.</p>"
    str60 <- "<p><b>flank.5.90bp.bioskryb</b> = bioskryb coverage data of the 90bp 5' flanking region of the microsatellite, normalized to 1 across the genome. Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra.</p>"
    str61 <- "<p><b>flank.3.90bp.bioskryb</b> = bioskryb coverage data of the 90bp 3' flanking region of the microsatellite, normalized to 1 across the genome. Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra.</p>"
    str62 <- "<p><b>flank.5.60bp.bioskryb</b> = bioskryb coverage data of the 60bp 5' flanking region of the microsatellite, normalized to 1 across the genome. Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra.</p>"
    str63 <- "<p><b>flank.3.60bp.bioskryb</b> = bioskryb coverage data of the 60bp 3' flanking region of the microsatellite, normalized to 1 across the genome. Data source: 40x whole-genome sequencing of PTA UMB5444 cell C1 from 11-11-2020 prepared by Adam, using the large fragment protocol, and sequenced with Truseq PCR-free on Novaseq. Raw reads were aligned using the lab’s standard WGS alignment workflow on Terra.</p>"
    str64 <- "<p><b>motif.size</b> = the length of the motif (as we define it under the 'motif' annotation; i.e. after reduction of 7bp consensus sequences to 6bp motifs).</p>"
    str65 <- "<p><b>ml_mu</b> = estimated mutation rate of the microsatellite based on the application of the mutea-autosomal mutation model, developed by Gymrek et. al. (<em>Nat Genet.</em>,2017), to our microsatellite data. Microsatellites selected for model training were chosen from a subset of the Gymrek lab's hg19 training intergenic dataset that had a period size between 2 and 6bp, a total length between 20 and 80bp, and a motif that occurred at least 100 times in the genome. Microsatellites with estimated mutation rates that had particularly high adjusted standard error values were also excluded from the final training dataset. The model that best predicted the training data's estimated mutation rate was a linear model that took uninterrupted length and period size of the microsatellite as the explanatory variables. Rates are displayed as log10 mutations per generation.</p>"
    str66 <- "<p><b>nearest.usat.5</b> = the distance in bps to the next nearest microsatellite in the 5' flank.</p>"
    str67 <- "<p><b>nearest.usat.3</b> = the distance in bps to the next nearest microsatellite in the 3' flank.</p>"
    strings_list <- paste0(rep("str",67),1:67)
    HTML(c(header,unlist(lapply(strings_list, function(x) eval(parse(text = x))))))
  })
  
  output$downloadWarning <- renderUI({
    max.download <- as.numeric(nrow(selected_data$df))
    userSubset <- as.numeric(input$subsetSize)
    if(userSubset > max.download){
      str1 <- "<p style='color:red'>The maximum subset size for this data set is: "
      str2 <- "</p>"
    }else{
      str1 <- "<p black='color:red'>The maximum subset size for this data set is: "
      str2 <- "</p>"
    }
    HTML(paste(str1,max.download,str2))
  })
  
  output$linkHeader = renderUI({HTML({paste0("<p><b>Require microsatellites to pass &#39Distance to Next Nearest Microsatellite&#39, &#39Region Filters&#39, and all applied flank mappability filters on the same flank?</b></p>")})})
  #--end of text outputs--#
  
  #this is where we put dataset outputs
  
  #initialize a reactiveValues object that contains a dataframe
  selected_data <- reactiveValues(
    df = data.frame(matrix(nrow = 0, ncol = 0))
  )
  
  #when the "Apply Changes" button is clicked, update the dataset displayed on the "View Data" page (the dataset available for download) as follows
  observeEvent(input$goButton,{
    #determine which dataset the user would like to see by checking the "dataView" input
    dataset <- as.character(input$dataView)
    #if the dataset the user would like to see is the entire set of microsatellite, then display the unfiltered set of microsatellites loaded into the global environment of the app at start up
    if(dataset == "All Microsatellites"){
      data <- usats
      #if the dataset the user would like to see is the set of microsatellites that pass the applied filters, then displayed the filtered_usats object
    }else if(dataset == "Microsatellites that pass filter(s)"){
      data <- filtered_usats()
    }else{
      #if the dataset the user would like to see is the set of microsatellites excluded by the applied filters, then proceed as follows:
      #make a granges object from the filtered_usats dataframe
      query <- makeGRangesFromDataFrame(filtered_usats())
      #use the usats.granges object that was generated in the global environment at the app start up as the subject
      subject <- usats.granges
      #use the filtered_usats granges object to probe the usats.granges object for overlaps
      hits <- findOverlaps(query, subject)
      #pintersect the regions that have findOverlaps hits to get a list of coordinates for the overlapping regions
      overlaps <- pintersect(query[queryHits(hits)],subject[subjectHits(hits)])
      #calculate the percent overlap by dividing the width of each overlapping region by the width of the corresponding region in the filtered_usats granges object
      percentOverlap <- width(overlaps)/width(query[queryHits(hits)])
      #retrieve the hits that have a percentOverlap of 1 (100% overlap)
      hits <- hits[percentOverlap == 1]
      #generate a list of indices for the full microsatellite dataset by listing the numbers from 1 to the number of rows in the dataset
      idx <- 1:nrow(usats)
      #remove the indices corresponding to the 100% overlap hits from the idx set
      idx <- idx[-c(subjectHits(hits))]
      #return a dataframe of microsatellites that includes only the microsatellites that have less than 100% overlap with any microsatellite in the filtered_usats dataset (i.e. the microsatellites that are excluded by the filters)
      data <- usats[idx,]
    }
    #make sure the dataset is formatted as a dataframe
    data <- as.data.frame(data)
    #subset the selected columns to display based on the user input to the annotations.select input
    df <- data[,as.numeric(input$annotation.select)]
    #save this dataframe to the selected_data reactiveValues object
    selected_data$df <- df
    #update the subsetSize available for download based on the newly selected dataset size
    updateNumericInput(session,"subsetSize", value = nrow(selected_data$df))
  }, ignoreNULL = FALSE)
  
  output$viewData <- renderDT(DT::datatable(selected_data$df, filter = "bottom"))
  #--end of dataset outputs--#
  
  #this is where we put any plots 
  #generate any plots displayed in the mainPanel of the ui using ggplot2 and the shiny renderCachedPlot function with the dataset of filtered microsatellites as the cache key (for faster retreival when generating a plot that has already been made before)
  
  output$motif.by.length.hist <- renderCachedPlot({
    motif.bins <- rownames(sort(table(filtered_usats()$motif.family), decreasing = T))[1:10]
    width.breaks = c(0,10,20,30,40,50,60,70,80,90,100,120)
    df <- filtered_usats()[which(filtered_usats()$motif.family %in% motif.bins),
                           c("motif.family","width")]
    other <- filtered_usats()[which(!(filtered_usats()$motif.family %in% motif.bins)),
                              c("motif.family","width")]
    other$motif.family <- "other"
    plot.df <- rbind(df,other)
    plot.df$width.bins <- cut(plot.df$width, width.breaks, include.lowest = T, right = F)
    plot.df$width.bins <- as.character(plot.df$width.bins)
    plot.df$width.bins[is.na(plot.df$width.bins)] <- "[120,)"
    
    plot.df$width.bins <- factor(plot.df$width.bins, 
                                 levels = unique(plot.df$width.bins[order(plot.df$width, decreasing = F)]))
    ggplot(data = plot.df, aes(x = width.bins, fill = motif.family)) +
      geom_bar(position = "dodge") +
      scale_fill_discrete(name = "Motif Family") +
      xlab("Microsatellite Length") +
      ylab("Microsatellite Count") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$motif.size.hist <- renderCachedPlot({
    ggplot(filtered_usats(),aes(x = as.factor(motif.size))) +
      geom_bar(fill = "dodgerblue3") +
      xlab("Period Size") +
      ylab("Total Microsatellites") +
      theme_bw()  +
      scale_x_discrete(breaks = c(1,2,3,4,5,6),labels = c("1","2","3","4","5","6"))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$width.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("width")]
    width.breaks = c(0,10,20,30,40,50,60,70,80,90,100,120)
    plot.df$width.bins <- cut(plot.df$width, width.breaks, include.lowest = T, right = F)
    plot.df$width.bins <- as.character(plot.df$width.bins)
    plot.df$width.bins[is.na(plot.df$width.bins)] <- "[120,)"
    
    plot.df$width.bins <- factor(plot.df$width.bins, 
                                 levels = unique(plot.df$width.bins[order(plot.df$width, decreasing = F)]))
    
    ggplot(data = plot.df, aes(x = width.bins)) +
      geom_bar(fill = "lightsteelblue4") +
      xlab("Microsatellite Length") +
      ylab("Number of Microsatellites") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$per.match.by.length <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("width","per.match")]
    width.breaks = c(0,10,20,30,40,50,60,70,80,90,100,120)
    plot.df$width.bins <- cut(plot.df$width, width.breaks, include.lowest = T, right = F)
    plot.df$width.bins <- as.character(plot.df$width.bins)
    plot.df$width.bins[is.na(plot.df$width.bins)] <- "[120,)"
    
    match.breaks = c(100,90,80,70,0)
    plot.df$match.bins <- cut(plot.df$per.match, match.breaks, include.lowest = T, right = T)
    
    plot.df$width.bins <- factor(plot.df$width.bins, 
                                 levels = unique(plot.df$width.bins[order(plot.df$width, decreasing = F)]))
    
    plot.df$match.bins <- factor(plot.df$match.bins,
                                 levels = unique(plot.df$match.bins[order(plot.df$per.match, decreasing = T)]))
    
    ggplot(data = plot.df, aes(x = width.bins, fill = match.bins)) +
      geom_bar(position = "fill") +
      scale_fill_discrete(name = "Percent Match") +
      xlab("Microsatellite Length") +
      ylab("Percent Total") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$per.indel.by.length <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("width","per.indel")]
    width.breaks = c(0,10,20,30,40,50,60,70,80,90,100,120)
    plot.df$width.bins <- cut(plot.df$width, width.breaks, include.lowest = T, right = F)
    plot.df$width.bins <- as.character(plot.df$width.bins)
    plot.df$width.bins[is.na(plot.df$width.bins)] <- "[120,)"
    
    indel.breaks = c(0,10,20,30,100)
    plot.df$indel.bins <- cut(plot.df$per.indel, indel.breaks, include.lowest = T, right = T)
    
    plot.df$width.bins <- factor(plot.df$width.bins, 
                                 levels = unique(plot.df$width.bins[order(plot.df$width, decreasing = F)]))
    
    plot.df$indel.bins <- factor(plot.df$indel.bins,
                                 levels = unique(plot.df$indel.bins[order(plot.df$per.indel, decreasing = T)]))
    
    ggplot(data = plot.df, aes(x = width.bins, fill = indel.bins)) +
      geom_bar(position = "fill") +
      scale_fill_discrete(name = "Percent Indel") +
      xlab("Microsatellite Length") +
      ylab("Percent Total") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$bioskryb.usat.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("msat.bioskryb")]
    coverage.breaks = seq(0,5,by = 0.1)
    plot.df$coverage.bins <- cut(plot.df$msat.bioskryb, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[5,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                 levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "darkorchid4") +
      xlab("Bioskryb Coverage of Microsatellite") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$mu.usat.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("ml_mu")]
    coverage.breaks = seq(-5.5,0,by = 0.1)
    plot.df$coverage.bins <- cut(plot.df$ml_mu, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    bins.record <- unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = T)])
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[0,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[0,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "turquoise4") +
      xlab("Microsatellite Mutation Rate") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  plotCount60 <- reactiveValues()
  
  plot_height.60 <- function() {
    # calculate values$facetCount
    plotCount60$facetCount <- as.numeric(length(input$umap60)) * 400
    return(plotCount60$facetCount)
  }
  
  output$umap.60bp.hist <- renderPlot({
    shiny::validate(need(input$umap60 != '','Please select one or more mappability tracks to view.'))
    umap.select <- input$umap60
    column.select.3 <- unlist(lapply(umap.select, function(x) paste0("umap.",x,".60bp.flank.3")))
    flank.3 <- filtered_usats() %>% select(all_of(column.select.3))
    colnames(flank.3) <- umap.select
    flank.3 <- melt(flank.3, measure.vars = umap.select)
    flank.3$id <- "3' Flank"
    column.select.5 <- unlist(lapply(umap.select, function(x) paste0("umap.",x,".60bp.flank.5")))
    flank.5 <- filtered_usats() %>% select(all_of(column.select.5))
    colnames(flank.5) <- umap.select
    flank.5 <- melt(flank.5, measure.vars = umap.select)
    flank.5$id <- "5' Flank"
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$value, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Mappability in the 60bp Flank Regions") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
      facet_wrap(. ~ variable, ncol = 1)
  })
  
  output$umap.60bp.hist.ui <- renderUI({
    plotOutput("umap.60bp.hist", height = plot_height.60(), width = "100%")
  })

  output$overlap.60bp.hist <- renderCachedPlot({
    flank.3 <- filtered_usats()[,c("overlap.3.prime.60bp")]
    flank.3$id <- "3' Flank"
    colnames(flank.3) <- c("data","id")
    flank.5 <- filtered_usats()[,c("overlap.5.prime.60bp")]
    flank.5$id <- "5' Flank"
    colnames(flank.5) <- c("data","id")
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$data, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Fraction of 60bp Microsatellite Flank Region that Overlaps with Another Microsatellite") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$bioskryb.60bp.hist <- renderCachedPlot({
    flank.3 <- filtered_usats()[,c("flank.3.60bp.bioskryb")]
    flank.3$id <- "3' Flank"
    colnames(flank.3) <- c("data","id")
    flank.5 <- filtered_usats()[,c("flank.5.60bp.bioskryb")]
    flank.5$id <- "5' Flank"
    colnames(flank.5) <- c("data","id")
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,5,by = 0.1)
    plot.df$coverage.bins <- cut(plot.df$data, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[5,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Bioskryb Coverage of Microsatellite in the 60bp Flank Regions") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$GC.60bp.hist <- renderCachedPlot({
    flank.3 <- filtered_usats()[,c("GC.60bp.flank.3")]
    flank.3$id <- "3' Flank"
    colnames(flank.3) <- c("data","id")
    flank.5 <- filtered_usats()[,c("GC.60bp.flank.5")]
    flank.5$id <- "5' Flank"
    colnames(flank.5) <- c("data","id")
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$data, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("GC Content in the 60bp Flank Regions") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  plotCount90 <- reactiveValues()
  
  plot_height.90 <- function() {
    # calculate values$facetCount
    plotCount90$facetCount <- as.numeric(length(input$umap90)) * 400
    return(plotCount90$facetCount)
  }
  
  output$umap.90bp.hist <- renderPlot({
    shiny::validate(need(input$umap90 != '','Please select one or more mappability tracks to view.'))
    umap.select <- input$umap90
    column.select.3 <- unlist(lapply(umap.select, function(x) paste0("umap.",x,".90bp.flank.3")))
    flank.3 <- filtered_usats() %>% select(all_of(column.select.3))
    colnames(flank.3) <- umap.select
    flank.3 <- melt(flank.3, measure.vars = umap.select)
    flank.3$id <- "3' Flank"
    column.select.5 <- unlist(lapply(umap.select, function(x) paste0("umap.",x,".90bp.flank.5")))
    flank.5 <- filtered_usats() %>% select(all_of(column.select.5))
    colnames(flank.5) <- umap.select
    flank.5 <- melt(flank.5, measure.vars = umap.select)
    flank.5$id <- "5' Flank"
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$value, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Mappability in the 90bp Flank Regions") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
      facet_wrap(. ~ variable, ncol = 1)
  })
  
  output$umap.90bp.hist.ui <- renderUI({
    plotOutput("umap.90bp.hist", height = plot_height.90(), width = "100%")
  })
  
  output$overlap.90bp.hist <- renderCachedPlot({
    flank.3 <- filtered_usats()[,c("overlap.3.prime.90bp")]
    flank.3$id <- "3' Flank"
    colnames(flank.3) <- c("data","id")
    flank.5 <- filtered_usats()[,c("overlap.5.prime.90bp")]
    flank.5$id <- "5' Flank"
    colnames(flank.5) <- c("data","id")
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$data, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Fraction of 90bp Microsatellite Flank Region that Overlaps with Another Microsatellite") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$bioskryb.90bp.hist <- renderCachedPlot({
    flank.3 <- filtered_usats()[,c("flank.3.90bp.bioskryb")]
    flank.3$id <- "3' Flank"
    colnames(flank.3) <- c("data","id")
    flank.5 <- filtered_usats()[,c("flank.5.90bp.bioskryb")]
    flank.5$id <- "5' Flank"
    colnames(flank.5) <- c("data","id")
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,5,by = 0.1)
    plot.df$coverage.bins <- cut(plot.df$data, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[5,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Bioskryb Coverage of Microsatellite in the 90bp Flank Regions") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$GC.90bp.hist <- renderCachedPlot({
    flank.3 <- filtered_usats()[,c("GC.90bp.flank.3")]
    flank.3$id <- "3' Flank"
    colnames(flank.3) <- c("data","id")
    flank.5 <- filtered_usats()[,c("GC.90bp.flank.5")]
    flank.5$id <- "5' Flank"
    colnames(flank.5) <- c("data","id")
    plot.df <- rbind(flank.3,flank.5)
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$data, coverage.breaks, include.lowest = T, right = F)
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = unique(plot.df$coverage.bins[order(plot.df$coverage.bins, decreasing = F)]))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("GC Content in the 90bp Flank Regions") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$copy.num.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("copy.num")]
    coverage.breaks = seq(0,100,by = 5)
    plot.df$coverage.bins <- cut(plot.df$copy.num, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    bins.record <- unique(as.character(plot.df$coverage.bins)[order(plot.df$coverage.bins, decreasing = F)])
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[100,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[100,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "orchid3", color = "orchid4") +
      xlab("Copy Number") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$per.match.hist <- renderCachedPlot({
    ggplot(filtered_usats(),aes(x = per.match)) +
      geom_histogram(aes(y=..count../sum(..count..)), fill = "aquamarine3", color = "aquamarine4") +
      xlab("Percent Match") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$per.indel.hist <- renderCachedPlot({
    ggplot(filtered_usats(),aes(x = per.indel)) +
      geom_histogram(aes(y=..count../sum(..count..)), fill = "plum3", color = "plum4") +
      xlab("Percent Indel") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$score.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("score")]
    coverage.breaks = seq(0,100,by = 5)
    plot.df$coverage.bins <- cut(plot.df$score, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    bins.record <- unique(as.character(plot.df$coverage.bins)[order(plot.df$coverage.bins, decreasing = F)])
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[100,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[100,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "darkolivegreen3", color = "darkolivegreen4") +
      xlab("score") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$entropy.hist <- renderCachedPlot({
    ggplot(filtered_usats(),aes(x = entropy)) +
      geom_histogram(aes(y=..count../sum(..count..)), fill = "hotpink3", color = "hotpink4") +
      xlab("Entropy") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw()
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$length.uninterrupted.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("length.uninterrupted")]
    coverage.breaks = seq(0,120,by = 5)
    plot.df$coverage.bins <- cut(plot.df$length.uninterrupted, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    bins.record <- unique(as.character(plot.df$coverage.bins)[order(plot.df$coverage.bins, decreasing = F)])
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[120,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[120,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "coral3", color = "coral4") +
      xlab("Uninterrupted Length") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$uninterrupted.copy.num.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("uninterrupted.copy.num")]
    coverage.breaks = seq(0,60,by = 5)
    plot.df$coverage.bins <- cut(plot.df$uninterrupted.copy.num, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    bins.record <- unique(as.character(plot.df$coverage.bins)[order(plot.df$coverage.bins, decreasing = F)])
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[60,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[60,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "cadetblue3", color = "cadetblue4") +
      xlab("Uninterrupted Copy Number") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$GC.msat.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("GC.msat")]
    coverage.breaks = seq(0,1,by = 0.05)
    plot.df$coverage.bins <- cut(plot.df$GC.msat, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    bins.record <- unique(as.character(plot.df$coverage.bins)[order(plot.df$coverage.bins, decreasing = F)])
    plot.df$coverage.bins <- as.character(plot.df$coverage.bins)
    plot.df$coverage.bins[is.na(plot.df$coverage.bins)] <- "[1,)"
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[1,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins)) +
      geom_bar(aes(y=..count../sum(..count..)), fill = "lightgoldenrod2", color = "lightgoldenrod3") +
      xlab("GC Content of the Microsatellite Sequence") +
      ylab("Proportion of Total Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  output$nearest.usat.hist <- renderCachedPlot({
    plot.df <- filtered_usats()[,c("nearest.usat.5","nearest.usat.3")]
    downstream <- data.frame(nearest.usat = plot.df$nearest.usat.3[which(!(is.na(plot.df$nearest.usat.3)))])
    coverage.breaks = seq(0,250,by = 10)
    downstream$coverage.bins <- cut(downstream$nearest.usat, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    bins.record <- unique(as.character(downstream$coverage.bins)[order(downstream$coverage.bins, decreasing = F)])
    downstream$coverage.bins <- as.character(downstream$coverage.bins)
    downstream$coverage.bins[is.na(downstream$coverage.bins)] <- "[250,)"
    downstream$id <- "3' Flank"
    
    upstream <- data.frame(nearest.usat = plot.df$nearest.usat.5[which(!(is.na(plot.df$nearest.usat.5)))])
    coverage.breaks = seq(0,250,by = 10)
    upstream$coverage.bins <- cut(upstream$nearest.usat, coverage.breaks, include.lowest = T, right = F, ordered_result = T)
    upstream$coverage.bins <- as.character(upstream$coverage.bins)
    upstream$coverage.bins[is.na(upstream$coverage.bins)] <- "[250,)"
    upstream$id <- "5' Flank"
    
    plot.df <-rbind(downstream,upstream)
    
    plot.df$coverage.bins <- factor(plot.df$coverage.bins, 
                                    levels = c(bins.record,"[250,)"))
    
    ggplot(data = plot.df,aes(x = coverage.bins, fill = id)) +
      geom_bar(position = position_dodge()) +
      xlab("Distance to Next Nearest Microsatellite") +
      ylab("Number of Microsatellites") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  },
  cacheKeyExpr = {filtered_usats()}
  )
  
  #this observeEvent allows the user to generate their own plots by changing values on the "View Data" sidebarPanel tab and clicking either "Update Plot" or "Apply Changes"
  observeEvent(input$changePlot,{
    #this allows the user to select which dataset to plot, just like the code for the dataView input allows the user to select which dataset to view/download. See that section for more details on how this code works.
    dataset <- as.character(input$dataPlot)
    if(dataset == "All Microsatellites"){
      data <- usats
    }else if(dataset == "Microsatellites that pass filter(s)"){
      data <- filtered_usats()
    }else{
      query <- makeGRangesFromDataFrame(filtered_usats())
      subject <- usats.granges
      hits <- findOverlaps(query, subject)
      overlaps <- pintersect(query[queryHits(hits)],subject[subjectHits(hits)])
      percentOverlap <- width(overlaps)/width(query[queryHits(hits)])
      hits <- hits[percentOverlap == 1]
      idx <- 1:nrow(usats)
      idx <- idx[-c(subjectHits(hits))]
      data <- usats[idx,]
    }
    data <- as.data.frame(data)
    
    #this section of code goes through the 4 possible combinations of selections the user can make for whether or not to scale the x and/or y axes, and generates a histogram of the x axis value (given the fact that the y axis value is set to "count")
    if(input$yAxis == "count"){
      #get x-axis value
      x.axis = input$xAxis
      #make a basic plot using the x-axis data
      gg <- ggplot(data,aes_string(x = x.axis))
      #if the x-axis is log-scaled and the y-axis is not, generate a histogram with a log10 transformation on the x-axis only
      if(input$xLog == TRUE & input$yLog == FALSE){
        gg <- gg + geom_histogram(fill = "lightsteelblue3",  boundary = 0) +
          xlab(paste("log10",input$xAxis)) + 
          ylab("Number of Microsatellites") +
          ylim(input$min.Yaxis,input$max.Yaxis) +
          theme_bw() + 
          scale_x_continuous(trans = 'log10', limits = c(input$min.Xaxis,input$max.Xaxis))
        #if the y-axis is log-scaled and the x-axis is not, generate a histogram with a log10 transformation on the y-axis only
      }else if(input$xLog == FALSE & input$yLog == TRUE){
        gg <- gg + geom_histogram(fill = "lightsteelblue3",  boundary = 0) +
          xlab(paste(input$xAxis)) + 
          ylab("log10 Number of Microsatellites") +
          xlim(input$min.Xaxis,input$max.Xaxis) +
          theme_bw() +
          scale_y_continuous(trans = 'log10', limits = c(input$min.Yaxis,input$max.Yaxis))
        #if both the x-axis and the y-axis are log-scaled, generate a histogram with a log10 transformations on both the x-axis and y-axis
      }else if(input$xLog == TRUE & input$yLog == TRUE){
        gg <- gg + geom_histogram(fill = "lightsteelblue3", boundary = 0) +
          xlab(paste("log10",input$xAxis)) + 
          ylab("log10 Number of Microsatellites") +
          theme_bw() +
          scale_x_continuous(trans = 'log10', limits = c(input$min.Xaxis,input$max.Xaxis)) +
          scale_y_continuous(trans = 'log10', limits = c(input$min.Yaxis,input$max.Yaxis))
        #if neither the x-axis nor the y-axis are log-scaled, generate a histogram WITHOUT a log10 transformation on either axis
      }else{
        gg <- gg + geom_histogram(fill = "lightsteelblue3", boundary = 0) +
          xlab(paste(input$xAxis)) + 
          ylab("Number of Microsatellites") +
          xlim(input$min.Xaxis,input$max.Xaxis) +
          ylim(input$min.Yaxis,input$max.Yaxis) +
          theme_bw()
      }
      #repeat the code above, except this time, since the y-axis is set to "proportion", the geometry of the histogram will always have the aes(y=..count../sum(..count..) aesthetic so that the total density of the histogram will always add up to 1, rather than just displaying a simple count
    }else if(input$yAxis == "proportion"){
      x.axis = input$xAxis
      gg <- ggplot(data,aes_string(x = x.axis))
      if(input$xLog == TRUE & input$yLog == FALSE){
        gg <- gg + geom_histogram(aes(y=..count../sum(..count..)),fill = "lightsteelblue3", boundary = 0) +
          xlab(paste("log10",input$xAxis)) + 
          ylab("Proportion of Microsatellites") +
          ylim(input$min.Yaxis,input$max.Yaxis) +
          theme_bw() + 
          scale_x_continuous(trans = 'log10', limits = c(input$min.Xaxis,input$max.Xaxis))
      }else if(input$xLog == FALSE & input$yLog == TRUE){
        gg <- gg + geom_histogram(aes(y=..count../sum(..count..)),fill = "lightsteelblue3", boundary = 0) +
          xlab(paste(input$xAxis)) + 
          ylab("log10 Proportion of Microsatellites") +
          xlim(input$min.Xaxis,input$max.Xaxis) +
          theme_bw() +
          scale_y_continuous(trans = 'log10', limits = c(input$min.Yaxis,input$max.Yaxis))
      }else if(input$xLog == TRUE & input$yLog == TRUE){
        gg <- gg + geom_histogram(aes(y=..count../sum(..count..)),fill = "lightsteelblue3", boundary = 0) +
          xlab(paste("log10",input$xAxis)) + 
          ylab("log10 Proportion of Microsatellites") +
          theme_bw() +
          scale_x_continuous(trans = 'log10', limits = c(input$min.Xaxis,input$max.Xaxis)) +
          scale_y_continuous(trans = 'log10', limits = c(input$min.Yaxis,input$max.Yaxis))
      }else{
        gg <- gg + geom_histogram(aes(y=..count../sum(..count..)),fill = "lightsteelblue3", boundary = 0) +
          xlab(paste(input$xAxis)) + 
          ylab("Proportion of Microsatellites") +
          xlim(input$min.Xaxis,input$max.Xaxis) +
          ylim(input$min.Yaxis,input$max.Yaxis) +
          theme_bw()
      }
      #if the user selects something that isn't "count" or "proportion" for the y-axis, then the following code will run through the options for generating a scatter plot, with the proper log10 transformations based on the user's selections.
    }else{
      x.axis = input$xAxis
      y.axis = input$yAxis
      gg <- ggplot(data,aes_string(x = x.axis, y = y.axis))
      
      if(input$xLog == TRUE & input$yLog == FALSE){
        gg <- gg + geom_point(color = "lightsteelblue3") +
          xlab(paste("log10",input$xAxis)) +
          ylab(paste(input$yAxis)) +
          ylim(input$min.Yaxis,input$max.Yaxis) +
          theme_bw() + 
          scale_x_continuous(trans = 'log10', limits = c(input$min.Xaxis,input$max.Xaxis))
      } else if(input$yLog == TRUE & input$xLog == FALSE){
        gg <- gg + geom_point(color = "lightsteelblue3") +
          xlab(paste(input$xAxis)) +
          ylab(paste("log10",input$yAxis)) +
          xlim(input$min.Xaxis,input$max.Xaxis) +
          theme_bw() + 
          scale_y_continuous(trans = 'log10', limits = c(input$min.Yaxis,input$max.Yaxis))
      } else if(input$yLog == TRUE & input$xLog == TRUE){
        gg <- gg + geom_point(color = "lightsteelblue3") +
          xlab(paste("log10",input$xAxis)) +
          ylab(paste("log10",input$yAxis)) +
          theme_bw() + 
          scale_x_continuous(trans = 'log10', limits = c(input$min.Xaxis,input$max.Xaxis)) +
          scale_y_continuous(trans = 'log10', limits = c(input$min.Yaxis,input$max.Yaxis))
      } else{
        gg <- gg + geom_point(color = "lightsteelblue3") +
          xlab(paste(input$xAxis)) +
          ylab(paste(input$yAxis)) +
          xlim(input$min.Xaxis,input$max.Xaxis) +
          ylim(input$min.Yaxis,input$max.Yaxis) +
          theme_bw()
      }
    }
    #whatever plot the user chooses to make will always be stored in the "gg" object, so the final step in this observe event is to render the plot
    output$userPlot <- renderPlot({gg})
  }, ignoreNULL = FALSE)
  
  #this allows the user to update the userPlot either by clicking the "Update Plot" button or by clicking the "Apply Changes" button
  observeEvent(input$goButton,{
    click("changePlot")
  })
  
  #this observeEvent causes changes in either the value selected to plot on the x-axis or the x-axis log scale status to automatically update the default min and max bounds values for that axis
  observeEvent({
    input$xAxis
    input$xLog},{
      #first we observe which dataset we are working with. For details on how this code works, see the section on the "dataView" input.
    dataset <- as.character(input$dataPlot)
    if(dataset == "All Microsatellites"){
      data <- usats
    }else if(dataset == "Microsatellites that pass filter(s)"){
      data <- filtered_usats()
    }else{
      query <- makeGRangesFromDataFrame(filtered_usats())
      subject <- usats.granges
      hits <- findOverlaps(query, subject)
      overlaps <- pintersect(query[queryHits(hits)],subject[subjectHits(hits)])
      percentOverlap <- width(overlaps)/width(query[queryHits(hits)])
      hits <- hits[percentOverlap == 1]
      idx <- 1:nrow(usats)
      idx <- idx[-c(subjectHits(hits))]
      data <- usats[idx,]
    }
    data <- as.data.frame(data)
    #get the name of the value plotted on the x axis
    xAxis = as.character(input$xAxis)
    #get the column in the dataset that matches the name of the x-axis value
    selected = data[,which(colnames(data) == xAxis)]
    #get the minimum value for that variable and round it to the next nearest whole number
    min.val = round(min(selected, na.rm = TRUE))
    #get the maximum value for that variable and round it to the next nearest whole number
    max.val = round(max(selected, na.rm = TRUE))
    #if the x-axis is set to log scale and either the minimum or maximum value is set to 0, change that value to 1e-10 (because you cannot take the log of 0).
    if(input$xLog == TRUE & min.val == 0){
      min.val = 1e-10
    }else if(input$xLog == TRUE & max.val == 0){
      max.val = 1e-10
    }
    #update the minimum and maximum x-axis values with the new minimum and maximum values
    updateNumericInput(session,"min.Xaxis",value = min.val)
    updateNumericInput(session,"max.Xaxis",value = max.val)
  }, ignoreInit = TRUE)
  
  #this observeEvent causes changes in either the value selected to plot on the y-axis or the y-axis log scale status to automatically update the default min and max bounds values for that axis
  observeEvent({
    input$yAxis
    input$yLog},{
      #first we observe which dataset we are working with. For details on how this code works, see the section on the "dataView" input.
    dataset <- as.character(input$dataPlot)
    if(dataset == "All Microsatellites"){
      data <- usats
    }else if(dataset == "Microsatellites that pass filter(s)"){
      data <- filtered_usats()
    }else{
      query <- makeGRangesFromDataFrame(filtered_usats())
      subject <- usats.granges
      hits <- findOverlaps(query, subject)
      overlaps <- pintersect(query[queryHits(hits)],subject[subjectHits(hits)])
      percentOverlap <- width(overlaps)/width(query[queryHits(hits)])
      hits <- hits[percentOverlap == 1]
      idx <- 1:nrow(usats)
      idx <- idx[-c(subjectHits(hits))]
      data <- usats[idx,]
    }
    data <- as.data.frame(data)
    #get the name of the variable plotted on the x axis
    xAxis = as.character(input$xAxis)
    #get the name of the variable plotted on the y axis
    yAxis = as.character(input$yAxis)
    #if the y-axis is set to "count" then we are going to have to make a ggplot object and use the ggplot_build function to get the max count
    if(yAxis == "count"){
      #build simple histogram using the x-axis information
      gg <- ggplot(data,aes_string(x = xAxis)) +
        geom_histogram() +
        xlim(input$min.Xaxis,input$max.Xaxis)
      #hard-code the minimum value as 0 because 0 is the minimum possible observations of any value in a given histogram
      min.val = 0
      #use the ggplot_build function to get the count of each bin in the histogram, then get the max value of those bins and round it to the next nearest integer
      max.val = round(max(ggplot_build(gg)$data[[1]]$count))
      #if the y-axis is set to log scale and either the minimum or maximum value is set to 0, change that value to 1e-10 (because you cannot take the log of 0).
      if(input$yLog == TRUE & min.val == 0){
        min.val = 1e-10
      }else if(input$yLog == TRUE & max.val == 0){
        max.val = 1e-10
      }
      #if the y-axis is set to "proportion", you will need to perform the same operations as above, except the calculation of the maximum value will be slightly different
    }else if(yAxis == "proportion"){
      gg <- ggplot(data,aes_string(x = xAxis)) +
        geom_histogram() +
        xlim(input$min.Xaxis,input$max.Xaxis)
      min.val = 0
      #calculate the maximum value by using the ggplot_build function to get the count of each bin in the histogram, then get the max value of those bins and divide that by the sum of all of the observations in all of the bins. Round the final fraction to the nearest 2 decimal places
      max.val = round(max(ggplot_build(gg)$data[[1]]$count)/sum(ggplot_build(gg)$data[[1]]$count),2)
      #if the y-axis is set to log scale and either the minimum or maximum value is set to 0, change that value to 1e-10 (because you cannot take the log of 0).
      if(input$yLog == TRUE & min.val == 0){
        min.val = 1e-10
      }else if(input$yLog == TRUE & max.val == 0){
        max.val = 1e-10
      }
      #if the y-axis is set to anything other than "count" or "proportion", then simply calculate the minimum and maximum values as follows:
    }else{
      #get the column in the dataset that matches the y-axis variable in the yAxis input
      selected = data[,which(colnames(data) == yAxis)]
      #find the minimum value of that column
      min.val = round(min(selected, na.rm = TRUE))
      #find the maximum value of that column
      max.val = round(max(selected, na.rm = TRUE))
      #if the y-axis is set to log scale and either the minimum or maximum value is set to 0, change that value to 1e-10 (because you cannot take the log of 0).
      if(input$yLog == TRUE & min.val == 0){
        min.val = 1e-10
      }else if(input$yLog == TRUE & max.val == 0){
        max.val = 1e-10
      }
    }
    #update the minimum and maximum y-axis values with the new minimum and maximum values
    updateNumericInput(session,"min.Yaxis",value = min.val)
    updateNumericInput(session,"max.Yaxis",value = max.val)
  }, ignoreInit = TRUE)
  
  #--end of plots--#  
  
  #this is where we put download button functionality
  
  #use the downloadHandler function to create a download button that exports a csv file containing the "selected_data$df" dataframe when the "Download All" button is clicked
  output$downloadData <- downloadHandler(
    #give the file a name that references the time and date of download followed by a common descriptive extension: _filtered.microsatellites.csv
    filename = function(){
      paste(format(Sys.time(), "%Y-%m-%d_%H-%M"),'_filtered.microsatellites.csv', sep='')
    },
    content = function(file) {
      #get data to download
      download.file <- selected_data$df
      #sort the data by chromosome, then by start position, and then by end position
      download.file <- download.file[order(as.numeric(substr(download.file$seqnames,4,nchar(download.file$seqnames))),download.file$start,download.file$end),]
      #write csv to format file download
      write.csv(download.file, file, row.names = F, quote = FALSE)
    }
  )
  
  #use the downloadHandler as above except this time, we will be downloading a subset of the data
  output$downloadSubset <- downloadHandler(
    filename = function(){
      paste(format(Sys.time(), "%Y-%m-%d_%H-%M"),'_filtered.microsatellites.random.subset.csv', sep='')
    },
    content = function(file) {
      #when loading the selected_data$df, use the "sample" function to get a random subset of rows from the dataset. The size of this random subset will be the number the user puts into the subsetSize input.
      sample.file <- selected_data$df[sample(1:nrow(selected_data$df),input$subsetSize),]
      sample.file <- sample.file[order(as.numeric(substr(sample.file$seqnames,4,nchar(sample.file$seqnames))),sample.file$start,sample.file$end),]
      write.csv(sample.file, file, row.names = F, quote = FALSE)
    }
  )
  
  #this observeEvent stops the user from trying to download a subset of the data that is larger than the actual dataset itself
  observeEvent(input$subsetSize,{
    max.download <- as.numeric(nrow(selected_data$df))
    userSubset <- as.numeric(input$subsetSize)
    if(userSubset > max.download){
      shinyjs::disable("downloadSubset")
    }else{
      shinyjs::enable("downloadSubset")
    }
  })
  #--end of download button functionality--#
  
  #--This is where we put bookmarking functionality--#
  
  #this observeEvent causes a shinyalert pop-up box with a fileInput button that only accepts .RDS files to appear for the user when they click "Restore Session"
  observeEvent(input$restoreSession,{
    shinyalert(html = TRUE, text = tagList(
      fileInput("sessionUpload","Upload usat.settings.RDS file:", accept = ".RDS")
    ))
  })
  
  # Save state (this is effectively creating a settings_history tracker that we then download as an RDS file)
  output$bookmarkButton <- downloadHandler(
    filename = function() {
      paste0(format(Sys.time(), "%Y-%m-%d_%H-%M"),'_usat.settings.RDS')
    },
    content = function(file) {
      numeric_history <- isolate(settings_history$numeric_history)
      period.size_history <- isolate(settings_history$period.size_history)
      motif_history <- isolate(settings_history$motif_history)
      REselect_history <- isolate(settings_history$REselect_history)
      dataView_history <- isolate(settings_history$dataView_history)
      xAxis_history <- isolate(settings_history$xAxis_history)
      yAxis_history <- isolate(settings_history$yAxis_history)
      xLog_history <- isolate(settings_history$xLog_history)
      yLog_history <- isolate(settings_history$yLog_history)
      dataPlot_history <- isolate(settings_history$dataPlot_history)
      umap60_history <- isolate(settings_history$umap60_history)
      umap90_history <- isolate(settings_history$umap90_history)
      umap60.radio.k24_history <- isolate(settings_history$umap60.radio.k24_history)
      umap60.radio.k36_history <- isolate(settings_history$umap60.radio.k36_history)
      umap60.radio.k50_history <- isolate(settings_history$umap60.radio.k50_history)
      umap60.radio.k100_history <- isolate(settings_history$umap60.radio.k100_history)
      umap90.radio.k24_history <- isolate(settings_history$umap90.radio.k24_history)
      umap90.radio.k36_history <- isolate(settings_history$umap90.radio.k36_history)
      umap90.radio.k50_history <- isolate(settings_history$umap90.radio.k50_history)
      umap90.radio.k100_history <- isolate(settings_history$umap90.radio.k100_history)
      nearest.usat.radio_history <- isolate(settings_history$nearest.usat.radio_history)
      LinkNearestUsatToMappability_history <- isolate(settings_history$LinkNearestUsatToMappability_history)
      regionfilter_checkboxes <- isolate(settings_history$regionfilter_checkboxes)
      regionfilter_region_radio <- isolate(settings_history$regionfilter_region_radio)
      regionfilter_which_flank <- isolate(settings_history$regionfilter_which_flank)
      regionfilter_numMin5 <- isolate(settings_history$regionfilter_numMin5)
      regionfilter_numMax5 <- isolate(settings_history$regionfilter_numMax5)
      regionfilter_numMin3 <- isolate(settings_history$regionfilter_numMin3)
      regionfilter_numMax3 <- isolate(settings_history$regionfilter_numMax3)
      save_list <- list(
        numeric_history = numeric_history,
        period.size_history = period.size_history,
        motif_history = motif_history,
        REselect_history = REselect_history,
        dataView_history = dataView_history,
        xAxis_history = xAxis_history,
        yAxis_history = yAxis_history,
        xLog_history = xLog_history,
        yLog_history = yLog_history,
        dataPlot_history = dataPlot_history,
        umap60_history = umap60_history,
        umap90_history = umap90_history,
        umap60.radio.k24_history = umap60.radio.k24_history,
        umap60.radio.k36_history = umap60.radio.k36_history,
        umap60.radio.k50_history = umap60.radio.k50_history,
        umap60.radio.k100_history = umap60.radio.k100_history,
        umap90.radio.k24_history = umap90.radio.k24_history,
        umap90.radio.k36_history = umap90.radio.k36_history,
        umap90.radio.k50_history = umap90.radio.k50_history,
        umap90.radio.k100_history = umap90.radio.k100_history,
        nearest.usat.radio_history = nearest.usat.radio_history,
        LinkNearestUsatToMappability_history = LinkNearestUsatToMappability_history,
        regionfilter_checkboxes = regionfilter_checkboxes,
        regionfilter_region_radio = regionfilter_region_radio,
        regionfilter_which_flank = regionfilter_which_flank,
        regionfilter_numMin5 = regionfilter_numMin5,
        regionfilter_numMax5 = regionfilter_numMax5,
        regionfilter_numMin3 = regionfilter_numMin3,
        regionfilter_numMax3 = regionfilter_numMax3
      )
      saveRDS(save_list, file, compress = FALSE, ascii = TRUE)
    }
  )
  
  #put uploaded file into a reactive object
  restore_file <- reactive({
    shiny::validate(need(input$sessionUpload, message = FALSE))
    input$sessionUpload
  })
  
  #read in the RDS file in the restore_file reactive object and put the data into another reactive object
  restored_state <- reactive({
    rs <- readRDS(restore_file()$datapath)
    rs
  })
  
  #initialize a reactive value that ensures everything is up-to-date before reloading the page and finishing the state restoration
  restoreDelay <- reactiveValues(n=0)
  
  #read in restore state values to the settings_history, part A (this will happen in multiple parts, as some inputs need to be generated based on other inputs before they are available to be edited)
  observeEvent(restored_state(), {
    rs <- restored_state()
    settings_history$numeric_history <- rs$numeric_history
    settings_history$period.size_history <- rs$period.size_history
    settings_history$motif_history <- rs$motif_history
    settings_history$REselect_history <- rs$REselect_history
    settings_history$dataView_history <- rs$dataView_history
    settings_history$xAxis_history <- rs$xAxis_history
    settings_history$yAxis_history <- rs$yAxis_history
    settings_history$xLog_history <- rs$xLog_history
    settings_history$yLog_history <- rs$yLog_history
    settings_history$dataPlot_history <- rs$dataPlot_history
    settings_history$umap60_history <- rs$umap60_history
    settings_history$umap90_history <- rs$umap90_history
    settings_history$umap60.radio.k24_history <- rs$umap60.radio.k24_history
    settings_history$umap60.radio.k36_history <- rs$umap60.radio.k36_history
    settings_history$umap60.radio.k50_history <- rs$umap60.radio.k50_history
    settings_history$umap60.radio.k100_history <- rs$umap60.radio.k100_history
    settings_history$umap90.radio.k24_history <- rs$umap90.radio.k24_history
    settings_history$umap90.radio.k36_history <- rs$umap90.radio.k36_history
    settings_history$umap90.radio.k50_history <- rs$umap90.radio.k50_history
    settings_history$umap90.radio.k100_history <- rs$umap90.radio.k100_history
    settings_history$nearest.usat.radio_history <- rs$nearest.usat.radio_history
    settings_history$LinkNearestUsatToMappability_history <- rs$LinkNearestUsatToMappability_history
    settings_history$regionfilter_checkboxes <- rs$regionfilter_checkboxes
    click("restoreGoA")
  })
  
  #update all values changed in the settings_history in part A of the restore state
  observeEvent(input$restoreGoA,{
    restoreDelay$n = 1
    n = restoreDelay$n
    if(n == 1){
      updateNumericInput(session,"minlength", value = settings_history$numeric_history$minlength)
      updateNumericInput(session,"maxlength", value = settings_history$numeric_history$maxlength)
      umap60_string <- settings_history$umap60_history
      umap60_set <- unlist(strsplit(umap60_string, split = "[/]"))
      updatePickerInput(session,"umap60",choices =  c("k24","k36","k50","k100"), selected = umap60_set)
      umap90_string <- settings_history$umap90_history
      umap90_set <- unlist(strsplit(umap90_string, split = "[/]"))
      updatePickerInput(session,"umap90",choices =  c("k24","k36","k50","k100"), selected = umap90_set)
      updateRadioGroupButtons(session,"nearest.usat.radio", choices = c("either","both"), selected = settings_history$nearest.usat.radio_history)
      updatePrettyToggle(session,"LinkNearestUsatToMappability", value = settings_history$LinkNearestUsatToMappability_history)
      updateNumericInput(session,"overlap60.5", value = c(settings_history$numeric_history$overlap60.min5, settings_history$numeric_history$overlap60.max5))
      updateNumericInput(session,"overlap60.3", value = c(settings_history$numeric_history$overlap60.min3, settings_history$numeric_history$overlap60.max3))
      updateNumericInput(session,"overlap90.5", value = c(settings_history$numeric_history$overlap90.min5, settings_history$numeric_history$overlap90.max5))
      updateNumericInput(session,"overlap90.3", value = c(settings_history$numeric_history$overlap90.min3, settings_history$numeric_history$overlap90.max3))
      updateNumericInput(session,"bioskryb.min", value = settings_history$numeric_history$bioskryb.min)
      updateNumericInput(session,"bioskryb.max", value = settings_history$numeric_history$bioskryb.max)
      updateNumericInput(session,"bioskryb90.min5", value = settings_history$numeric_history$bioskryb90.min5)
      updateNumericInput(session,"bioskryb90.max5", value = settings_history$numeric_history$bioskryb90.max5)
      updateNumericInput(session,"bioskryb90.min3", value = settings_history$numeric_history$bioskryb90.min3)
      updateNumericInput(session,"bioskryb90.max3", value = settings_history$numeric_history$bioskryb90.max3)
      updateNumericInput(session,"bioskryb60.min5", value = settings_history$numeric_history$bioskryb60.min5)
      updateNumericInput(session,"bioskryb60.max5", value = settings_history$numeric_history$bioskryb60.max5)
      updateNumericInput(session,"bioskryb60.min3", value = settings_history$numeric_history$bioskryb60.min3)
      updateNumericInput(session,"bioskryb60.max3", value = settings_history$numeric_history$bioskryb60.max3)
      updateNumericInput(session,"GC60.flank5", value = c(settings_history$numeric_history$GC60.flank5.min,settings_history$numeric_history$GC60.flank5.max))
      updateNumericInput(session,"GC60.flank3", value = c(settings_history$numeric_history$GC60.flank3.min,settings_history$numeric_history$GC60.flank3.max))
      updateNumericInput(session,"GC90.flank5", value = c(settings_history$numeric_history$GC90.flank5.min,settings_history$numeric_history$GC90.flank5.max))
      updateNumericInput(session,"GC90.flank3", value = c(settings_history$numeric_history$GC90.flank3.min,settings_history$numeric_history$GC90.flank3.max))
      updateNumericInput(session,"mu.min", value = settings_history$numeric_history$mu.min)
      updateNumericInput(session,"mu.max", value = settings_history$numeric_history$mu.max)
      updateNumericInput(session,"copyNum.min", value = settings_history$numeric_history$copyNum.min)
      updateNumericInput(session,"copyNum.max", value = settings_history$numeric_history$copyNum.max)
      updateNumericInput(session,"perMatch", value = c(settings_history$numeric_history$perMatch.min,settings_history$numeric_history$perMatch.max))
      updateNumericInput(session,"perIndel", value = c(settings_history$numeric_history$perIndel.min,settings_history$numeric_history$perIndel.max))
      updateNumericInput(session,"scoreMin", value = settings_history$numeric_history$score.min)
      updateNumericInput(session,"scoreMax", value = settings_history$numeric_history$score.max)
      updateNumericInput(session,"entropy", value = c(settings_history$numeric_history$entropy.min,settings_history$numeric_history$entropy.max))
      updateNumericInput(session, "ulengthMin", value = settings_history$numeric_history$length.uninterrupted.min)
      updateNumericInput(session, "ulengthMax", value = settings_history$numeric_history$length.uninterrupted.max)
      updateNumericInput(session, "uCopyNumMin", value = settings_history$numeric_history$uninterrupted.copy.num.min)
      updateNumericInput(session, "uCopyNumMax", value = settings_history$numeric_history$uninterrupted.copy.num.max)
      updateNumericInput(session, "GC.msat", value = c(settings_history$numeric_history$GC.msat.min,settings_history$numeric_history$GC.msat.max))
      updateNumericInput(session, "overlapUsatMin", value = settings_history$numeric_history$overlapUsat.min)
      updateNumericInput(session, "overlapUsatMax", value = settings_history$numeric_history$overlapUsat.max)
      updateNumericInput(session, "nearestUsatMin.5", value = settings_history$numeric_history$nearest.usat.min.5)
      updateNumericInput(session, "nearestUsatMax.5", value = settings_history$numeric_history$nearest.usat.max.5)
      updateNumericInput(session, "nearestUsatMin.3", value = settings_history$numeric_history$nearest.usat.min.3)
      updateNumericInput(session, "nearestUsatMax.3", value = settings_history$numeric_history$nearest.usat.max.3)
      updateNumericInput(session,"min.Xaxis", value = settings_history$numeric_history$min.Xaxis)
      updateNumericInput(session,"max.Xaxis", value = settings_history$numeric_history$max.Xaxis)
      updateNumericInput(session,"min.Yaxis", value = settings_history$numeric_history$min.Yaxis)
      updateNumericInput(session,"max.Yaxis", value = settings_history$numeric_history$max.Yaxis)
      period_string <- settings_history$period.size_history
      period_set <- unlist(strsplit(period_string, split = "[.]"))
      updateCheckboxGroupInput(session,"period",
                               choices = levels(factor(usats$motif.size)),
                               selected = period_set)
      motif_string <- settings_history$motif_history
      motif_set <- unlist(strsplit(motif_string, split = "[.]"))
      updatePickerInput(session,"motif",
                        choices = unique(usats$motif.family[order(usats$motif.size,usats$motif.family)]),
                        selected = motif_set)
      rf.checkbox.options <- unlist(grep("rf_checkbox", names(input), value = TRUE))
      region_checkbox_string <- settings_history$regionfilter_checkboxes
      region_checkbox_set <- unlist(strsplit(region_checkbox_string, split = "[&]"))
      lapply(rf.checkbox.options, function(x) updateCheckboxInput(session,x, value = as.logical(region_checkbox_set[which(rf.checkbox.options == x)])))
      REselect_string <- settings_history$REselect_history
      REselect_set <- unlist(strsplit(REselect_string, split = "[/]"))
      updatePickerInput(session, "selectREs",
                        choices = RE.capture_filters.name,
                        selected = REselect_set)
      RE.settings_string <- settings_history$RE_observer_history
      RE.settings_sets <- unlist(strsplit(RE.settings_string,split = "[.]"))
      RE.settings_list <- lapply(RE.settings_sets, function(x) unlist(strsplit(x, split = "[-]")))
      RE.settings_checkbox <- grep("checkbox", RE.settings_list)
      lapply(RE.settings_checkbox, function(x) updateCheckboxInput(session,RE.settings_list[[x]][1],value = eval(parse(text=RE.settings_list[[x]][2]))))
      RE.settings_slider <- grep("slider", RE.settings_list)
      lapply(RE.settings_slider, function(x) updateSliderInput(session, RE.settings_list[[x]][1], value = c(eval(parse(text=RE.settings_list[[x]][2])),eval(parse(text=RE.settings_list[[x]][3])))))
      RE.settings_numeric <- grep("numeric", RE.settings_list)
      lapply(RE.settings_numeric, function(x) updateNumericInput(session, RE.settings_list[[x]][1], value = eval(parse(text=RE.settings_list[[x]][2]))))
      updateRadioButtons(session,"dataView",
                         choices = c("All Microsatellites","Microsatellites that pass filter(s)","Microsatellites excluded by filter(s)"),
                         selected = settings_history$dataView_history)
      annotations.select_string <- settings_history$annotation.select_history
      annotations.select_set <- unlist(strsplit(annotations.select_string, split = "[/]"))
      updatePickerInput(session, "annotation.select",
                        choices = columns,
                        selected = annotations.select_set)
      updateRadioButtons(session,"dataPlot",
                         choices = c("All Microsatellites","Microsatellites that pass filter(s)","Microsatellites excluded by filter(s)"),
                         selected = settings_history$dataPlot_history)
      updatePickerInput(session,"xAxis",
                        choices = names(columns)[which(!(names(columns) %in% c("seqnames","start","end",
                                                                               "strand","consensus","sequence",
                                                                               "motif","motif.family","flank.5.prime.seq",
                                                                               "flank.3.prime.seq")))],
                        selected = settings_history$xAxis_history)
      updatePickerInput(session,"yAxis",
                        choices = c("count","proportion",names(columns)[which(!(names(columns) %in% c("seqnames","start","end",
                                                                                                      "strand","consensus","sequence",
                                                                                                      "motif","motif.family","flank.5.prime.seq",
                                                                                                      "flank.3.prime.seq")))]),
                        selected = settings_history$yAxis_history)
      updateMaterialSwitch(session,"xLog",value = settings_history$xLog_history)
      updateMaterialSwitch(session,"yLog",value = settings_history$yLog_history)
    }
  })
  
  #check that all inputs in part A of the state restore have been updated
  revertStatusA <- reactive({
    status <- input$minlength == settings_history$numeric_history$minlength &
      input$maxlength == settings_history$numeric_history$maxlength &
      paste(input$umap60, collapse = "/") == settings_history$umap60_history &
      paste(input$umap90, collapse = "/") == settings_history$umap90_history &
      min(input$overlap60.5) == settings_history$numeric_history$overlap60.min5 &
      max(input$overlap60.5) == settings_history$numeric_history$overlap60.max5 &
      min(input$overlap60.3) == settings_history$numeric_history$overlap60.min3 &
      max(input$overlap60.3) == settings_history$numeric_history$overlap60.max3 &
      min(input$overlap90.5) == settings_history$numeric_history$overlap90.min5 &
      max(input$overlap90.5) == settings_history$numeric_history$overlap90.max5 &
      min(input$overlap90.3) == settings_history$numeric_history$overlap90.min3 &
      max(input$overlap90.3) == settings_history$numeric_history$overlap90.max3 &
      input$bioskryb.min == settings_history$numeric_history$bioskryb.min &
      input$bioskryb.max == settings_history$numeric_history$bioskryb.max &
      input$bioskryb90.min5 == settings_history$numeric_history$bioskryb90.min5 &
      input$bioskryb90.max5 == settings_history$numeric_history$bioskryb90.max5 &
      input$bioskryb90.min3 == settings_history$numeric_history$bioskryb90.min3 &
      input$bioskryb90.max3 == settings_history$numeric_history$bioskryb90.max3 &
      input$bioskryb60.min5 == settings_history$numeric_history$bioskryb60.min5 &
      input$bioskryb60.max5 == settings_history$numeric_history$bioskryb60.max5 &
      input$bioskryb60.min3 == settings_history$numeric_history$bioskryb60.min3 &
      input$bioskryb60.max3 == settings_history$numeric_history$bioskryb60.max3 &
      min(input$GC60.flank5) == settings_history$numeric_history$GC60.flank5.min &
      max(input$GC60.flank5) == settings_history$numeric_history$GC60.flank5.max &
      min(input$GC60.flank3) == settings_history$numeric_history$GC60.flank3.min &
      max(input$GC60.flank3) == settings_history$numeric_history$GC60.flank3.max &
      min(input$GC90.flank5) == settings_history$numeric_history$GC90.flank5.min &
      max(input$GC90.flank5) == settings_history$numeric_history$GC90.flank5.max &
      min(input$GC90.flank3) == settings_history$numeric_history$GC90.flank3.min &
      max(input$GC90.flank3) == settings_history$numeric_history$GC90.flank3.max &
      input$min.Xaxis == settings_history$numeric_history$min.Xaxis &
      input$max.Xaxis == settings_history$numeric_history$max.Xaxis &
      input$min.Yaxis == settings_history$numeric_history$min.Yaxis &
      input$max.Yaxis == settings_history$numeric_history$max.Yaxis &
      input$mu.min == settings_history$numeric_history$mu.min &
      input$mu.max == settings_history$numeric_history$mu.max &
      input$copyNum.min == settings_history$numeric_history$copyNum.min &
      input$copyNum.max == settings_history$numeric_history$copyNum.max &
      min(input$perMatch) == settings_history$numeric_history$perMatch.min &
      max(input$perMatch) == settings_history$numeric_history$perMatch.max &
      min(input$perIndel) == settings_history$numeric_history$perIndel.min &
      max(input$perIndel) == settings_history$numeric_history$perIndel.max &
      input$scoreMin == settings_history$numeric_history$score.min &
      input$scoreMax == settings_history$numeric_history$score.max &
      min(input$entropy) == settings_history$numeric_history$entropy.min & 
      max(input$entropy) == settings_history$numeric_history$entropy.max &
      input$ulengthMin == settings_history$numeric_history$length.uninterrupted.min &
      input$ulengthMax == settings_history$numeric_history$length.uninterrupted.max &
      input$uCopyNumMin == settings_history$numeric_history$uninterrupted.copy.num.min &
      input$uCopyNumMax == settings_history$numeric_history$uninterrupted.copy.num.max &
      min(input$GC.msat) == settings_history$numeric_history$GC.msat.min &
      max(input$GC.msat) == settings_history$numeric_history$GC.msat.max &
      input$overlapUsatMin == settings_history$numeric_history$overlapUsat.min &
      input$overlapUsatMax == settings_history$numeric_history$overlapUsat.max &
      input$nearestUsatMin.5 == settings_history$numeric_history$nearest.usat.min.5 &
      input$nearestUsatMax.5 == settings_history$numeric_history$nearest.usat.max.5 &
      input$nearestUsatMin.3 == settings_history$numeric_history$nearest.usat.min.3 &
      input$nearestUsatMax.3 == settings_history$numeric_history$nearest.usat.max.3 &
      paste(input$period, collapse = ".") == settings_history$period.size_history &
      paste(input$motif, collapse = ".") == settings_history$motif_history &
      paste(input$selectREs, collapse = "/") == settings_history$REselect_history &
      as.character(RE_inputs_observer()) == settings_history$RE_observer_history &
      as.character(paste(unlist(lapply(grep("rf_checkbox",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_checkboxes &
      input$dataView == settings_history$dataView_history &
      paste(input$annotation.select, collapse = "/") == settings_history$annotation.select_history &
      input$xAxis == settings_history$xAxis_history &
      input$yAxis == settings_history$yAxis_history &
      input$xLog == settings_history$xLog_history &
      input$yLog == settings_history$yLog_history &
      input$dataPlot == settings_history$dataPlot_history &
      input$nearest.usat.radio == settings_history$nearest.usat.radio_history &
      input$LinkNearestUsatToMappability == settings_history$LinkNearestUsatToMappability_history
      return(status)
  })
  
  #if all parts of the restore state part A are complete, proceed to restore state part B
  observeEvent(revertStatusA(),{
    rs <- restored_state()
    status <- as.logical(isolate(revertStatusA()))
    if(isTRUE(status)){
      settings_history$regionfilter_region_radio <- rs$regionfilter_region_radio
      click("restoreGoB")
    }
  })
  
  #repeat above for restore state part B
  observeEvent(input$restoreGoB,{
    restoreDelay$n = 2
    n = restoreDelay$n
    if(n == 2){
      {if("k24" %in% input$umap60) updateNumericInput(session,"umap60.5.k24", value = c(settings_history$numeric_history$umap60.min5.k24, settings_history$numeric_history$umap60.max5.k24)) else NULL}
      {if("k24" %in% input$umap60) updateNumericInput(session,"umap60.3.k24", value = c(settings_history$numeric_history$umap60.min3.k24, settings_history$numeric_history$umap60.max3.k24)) else NULL}
      {if("k24" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k24", choices = c("either","both"), selected = settings_history$umap60.radio.k24_history) else NULL}
      {if("k24" %in% input$umap90) updateNumericInput(session,"umap90.5.k24", value = c(settings_history$numeric_history$umap90.min5.k24, settings_history$numeric_history$umap90.max5.k24)) else NULL}
      {if("k24" %in% input$umap90) updateNumericInput(session,"umap90.3.k24", value = c(settings_history$numeric_history$umap90.min3.k24, settings_history$numeric_history$umap90.max3.k24)) else NULL}
      {if("k24" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k24", choices = c("either","both"), selected = settings_history$umap90.radio.k24_history) else NULL}
      {if("k36" %in% input$umap60) updateNumericInput(session,"umap60.5.k36", value = c(settings_history$numeric_history$umap60.min5.k36, settings_history$numeric_history$umap60.max5.k36)) else NULL}
      {if("k36" %in% input$umap60) updateNumericInput(session,"umap60.3.k36", value = c(settings_history$numeric_history$umap60.min3.k36, settings_history$numeric_history$umap60.max3.k36)) else NULL}
      {if("k36" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k36", choices = c("either","both"), selected = settings_history$umap60.radio.k36_history) else NULL}
      {if("k36" %in% input$umap90) updateNumericInput(session,"umap90.5.k36", value = c(settings_history$numeric_history$umap90.min5.k36, settings_history$numeric_history$umap90.max5.k36)) else NULL}
      {if("k36" %in% input$umap90) updateNumericInput(session,"umap90.3.k36", value = c(settings_history$numeric_history$umap90.min3.k36, settings_history$numeric_history$umap90.max3.k36)) else NULL}
      {if("k36" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k36", choices = c("either","both"), selected = settings_history$umap90.radio.k36_history) else NULL}
      {if("k50" %in% input$umap60) updateNumericInput(session,"umap60.5.k50", value = c(settings_history$numeric_history$umap60.min5.k50, settings_history$numeric_history$umap60.max5.k50)) else NULL}
      {if("k50" %in% input$umap60) updateNumericInput(session,"umap60.3.k50", value = c(settings_history$numeric_history$umap60.min3.k50, settings_history$numeric_history$umap60.max3.k50)) else NULL}
      {if("k50" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k50", choices = c("either","both"), selected = settings_history$umap60.radio.k50_history) else NULL}
      {if("k50" %in% input$umap90) updateNumericInput(session,"umap90.5.k50", value = c(settings_history$numeric_history$umap90.min5.k50, settings_history$numeric_history$umap90.max5.k50)) else NULL}
      {if("k50" %in% input$umap90) updateNumericInput(session,"umap90.3.k50", value = c(settings_history$numeric_history$umap90.min3.k50, settings_history$numeric_history$umap90.max3.k50)) else NULL}
      {if("k50" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k50", choices = c("either","both"), selected = settings_history$umap90.radio.k50_history) else NULL}
      {if("k100" %in% input$umap60) updateNumericInput(session,"umap60.5.k100", value = c(settings_history$numeric_history$umap60.min5.k100, settings_history$numeric_history$umap60.max5.k100)) else NULL}
      {if("k100" %in% input$umap60) updateNumericInput(session,"umap60.3.k100", value = c(settings_history$numeric_history$umap60.min3.k100, settings_history$numeric_history$umap60.max3.k100)) else NULL}
      {if("k100" %in% input$umap60) updateRadioGroupButtons(session,"umap60.radio.k100", choices = c("either","both"), selected = settings_history$umap60.radio.k100_history) else NULL}
      {if("k100" %in% input$umap90) updateNumericInput(session,"umap90.5.k100", value = c(settings_history$numeric_history$umap90.min5.k100, settings_history$numeric_history$umap90.max5.k100)) else NULL}
      {if("k100" %in% input$umap90) updateNumericInput(session,"umap90.3.k100", value = c(settings_history$numeric_history$umap90.min3.k100, settings_history$numeric_history$umap90.max3.k100)) else NULL}
      {if("k100" %in% input$umap90) updateRadioGroupButtons(session,"umap90.radio.k100", choices = c("either","both"), selected = settings_history$umap90.radio.k100_history) else NULL}
      rf.radio.options <- unlist(grep("rf_radio", names(input), value = TRUE))
      region_radio_string <- settings_history$regionfilter_region_radio
      region_radio_set <- unlist(strsplit(region_radio_string, split = "[&]"))
      lapply(rf.radio.options, function(x) updateRadioGroupButtons(session,x, choices = c("microsatellite","flank","both"), selected = region_radio_set[which(rf.radio.options == x)]))
    }
  })
  
  revertStatusB <- reactive({
    status <- {if("k24" %in% input$umap60) min(input$umap60.5.k24) == settings_history$numeric_history$umap60.min5.k24 else TRUE} &
      {if("k24" %in% input$umap60) max(input$umap60.5.k24) == settings_history$numeric_history$umap60.max5.k24 else TRUE} &
      {if("k24" %in% input$umap60) min(input$umap60.3.k24) == settings_history$numeric_history$umap60.min3.k24 else TRUE} &
      {if("k24" %in% input$umap60) max(input$umap60.3.k24) == settings_history$numeric_history$umap60.max3.k24 else TRUE} &
      {if("k24" %in% input$umap60) input$umap60.radio.k24 == settings_history$umap60.radio.k24_history else TRUE} &
      {if("k24" %in% input$umap90) min(input$umap90.5.k24) == settings_history$numeric_history$umap90.min5.k24 else TRUE} &
      {if("k24" %in% input$umap90) max(input$umap90.5.k24) == settings_history$numeric_history$umap90.max5.k24 else TRUE} &
      {if("k24" %in% input$umap90) min(input$umap90.3.k24) == settings_history$numeric_history$umap90.min3.k24 else TRUE} &
      {if("k24" %in% input$umap90) max(input$umap90.3.k24) == settings_history$numeric_history$umap90.max3.k24 else TRUE} &
      {if("k24" %in% input$umap90) input$umap90.radio.k24 == settings_history$umap90.radio.k24_history else TRUE} &
      {if("k36" %in% input$umap60) min(input$umap60.5.k36) == settings_history$numeric_history$umap60.min5.k36 else TRUE} &
      {if("k36" %in% input$umap60) max(input$umap60.5.k36) == settings_history$numeric_history$umap60.max5.k36 else TRUE} &
      {if("k36" %in% input$umap60) min(input$umap60.3.k36) == settings_history$numeric_history$umap60.min3.k36 else TRUE} &
      {if("k36" %in% input$umap60) max(input$umap60.3.k36) == settings_history$numeric_history$umap60.max3.k36 else TRUE} &
      {if("k36" %in% input$umap60) input$umap60.radio.k36 == settings_history$umap60.radio.k36_history else TRUE} &
      {if("k36" %in% input$umap90) min(input$umap90.5.k36) == settings_history$numeric_history$umap90.min5.k36 else TRUE} &
      {if("k36" %in% input$umap90) max(input$umap90.5.k36) == settings_history$numeric_history$umap90.max5.k36 else TRUE} &
      {if("k36" %in% input$umap90) min(input$umap90.3.k36) == settings_history$numeric_history$umap90.min3.k36 else TRUE} &
      {if("k36" %in% input$umap90) max(input$umap90.3.k36) == settings_history$numeric_history$umap90.max3.k36 else TRUE} &
      {if("k36" %in% input$umap90) input$umap90.radio.k36 == settings_history$umap90.radio.k36_history else TRUE} &
      {if("k50" %in% input$umap60) min(input$umap60.5.k50) == settings_history$numeric_history$umap60.min5.k50 else TRUE} &
      {if("k50" %in% input$umap60) max(input$umap60.5.k50) == settings_history$numeric_history$umap60.max5.k50 else TRUE} &
      {if("k50" %in% input$umap60) min(input$umap60.3.k50) == settings_history$numeric_history$umap60.min3.k50 else TRUE} &
      {if("k50" %in% input$umap60) max(input$umap60.3.k50) == settings_history$numeric_history$umap60.max3.k50 else TRUE} &
      {if("k50" %in% input$umap60) input$umap60.radio.k50 == settings_history$umap60.radio.k50_history else TRUE} &
      {if("k50" %in% input$umap90) min(input$umap90.5.k50) == settings_history$numeric_history$umap90.min5.k50 else TRUE} &
      {if("k50" %in% input$umap90) max(input$umap90.5.k50) == settings_history$numeric_history$umap90.max5.k50 else TRUE} &
      {if("k50" %in% input$umap90) min(input$umap90.3.k50) == settings_history$numeric_history$umap90.min3.k50 else TRUE} &
      {if("k50" %in% input$umap90) max(input$umap90.3.k50) == settings_history$numeric_history$umap90.max3.k50 else TRUE} &
      {if("k50" %in% input$umap90) input$umap90.radio.k50 == settings_history$umap90.radio.k50_history else TRUE} &
      {if("k100" %in% input$umap60) min(input$umap60.5.k100) == settings_history$numeric_history$umap60.min5.k100 else TRUE} &
      {if("k100" %in% input$umap60) max(input$umap60.5.k100) == settings_history$numeric_history$umap60.max5.k100 else TRUE} &
      {if("k100" %in% input$umap60) min(input$umap60.3.k100) == settings_history$numeric_history$umap60.min3.k100 else TRUE} &
      {if("k100" %in% input$umap60) max(input$umap60.3.k100) == settings_history$numeric_history$umap60.max3.k100 else TRUE} &
      {if("k100" %in% input$umap60) input$umap60.radio.k100 == settings_history$umap60.radio.k100_history else TRUE} &
      {if("k100" %in% input$umap90) min(input$umap90.5.k100) == settings_history$numeric_history$umap90.min5.k100 else TRUE} &
      {if("k100" %in% input$umap90) max(input$umap90.5.k100) == settings_history$numeric_history$umap90.max5.k100 else TRUE} &
      {if("k100" %in% input$umap90) min(input$umap90.3.k100) == settings_history$numeric_history$umap90.min3.k100 else TRUE} &
      {if("k100" %in% input$umap90) max(input$umap90.3.k100) == settings_history$numeric_history$umap90.max3.k100 else TRUE} &
      {if("k100" %in% input$umap90) input$umap90.radio.k100 == settings_history$umap90.radio.k100_history else TRUE} &
      as.character(paste(unlist(lapply(grep("rf_radio",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_region_radio
    return(status)
  })
  
  observeEvent(revertStatusB(),{
    rs <- restored_state()
    status <- as.logical(isolate(revertStatusB()))
    if(isTRUE(status)){
      settings_history$regionfilter_which_flank <- rs$regionfilter_which_flank
      settings_history$regionfilter_numMin5 <- rs$regionfilter_numMin5
      settings_history$regionfilter_numMax5 <- rs$regionfilter_numMax5
      settings_history$regionfilter_numMin3 <- rs$regionfilter_numMin3
      settings_history$regionfilter_numMax3 <- rs$regionfilter_numMax3
      click("restoreGoC")
    }
  })
  
  #repeat above for restore state part C, final restore
  observeEvent(input$restoreGoC,{
    restoreDelay$n = 3
    n = restoreDelay$n
    if(n == 3){
      flank.options <- unlist(grep("flank_rf", names(input), value = TRUE))
      regionfilter_which_flank_string <- settings_history$regionfilter_which_flank
      regionfilter_which_flank_set <- unlist(strsplit(regionfilter_which_flank_string, split = "[&]"))
      lapply(flank.options, function(x) updateRadioGroupButtons(session,x, choices = c("one","both"), selected = regionfilter_which_flank_set[which(flank.options == x)]))
      numMin5.options <- unlist(grep("rf_numMin_flank5", names(input), value = TRUE))
      regionfilter_numMin5_string <- settings_history$regionfilter_numMin5
      regionfilter_numMin5_set <- unlist(strsplit(regionfilter_numMin5_string, split = "[&]"))
      lapply(numMin5.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMin5_set[which(numMin5.options == x)])))
      numMax5.options <- unlist(grep("rf_numMax_flank5", names(input), value = TRUE))
      regionfilter_numMax5_string <- settings_history$regionfilter_numMax5
      regionfilter_numMax5_set <- unlist(strsplit(regionfilter_numMax5_string, split = "[&]"))
      lapply(numMax5.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMax5_set[which(numMax5.options == x)])))
      numMin3.options <- unlist(grep("rf_numMin_flank3", names(input), value = TRUE))
      regionfilter_numMin3_string <- settings_history$regionfilter_numMin3
      regionfilter_numMin3_set <- unlist(strsplit(regionfilter_numMin3_string, split = "[&]"))
      lapply(numMin3.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMin3_set[which(numMin3.options == x)])))
      numMax3.options <- unlist(grep("rf_numMax_flank3", names(input), value = TRUE))
      regionfilter_numMax3_string <- settings_history$regionfilter_numMax3
      regionfilter_numMax3_set <- unlist(strsplit(regionfilter_numMax3_string, split = "[&]"))
      lapply(numMax3.options, function(x) updateNumericInput(session,x, value = as.numeric(regionfilter_numMax3_set[which(numMax3.options == x)])))
    }
  })
  
  revertStatusC <- reactive({
    status <- as.character(paste(unlist(lapply(grep("flank_rf",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_which_flank &
      as.character(paste(unlist(lapply(grep("rf_numMin_flank5",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMin5 &
      as.character(paste(unlist(lapply(grep("rf_numMax_flank5",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMax5 &
      as.character(paste(unlist(lapply(grep("rf_numMin_flank3",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMin3 &
      as.character(paste(unlist(lapply(grep("rf_numMax_flank3",names(input),value = TRUE),function(x) input[[x]])), collapse = "&")) == settings_history$regionfilter_numMax3
    return(status)
  })
  
  #after revert state C has been completed (based on the status returned by revertStatusC), trigger the "Apply Changes" button to update the displayed plots/dataset and reset the restoreDelay counter to 0 so it can be used again
  observeEvent(revertStatusC(),{
    status <- as.logical(isolate(revertStatusC()))
    n <- as.numeric(restoreDelay$n) + 1
    if(isTRUE(status) & n == 4){
      click("goButton")
      restoreDelay$n <- 0
    }
  })
  
  #this allows hidden parts of the ui to recalculate even when they are not visible
  lapply(c("sliderInput60_group_ui","sliderInput90_group_ui","numericInput_buttons_ui","regionfilter.checkboxes_ui"), function(x) outputOptions(output,x,suspendWhenHidden = FALSE))
  #--end of bookmarking functionality--#
}

# Run the app ----
shinyApp(ui = ui, server = server)
