.SetDimNames <- function(dimnames = NULL, restore = FALSE) {
  if (!restore) { 
    # Check dimnames
    if (!is.character(dimnames)) {
      stop("Parameter 'dimnames' must be a character string vector.")
    }
    if (length(dimnames) > 20) {
      stop("Parameter 'dimnames' can contain only up to 20 dimension names.")
    }
    if (sum(sapply(dimnames, nchar) > 20) > 0) {
      stop("Dimension names provided in 'dimnames' can only be up to 20 characters long.")
    }
    
    save(dimnames, file = paste0(tempdir(), '/s2dv.dimnames.RData'))
  } else {
    if (file.exists(paste0(tempdir(), '/s2dv.dimnames.RData'))) {
      file.remove(paste0(tempdir(), '/s2dv.dimnames.RData'))
    }
  }
}

.GetDimNames <- function() {
  if (file.exists(paste0(tempdir(), '/s2dv.dimnames.RData'))) {
    load(paste0(tempdir(), '/s2dv.dimnames.RData'))
    dimnames
  } else {
    c('dataset', 'var', 'level', 'member', 'sdate', 'ftime', 
      'lat', 'lon', 'test')
  }
}

PlotTimeSeries <- function(var, curves_along = NULL, colour_along = NULL, 
                           mean_along = NULL, test_along = NULL, 
                           error_bar_along = NULL, lower = 1, test = 2, upper = 3,
                           x_axis_dim = NULL, #start_at = NULL, 
                           title = NULL, xtitle = NULL, ytitle = NULL,
                           ref_dimnames = .GetDimNames(),
                           file_name = NULL, file_path = NULL,
                           file_width = NA, file_height = NA, 
                           file_size_units = "in") {
  library(ggplot2)

  # Check 'var'. Forcing 'var' to contain only a numeric array with the
  # data, whichever the input type. This way a common code can be used
  # afterwards, independently of the input type.
  # Also taking the dimension names, if available.
  dim_names <- NULL
  metadata <- NULL
  if (class(var) == 'multiCDM') {
    # Change 'var' to contain only the data array. The rest of metadata
    # is moved to 'metadata'.
    attr_bk <- attributes(var)
    metadata <- var[-(which(names(var) == 'Data'))]
    attr_bk[['names']] <- attr_bk[['names']][-which(attr_bk[['names']] == 'Data')]
    attributes(metadata) <- attr_bk
    var <- var$Data
  }
  if (is.numeric(var) && is.array(var) && length(var) > 0) {
    if (all(is.na(var))) {
      stop("All values in 'var' are NA.")
    } 
    dimnames <- attr(var, 'dimensions')
    if (is.null(dimnames)) {
      dimnames <- names(dim(var))
    }
  } else {
    stop("Parameter 'var' must be a 'multiCDM' object or a numeric array.")
  }
  # Check there are no repeated dimension names, for safety.
  if (!is.null(dimnames)) {
    if (length(dimnames) != length(unique(dimnames))) {
      stop("There must be no repeated names in the provided dimension names in 'var'.")
    }
  }

  # Convert array into data frame for ggplot2.
  dims <- dim(var)
  var <- as.data.frame.table(var)
  if (is.null(dimnames)) {
    dimnames <- paste0('dim', 1:length(dims))
  }
  names(var) <- c(dimnames, 'value')

  # Check ref_dimnames
  if (!is.character(ref_dimnames)) {
    stop("Parameter 'ref_dimnames' must be a vector of character strings.")
  }
  if (length(ref_dimnames) != length(.GetDimNames())) {
    stop("Parameter 'ref_dimnames' must contain as many dimension names as defined in s2dv::.GetDimNames().")
  }

  # Check that the '_along' parameters contain valid dimension names, 
  # and convert always to integer numbers. NA values are also allowed to
  # deactivate the corresponding option. So, afther the parameter 
  # checks all the '_along' parameters will be either a valid dimension
  # index or a NA value.

  # The following function checks a single '_along' parameter, processes
  # it and returns its new value. 
  # 'param' is the value provided for one of the '_along' parameters. 
  # 'dimnames' is the character string vector of detected dimension 
  # names in the input 'var' (can also be NULL). 'seek_names' is a 
  # vector of dimensions to look for and to assign to the '_along' 
  # parameter if no value has been provided for it. The index of the 
  # first found dimension name in 'seek_names' is assigned to the 
  # parameter. If no dimension is provided or found, NA (deactivation) 
  # is assigned to the '_along' parameter'.
  CheckDimensionSpec <- function(param, dimnames, seek_names = NULL) {
    if (is.null(param)) {
      if (is.null(dimnames)) {
        param <- NA
      } else {
        param <- NA
        found <- FALSE
        i <- 1
        while ((i <= length(seek_names)) && !found) {
          if (seek_names[i] %in% dimnames) {
            # Do not consider dimensions of length 1 while seeking
            if (dims[grep(seek_names[i], dimnames)] > 1) {
              param <- seek_names[i]
              found <- TRUE
            }
          }
          i <- i + 1
        }
      }
    }
    # If it's a NA value, transform to a logical NA always, for safety.
    if (is.na(param)) {
      param <- as.logical(param)
    }
    if (is.character(param)) {
      if (param %in% dimnames) {
        param <- which(dimnames == param)
      } else {
        stop("All the dimension names provided as character strings in the '_along' parameters must be one of the dimension names of 'var'.")
      }
    }
    if (is.numeric(param)) {
      param <- round(param)
      if (param > length(dimnames)) {
        stop("All the dimension indices provided as integer numbers in the '_along' parameters must be in the range 1:length(dim(var)).")
      }
    } else if (!is.na(param)) {
      stop("Each provided '_along' parameter must be either a character string, a numeric value or NA.")
    }

    param
  }

  # Check curves_along. Time dimensions are seeked in the input
  # dimension names.
  curves_along <- CheckDimensionSpec(curves_along, dimnames, 
                                     ref_dimnames[6:5])

  # Check colour_along.
  if (all(ref_dimnames[5:6] %in% dimnames)) {
    colour_along_priorities <- ref_dimnames[5]
  } else {
    colour_along_priorities <- ref_dimnames[1:4]
  }
  colour_along <- CheckDimensionSpec(colour_along, dimnames,
                                     colour_along_priorities)

  # Check mean_along
  if (is.null(mean_along) && all(ref_dimnames[5:6] %in% dimnames)) {
    mean_along <- ref_dimnames[5]
  }
  mean_along <- CheckDimensionSpec(mean_along, dimnames)

  # Check test_along
  if (is.null(test_along) && all(ref_dimnames[c(6, 9)] %in% dimnames)) {
    test_along <- ref_dimnames[9]
  }
  test_along <- CheckDimensionSpec(test_along, dimnames)

  # Check error_bar_along
  if (is.null(error_bar_along) && all(ref_dimnames[c(5, 9)] %in% dimnames) &&
      !(ref_dimnames[6] %in% dimnames)) {
    error_bar_along <- ref_dimnames[9]
  }
  error_bar_along <- CheckDimensionSpec(error_bar_along, dimnames)

  # Check x_axis_dim.
  x_axis_dim <- CheckDimensionSpec(x_axis_dim, dimnames,
                                   ref_dimnames[6:5])
  # If no time dimension is found, pick the longest one for the x axis.
  if (is.na(x_axis_dim)) {
    x_axis_dim <- which.max(dims)
    print(paste0("No 'x_axis_dim' specified and no time dimension found in 'var'. Taking the longest dimension (", 
                 dimnames[x_axis_dim], ") to be represented along x axis."))
    # Put the x_axis_dim as last dim before asigning 'dates' (indices 
    # in this case).
    reorder <- c((1:length(dims))[-x_axis_dim], x_axis_dim)
    var <- var[do.call(order, lapply(reorder, function(x) `[[`(var, x))), ]
    var$date <- 1:prod(dims)
  # If time dimension is found, reorder the data frame with the time 
  # dimension at the right-most position so that dates found in 
  # metadata match, as they come, with the reordered values.
  } else {
    if ('Dates' %in% names(metadata)) {
      offset <- (metadata$Dates$end[1] - metadata$Dates$start[1]) / 2
      ftime_pos <- grep(ref_dimnames[6], dimnames, fixed = TRUE)
      sdate_pos <- grep(ref_dimnames[5], dimnames, fixed = TRUE)
      reorder <- c(1:length(dims))
      n_ftime <- 1
      # If ftime is one of the dimensions
      if (length(ftime_pos) > 0) {
        # Reorder with ftime as right-most dim
        reorder <- c(reorder[-ftime_pos], ftime_pos)
        n_ftime <- dims[ftime_pos]
      # If sdate is one of the dimensions
      } else if (length(sdate_pos) > 0) {
        # Reorder with sdate as right-most dim
        reorder <- c(reorder[-sdate_pos], sdate_pos)
      }
      var <- var[do.call(order, lapply(reorder, function(x) `[[`(var, x))), ]
      var$date <- metadata$Dates$start + offset
      sdates <- metadata$Dates$start[seq(1, length(metadata$Dates$start), n_ftime)]
    } else {
      #first_date <- as.POSIXct('00000101', format = '%Y%m%d')
      # Check freq and period
    }
    ## TODO: If start_at != NULL
  }

  # Check title
  if (is.null(title)) {
    title <- NA
    if (!is.null(metadata)) {
      if (!is.null(attr(metadata$Variable, 'longname'))) {
        title <- attr(metadata$Variable, 'longname')
      }
    }
  } else if (is.na(title)) {
    title <- as.logical(title)
  } else if (!is.character(title)) {
    stop("Parameter 'title' must be a character string.")
  }

  # Check xtitle
  if (is.null(xtitle)) {
    xtitle <- NA
    xtitle <- dimnames[x_axis_dim]
  } else if (is.na(xtitle)) {
    xtitle <- as.logical(xtitle)
  } else if (!is.character(xtitle)) {
    stop("Parameter 'xtitle' must be a character string.")
  }

  # Check ytitle
  if (is.null(ytitle)) {
    ytitle <- 'value'
    if (!is.null(metadata)) {
      ytitle <- metadata$Variable$varName
    }
  } else if (is.na(ytitle)) {
    ytitle <- as.logical(ytitle)
  } else if (!is.character(ytitle)) {
    stop("Parameter 'ytitle' must be a character string.")
  }

  # Check device parameters
  if (!is.character(file_name) && !is.null(file_name)) {
    stop("Parameter 'file_name' must be a character string.")
  }
  if (is.null(file_name) && !is.null(file_path)) {
    print("'file_name' not specified for the 'file_path', it will be ignored.")
  } else if (!is.null(file_name)) {
    # Chek file_path
    if (is.null(file_path)) {
      file_path <- getwd()
    } else if (!is.character(file_path)) {
      print("'file_path' must be a character string of the desired path. Saving to default (working directory).")
      file_path <- getwd()
    } ## TODO: check if it is a correct pathname?
    
    # Check size parameters
    if (!is.numeric(file_width) && !is.na(file_width)) {
      print("'file_width' must be a number, setting width to default.")
      file_width <- NA
    }
    if (!is.numeric(file_height) && !is.na(file_height)) {
      print("'file_height' must be a number, setting height to default.")
      file_height <- NA
    }
    if (!(file_size_units %in% c("cm", "in", "mm"))) {
      print("'file_size_units' must be 'cm', 'in' or 'mm', setting units to default (inches).")
      file_size_units <- "in"
    }
  }

  # Plot
  group_dims <- dimnames
  if (!is.na(curves_along)) {
    group_dims <- group_dims[-curves_along]
  }
  g <- ggplot(var, aes(x = date, y = value, group = interaction(mget(group_dims))), environment = environment())
  if (!is.na(test_along) || !is.na(error_bar_along)) {
    upper <- levels(var[[ref_dimnames[9]]])[upper]
    test  <- levels(var[[ref_dimnames[9]]])[test]
    lower <- levels(var[[ref_dimnames[9]]])[lower]
    if (!is.na(test_along)) {
      g <- g + geom_point(aes(x = var$date[var[[ref_dimnames[9]]] == test], y = var$value[var[[ref_dimnames[9]]] == test]), colour = "red") + 
            geom_errorbar(aes(x = var$date[var[[ref_dimnames[9]]] == test], ymin = var$value[var[[ref_dimnames[9]]] == lower], ymax = var$value[var[[ref_dimnames[9]]] == upper]), 
                          colour = "red")
    } else {
      
    }
  } else {
    if (!is.na(curves_along)) {
      g <- g + geom_line(aes_string(colour = dimnames[colour_along]), size = 0.4)
    } else {
      g <- g + geom_point(aes_string(colour = dimnames[colour_along]))
    }
  }
  g <- g + labs(title = title) + xlab(xtitle) + ylab(ytitle)
  # Adjust scale for colour
  if (!is.na(colour_along)) {
    g <- g + scale_colour_discrete(name = dimnames[colour_along])
    # If the dimension coloured along is the members
    if (dimnames[colour_along] == ref_dimnames[4]) {
      if (!is.null(metadata$Datasets)) {
        labels <- names(metadata$Datasets[[1]]$InitializationDates)
      } else {
        labels <- paste(ref_dimnames[4], 1:dims[colour_along])
      }
    # If the dimension coloured along is the sdates
    } else if (dimnames[colour_along] == ref_dimnames[5]) {
      if ('Dates' %in% names(metadata)) {
        labels <- sdates
      } else {
        labels <- paste(ref_dimnames[5], 1:dims[colour_along])
      }
    }
    g <- g + scale_colour_discrete(labels = labels)
  }
  # Draw mean line
  if (!is.na(mean_along)) {
    g <- g + stat_smooth(mapping = aes_string(x = 'date', y = 'value', 
                         group = dimnames[mean_along], 
                         colour = dimnames[mean_along], 
                         fill = dimnames[mean_along]), 
                         alpha = 0.3, show.legend = FALSE)
  }

  # Return ggplot2 object or save to file
  if (!is.null(file_name)) {
    # Save to file
    g <- ggsave(filename = file_name, plot = g, path = file_path,
                width = file_width, height = file_height, 
                units = file_size_units)
    invisible(NULL)
  } else {
    g
  }
}
