// Copyright (c) 2018-2026  Robert J. Hijmans
//
// This file is part of the "spat" library.
//
// spat is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// spat is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with spat. If not, see <http://www.gnu.org/licenses/>.

#include <vector>
#include <algorithm>
#include "spatRaster.h"
#include "spatTime.h"

/*
#include "string_utils.h"

void SpatRasterSource::fsopen(std::string filename) {
    std::string grifile = setFileExt(filename, ".gri");
 	std::ofstream fstr(grifile, std::ios::out | std::ios::binary);
    *ofs = &fstr;
}

bool SpatRasterSource::fswrite(std::vector<double> &v) {
	unsigned sz = v.size() * sizeof(double);
	bool result = (*ofs).write(reinterpret_cast<const char*>(&v[0]), sz);
 	return result;
}

void SpatRasterSource::fsclose() {
	(*ofs).close();
}
*/


SpatRasterSource::SpatRasterSource() {
	open_write = false;
	open_read = false;
}



SpatRaster SpatRaster::combineSources(SpatRaster &x, bool warn) {

	SpatRaster out = geometry();
	if (!hasValues()) {
		if (!x.hasValues()) {
			if (out.compare_geom(x, false, false, 0.1)) {
				out.source.insert(out.source.end(), x.source.begin(), x.source.end());
				out.setNames(out.getNames());
			} else {
				out = x.deepCopy();
				if (warn) {
					out.addWarning("both rasters were empty, but had different geometries. The first one was ignored");
				}
			}
		} else {
			out = x.deepCopy();
			if (warn) {
				out.addWarning("the first raster was empty and ignored");
			}
		}
		return out;
	}

	if (!out.compare_geom(x, false, false, 0.1)) {
		return out;
	}

	out = deepCopy();
	if (!x.hasValues()) {
		out.addWarning("you cannot add SpatRaster with no values to one that has values");
		return(out);
	}
	out.checkTime(x);
	out.checkDepth(x);
	out.source.insert(out.source.end(), x.source.begin(), x.source.end());
    // to make names unique (not great if called several times
	//out.setNames(out.getNames());
	return(out);
}


void SpatRaster::combine(SpatRaster &x) {

	if (!compare_geom(x, false, false, 0.1)) {
		return;
	}

	bool hv = hasValues();
	if (hv != x.hasValues()) {
		setError("combined sources must all have values; or none should have values");
		return;
	}

	checkTime(x);
	checkDepth(x);
	source.insert(source.end(), x.source.begin(), x.source.end());
	//setNames(getNames());
	return;
}


void SpatRaster::checkTime(SpatRaster &x) {
	if (!hasTime() || !x.hasTime()) {
		return;
	}
	std::string step = unify_time_step(getTimeStep(), x.getTimeStep());
	if (step.empty()) {
		addWarning("time not combined: incompatible time step");
		std::vector<int64_t> time;
		setTime(time, "remove", "");
		x.setTime(time, "remove", "");
		return;
	}
	std::string zone = unify_time_zone(getTimeZone(), x.getTimeZone());
	for (size_t i=0; i<source.size(); i++) {
		if (source[i].hasTime) {
			source[i].timestep = step;
			source[i].timezone = zone;
		}
	}
	for (size_t i=0; i<x.source.size(); i++) {
		if (x.source[i].hasTime) {
			x.source[i].timestep = step;
			x.source[i].timezone = zone;
		}
	}
}

