# Author: Robert J. Hijmans
# Date :  June 2018
# Version 0.9
# License GPL v3

setMethod("hasValues", signature(x="SpatRaster"),
	function(x) {
		x@pntr$hasValues
	}
)

ok_int <- function(x) {
	r <- suppressWarnings(range(x, na.rm=TRUE))
	isTRUE((r[1] >= -.Machine$integer.max) && (r[2] <= .Machine$integer.max))
}

.makeDataFrame <- function(x, v, ...) {

	v <- data.frame(v, check.names=FALSE, ...)

#	factors=TRUE,
#	if (factors) {
	ff <- is.factor(x)
	if (any(ff)) {
		fs <- which(ff)
		cgs <- levels(x)
		for (f in fs) {
			cg <- cgs[[f]]
			i <- match(v[,f], cg[,1])
			if (!inherits(cg[[2]], "numeric")) {
				v[[f]] <- factor(cg[i, 2], levels=unique(cg[[2]]))
			} else {
				v[[f]] <- cg[i, 2]
			}
		}
	} 
	bb <- is.bool(x)
	if (any(bb)) {
		for (b in which(bb)) {
			v[[b]] = as.logical(v[[b]])
		}
	}
	ii <- (is.int(x) & (!ff))
	if (any(ii)) {
		for (i in which(ii)) {
			if (ok_int(v[[i]])) {
				v[[i]] = as.integer(v[[i]])
			}
		}
	}
	dd <- !(bb | ii | ff)
	if (any(dd)) {
		d <- which(dd)
		v[,d] <- replace(v[,d], is.na(v[,d]), NA)
	}
	v
}


setMethod("readValues", signature(x="SpatRaster"),
function(x, row=1, nrows=nrow(x), col=1, ncols=ncol(x), mat=FALSE, dataframe=FALSE, ...) {
	stopifnot(row > 0 && nrows > 0)
	stopifnot(col > 0 && ncols > 0)
	v <- x@pntr$readValues(row-1, nrows, col-1, ncols)
	messages(x, "readValues")
	if (dataframe) {
		v <- matrix(v, ncol = nlyr(x))
		colnames(v) <- names(x)
		return(.makeDataFrame(x, v, ...) )
	} else if (mat) {
		if (all(is.int(x))) {
			if (ok_int(v)) {
				v <- matrix(as.integer(v), ncol = nlyr(x))
			} else {
				v <- matrix(v, ncol = nlyr(x))				
			}
		} else if (all(is.bool(x))) {
			v <- matrix(as.logical(v), ncol = nlyr(x))		
		} else {
			v <- matrix(v, ncol = nlyr(x))		
		}
		colnames(v) <- names(x)
	} else if (all(is.int(x))) {
		if (ok_int(v)) {
			v <- as.integer(v)
		}
	} else if (all(is.bool(x))) {
		v <- as.logical(v)
	}
	v
}
)


setMethod("readValues", signature(x="SpatRasterDataset"),
	function(x, row=1, nrows=nrow(x), col=1, ncols=ncol(x), mat=FALSE, dataframe=FALSE, ...) {
		lapply(1:length(x), function(i) readValues(x[i], row, nrows, 1, ncols, mat=mat, dataframe=dataframe, ...))
	}
)


setMethod("values", signature(x="SpatRaster"),
function(x, mat=TRUE, dataframe=FALSE, row=1, nrows=nrow(x), col=1, ncols=ncol(x), na.rm=FALSE, ...) {
	readStart(x)
	on.exit(readStop(x))
	v <- readValues(x, row, nrows, col, ncols, mat=mat, dataframe=dataframe, ...)
	messages(x, "values")
	if (na.rm) {
		if (!is.null(dim(v))) {
			v[stats::complete.cases(v), , drop=FALSE]
		} else {
			v[!is.na(v)]
		}
	} else {
		v
	}
}
)

setMethod("values<-", signature("SpatRaster", "ANY"),
	function(x, value) {
		setValues(x, value, keepnames=TRUE)
	}
)

