#'Conversion of 'startR_array' or 'list' objects to 's2dv_cube'
#'
#'This function converts data loaded using startR package or s2dv 
#'Load function into a 's2dv_cube' object.
#'
#'@author Perez-Zanon Nuria, \email{nuria.perez@bsc.es}
#'@author Nicolau Manubens, \email{nicolau.manubens@bsc.es}
#'
#'@param object An object of class 'startR_array' generated from function 
#'  \code{Start} from startR package or a list output from function \code{Load} 
#'  from s2dv package. Any other object class will not be accepted.
#'@param remove_attrs_coords A logical value indicating whether to remove the 
#'  attributes of the coordinates (TRUE) or not (FALSE) when the data is loaded 
#'  from Start(). It is TRUE by default.
#'@param remove_null A logical value indicating whether to remove the elements 
#'  that are NULL (TRUE) or not (FALSE) of the output object. It is TRUE by 
#'  default.
#' 
#'@return The function returns an 's2dv_cube' object to be easily used with 
#'functions \code{CST} from CSTools and CSIndicators packages. The object is 
#'mainly a list with the following elements:\cr
#'\itemize{
#'  \item{'data', array with named dimensions.}
#'  \item{'dims', named vector of the data dimensions.}
#'  \item{'coords', named list with elements of the coordinates corresponding to
#'  the dimensions of the data parameter. If any coordinate is not provided, it 
#'  is set as an index vector with the values from 1 to the length of the 
#'  corresponding dimension. The attribute 'indices' indicates wether the 
#'  coordinate is an index vector (TRUE) or not (FALSE).}
#'  \item{'attrs', named list with elements:
#'    \itemize{
#'      \item{'Dates', array with named temporal dimensions of class 'POSIXct' from 
#'      time values in the data.}
#'      \item{'Variable', has the following components:
#'        \itemize{
#'          \item{'varName', with the short name of the loaded variable as specified 
#'          in the parameter 'var'.}
#'          \item{'level', with information on the pressure level of the variable. 
#'          Is kept to NULL by now.}
#'        }
#'      }
#'      \item{'Datasets', character strings indicating the names of the dataset.}
#'      \item{'source_files', a vector of character strings with complete paths to 
#'      all the found files involved in loading the data.}
#'      \item{'when', a time stamp of the date issued by the Start() or Load() call to 
#'      obtain the data.}
#'      \item{'load_parameters', it contains the components used in the arguments to 
#'      load the data from Start() or Load() functions.}
#'    }
#'  }
#'}
#'
#'@seealso \code{\link{s2dv_cube}}, \code{\link[s2dv]{Load}}, 
#'\code{\link[startR]{Start}} and \code{\link{CST_Load}}
#'@examples
#'\dontrun{
#'library(startR)
#'repos <- '/esarchive/exp/ecmwf/system5_m1/monthly_mean/$var$_f6h/$var$_$sdate$.nc'
#'data <- Start(dat = repos,
#'              var = 'tas',
#'              sdate = c('20170101', '20180101'),
#'              ensemble = indices(1:5),
#'              time = 'all',
#'              latitude = indices(1:5),
#'              longitude = indices(1:5),
#'              return_vars = list(latitude = 'dat', longitude = 'dat', time = 'sdate'),
#'              retrieve = TRUE)
#'data <- as.s2dv_cube(data)
#'class(data)
#'startDates <- c('20001101', '20011101', '20021101',
#'                '20031101', '20041101', '20051101')
#'data <- Load(var = 'tas', exp = 'system5c3s', 
#'             nmember = 2, sdates = startDates,
#'             leadtimemax = 3, latmin = 10, latmax = 30,
#'             lonmin = -10, lonmax = 10, output = 'lonlat')
#'data <- as.s2dv_cube(data)
#'class(data)
#'}
#'@export
as.s2dv_cube <- function(object, remove_attrs_coords = TRUE, remove_null = TRUE) {
  
  if (is.list(object) & length(object) == 11) {
    if (is.null(object) || (is.null(object$mod) && is.null(object$obs))) {
      stop("The s2dv::Load call did not return any data.")
    }

    obs <- object
    obs$mod <- NULL
    object$obs <- NULL
    names(object)[[1]] <- 'data' # exp
    names(obs)[[1]] <- 'data' # obs

    # functions
    remove_matches <- function(v, patterns) {
      if (length(v) > 0) {
        matches <- c()
        for (pattern in patterns) {
          matches <- c(matches, which(grepl(pattern, v)))
        }
        if (length(matches) > 0) {
          v <- v[-matches]
        }
      }
      v
    }

    harmonize_patterns <- function(v) {
      matches <- grepl('.*\\.nc$', v)
      if (sum(!matches) > 0) {
        match_indices <- which(!matches)
        v[match_indices] <- sapply(v[match_indices], function(x) paste0(x, '*'))
      }
      v <- glob2rx(v)
      v <- gsub('\\$.*\\$', '*', v)
      v
    }

    # obs
    if (!is.null(obs$data)) {
      obs_exist <- TRUE
      obs$Datasets$exp <- NULL
      obs$Datasets <- obs$Datasets$obs
      obs_path_patterns <- sapply(obs$Datasets, function(x) attr(x, 'source'))
      obs_path_patterns <- harmonize_patterns(obs_path_patterns)
    } else {
      obs_exist <- FALSE
    }

    # object
    if (!is.null(object$data)) {
      exp_exist <- TRUE
      object$Datasets$obs <- NULL
      object$Datasets <- object$Datasets$exp
      exp_path_patterns <- sapply(object$Datasets, function(x) attr(x, 'source'))
      exp_path_patterns <- harmonize_patterns(exp_path_patterns)
    } else {
      exp_exist <- FALSE
    }

    if (!is.null(obs$data) && !is.null(object$data)) {
      obs$source_files <- remove_matches(obs$source_files, exp_path_patterns)
      obs$not_found_files <- remove_matches(obs$not_found_files, exp_path_patterns)
  
      object$source_files <- remove_matches(object$source_files, obs_path_patterns)
      object$not_found_files <- remove_matches(object$not_found_files, obs_path_patterns)
    }  

    result <- list()

    # obs and exp
    if (obs_exist & exp_exist) {
      obs_exp = list(obs = obs, exp = object)
    } else if (obs_exist & !exp_exist) {
      obs_exp = list(obs = obs)
    } else {
      obs_exp = list(exp = object)
    }

    i <- 0
    for (obj_i in obs_exp) {
      i <- i + 1
      # attrs
      obj_i$attrs <- within(obj_i, rm(list = c('data')))
      obj_i <- within(obj_i, rm(list = names(obj_i$attrs)))
      dates <- obj_i$attrs$Dates$start
      if (!is.null(dates)) {
        dim(dates) <- dim(obj_i$data)[c('ftime', 'sdate')]
        obj_i$attrs$Dates <- dates
      }
      # dims
      obj_i$dims <- dim(obj_i$data)
      # coords
      obj_i$coords <- sapply(names(dim(obj_i$data)),function(x) NULL)
      # sdate
      obj_i$coords$sdate <- obj_i$attrs$load_parameters$sdates
      attr(obj_i$coords$sdate, 'indices') <- FALSE
      # lon
      if (!is.null(obj_i$attrs$lon)) {
        if (isTRUE(remove_attrs_coords)) {
          obj_i$coords$lon <- as.vector(obj_i$attrs$lon)
        } else {
          obj_i$coords$lon <- obj_i$attrs$lon
        }
        attr(obj_i$coords$lon, 'indices') <- FALSE
        obj_i$attrs <- within(obj_i$attrs, rm(lon))
      }
      # lat
      if (!is.null(obj_i$attrs$lat)) {
        if (isTRUE(remove_attrs_coords)) {
          obj_i$coords$lat <- as.vector(obj_i$attrs$lat)
        } else {
          obj_i$coords$lat <- obj_i$attrs$lat
        }
        attr(obj_i$coords$lat, 'indices') <- FALSE
        obj_i$attrs <- within(obj_i$attrs, rm(lat))
      }
      # member
      obj_i$coords$member <- 1:obj_i$dims['member']
      attr(obj_i$coords$member, 'indices') <- TRUE
      # dataset
      obj_i$coords$dataset <- 1:obj_i$dims['dataset']
      attr(obj_i$coords$dataset, 'indices') <- TRUE
      # ftime
      obj_i$coords$ftime <- 1:obj_i$dims['ftime']
      attr(obj_i$coords$ftime, 'indices') <- TRUE
      
      # remove NULL values
      if (isTRUE(remove_null)) {
        obj_i <- .rmNullObs(obj_i)
      }

      obj_i <- obj_i[c('data','dims','coords','attrs')]
      class(obj_i) <- 's2dv_cube'

      if (names(obs_exp)[[i]] == 'exp') {
        result$exp <- obj_i
      } else {
        result$obs <- obj_i
      }
    }

    if (is.list(result)) {
      if (is.null(result$exp)) {
          result <- result$obs
      } else if (is.null(result$obs)) {
          result <- result$exp
      } else {
          warning("The output is a list of two 's2dv_cube' objects",
                  " corresponding to 'exp' and 'obs'.")
      }
    }
  } else if (inherits(object,'startR_array')) {
    # From Start:
    result <- list()
    result$data <- as.vector(object)

    ## dims
    dims <- dim(object)
    dim(result$data) <- dims
    result$dims <- dims

    ## coords
    result$coords <- sapply(names(dims), function(x) NULL)

    # Find coordinates
    FileSelector <- attributes(object)$FileSelectors
    VariablesCommon <- names(attributes(object)$Variables$common)
    dat <- names(FileSelector)[1]
    VariablesDat <- names(attributes(object)$Variables[[dat]])
    varName <- NULL

    for (i_coord in names(dims)) {
      if (i_coord %in% names(FileSelector[[dat]])) { # coords in FileSelector
        coord_in_fileselector <- FileSelector[[dat]][which(i_coord == names(FileSelector[[dat]]))]
        if (length(coord_in_fileselector) == 1) {
          if (length(coord_in_fileselector[[i_coord]][[1]]) == dims[i_coord]) {
            if (i_coord %in% c('var', 'vars')) {
              varName <- as.vector(coord_in_fileselector[[i_coord]][[1]])
            }
            if (remove_attrs_coords) {
              result$coords[[i_coord]] <- as.vector(coord_in_fileselector[[i_coord]][[1]])
            } else {
              result$coords[[i_coord]] <- coord_in_fileselector[[i_coord]][[1]]
            }
            attr(result$coords[[i_coord]], 'indices') <- FALSE
          } else {
            result$coords[[i_coord]] <- 1:dims[i_coord]
            attr(result$coords[[i_coord]], 'indices') <- TRUE
          }
        } else {
          print(paste0("Length of coordinate", i_coord, "in FileSelector is more than 1.")) # Can this be TRUE?
        }
      } else if (i_coord %in% VariablesCommon) { # coords in common
        coord_in_common <- attributes(object)$Variables$common[[which(i_coord == VariablesCommon)]]
        if (inherits(coord_in_common, "POSIXct")) {
          result$attrs$Dates <- coord_in_common
        }
        if (length(coord_in_common) == dims[i_coord]) {
          if (remove_attrs_coords) {
            if (inherits(coord_in_common, "POSIXct")) {
              result$coords[[i_coord]] <- coord_in_common
            } else {
              result$coords[[i_coord]] <- as.vector(coord_in_common)
            }
          } else {
            result$coords[[i_coord]] <- coord_in_common
          }
          attr(result$coords[[i_coord]], 'indices') <- FALSE
        } else {
          result$coords[[i_coord]] <- 1:dims[i_coord]
          attr(result$coords[[i_coord]], 'indices') <- TRUE
        }
      } else if (!is.null(VariablesDat)) { # coords in dat
        if (i_coord %in% VariablesDat) {
          coord_in_dat <- attributes(object)$Variables[[dat]][[which(i_coord == VariablesDat)]]
          if (inherits(coord_in_dat, "POSIXct")) {
            result$attrs$Dates <- coord_in_dat
          }
          if (length(coord_in_dat) == dims[i_coord]) {
            if (remove_attrs_coords) {
              if (inherits(coord_in_dat, "POSIXct")) {
                result$coords[[i_coord]] <- coord_in_dat
              } else {
                result$coords[[i_coord]] <- as.vector(coord_in_dat)
              }
            } else {
              result$coords[[i_coord]] <- coord_in_dat
            }
            attr(result$coords[[i_coord]], 'indices') <- FALSE
          } else {
            result$coords[[i_coord]] <- 1:dims[i_coord]
            attr(result$coords[[i_coord]], 'indices') <- TRUE
          }
        } else {
          result$coords[[i_coord]] <- 1:dims[i_coord]
          attr(result$coords[[i_coord]], 'indices') <- TRUE
        }
      } else { # missing other dims
        result$coords[[i_coord]] <- 1:dims[i_coord]
        attr(result$coords[[i_coord]], 'indices') <- TRUE
      }
    }
    # attrs
    ## varName
    if (!is.null(varName)) {
      result$attrs$Variable$varName <- varName
    }

    ## Variables
    for (var_type in names(attributes(object)$Variables)) {
      if (!is.null(attributes(object)$Variables[[var_type]])) {
        for (var in names(attributes(object)$Variables[[var_type]])) {
          attr_variable <- attributes(object)$Variables[[var_type]][[var]]
          if (is.null(result$attrs$Dates)) {
            if (inherits(attr_variable, "POSIXct")) {
              result$attrs$Dates <- attr_variable
            }
          }
          result$attrs$Variable$variables[[var]] <- attr_variable
        }
      }
    }

    ## Datasets
    if (length(names(FileSelector)) > 1) {
      # lon name
      known_lon_names <- .KnownLonNames()
      lon_name_dat <- names(dims)[which(names(dims) %in% known_lon_names)]
      # lat name
      known_lat_names <- .KnownLatNames()
      lat_name_dat <- names(dims)[which(names(dims) %in% known_lat_names)]
      result$attrs$Datasets <- names(FileSelector)
      for (i in 2:length(names(FileSelector))) {
        if (!is.null(lon_name_dat)) {
          if (any(result$coords[[lon_name_dat]] != as.vector(attributes(object)$Variables[[names(FileSelector)[i]]][[lon_name_dat]]))) {
            warning("'lon' values are different for different datasets. Only values from the first will be used.")
          }
        }
        if (!is.null(lat_name_dat)) {
          if (any(result$coords[[lat_name_dat]] != as.vector(attributes(object)$Variables[[names(FileSelector)[i]]][[lat_name_dat]]))) {
            warning("'lat' values are different for different datasets. Only values from the first will be used.")
          }
        }
      }
    } else {
      result$attrs$Datasets <- names(FileSelector)
    }

    ## when
    result$attrs$when <- Sys.time()

    ## source_files
    result$attrs$source_files <- attributes(object)$Files

    ## load_parameters
    result$attrs$load_parameters <- attributes(object)$FileSelectors
    
    ## remove NULL values
    if (isTRUE(remove_null)) {
      result <- .rmNullObs(result)
    }
    class(result) <- 's2dv_cube'
  } else {
    stop("The class of parameter 'object' is not implemented",
         " to be converted into 's2dv_cube' class yet.")
  }
  return(result)
}