void SpatRaster::checkDepth(SpatRaster &x) {
	std::vector<double> dpth;
	if (!hasDepth()) {
		x.setDepth(dpth);
		return;
	}
	if (!x.hasDepth()) {
		setDepth(dpth);
		return;
	}
	std::string uname, dname;
	std::string un = source[0].depthunit;
	std::string xun = x.source[0].depthunit;
	if (un != xun) {
		if ((un == "") && (xun != "")) {
			uname = xun;
		} else if ((un != "") && (xun == "")) {
			uname = un;
		} else if ((un != "") && (xun != "")) {
			uname = "";
		}
	} else {
		uname = un;
	}
	setDepthName(uname);
	x.setDepthName(uname);

	std::string nm = source[0].depthname;
	std::string xnm = x.source[0].depthname;
	if (nm != xnm) {
		if ((nm == "") && (xnm != "")) {
			dname = xnm;
		} else if ((nm != "") && (xnm == "")) {
			dname = nm;
		} else if ((nm != "") && (xnm != "")) {
			dname = "";
		}
	} else {
		dname = nm;
	}

	setDepthName(dname);
	x.setDepthName(dname);
}


void SpatRaster::addSource(SpatRaster &x, bool warn, SpatOptions &opt) {

	if (!hasValues()) {
		if (!x.hasValues()) {
			if (compare_geom(x, false, true, 0.1, true)) {
				source.insert(source.end(), x.source.begin(), x.source.end());
			} else {
				source = x.source;
				if (warn) {
					addWarning("both rasters were empty, but had different geometries. The first one was ignored");
				}
			}
		} else {
			source = x.source;
			if (warn) {
				addWarning("the first raster was empty and was ignored");
			}
		}
		return;
	}

	if (compare_geom(x, false, true, 0.1, true)) {
		if (!x.hasValues()) {
			x = x.init({NAN}, opt);
		}
		checkTime(x);
		checkDepth(x);
        source.insert(source.end(), x.source.begin(), x.source.end());
	}
}


size_t SpatRaster::nsrc() {
    return source.size();
}


int SpatRaster::sourceFromLyr(size_t lyr) {
    if (lyr >= nlyr()) {
        return(-1);
    }
    size_t nsrc = 0;
    size_t nlyrs = -1;
    for (size_t i=0; i<source.size(); i++) {
        nlyrs += source[i].nlyr;
        if (nlyrs >= lyr) break;
        nsrc++;
    }
    return nsrc;
}


std::vector<size_t> SpatRaster::nlyrBySource() {
    std::vector<size_t> lyrs(source.size());
    for (size_t i=0; i<source.size(); i++) {
        lyrs[i] = source[i].nlyr;
    }
    return lyrs;
}

std::vector<size_t> SpatRaster::lyrsBySource() {
    std::vector<size_t> lyrs(nlyr());
    size_t start = 0;
    for (size_t i=0; i<source.size(); i++) {
        size_t n = source[i].nlyr;
        size_t stop = start + n;
        for (size_t j=start; j<stop; j++) {
            lyrs[j] = i;
        }
        start = stop;
    }
    return lyrs;
}


std::vector<size_t> SpatRaster::findLyr(size_t lyr) {
    std::vector<size_t> sl(2);
    size_t nlyrs = 0;
    size_t start = 0;
	bool done = false;
    for (size_t i=0; i<source.size(); i++) {
		if ((nlyrs + source[i].nlyr) >= lyr) {
			sl[0] = i;
			for (size_t j=start; j<source[i].nlyr; j++) {
				if ((nlyrs + j) == lyr) {
					sl[1] = j;
					done = true;
					break;
				}
			}
		}
		if (done) break;
        nlyrs += source[i].nlyr;
    }
    return sl;
}

std::vector<size_t> SpatRaster::getBands() {
	std::vector<size_t> out;
    for (size_t i=0; i<source.size(); i++) {
		out.insert(out.end(), source[i].layers.begin(), source[i].layers.end());
	}
	return out;
}






std::vector<size_t> SpatRaster::sourcesFromLyrs(std::vector<size_t> lyrs) {
    std::vector<size_t> s(lyrs.size());
    std::vector<size_t> slyrs = lyrsBySource();
    for (size_t i=0; i<lyrs.size(); i++) {
        s[i] = slyrs[ lyrs[i] ];
    }
    return s;
}