setMethod("focalValues", signature("SpatRaster"),
	function(x, w=3, row=1, nrows=nrow(x), fill=NA) {
		if (is.matrix(w)) {
			#m <- as.vector(t(w))
			w <- dim(w)
		} else {
			w <- rep_len(w, 2)
		}
		readStart(x)
		on.exit(readStop(x))
		opt <- spatOptions()
		m <- matrix(x@pntr$focalValues(w, fill, max(0, row-1), nrows, opt), ncol=prod(w), byrow=TRUE)
		messages(x, "focalValues")
		m
	}
)


mtrans <- function(mm, nc) {
	v <- NULL
	n <- ncol(mm) / nc
	for (i in 1:n) {
		j <- 1:nc + (i-1)*nc
		v <- c(v, as.vector(t(mm[, j])))
	}
	v
}


setMethod("setValues", signature("SpatRaster"),
	function(x, values, keeptime=TRUE, keepunits=TRUE, keepnames=FALSE, props=FALSE) {

		y <- rast(x, keeptime=keeptime, keepunits=keepunits, props=props)
		if (is.data.frame(values)) {
			# needs improvement to deal with mixed data types
			values <- as.matrix(values)
		}
		if (is.matrix(values)) {
			nl <- nlyr(x)
			d <- dim(values)
			if (!all(d == c(ncell(x), nl))) {
				ncx <- ncol(x)
				if ((d[1] == nrow(x)) && ((d[2] %% nl*ncx) == 0)) { 
					# raster-shaped matrix 
					if (ncx < d[2]) {
						values <- mtrans(values, ncx)
					} else {
						values <- as.vector(t(values))
					}
				} else if ((d[2] == nl) && (d[1] < ncell(x))) {
					if (d[1] > 1) warn("setValues", "values were recycled")
					values <- as.vector(apply(values, 2, function(i) rep_len(i, ncell(x))))
				} else {
					error("setValues","dimensions of the matrix do not match the SpatRaster")
				}
			} 
			if (!keepnames) {
				nms <- colnames(values)
				if (!is.null(nms)) names(y) <- nms
			}
		} else if (is.array(values)) {
			stopifnot(length(dim(values)) == 3)
			values <- as.vector(aperm(values, c(2,1,3)))
		}
		make_factor <- FALSE
		set_coltab <- FALSE
		if (is.character(values)) {
			if (all(substr(stats::na.omit(values), 1, 1) == "#")) {
				fv <- as.factor(values)
				if (length(levels(fv)) <= 256) {
					values <- as.integer(fv) #-1
					fv <- levels(fv)
					set_coltab <- TRUE
				} else {
					fv <- NULL
					values <- t(grDevices::col2rgb(values))
					y <- rast(y, nlyr=3, names=c("red", "green", "blue"))
					RGB(y) <- 1:3
				}
			} else {
				values <- as.factor(values)
				levs <- levels(values)
				values <- as.integer(values) # -1 not needed anymore?
				make_factor <- TRUE
			}
		} else if (is.factor(values)) {
			levs <- levels(values)
			values <- as.integer(values)# - 1
			make_factor <- TRUE
		}

		if (!(is.numeric(values) || is.integer(values) || is.logical(values))) {
			error("setValues", "values must be numeric, integer, logical, factor or character")
		}

		lv <- length(values)
		nc <- ncell(y)
		nl <- nlyr(y)
		opt <- spatOptions()

		if (lv == 1) {
			y@pntr$setValues(values, opt)
		} else {
			if (lv > (nc * nl)) {
				warn("setValues", "values is larger than the size of cells")
				values <- values[1:(nc*nl)]
			} else if (lv < (nc * nl)) {
				warn("setValues", "values were recycled")
				values <- rep(values, length.out=nc*nl)
			}
			y@pntr$setValues(values, opt)
		}
		y <- messages(y, "setValues")
		if (make_factor) {
			for (i in 1:nlyr(y)) {
				levs <- data.frame(value=1:length(levs), label=levs)
				set.cats(y, i, levs)
			}
			names(y) <- names(x)
		}
		if (set_coltab) {
			coltab(y) <- fv
		} else if (is.logical(values)) {
			if (!all(is.na(values))) y@pntr$setValueType(3)
		} else if (is.integer(values)) {
			y@pntr$setValueType(1)
		}
		y
	}
)



