setClass(Class="citizenair_stations",
         representation=representation(phenomena = "data.frame", 
                                       stations = "data.frame"))
setClass(Class="citizenair_userdata",
         representation=representation(filename = "character", 
                                       file = "character", 
                                       time = "POSIXct", 
                                       content = "list", 
                                       phenomena = "character"))

citizenair_userdata_empty <- function(){
  result <- list()
  result$filename <- NA_character_
  result$file <- NA_character_
  result$time <- Sys.time()
  result$content <- list()
  result$phenomena <- character()
  class(result) <- "citizenair_userdata"
  result
}

#' @title CitizenAir app data
#' @description CitizenAir app data
#' @docType class
#' @field endpoint object of class \code{Endpoint} as returned by \code{as.Endpoint}
#' @field stations object of class \code{citizenair_stations} as returned by \code{\link{read_stations}}
#' @field data object of class \code{citizenair_userdata} as returned by \code{\link{read_citizenair}}
#' @field closest_station a character string with the id of the closest station.
#' @field timeseries timeseries
#' @format The CitizenAir object is an R6Class
#' @section Methods:
#' \describe{
#'   \item{\code{fetch_timeseries(id = "123", time_span)}}{Get timeseries for Station with \code{id} and a certain time_span The argument \code{timespan.} Passed on to \code{sensorweb4R::getData}}
#'   \item{\code{getStations()}}{Get a SpatialPointsDataFrame of all stations}
#'   \item{TODO}{TODO}
#' }
#' @export
#' @examples
#' library(sensorweb4R)
#' e <- as.Endpoint("http://geo.irceline.be/sos/api/v1")
#' ca <- CitizenAir$new(e)
#' ca
#' library(BelgiumMaps.StatBel)
#' data("BE_ADMIN_REGION", package = "BelgiumMaps.StatBel")
#' vlaanderen <- subset(BE_ADMIN_REGION, TX_RGN_DESCR_NL %in% "Vlaams Gewest")
#' ca <- CitizenAir$new(e, stations_area = vlaanderen)
#' ca
#' ca$getPhenomena()
#' ca$getPhenomenaLabel("20")
#' 
#' ## get stations
#' library(sp)
#' x <- ca$getStations()
#' plot(x)
#' plot(ca$getStationsArea())
#' 
#' ## get official measurements
#' library(lubridate)
#' x <- ca$fetch_timeseries(id = "1030")
#' period <- as.interval(as.POSIXct(Sys.Date() - 14), Sys.time())
#' x <- ca$fetch_timeseries(id = "1030", time_span = period)
#' ## set citizen measurements
#' filename <- system.file(package = "citizenair", "data-raw", "citizenair-example.xls")
#' ca$setCitizenData(filename)
#' x <- ca$getCitizenData("meta")
#' x <- ca$getCitizenData("timeseries")
#' x <- ca$getCitizenData("phenomena")
#' x <- ca$getCitizenData("fileinfo")
#' x <- ca$getPhenomena("citizen")
#' ca$getClosestStation()
#' id <- ca$getClosestStation()
#' ca$setComparisonStation(id)
#' x <- ca$fetch_timeseries(id)
#' x <- ca$fetch_timeseries()
CitizenAir <- R6::R6Class("CitizenAir",
                      public = list(
                        endpoint = NULL,
                        stations = NULL, 
                        data = NULL,
                        timeseries = NULL,
                        closest_station = NA_character_,
                        initialize = function(endpoint, stations_area) {
                          self$endpoint <- endpoint
                          self$stations <- read_stations(endpoint)
                          if(!missing(stations_area)){
                            private$stations_area <- stations_area
                            stations_in_region <- self$getStations()
                            stations_in_region <- stations_in_region[sapply(1:nrow(stations_in_region), 
                                                                            FUN=function(idx) rgeos::gIntersects(stations_area, stations_in_region[idx, ])), ]
                            self$stations$stations <- self$stations$stations[self$stations$stations$id %in% stations_in_region$id, ]
                          }
                          self$data <- citizenair_userdata_empty()
                          self$timeseries <- private$default_timeseries
                          self$closest_station <- NA_character_
                        },
                        setCitizenData = function(file, name=basename(file)){
                          x <- read_citizenair(file, name = name)
                          self$setComparisonStation()
                          self$data <- x
                        },
                        setComparisonStation = function(id = NA_character_){
                          self$closest_station <- as.character(id)
                        },
                        setCombinedTimeseries = function(data){
                          if(missing(data)){
                            self$timeseries <- private$default_timeseries 
                          }else if(is.null(data)){
                            self$timeseries <- private$default_timeseries 
                          }else{
                            self$timeseries <- data  
                          }
                          self$timeseries <- setkeyv(self$timeseries, cols = c("type", "timeseries_id", "timeseries_label", "phenomena_id", "phenomena"))
                        },
                        fetch_timeseries = function(id = self$getComparisonStation(), time_span=NULL) {
                          if(length(id) == 0){
                            return(private$default_timeseries)
                          }
                          stopifnot(length(id) == 1)
                          stopifnot(!is.na(id))
                          ## Find and create a stations object
                          x <- self$stations$stations
                          x <- as.data.frame(x)
                          x <- x[x$id %in% id, ]
                          x <- sp::SpatialPointsDataFrame(coords = x[, c("lon", "lat")], 
                                                          data = x, 
                                                          proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))  
                          st <- sensorweb4R::Station(id = x$id, label = x$label, geometry = x, endpoint = self$endpoint)
                          ## Get all metadata of time series of that station
                          ts <- sensorweb4R::timeseries(self$endpoint, station = st)
                          ts <- sensorweb4R::as.Timeseries(ts)
                          ts <- sensorweb4R::fetch(ts)
                          ## Fetch the data of the specified period
                          result <- list()
                          for(i in seq_along(ts)){
                            x <- ts[i]
                            if(is.null(time_span)){
                              to <- sensorweb4R::time(sensorweb4R::lastValue(x))
                              from <- as.POSIXct(seq.Date(from = as.Date(to), by = "-1 year", length.out = 2)[2], tz = "UTC")
                              timespan <- lubridate::as.interval(from, to)  
                            }else{
                              timespan <- time_span
                            }
                            df <- sensorweb4R::getData(x, timespan = timespan)
                            df <- as.data.frame(df)
                            df$date <- as.Date(df$time)
                            df$phenomena_id <- sensorweb4R::id(sensorweb4R::phenomenon(x))
                            df$phenomena <- self$getPhenomenaLabel(df$phenomena_id)
                            df$timeseries_id <- sensorweb4R::id(x)
                            df$timeseries_label <- sensorweb4R::label(x)
                            df$type <- rep("official", nrow(df))
                            result[[i]] <- df[, c("type", "timeseries_id", "timeseries_label", "phenomena_id", "phenomena", "time", "date", "value")]
                          }
                          result <- rbindlist(result)
                          result <- setDT(result)
                          result
                        },
                        recode_phenomena = function(x){
                          x <- as.character(x)
                          from <- unique(x)
                          from_cleaned <- trimws(tolower(from))
                          ## ONLY PM-10/PM-2.5/PM1 and Temperature are covered in this setup
                          idx <- which(from_cleaned %in% c("pm10", "pm2.5", "pm1", "t"))
                          to <- from
                          if(length(idx) > 0){
                            to[idx] <- txt_recode(from_cleaned[idx], 
                                                  from = c("pm10", "pm2.5", "pm1", "t"),
                                                  to = c("5", "6001", "6002", "62101"))  
                          }
                          txt_recode(x, 
                                     from = from,
                                     to = to)
                        },
                        getStations = function(){
                          x <- as.data.frame(self$stations$stations)
                          x <- sp::SpatialPointsDataFrame(coords = x[, c("lon", "lat")], 
                                                          data = x, proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
                          x
                        },
                        getStationLabel = function(id){
                          x <- self$stations$stations$label[self$stations$stations$id %in% id]
                          x
                        },
                        getStationsArea = function(){
                          private$stations_area
                        },
                        getPhenomena = function(type = c("official", "citizen")){
                          type <- match.arg(type)
                          if(type == "official"){
                            self$stations$phenomena$id  
                          }else if(type == "citizen"){
                            ts <- self$getCitizenData("timeseries")
                            sort(unique(ts$phenomena_id))
                          }
                        },
                        getPhenomenaLabel = function(id){
                          #stopifnot(all(id %in% self$stations$phenomena$id))
                          txt_recode(id, from = self$stations$phenomena$id, to = self$stations$phenomena$label)
                        },
                        getComparisonStation = function(){
                          self$closest_station
                        },
                        getClosestStation = function(){
                          ## Get locations of own measurements
                          point <- self$getCitizenData(type = "meta")
                          point <- point[!is.na(point$lon) & !is.na(point$lat), ]
                          station <- NA_character_
                          if(nrow(point) > 0){
                            point <- head(point, 1)
                            station <- private$nearest_station(stations = self$getStations(), as.matrix(point[, c("lon", "lat")]))  
                            station <- station$id
                          }
                          station
                        },
                        getCombinedTimeseries = function(data = self$timeseries, type = c("rawdata", "min", "hour", "day", "week", "month"), limit = FALSE){
                          type <- match.arg(type)
                          x <- data
                          x <- setDT(x)
                          if(type == "min"){
                            x <- x[, list(value = mean(value, na.rm=TRUE)), 
                                   by = list(type, timeseries_id, timeseries_label, phenomena_id, phenomena, time = fasttime::fastPOSIXct(format(time, "%Y-%m-%d %H:%M"), tz = "UTC"))] 
                          }else if(type == "hour"){
                            x <- x[, list(value = mean(value, na.rm=TRUE)), 
                                   by = list(type, timeseries_id, timeseries_label, phenomena_id, phenomena, time = fasttime::fastPOSIXct(format(time, "%Y-%m-%d %H"), tz = "UTC"))] 
                          }else if(type == "day"){
                            x <- x[, list(value = mean(value, na.rm=TRUE)), 
                                   by = list(type, timeseries_id, timeseries_label, phenomena_id, phenomena, time = date)] 
                          }else if(type == "week"){
                            x <- x[, list(value = mean(value, na.rm=TRUE)), 
                                   by = list(type, timeseries_id, timeseries_label, phenomena_id, phenomena, time = mondayofweek(date))] 
                          }else if(type == "month"){
                            x <- x[, list(value = mean(value, na.rm=TRUE)), 
                                   by = list(type, timeseries_id, timeseries_label, phenomena_id, phenomena, time = startofmonth(date))] 
                          }
                          if(limit){
                            citizen_timepoints <- x$time[x$type %in% "citizen"]
                            if(length(citizen_timepoints) > 0){
                              x <- x[as.Date(x$time) >= (as.Date(min(citizen_timepoints))) & as.Date(x$time) <= (as.Date(max(citizen_timepoints))), ]
                            }
                          }
                          x
                        },
                        getCitizenData = function(type = c("meta", "timeseries", "phenomena", "fileinfo", "timerange")){
                          type <- match.arg(type)
                          if(type == "meta"){
                            if(is.na(self$data$filename)){
                              x <- data.frame(sheet_id = integer(), sheet = character(), device = character(), 
                                              lat = numeric(), lon = numeric(), 
                                              phenomena = character(), stringsAsFactors = FALSE)
                            }else{
                              x <- as.data.frame(self$data, type = "meta")  
                            }
                            
                          }else if(type == "timeseries"){
                            if(is.na(self$data$filename)){
                              x <- private$default_timeseries
                            }else{
                              x <- as.data.frame(self$data, type = "measurements")
                            }
                            x$type <- rep("citizen", nrow(x))
                            x$phenomena_id <- rep(NA_character_, nrow(x))
                            x$phenomena_id <- self$recode_phenomena(x$phenomena)
                            x$timeseries_id <- as.character(x$sheet_id)
                            sheetinfo <- self$getCitizenData("meta")
                            if(nrow(x) > 0){
                              x$timeseries_label <- txt_recode(x$sheet_id, from = sheetinfo$sheet_id, to = sheetinfo$sheet)  
                            }else{
                              x$timeseries_label <- character()
                            }
                            x <- x[, c("type", "timeseries_id", "timeseries_label", "phenomena_id", "phenomena", "time", "date", "value")]
                            x <- setDT(x)
                          }else if(type == "phenomena"){
                            if(is.na(self$data$filename)){
                              x <- character()
                            }else{
                              x <- self$data$phenomena
                            }
                          }else if(type == "fileinfo"){
                            if(is.na(self$data$filename)){
                              x <- data.frame(name = character(), file = character(), stringsAsFactors = FALSE)
                            }else{
                              x <- data.frame(name = self$data$filename, file = self$data$file, stringsAsFactors = FALSE)
                            }
                          }else if(type == "timerange"){
                            x <- self$getCitizenData("timeseries")
                            x <- setDT(x)
                            startstop <- list()
                            if(nrow(x) > 0){
                              startstop$valid <- TRUE
                              startstop$start <- min(x$time)
                              startstop$end <- max(x$time)
                            }else{
                              startstop$valid <- FALSE
                              startstop$start <- as.POSIXct(character())
                              startstop$end <- as.POSIXct(character())
                            }
                            x <- startstop
                            x
                          }
                          x
                        }
                      ),
                      private = list(
                        stations_area = NULL,
                        nearest_station = function(stations, x){
                          d <- sp::spDistsN1(stations, x, longlat = TRUE)
                          stations[which.min(d), ]
                        },
                        default_timeseries = data.table(type = character(), 
                                                        timeseries_id = character(), timeseries_label = character(), 
                                                        phenomena_id = character(), phenomena = character(), 
                                                        time = as.POSIXct(character()), date = as.Date(character(), tz = "UTC"), 
                                                        value = numeric(), stringsAsFactors = FALSE)
                      )
)