/*
void SpatRasterSource::getValues(std::vector<double> &v, size_t lyr) {
	size_t nc ;
	if (hasWindow) {
		nc = window.full_ncol * window.full_nrow;
	} else {
		nc = nrow * ncol;
	}
	size_t start = lyr * nc;
	v = std::vector<double>(values.begin()+start, values.begin()+start+nc);
}
*/


void SpatRasterSource::appendValues(std::vector<double> &v, size_t lyr) {
	size_t nc ;
	if (hasWindow) {
		nc = window.full_ncol * window.full_nrow;
	} else {
		nc = nrow * ncol;
	}
	size_t start = lyr * nc;
	v.insert(v.end(), values.begin()+start, values.begin()+start+nc);
}


bool SpatRasterSource::in_order(bool all) {
	(void) all;
	// Subset layers: cannot use a single GDAL Read sized to nlyr file layers
	// (see readRowColMulti fast path vs buffer of length nlyr).
	if (nlyr != nlyrfile) {
		return false;
	}
	if (memory) return true;
	for (size_t i = 0; i < layers.size(); i++) {
		if (layers[i] != i) {
			return false;
		}
	}
	return true;
}


void SpatRasterSource::resize(size_t n) {
	names.resize(n, "");
	time.resize(n);
	unit.resize(n);
	depth.resize(n);
	valueType.resize(n, 0);
//	dataType.resize(n, "");
    hasRange.resize(n, false);
    range_min.resize(n, NAN);
    range_max.resize(n, NAN);
    blockcols.resize(n);
    blockrows.resize(n);

	has_scale_offset.resize(n, false);
	scale.resize(n, 1);
	offset.resize(n, 0);
    hasColors.resize(n, false);
    hasCategories.resize(n, false);
	// sparse cats/cols: drop entries for layers that no longer exist
	// (also clears everything when n == 0)
	for (auto it = cats.begin(); it != cats.end(); ) {
		if (it->first >= n) it = cats.erase(it); else ++it;
	}
	for (auto it = cols.begin(); it != cols.end(); ) {
		if (it->first >= n) it = cols.erase(it); else ++it;
	}
    //hasAttributes.resize(n);
	//atts.resize(n);
    //attsIndex.resize(n);
	nlyr = n;
	layers.resize(n);
	std::iota(layers.begin(), layers.end(), 0);
}


void SpatRasterSource::reserve(size_t n) {
	names.reserve(n);
	time.reserve(n);
	unit.reserve(n);
	depth.reserve(n);
	valueType.reserve(n);
//	dataType.reserve(n);
    hasRange.reserve(n);
    range_min.reserve(n);
    range_max.reserve(n);
    blockcols.reserve(n);
    blockrows.reserve(n);

	has_scale_offset.reserve(n);
	scale.reserve(n);
	offset.reserve(n);
    hasColors.reserve(n);
    hasCategories.reserve(n);
    //hasAttributes.reserve(n);
	//atts.reserve(n);
    //attsIndex.reserve(n);
	nlyr = n;
	layers.reserve(n);
}


