#'Plot map in Robinson projection or others
#'@description Transform a regular grid to a new projection (Robinson) and display it
#'
#'@param data an array (only draws a map each time)
#'@param original_proj the data projection by default long-lat regular WGS 84               
#'@param target_proj the desired projection by default Robinson 
#'
#'@param legend possibilities are NULL (no color bar), 's2dv' adds ColorBar, 'ggplot2' 
#'  the result from ggplot2 and 'test' that makes a filled scale
#'@param size_units Units of the size of the device (file or window) to plot 
#'  in. Inches ('in') by default. See ?Devices and the creator function of 
#'  the corresponding device.

#'@param ... extra arguments for s2dv::ColorBar
#'@import sf ggplot2 rnaturalearth cowplot
PlotRobinson <- function(data, lon, lat, target_proj = "+proj=robin", 
                         brks = NULL, cols = NULL, colNA = NULL, style = 'point', units = NULL,
                         data_size = "auto", coastlines_width = 0.3,
                         dots = NULL, dots_shape = 47, dots_size = 0.5,
                         mask = NULL,
                         toptitle = NULL, caption = NULL,
                         fileout = NULL, legend = NULL,
                         lon_dim = NULL, lat_dim = NULL, crop_coastlines = NULL,
                         width = 8, height = 4, dpi = 300, size_units = "in", 
                         device = 'png', triangle_ends = NULL,
                         bar_extra_margin = rep(0, 4),
                         col_inf = NULL, col_sup = NULL, color_fun = clim.palette(),
                         vertical = TRUE,
                         ...) {
  data <- drop(data)
  dims <- dim(data)
  if (length(dims) > 2) {
    stop("Parameter 'data' has more than two dimension.")
  }
  # data_size
  if (data_size == 'auto') {
    # 360x181 with default setting, 0.05
    data_size <- round(0.05 * (360 * 181) / (length(lon) * length(lat)), 2)
  } else if (!is.numeric(data_size)) {
    stop("Parameter 'data_size' must be a number.")
  }

  if (is.null(lon_dim)) {
    lon_dim <- names(dim(data))[names(dim(data)) %in% .KnownLonNames()]
    if (identical(lon_dim, character(0))) {
      stop("Cannot find known longitude name in data dimension. Please define parameter 'lon_dim'.")
    }
  }
  if (is.null(lat_dim)) {
    lat_dim <- names(dim(data))[names(dim(data)) %in% .KnownLatNames()]
    if (identical(lat_dim, character(0))) {
      stop("Cannot find known latitude name in data dimension. Please define parameter 'lat_dim'.")
    }
  }
  if (!all(names(dims) %in% c(lat_dim, lon_dim))) {
    stop("Dimensions names in paramter 'data' should match 'lat_dim' and 'lon_dim.")
  }
  if (all(names(dims) != c(lon_dim, lat_dim))) {
    data <- s2dv::Reorder(data, c(lon_dim, lat_dim))
  }
  if (length(lon) != dim(data)[lon_dim]) {
    stop("Length of parameter 'lon' should match dimension 'lon_dim' in 'data'.")
  }
  if (length(lat) != dim(data)[lat_dim]) {
    stop("Length of parameter 'lat' should match dimension 'lat_dim' in 'data'.")
  }
  # original_proj: it can only be regular grid now
  original_proj <- st_crs(4326)

  if (is.null(target_proj)) {
    stop("Parameter 'target_proj' cannot be NULL.")
  } else {
    target_proj <- st_crs(target_proj)
  }
  if (is.unsorted(lon)) {
    warning("Parameter 'lon' should be sorted to guarantee the correct result.")
  }
  # Make lat always from 90 to -90
  sort_lat <- FALSE
  if (!is.unsorted(lat)) {
    lat <- rev(lat)
    data <- ClimProjDiags::Subset(data, along = lat_dim, indices = seq(length(lat), 1, -1))
    sort_lat <- TRUE
  }
#  if (length(brks) != length(cols) + 1) {
#    stop("Parameter 'brks' should be more one than the number of colors in 'col'.")
#  }

  # Create colorbar parameters
  var_limits <- c(min(data, na.rm = TRUE), max(data, na.rm = TRUE))
  colorbar <- ColorBar(brks = brks, cols = cols, vertical = vertical, subsampleg = NULL,
                       bar_limits = NULL, var_limits = var_limits, triangle_ends = triangle_ends, 
                       col_inf = col_inf, col_sup = col_sup, color_fun = color_fun, 
                       plot = FALSE, draw_ticks = TRUE, draw_separators = FALSE,
                       triangle_ends_scale = 1, extra_labels = NULL,
                       title = units, title_scale = 1, # units_scale
                       label_scale = 1, tick_scale = 1, #bar_tick_scale
                       extra_margin = bar_extra_margin, label_digits = 4)
  brks <- colorbar$brks
  cols <- colorbar$cols
  col_inf <- colorbar$col_inf
  col_sup <- colorbar$col_sup
  bar_limits <- c(head(brks, 1), tail(brks, 1))

  # colNA
  if (is.null(colNA)) {
    if ('na_color' %in% names(attributes(cols))) {
      colNA <- attr(cols, 'na_color')
      if (!.IsColor(colNA)) {
        stop("The 'na_color' provided as attribute of the colour vector must be a valid colour identifier.")
      }
    } else {
      colNA <- 'pink'
    }
  } else if (!.IsColor(colNA)) {
    stop("Parameter 'colNA' must be a valid colour identifier.")
  }
  # dots
  if (!is.null(dots)) {
    dots <- drop(dots)
    if (!identical(dim(dots), dim(data))) {
      stop("Parameter 'dots' must have the same dimensions as 'data'.")
    } else if (is.numeric(dots) && (all(dots %in% c(0, 1)))) {
      dots <- array(as.logical(dots), dim = dim(dots))
    } else if (!is.logical(dots)) {
      stop("Parameter 'dots' must be logical or array of 0 and 1.")
    }
    dots <- Reorder(dots, c(lon_dim, lat_dim))
  }
  # mask
  if (!is.null(mask)) {
    mask <- drop(mask)
    if (!identical(dim(mask), dim(data))) {
      stop("Parameter 'mask' must have the same dimensions as 'data'.")
    } else if (is.numeric(mask) && (all(mask %in% c(0, 1)))) {
      mask <- array(as.logical(mask), dim = dim(mask))
    } else if (!is.logical(mask)) {
      stop("Parameter 'mask' must be logical or array of 0 and 1.")
    }
    mask <- Reorder(mask, c(lon_dim, lat_dim))
  }
  # legend
  if (!is.null(legend) && (!legend %in% c('s2dv', 'ggplot2'))) {
    stop("Parameter 'legend' must be NULL, ggplot2 or s2dv.")
  }

  # Adapt s2dv ColorBar parameters to ggplot plot
  # If legend is NULL, still tune with s2dv legend way
  if (is.null(legend) || legend == 's2dv') {
    # the colorbar triangle color. If it is NULL (no triangle plotted), use colNA
    col_inf_image <- ifelse(is.null(col_inf), colNA, col_inf)
    col_sup_image <- ifelse(is.null(col_sup), colNA, col_sup)
    cols_ggplot <- c(col_inf_image, cols, col_sup_image)
    # Add brks to triangles
    step_brk <- abs(brks[2] - brks[1])
    brks_ggplot <- c(min(brks) - step_brk, brks, max(brks) + step_brk)

  } else {  # ggplot2 legend
    brks_ggplot <- brks
    cols_ggplot <- cols
  }

  # Build data dataframe
  lonlat_df <- data.frame(lon = rep(as.vector(lon), length(lat)),
                          lat = sort(rep(as.vector(lat), length(lon)), decreasing = TRUE))
  data_df <- lonlat_df %>%
    dplyr::mutate(dat = as.vector(data))

  # Remove the points where mask = FALSE
  if (!is.null(mask)) {
    if (sort_lat) {
      mask <- ClimProjDiags::Subset(mask, along = lat_dim, indices = seq(length(lat), 1, -1))
    }
    mask_df <- data.frame(lon = rep(as.vector(lon), length(lat)),
                          lat = sort(rep(as.vector(lat), length(lon)), decreasing = TRUE),
                          mask = as.vector(mask))
    data_df <- data_df[mask_df$mask == TRUE, ]
    lonlat_df <- data_df[, 1:2]
  }

  #NOTE: if target_proj = "ESRI:54030", Nord3v2 has different behavior from hub and ws!!
  data_df <- st_as_sf(data_df, coords = c("lon", "lat"), crs = original_proj)
  data_df <- st_transform(data_df, crs = target_proj)
  data_df <- data_df %>%
    dplyr::mutate(long = st_coordinates(data_df)[, 1],
                  lat = st_coordinates(data_df)[, 2])

  # Re-project dots
  if (!is.null(dots)) {
    if (sort_lat) {
      dots <- ClimProjDiags::Subset(dots, along = lat_dim, indices = seq(length(lat), 1, -1))
    }
    dots_df <- data.frame(lon = rep(as.vector(lon), length(lat)),
                          lat = sort(rep(as.vector(lat), length(lon)), decreasing = TRUE),
                          dot = as.vector(dots))

    dots_df <- st_as_sf(dots_df, coords = c("lon", "lat"), crs = original_proj)
    dots_df <- st_transform(dots_df, crs = target_proj)
    dots_df <- dots_df %>%
      dplyr::mutate(long = st_coordinates(dots_df)[, 1],
                    lat = st_coordinates(dots_df)[, 2])
    dots_df <- subset(dots_df, dot == FALSE)
  }

  # coastlines
  coastlines <- rnaturalearth::ne_coastline(scale = "medium", returnclass = "sf")
  ## crop the coastlines to the desired range
  if (!is.null(crop_coastlines)) {
    # if crop_coastlines doesn't have name, [lonmin, lonmax, latmin, latmax]
    if (is.null(names(crop_coastlines))) {
      names(crop_coastlines) <- c("lonmin", "lonmax", "latmin", "latmax")
    } else if (!identical(sort(names(crop_coastlines)), sort(c("latmax", "latmin", "lonmax", "lonmin")))) {
      stop("Parameter 'crop_coastlines' needs to have names 'latmax', 'latmin', 'lonmax', 'lonmin'.")
    }
    suppressWarnings({
    coastlines <- st_crop(coastlines, 
                          xmin = as.numeric(crop_coastlines['lonmin']), 
                          xmax = as.numeric(crop_coastlines['lonmax']), 
                          ymin = as.numeric(crop_coastlines['latmin']), 
                          ymax = as.numeric(crop_coastlines['latmax']))
    })
  }
  coastlines <- st_transform(coastlines, crs = target_proj) 

  if (style == 'polygon') {
  # Calculate polygon points from regular lat/lon
  #NOTE: The original grid must be regular grid with same space
  d_lon <- abs(lon[2] - lon[1]) / 2
  d_lat <- abs(lat[2] - lat[1]) / 2
  lon_poly <- lat_poly <- rep(NA, 4 * dim(lonlat_df)[1])
  for (ii in 1:dim(lonlat_df)[1]) {
    lon_poly[(ii*4-3):(ii*4)] <- c(lonlat_df$lon[ii] - d_lon, lonlat_df$lon[ii] + d_lon,
                                   lonlat_df$lon[ii] + d_lon, lonlat_df$lon[ii] - d_lon)
    lat_poly[(ii*4-3):(ii*4)] <- c(lonlat_df$lat[ii] - d_lat, lonlat_df$lat[ii] - d_lat,
                                   lonlat_df$lat[ii] + d_lat, lonlat_df$lat[ii] + d_lat)
  }
#  # To prevent out-of-global lon
#  lon_poly[which(lon_poly >= 180)] <- 179.9
#  lon_poly[which(lon_poly < -180)] <- -180
  # To prevent out-of-global lat
  lat_poly[which(lat_poly > 90)] <- 90
  lat_poly[which(lat_poly < -90)] <- -90

  lonlat_df <- data.frame(lon = lon_poly, lat = lat_poly)
  # Transfer lon/lat to projection
  proj_lonlat <- st_as_sf(lonlat_df, coords = c("lon", "lat"), crs = original_proj)
  #NOTE: st_transform doesn't work for Robinson & Mollweide on Nord3v2!
  proj_lonlat <- st_transform(proj_lonlat, crs = target_proj)
  lonlat_df_proj <- st_coordinates(proj_lonlat)

  # Use id to create groups for each polygon
  ids <- factor(paste0("id_", 1:dim(data_df)[1]))
  values <- data.frame(id = ids,
                       value = data_df$dat)
  positions <- data.frame(id = rep(ids, each = 4),
                          x = lonlat_df_proj[, 1],
                          y = lonlat_df_proj[, 2])
  datapoly <- merge(values, positions, by = "id")
  datapoly <- st_as_sf(datapoly, coords = c("x", "y"), crs = target_proj)
  datapoly <- datapoly %>%
              dplyr::group_by(id) %>%
              dplyr::summarise() %>%  #NOTE: VERY SLOW if plot global
              dplyr::mutate(value = values[order(values$id), ]$value) %>%
              st_cast("POLYGON") %>%
              st_convex_hull()  # maintain outer polygen (no bowtie shape)
  }

  # Plots
  if (style == 'polygon') {
    res_p <- ggplot(data = data_df) +  #NOTE: must be data_df?
             geom_sf(data = datapoly,
                     aes(col = cut(value, breaks = brks_ggplot, include.lowest = T),
                         fill = cut(value, breaks = brks_ggplot, include.lowest = T)))
  } else if (style == 'point') {
    res_p <- ggplot(data = data_df) +
             geom_point(aes(x = long, y = lat,
                            col = cut(dat, breaks = brks_ggplot, include.lowest = T)),
                        #NOTE: These two lines make point size vary with lat
                        #size = data_size /  (data_df$lat / min(data_df$lat))) +
                        #size = (sort(rep(as.vector(lat), length(lon))) / max(lat)) * data_size) +
                        size = data_size)
  }
  res_p <- res_p +
           geom_sf(data = coastlines, colour ='black', size = coastlines_width) +
           theme_bw() +
           # crop the projection
           coord_sf(xlim = range(data_df$long), ylim = range(data_df$lat),
                    expand = TRUE, datum = target_proj)

  if (!is.null(dots)) {
    res_p <- res_p + geom_point(data = dots_df, aes(x = long, y = lat),
                                shape = dots_shape, size = dots_size)
                                #NOTE: This line makes point size vary with lat
                                #size = dots_size / (dots_df$lat / min(dots_df$lat)))
  }

  if (legend == 'ggplot2') {
    if (style == 'polygon') {
    res_p <- res_p + scale_colour_manual(values = cols_ggplot,
                                         aesthetics = c("colour", "fill"),
                                         drop = FALSE, na.value = colNA) +
             guides(fill = guide_legend(title = units, override.aes = list(size = 1)),
                    color = "none")
   } else if (style == 'point') {
    res_p <- res_p + scale_colour_manual(values = cols_ggplot,
                                         drop = FALSE, na.value = colNA) +
             guides(colour = guide_legend(title = units, override.aes = list(size = 1)))
   }

  } else {  # s2dv or NULL
    if (style == 'polygon') {
      res_p <- res_p + scale_colour_manual(values = cols_ggplot,
                                           aesthetics = c("colour", "fill"),
                                           drop = FALSE, na.value = colNA)
    } else if (style == 'point') {
      res_p <- res_p + scale_colour_manual(values = cols_ggplot,
                                           drop = FALSE, na.value = colNA)
    }
    res_p <- res_p + theme_void() +  # Remove background grid and lat/lon label
             # Remove ggplot legend
             theme(legend.position = "none", plot.margin = margin(0.5, 0, 0, 0, 'cm'))
  }

  if (!is.null(toptitle)) {
    res_p <- res_p + ggtitle(toptitle) +
      theme(plot.title = element_text(hjust = 0.5, vjust = 3))
  }
  if (!is.null(caption)) {
    res_p <- res_p + labs(caption = caption) +
      theme(plot.caption = element_text(hjust = 0, vjust = 1, margin = margin(0, 0, 0, 0, 'cm')))
  }

  # s2dv legend fun to put in cowplot::plot_grid
  if (identical(legend, 's2dv')) {
    fun_legend <- function() {
      if (vertical) {
        par(mar = c(7.1, 2.2, 7.1, 3.1), mgp = c(3, 1, 0))
      } else {
        par(mar = c(1.1, 1.2, 0.1, 1.1), mgp = c(3, 1, 0))
      }
      ColorBar(brks = brks, cols = cols, vertical = vertical, subsampleg = NULL,
               bar_limits = NULL, var_limits = NULL, triangle_ends = triangle_ends,
               col_inf = col_inf, col_sup = col_sup, color_fun = color_fun,
               plot = TRUE, draw_ticks = TRUE, draw_separators = FALSE,
               triangle_ends_scale = 1, extra_labels = NULL,
               title = units, title_scale = 1, # units_scale
               label_scale = 1, tick_scale = 1, #bar_tick_scale
               extra_margin = bar_extra_margin, label_digits = 4)
    }
    if (vertical) {
      res_p <- cowplot::plot_grid(res_p, fun_legend, rel_widths = c(6, 1))
    } else {
      res_p <- cowplot::plot_grid(res_p, fun_legend, rel_heights = c(5, 1), ncol = 1)
    }
    res_p <- res_p +  theme(plot.background = element_rect(fill = "white"))
  }

  if (!is.null(fileout)) {
    ggsave(fileout, res_p, width = width, height = height, dpi = dpi, units = size_units, 
           device = device)
  } else { # pop-up window
    dev.new(units = size_units, width = width, height = height)
    res_p 
  } 

}