setMethod("inMemory", signature(x="SpatRaster"),
	function(x, bylayer=FALSE) {
		r <- x@pntr$inMemory
		if (bylayer) {
			nl <- .nlyrBySource(x)
			r <- rep(r, nl)
		}
		r
	}
)


#..hasValues <- function(x) { x@pntr$hasValues}
#..inMemory <- function(x) { x@pntr$inMemory }
#..filenames <- function(x) {	x@pntr$filenames }

subsetSource <- function(x, i) {
	x@pntr <- x@pntr$subsetSource(i-1)
	messages(x)
}

setMethod("sources", signature(x="SpatRaster"),
	function(x, nlyr=FALSE, bands=FALSE) {
		src <- x@pntr$filenames()
		Encoding(src) <- "UTF-8"
		if (bands) {
			nls <- x@pntr$nlyrBySource()
			d <- data.frame(sid=rep(1:length(src), nls),
						 source=rep(src, nls),
						 bands=x@pntr$getBands()+1, stringsAsFactors=FALSE)
			if (nlyr) {
				d$nlyr <- rep(nls, nls)
			}
			d
		} else if (nlyr) {
			data.frame(source=src, nlyr=x@pntr$nlyrBySource(), stringsAsFactors=FALSE)
		} else {
			src
		}
	}
)

setMethod("sources", signature(x="SpatRasterCollection"),
	function(x, nlyr=FALSE, bands=FALSE) {
		if (nlyr | bands) {
			x <- lapply(x, function(i) sources(i, nlyr, bands))
			x <- lapply(1:length(x), function(i) cbind(cid=i, x[[i]]))
			do.call(rbind, x)
		} else {
			sapply(x, sources)
		}
	}
)

setMethod("sources", signature(x="SpatRasterDataset"),
	function(x, nlyr=FALSE, bands=FALSE) {
		if (nlyr | bands) {
			x <- lapply(x, function(i) sources(i, nlyr, bands))
			x <- lapply(1:length(x), function(i) cbind(cid=i, x[[i]]))
			do.call(rbind, x)
		} else {
			x@pntr$filenames()
		}
	}
)


setMethod("sources", signature(x="SpatVector"),
	function(x) {
		if (x@pntr$source != "") {
			if (x@pntr$layer != tools::file_path_sans_ext(basename(x@pntr$source))) {
				paste0(x@pntr$source, "::", x@pntr$layer)
			} else {
				x@pntr$source
			}
		} else {
			""
		}
	}
)

setMethod("sources", signature(x="SpatVectorProxy"),
	function(x) {
		if (x@pntr$v$layer != tools::file_path_sans_ext(basename(x@pntr$v$source))) {
			paste0(x@pntr$v$source, "::", x@pntr$v$layer)
		} else {
			x@pntr$v$source
		}
	}
)

setMethod("hasMinMax", signature(x="SpatRaster"),
	function(x) {
		x@pntr$hasRange
	}
)

setMethod("minmax", signature(x="SpatRaster"),
	function(x, compute=FALSE) {
		have <- x@pntr$hasRange
		if (!all(have)) {
			if (compute) {
				opt <- spatOptions()
				x@pntr$setRange(opt, FALSE)
			} else {
				warn("minmax", "min and max values not available for all layers. See 'setMinMax' or 'global'")
			}
		}
		r <- rbind(x@pntr$range_min, x@pntr$range_max)
		if (!compute) {
			r[,!have] <- c(Inf, -Inf)
		}
		colnames(r) <- names(x)
		rownames(r) <- c("min", "max")
		r
	}
)


setMethod("setMinMax", signature(x="SpatRaster"),
	function(x, force=FALSE) {
		opt <- spatOptions()
		if (force) {
			x@pntr$setRange(opt, TRUE)
		} else if (!all(hasMinMax(x))) {
			x@pntr$setRange(opt, FALSE)
		}
		x <- messages(x, "setMinMax")
	}
)