//std::vector<SpatRasterSource> SpatRasterSource::subset(std::vector<size_t> lyrs) {
SpatRasterSource SpatRasterSource::subset(std::vector<size_t> lyrs) {

    size_t nl = lyrs.size();
    bool all = true;
    if (lyrs.size() == nlyr) {
        for (size_t i=0; i<nl; i++) {
            if (lyrs[i] != i) {
                all = false;
                break;
            }
        }
    } else {
        all = false;
    }
	if (all) {
		return *this ;
	}

	SpatRasterSource out;
	if (memory) {
		out.srs = srs;
		out.ncol = ncol;
		out.nrow = nrow;
		out.nlyrfile = nlyrfile;
		out.extent = extent;
		out.rotated = rotated;
		out.flipped = flipped;
		out.has_geolocation = has_geolocation;
		out.has_gcps = has_gcps;
		out.geoloc_srs = geoloc_srs;
		out.geoloc_x = geoloc_x;
		out.geoloc_y = geoloc_y;
		out.hasWindow = hasWindow;
		out.window = window;
		out.source_name = source_name;
		out.source_name_long = source_name_long;
		out.timestep = timestep;
		out.hasTime = hasTime;
		out.hasUnit = hasUnit;
		out.memory = memory;
		out.hasValues = hasValues;
		//out.filename = filename;
		//out.driver = driver;
		//out.valueType = valueType;
		//out.hasNAflag = hasNAflag;
		//out.NAflag = NAflag;
	} else {
		//no values, deep copy is cheap
		out = *this;
		out.resize(0);
	}

	out.reserve(nl);
    for (size_t i=0; i<nl; i++) {
        size_t j = lyrs[i];
		out.names.push_back(names[j]);
		out.time.push_back(time[j]);
		out.depth.push_back(depth[j]);
		out.unit.push_back(unit[j]);
		out.valueType.push_back(valueType[j]);
//		out.dataType.push_back(dataType[j]);
        out.hasRange.push_back(hasRange[j]);
        out.range_min.push_back(range_min[j]);
        out.range_max.push_back(range_max[j]);
        out.blockrows.push_back(blockrows[j]);
        out.blockcols.push_back(blockcols[j]);
        out.hasColors.push_back(hasCol(j));
        if (hasCol(j)) out.cols[i] = getCol(j);
        out.hasCategories.push_back(hasCat(j));
        if (hasCat(j)) out.cats[i] = getCat(j);
		out.has_scale_offset.push_back(has_scale_offset[j]);
		out.scale.push_back(scale[j]);
		out.offset.push_back(offset[j]);

		if (memory) {
			out.layers.push_back(i);
			if (hasValues) {
				appendValues(out.values, j);
			}
		} else {
			out.layers.push_back(layers[j]);
		}
    }
    out.nlyr = nl;
	out.hasValues = hasValues;
    return out;
}


std::vector<size_t> validLayers( std::vector<size_t> lyrs , size_t nl) {
    size_t s = lyrs.size();
    for (size_t i=0; i<s; i++) {
        size_t j = s - i - 1; // start from the back
        if (lyrs[j] >= nl) {
			lyrs.erase(lyrs.begin() + j);
		}
	}

	/* or
    size_t s = lyrs.size() - 1;
    for (long i=s; i>=0; i--) {
        if ((lyrs[i] < 0) | (lyrs[i] >= nl)) {
			lyrs.erase(lyrs.begin() + i);
		}
	}
	*/
	return lyrs;
}


SpatRaster SpatRaster::subset(std::vector<size_t> lyrs, SpatOptions &opt) {

    SpatRaster out = geometry(1);
    out.source.resize(0);

    size_t oldsize = lyrs.size();
    lyrs = validLayers(lyrs, nlyr());

	if (lyrs.empty()) {
		out.setError("no (valid) layer selected");
		return(out);
	} else if (lyrs.size() != oldsize) {
        out.addWarning("ignored " + std::to_string(oldsize - lyrs.size()) + " invalid layer reference(s)");
	}

    std::vector<size_t> srcs = sourcesFromLyrs(lyrs);
    size_t ss = srcs[0];
    std::vector<size_t> slyr;
    std::vector<size_t> lyrbys = nlyrBySource();
//    SpatRasterSource rs;
    size_t offset = 0;
    for (size_t i=0; i<ss; i++) { offset += lyrbys[i]; }

    for (size_t i=0; i<lyrs.size(); i++) {
        if (srcs[i] == ss) {
            slyr.push_back( (lyrs[i] - offset) );
        } else {
            out.source.push_back( source[ss].subset(slyr) );
            ss = srcs[i];
            offset = 0;
            for (size_t j=0; j<ss; j++) { offset += lyrbys[j]; }
            slyr = { lyrs[i] - offset } ;
		}
    }

    out.source.push_back( source[ss].subset(slyr) );
    if (!opt.get_filename().empty()) {
        out = out.writeRaster(opt);
    } //else {
	//	out.collapse();
	//}

    return out;
}




