PlotTimeSeries <- function(var, curves_along = NULL, points = FALSE, colour_along = NULL, style_along = NULL,
                           mean_along = NULL, minmax_along = mean_along, shape_along = NULL,
                           group_along = NULL, interval_along = NULL, interact_along = c(1,2,3),
                           error_bar_along = NULL, intercept = 0, interval_type = 'errorbar',
                           lower = 1, test = 2, upper = 3,
                           x_axis_dim = NULL, first_date = NULL, 
                           sdates = NULL, period = NULL, sdate_period = NULL,
                           title = NULL, xtitle = NULL, ytitle = NULL,
                           ref_dimnames = NULL,
                           file_name = NULL, file_path = NULL,
                           file_width = NA, file_height = NA, 
                           file_size_units = 'in', add = FALSE) {
  
  library(ggplot2)
  if (class(var) == 'multiCDM') {
    # Store the multidata in a seperate object for convenience
    metadata <- attributes(var)
    dimnames <- metadata$dimensionNames
  } else if (is.numeric(var) && is.array(var) && length(var) > 0) {
    if (all(is.na(var))) {
      stop("All values in 'var' are NA.")
    } 
  } 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'.")
    }
  }
  if (is.null(ref_dimnames)) {
    ref_dimnames <- c('dataset', 'var', 'level', 'member', 'sdate', 'ftime', 
                      'lat', 'lon', 'test')  
  }
  
  # 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')
  
  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
  }
  curves_along <- CheckDimensionSpec(curves_along, dimnames, 
                                     ref_dimnames[6:5])
  if (is.null(interval_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)
  }
  
  if (is.null(mean_along) && all(ref_dimnames[5:6] %in% dimnames)) {
    mean_along <- ref_dimnames[5]
  }
  mean_along <- CheckDimensionSpec(mean_along, dimnames)
  
  # Check interval_along
  if (is.null(interval_along) && all(ref_dimnames[c(6, 9)] %in% dimnames)) {
    interval_along <- ref_dimnames[9]
  }
  interval_along <- CheckDimensionSpec(interval_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 error_type
  if (!interval_type %in% c('errorbar', 'line')) {
    stop("Parameter 'interval_type' must be either 'line' or 'errobar'.")
  }
  
  # Check lower, test and upper
  if (is.numeric(lower)) {
    lower <- round(lower)
  } else {
    stop("Parameter 'lower' must be numeric.")
  }
  if (is.numeric(test)) {
    test <- round(test)
  } else {
    stop("Parameter 'test' must be numeric.")
  }
  if (is.numeric(upper)) {
    upper <- round(upper)
  } else {
    stop("Parameter 'upper' must be numeric.")
  }
  
  # Check horizontal line
  if (!is.na(intercept) & !is.numeric(intercept)) {
    stop("Parameter 'intercept' must be either NA or numeric.")
  }
  is.POSIXct <- function(date) 'POSIXct' %in% class(date)
  # Check first_date
  if (is.null(first_date)) {
    first_date <- as.POSIXct('00000101', format = '%Y%m%d')
  }
  if (!is.POSIXct(first_date)) {
    stop("Parameter 'first_date' must be a POSIXct object.")
  }
  if (is.null(period)) {
    period <- 1
  }
  if (is.numeric(period)) {
    period <- paste(period, 'month')
  } else if (!is.character(period)) {
    stop("Parameter 'period' must be numeric or a character string.")
  }
  # Check sdate_period
  if (is.null(sdate_period)) {
    ftime_pos <- grep(ref_dimnames[6], dimnames, fixed = TRUE)
    if (length(ftime_pos) > 0) {
      sdate_period <- paste(dims[ftime_pos] * as.numeric(strsplit(period, ' ')[[1]][1]),
                            strsplit(period, ' ')[[1]][2])
    } else {
      sdate_period <- 1
    }
  }
  if (is.numeric(sdate_period)) {
    sdate_period <- paste(sdate_period, 'year')
  } else if (!is.character(sdate_period)) {
    stop("Parameter 'sdate_period' must be numeric or a character string.")
  }
  
  # 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:dims[x_axis_dim]
    # 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 {
    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 (length(sdate_pos) > 0) {
      # Reorder with sdate as right-most dim
      reorder <- c(reorder[-sdate_pos], sdate_pos)
    }
    # If ftime is one of the dimensions
    if (length(ftime_pos) > 0) {
      # Reorder with ftime as right-most dim
      reorder <- c(reorder[-(ftime_pos - ifelse(length(sdate_pos) > 0, 1, 0))], ftime_pos)
      n_ftime <- dims[ftime_pos]
      # If sdate is one of the dimensions
    }
    
    var <- var[do.call(order, lapply(reorder, function(x) `[[`(var, x))), ]
    if ('hasStartyear' %in% names(metadata$Dataset$TemporalPeriod)) {
      
      offset <- (metadata$Dataset$TemporalPeriod$hasEndyear[1] - metadata$Dataset$TemporalPeriod$hasStartyear[1]) / 2
      var$date <- metadata$Dataset$TemporalPeriod$hasStartyear + offset
      sdates <- metadata$Dataset$TemporalPeriod$hasStartyear[seq(1, length(metadata$Dataset$TemporalPeriod$hasStartyear), n_ftime)]
    } else {
      if (is.null(sdates)) {
        if (length(sdate_pos) > 0) {
          sdates <- seq(first_date, length = dims[sdate_pos], by = sdate_period)
        } else {
          sdates <- first_date
        }
      }
      if (length(ftime_pos) > 0) {
        var$date <- do.call(c, lapply(sdates, seq, length = n_ftime, by = period))
      } else {
        var$date <- sdates
      }
    }
  }
  if (is.null(title)) {
    title <- NA
    if (!is.null(metadata)) {
      if (!is.null(metadata$Dataset$Variable$hasVariable)) {
        title <- metadata$Dataset$Variable$hasVariable
      }
    }
  } 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) && metadata$Step == "Verification") {
    xtitle <- "Forecast time"
  }
  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$Command)) {
      ytitle <- metadata$Command$generatedbyCommand
    }
  } 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()
  
  if (!is.na(interval_along) || !is.na(error_bar_along)) {
    
    if (is.na(colour_along)) {
      if (add) {
        colour_along <- 'blue'
      } else {
        colour_along <- 'red'
      }
    }
    if (is.numeric(colour_along)) {
      colour_along <- var[, colour_along]
    }
     
    if (interval_type == "errorbar") {
      g <- g + geom_point(aes_string(x = var$date[var$test == "test"], y = var$value[var$test == "test"]), colour = colour_along) + 
        geom_errorbar(aes_string(x = var$date[var$test == "test"], ymin = var$value[var$test == "conf_low"], 
                                 ymax = var$value[var$test == "conf_high"]), colour = colour_along)
      colour_along <- NA
    } else if (interval_type == "line") {
      g <- g + geom_line(aes_string(x = var$date[var$test == "test"], y = var$value[var$test == "test"]), colour = colour_along) + 
        geom_line(aes_string(x = var$date[var$test == "test"], y = var$value[var$test == "conf_low"]), colour = colour_along, linetype = 2) +  
        geom_line(aes_string(x = var$date[var$test == "test"], 
                             y = var$value[var$test == "conf_high"]), colour = colour_along, linetype = 2) 
      colour_along <- NA
    }
  } else {
    g <- ggplot()
    plot_aes <- aes_string(x = var$date, y = var$value, group = interaction(var[, interact_along]))
    if (!is.na(colour_along)) {
      plot_aes[['colour']] <- var[, colour_along]
    }
    if (!is.null(style_along)) {
      plot_aes[['linetype']] <-  var[, style_along]
    }
    if (!is.null(shape_along)) {
      plot_aes[['shape']] <-  var[, shape_along]
    }
    if (!is.na(curves_along)) {
      g <- g + geom_line(plot_aes, size = 0.4)
    } 
    if (points) {
      g <- g + geom_point(data = var, plot_aes, size = 2)
    }
    
    
    # Draw mean line
    if (!is.na(mean_along)) {
      g <- g + stat_summary(data = var, fun.y= "mean", mapping = aes_string(x = var$date, y = var$value,
                                                                            group = var[, mean_along], 
                                                                            colour = var[, mean_along], 
                                                                            fill = var[, mean_along]),
                            geom = "line", size = 1, show.legend = FALSE)
    }   
    if (is.null(minmax_along) & !is.na(mean_along)) {
      minmax_along <- mean_along
    }
    if (!is.na(minmax_along)) {
      g <- g +  stat_summary(data = var, geom = "ribbon", fun.ymin = "min", fun.ymax = "max", aes(x = var$date, y = var$value, group = var[, minmax_along], 
                                                                                                  colour = var[, minmax_along], fill = var[, minmax_along]), alpha = 0.3, show.legend = FALSE)
    }
  }
  
  if (!add) {
    if (is.numeric(intercept)) {
      g <- g + geom_hline(yintercept = intercept, size = 0.3, linetype = 2)
    }
  }
  g <- g + labs(title = na.omit(title)) + xlab(xtitle) + ylab(ytitle)
  # Adjust scale for colour
  
  if (!is.na(colour_along)) {
    g <- g + labs(colour = 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)
  }
  if (!is.null(style_along)) {
    if (dimnames[style_along] == ref_dimnames[4]) {
      if (!is.null(metadata$Datasets)) {
        labels <- names(metadata$Datasets[[1]]$InitializationDates)
      } else {
        labels <- paste(ref_dimnames[4], 1:dims[style_along])
      }
      # If the dimension styled along is the sdates
    } else if (dimnames[style_along] == ref_dimnames[5]) {
      if ('Dates' %in% names(metadata)) {
        labels <- sdates
      } else {
        labels <- paste(ref_dimnames[5], 1:dims[style_along])
      } 
    }
    g <- g + scale_linetype_discrete(dimnames[style_along],labels = labels)
  }
  if (!is.null(shape_along)) {
    if (dimnames[shape_along] == ref_dimnames[4]) {
      if (!is.null(metadata$Datasets)) {
        labels <- names(metadata$Datasets[[1]]$InitializationDates)
      } else {
        labels <- paste(ref_dimnames[4], 1:dims[shape_along])
      }
      # If the dimension shaped along is the sdates
    } else if (dimnames[shape_along] == ref_dimnames[5]) {
      if ('Dates' %in% names(metadata)) {
        labels <- sdates
      } else {
        labels <- paste(ref_dimnames[5], 1:dims[shape_along])
      }
    }
    g <- g + scale_shape_discrete(dimnames[shape_along],labels = labels)
  }
  
  if (add) {
    g <- g$layers 
  }
  
  # 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
  }
} 

  
  
  
  