setMethod("compareGeom", signature(x="SpatRaster", y="SpatRaster"),
	function(x, y, ..., lyrs=FALSE, crs=TRUE, warncrs=FALSE, ext=TRUE, rowcol=TRUE, res=FALSE, stopOnError=TRUE, messages=FALSE, tolerance=NULL) {
		opt <- spatOptions("")
		if (is.null(tolerance)) tolerance <- opt$tolerance
		out <- x@pntr$compare_geom(y@pntr, lyrs, crs, tolerance, warncrs, ext, rowcol, res)
		if (stopOnError) {
			messages(x, "compareGeom")
		} else {
			m <- NULL
			if (x@pntr$has_warning()) {
				m <- x@pntr$getWarnings()
			}
			if (x@pntr$has_error()) {
				m <- c(m, x@pntr$getError())
			}
			if (!is.null(m) && messages) {
				message(paste(m, collapse="\n"))
			}
		}
		dots <- list(...)
		if (length(dots) > 0) {
			for (i in 1:length(dots)) {
				if (!inherits(dots[[i]], "SpatRaster")) {
					error("compareGeom", "all additional arguments must be a SpatRaster")
				}
				out <- out & 
					compareGeom(x, dots[[i]], lyrs=lyrs, crs=crs, warncrs=warncrs, ext=ext, rowcol=rowcol, res=res, stopOnError=stopOnError, messages=messages, tolerance=tolerance)
			}
		}
		out
	}
)


setMethod("compareGeom", signature(x="SpatRaster", y="list"),
	function(x, y, ..., lyrs=FALSE, crs=TRUE, warncrs=FALSE, ext=TRUE, rowcol=TRUE, res=FALSE, stopOnError=TRUE, messages=FALSE, tolerance=NULL) {
		dots <- list(...)
		if (length(dots) > 0) {
			y <- c(y, dots)
		}
		isr <- sapply(y, inherits, "SpatRaster")
		if (!all(isr)) {
			y <- y[isr]
			if (length(y) == 0) error("compareGeom", "none of the elements of y is a SpatRaster")
			n <- sum(!isr)
			if (n > 1) {
				warn("compareGeom", paste(n, "elements of y are not a SpatRaster"))
			} else {
				warn("compareGeom", paste("1 element of y is not a SpatRaster"))
			}
		}
		out <- sapply(y, compareGeom, y=x, lyrs=lyrs, crs=crs, warncrs=warncrs, ext=ext, rowcol=rowcol, res=res, stopOnError=stopOnError, messages=messages, tolerance=tolerance)
		if (!all(isr)) {
			res <- rep(NA, length(isr))
			res[isr] <- out
			return(res)
		}
		all(out)
	}
)


setMethod("compareGeom", signature(x="SpatRasterCollection", y="missing"),
	function(x, y, ..., lyrs=FALSE, crs=TRUE, warncrs=FALSE, ext=TRUE, rowcol=TRUE, res=FALSE, stopOnError=FALSE, messages=FALSE, tolerance=NULL) {
		x <- as.list(x)
		dots <- list(...)
		if (length(dots) > 0) {
			error("compareGeom", "when x is a SpatRasterCollection, additional arguments are ignored")
		}
		if (length(x) == 1) return(TRUE)
		out <- sapply(x[-1], compareGeom, y=x[1], lyrs=lyrs, crs=crs, warncrs=warncrs, ext=ext, rowcol=rowcol, res=res, stopOnError=stopOnError, messages=messages, tolerance=tolerance)
		out
	}
)


setMethod("compareGeom", signature(x="SpatRaster", y="SpatRasterCollection"),
	function(x, y, ..., lyrs=FALSE, crs=TRUE, warncrs=FALSE, ext=TRUE, rowcol=TRUE, res=FALSE, stopOnError=FALSE, messages=FALSE, tolerance=NULL) {
		dots <- list(...)
		if (length(dots) > 0) {
			error("compareGeom", "when y is a SpatRasterCollection, additional arguments are ignored")
		}
		out <- sapply(as.list(y), compareGeom, y=x, lyrs=lyrs, crs=crs, warncrs=warncrs, ext=ext, rowcol=rowcol, res=res, stopOnError=stopOnError, messages=messages, tolerance=tolerance)
		all(out)
	}
)


setMethod("compareGeom", signature(x="SpatVector", y="SpatVector"),
	function(x, y, tolerance=0) {
		out <- x@pntr$equals_between(y@pntr, tolerance)
		x <- messages(x, "compareGeom")
		out[out == 2] <- NA
		matrix(as.logical(out), nrow=nrow(x), byrow=TRUE)
	}
)