bool SpatRasterSource::combine_sources(const SpatRasterSource &x) {
	if (memory & x.memory) {
		if ((values.size() + x.values.size()) < (values.max_size()/8) ) {
			values.insert(values.end(), x.values.begin(), x.values.end());
			layers.resize(nlyr + x.nlyr);
			std::iota(layers.begin(), layers.end(), 0);
		} else {
			return false;
		}
	} else if (filename == x.filename) {
		layers.insert(layers.end(), x.layers.begin(), x.layers.end());
	} else {
		return false;
	}
	nlyr += x.nlyr;
	names.insert(names.end(), x.names.begin(), x.names.end());
	if (hasTime && x.hasTime) {
		std::string st = unify_time_step(timestep, x.timestep);
		if (st.empty()) {
			hasTime = false;
			time.insert(time.end(), x.nlyr, NA_TIME);
		} else {
			timestep = st;
			timezone = unify_time_zone(timezone, x.timezone);
			time.insert(time.end(), x.time.begin(), x.time.end());
			hasTime = true;
		}
	} else if (hasTime && !x.hasTime) {
		time.insert(time.end(), x.nlyr, NA_TIME);
		hasTime = true;
	} else if (!hasTime && x.hasTime) {
		std::fill(time.begin(), time.end(), NA_TIME);
		time.insert(time.end(), x.time.begin(), x.time.end());
		timestep = x.timestep;
		timezone = x.timezone;
		hasTime = true;
	} else {
		time.insert(time.end(), x.time.begin(), x.time.end());
		hasTime = false;
	}
	unit.insert(unit.end(), x.unit.begin(), x.unit.end());

	depth.insert(depth.end(), x.depth.begin(), x.depth.end());
	valueType.insert(valueType.end(), x.valueType.begin(), x.valueType.end());
//	dataType.insert(dataType.end(), x.dataType.begin(), x.dataType.end());
	hasRange.insert(hasRange.end(), x.hasRange.begin(), x.hasRange.end());
	range_min.insert(range_min.end(), x.range_min.begin(), x.range_min.end());
	range_max.insert(range_max.end(), x.range_max.begin(), x.range_max.end());

	blockrows.insert(blockrows.end(), x.blockrows.begin(), x.blockrows.end());
	blockcols.insert(blockcols.end(), x.blockcols.begin(), x.blockcols.end());
	//hasAttributes.insert(hasAttributes.end(), x.hasAttributes.begin(), x.hasAttributes.end());
	//atts.insert(atts.end(), x.atts.begin(), x.atts.end());
	//attsIndex.insert(attsIndex.end(), x.attsIndex.begin(), x.attsIndex.end());
	// x's layers are appended after this source's original layers; shift the
	// sparse category/color keys accordingly (nlyr was already increased above)
	size_t coff = nlyr - x.nlyr;
	hasCategories.insert(hasCategories.end(), x.hasCategories.begin(), x.hasCategories.end());
	for (const auto &kv : x.cats) cats[coff + kv.first] = kv.second;
	hasColors.insert(hasColors.end(), x.hasColors.begin(), x.hasColors.end());
	for (const auto &kv : x.cols) cols[coff + kv.first] = kv.second;
	has_scale_offset.insert(has_scale_offset.end(), x.has_scale_offset.begin(), x.has_scale_offset.end());
	scale.insert(scale.end(), x.scale.begin(), x.scale.end());
	offset.insert(offset.end(), x.offset.begin(), x.offset.end());
	if (dtype != x.dtype) {
		dtype = "";
	}
	return true;
}