setMethod("compareGeom", signature(x="SpatVector", y="missing"),
	function(x, y, tolerance=0) {
		symmetrical=FALSE # this is inefficient
		out <- x@pntr$equals_within(symmetrical, tolerance)
		x <- messages(x, "compareGeom")
		out[out == 2] <- NA
		out <- matrix(as.logical(out), nrow=nrow(x), byrow=TRUE)
		out
	}
)
setMethod("all.equal", signature(target="SpatRaster", current="SpatRaster"),
	function(target, current, maxcell=100000, ...) {
		a <- base::all.equal(rast(target), rast(current))
		maxcell <- round(maxcell)
		if (isTRUE(a) && maxcell > 0) {
			hvT <- hasValues(target)
			hvC <- hasValues(current)
			if (hvT && hvC) {
				if (maxcell < ncell(target)) {
					warn(paste("all.equal", "taking a sample of ", maxcell, "cells"))
					s1 <- spatSample(current, maxcell, "regular")
					s2 <- spatSample(target, maxcell, "regular")
				} else {
					s1 <- values(current)
					s2 <- values(target)
				}
				a <- all.equal(s1, s2, ...)
			} else if (hvT || hvC) {
				if (hvT) {
					a <- "target has cell values, current does not"
				} else {
					a <- "current has cell values, target does not"				
				}
			}
		}
		a
	}
)

setMethod("all.equal", signature(target="SpatExtent", current="SpatExtent"),
	function(target, current, ...) {
		target == current
	}
)

setMethod("all.equal", signature(target="SpatVector", current="SpatVector"),
	function(target, current, ...) {
		if (any(dim(target) != dim(current))) {
			message("different dimensions")
			return(FALSE)
		}
		if (crs(target) != crs(current)) {
			message("different crs")
			return(FALSE)
		}
		if (geomtype(target) != geomtype(current)) {
			message("different geometry type")
			return(FALSE)
		}
		if (!isTRUE(all.equal(values(target), values(current), ...))) {
			message("different values")
			return(FALSE)
		}
		if (!isTRUE(all.equal(geom(target), geom(current), ...))) {
			message("different geometry")
			return(FALSE)
		}
		TRUE
	}
)



setMethod("identical", signature(x="SpatRaster", y="SpatRaster"),
	function(x, y) {
		a <- isTRUE(all.equal(x, y, maxcell=0))
		if (a && hasValues(x))  {
			v <- unique(unlist(unique(x - y) ))
			a <- identical(v, 0)
			if (a) {
				v <- unique(unlist(unique(is.na(x) - is.na(y)) ))
				a <- all(v == 0)
			}
		}
		a
	}
)


setMethod("identical", signature(x="SpatExtent", y="SpatExtent"),
	function(x, y) {
		identical(as.vector(x), as.vector(y))
	}
)

setMethod("identical", signature(x="SpatVector", y="SpatVector"),
	function(x, y) {
		all.equal(x, y)
	}
)

setMethod("values", signature("SpatVector"),
	function(x, ...) {
		as.data.frame(x, ...)
	}
)


setMethod("values<-", signature("SpatVector", "data.frame"),
	function(x, value) {
		x@pntr <- x@pntr$deepcopy()
		if (ncol(value) == 0) {
			x@pntr$remove_df()
			return(x)
		}
		value <- .makeSpatDF(value)
		x@pntr$set_df(value)
		messages(x)
	}
)

setMethod("values<-", signature("SpatVector", "matrix"),
	function(x, value) {
		`values<-`(x, data.frame(value))
	}
)


setMethod("values<-", signature("SpatVector", "ANY"),
	function(x, value) {
		if (!is.vector(value)) {
			error("values<-", "the values must be a data.frame, matrix or vector")
		}
		value <- rep(value, length.out=nrow(x))
		value <- data.frame(value=value)
		`values<-`(x, data.frame(value))
	}
)



setMethod("values<-", signature("SpatVector", "NULL"),
	function(x, value) {
		x@pntr <- x@pntr$deepcopy()
		x@pntr$remove_df()
		x
	}
)

setMethod("setValues", signature("SpatVector"),
	function(x, values) {
		x@pntr <- x@pntr$deepcopy()
		`values<-`(x, values)
	}
)