bool SpatRasterSource::combine(SpatRasterSource &x) {
	if (memory & x.memory) {
		if ((values.size() + x.values.size()) < (values.max_size()/8) ) {
			values.insert(values.end(), x.values.begin(), x.values.end());
			layers.resize(nlyr + x.nlyr);
			std::iota(layers.begin(), layers.end(), 0);
			x.values.resize(0);
		} else {
			return false;
		}
	} else if (filename == x.filename) {
		layers.insert(layers.end(), x.layers.begin(), x.layers.end());
	} else {
		return false;
	}
	nlyr += x.nlyr;
	names.insert(names.end(), x.names.begin(), x.names.end());
	if (hasTime && x.hasTime) {
		std::string st = unify_time_step(timestep, x.timestep);
		if (st.empty()) {
			hasTime = false;
			time.insert(time.end(), x.nlyr, NA_TIME);
		} else {
			timestep = st;
			timezone = unify_time_zone(timezone, x.timezone);
			time.insert(time.end(), x.time.begin(), x.time.end());
			hasTime = true;
		}
	} else if (hasTime && !x.hasTime) {
		time.insert(time.end(), x.nlyr, NA_TIME);
		hasTime = true;
	} else if (!hasTime && x.hasTime) {
		std::fill(time.begin(), time.end(), NA_TIME);
		time.insert(time.end(), x.time.begin(), x.time.end());
		timestep = x.timestep;
		timezone = x.timezone;
		hasTime = true;
	} else {
		time.insert(time.end(), x.time.begin(), x.time.end());
		hasTime = false;
	}
	unit.insert(unit.end(), x.unit.begin(), x.unit.end());
	depth.insert(depth.end(), x.depth.begin(), x.depth.end());
//	dataType.insert(dataType.end(), x.dataType.begin(), x.dataType.end());
	valueType.insert(valueType.end(), x.valueType.begin(), x.valueType.end());
	hasRange.insert(hasRange.end(), x.hasRange.begin(), x.hasRange.end());
	range_min.insert(range_min.end(), x.range_min.begin(), x.range_min.end());
	range_max.insert(range_max.end(), x.range_max.begin(), x.range_max.end());
	blockrows.insert(blockrows.end(), x.blockrows.begin(), x.blockrows.end());
	blockcols.insert(blockcols.end(), x.blockcols.begin(), x.blockcols.end());
	//hasAttributes.insert(hasAttributes.end(), x.hasAttributes.begin(), x.hasAttributes.end());
	//atts.insert(atts.end(), x.atts.begin(), x.atts.end());
	//attsIndex.insert(attsIndex.end(), x.attsIndex.begin(), x.attsIndex.end());
	size_t coff = nlyr - x.nlyr;
	hasCategories.insert(hasCategories.end(), x.hasCategories.begin(), x.hasCategories.end());
	for (const auto &kv : x.cats) cats[coff + kv.first] = kv.second;
	hasColors.insert(hasColors.end(), x.hasColors.begin(), x.hasColors.end());
	for (const auto &kv : x.cols) cols[coff + kv.first] = kv.second;
	valueType.insert(valueType.end(), x.valueType.begin(), x.valueType.end());
//	dataType.insert(dataType.end(), x.dataType.begin(), x.dataType.end());
	has_scale_offset.insert(has_scale_offset.end(), x.has_scale_offset.begin(), x.has_scale_offset.end());
	scale.insert(scale.end(), x.scale.begin(), x.scale.end());
	offset.insert(offset.end(), x.offset.begin(), x.offset.end());
	return true;
}



void SpatRaster::collapse() {
	size_t n = nsrc();
	if (n < 2) return;
	std::vector<size_t> rem;
	for (size_t i=1; i<n; i++) {
		if (source[0].combine(source[i])) {
			rem.push_back(i);
		}
	}
	for (int i=rem.size(); i>= 0; i--) {
		source.erase(source.begin()+i);
	}
}



SpatRaster SpatRaster::collapse_sources() {
	SpatRaster out;
	std::vector<SpatRasterSource> src;
	SpatRasterSource s = source[0];
	for (size_t i=1; i<nsrc(); i++) {
		if (! s.combine_sources(source[i])) {
			src.push_back(s);
			s = source[i];
		}
	}
	src.push_back(s);
	out.setSources(src);
	return out;
}


