// Copyright (c) 2018-2023  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/>.

#ifndef SPATBASE_GUARD
#define SPATBASE_GUARD

#include <vector>
#include <algorithm>
#include <string>
#include <cmath>

#ifndef standalone
	#define useRcpp
#endif


#ifndef nogdal
  #define useGDAL
#endif


/*
#ifdef useGDAL
	#include "gdal_priv.h"
#endif
*/

#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif




class SpatMessages {
	public:
	
		virtual ~SpatMessages(){}

		bool has_error = false;
		bool has_warning = false;
		std::string error;
		std::string message;
		std::vector<std::string> warnings;

		void setError(std::string s) {
			has_error = true;
			error = s;
		}

		std::string getError() {
			has_error = false;
			std::string err = error;
			error = "";
			return err;
		}

		void clearError() {
			has_error = false;
			error = "";
		}
		
		void addWarning(std::string s) {
			has_warning = true;
			warnings.push_back(s);
		}

		std::vector<std::string> getWarnings() {
			std::vector<std::string> w = warnings; 		
			warnings.resize(0);
			has_warning = false;
			return w;
		}

		std::string getMessage() {
			std::string out = message;
			message = "";
			return out;
		}
		void setMessage(std::string s) {
			message = s;
		}
		
/*		std::vector<std::string> getAll() {
			std::string warns = getWarnings();
			std::string error = getError();
			std::string msg = getMessage();
			std::vector<std::string> amsgs = { error, warns, msg};
			return amsgs;
		}
*/
};


class SpatOptions {
	private:
		std::string tempdir = "";
		bool todisk = false;
		double memmax = 2147483648; // 16 GB (in doubles: 16 * 1024^3 / 8)
		double memmin = 134217728; // 1024^3 / 8
		double memfrac = 0.5;
		double tolerance = 0.1;
		std::vector<double> offset = {0};
		std::vector<double> scale = {1};
		
	public:
		SpatOptions();
		SpatOptions(const SpatOptions &opt);
		SpatOptions deepCopy();
		virtual ~SpatOptions(){}

		bool parallel = true;
		std::vector<std::string> tags;

		size_t ncopies = 4;
		size_t minrows = 1;
		// Maximum number of threads to use for parallel kernels (TBB
		// task_arena cap, GDAL warp NUM_THREADS). 0 means "no cap"
		// (use the TBB / GDAL default, typically all logical CPUs).
		// Backwards compatible with the legacy bool semantics: a value
		// of 0 acts as FALSE, anything > 0 acts as TRUE.
		unsigned threads = 0;
		std::string def_datatype = "FLT4S";
		std::string def_filetype = "GTiff";
		//std::string def_bandorder = "BIL";
		bool overwrite = false;
		size_t progress = 3;
		size_t steps = 0;
		bool hasNAflag = false;
		double NAflag = NAN;
		bool def_verbose = false;
		bool verbose = false;
		//bool append = false;
		int statistics = 1;
		bool datatype_set = false;
		//bool ncdfcopy = false;
		unsigned char value_type = 0;
		std::string tmpfile = "";
		
		std::string datatype = "";
		//std::string bandorder = "";
		std::string filetype = "";
		std::vector<std::string> filenames = {""};
		std::vector<std::string> gdal_options;
		std::vector<std::string> names;

		// permanent
		bool get_todisk();
		void set_todisk(bool b);
		double get_memfrac();
		void set_memfrac(double d);
		double get_memmax();
		void set_memmax(double d);
		double get_memmin();
		void set_memmin(double d);
		std::string get_tempdir();
		void set_tempdir(std::string d);
		double get_tolerance();
		void set_tolerance(double d);

		std::string get_def_datatype();
		std::string get_def_bandorder();
		std::string get_def_filetype();
		bool get_def_verbose();
		void set_def_datatype(std::string d);
		//void set_def_bandorder(std::string d);
		void set_def_filetype(std::string d);

		// single use
		
		void set_verbose(bool v);
		void set_def_verbose(bool v);
		void set_NAflag(double flag);
		//void set_ncdfcopy(bool x);
		void set_statistics(int s);
		//void set_filename(std::string f);
		void set_filenames(std::vector<std::string> f);
		void set_filetype(std::string d);
		void set_datatype(std::string d);
		//void set_bandorder(std::string d);
		void set_overwrite(bool b);
		//void set_append(bool b);
		void set_progress(size_t p);
		std::string get_filename();
		std::vector<std::string> get_filenames();
		std::string get_filetype();
		std::string get_datatype();
		//std::string get_bandorder();
		bool get_verbose();
		//bool get_ncdfcopy();
		int get_statistics();
		double get_NAflag();
		bool has_NAflag(double &flag);
		bool get_overwrite();
		//bool get_append();
		size_t get_progress();
		bool show_progress(size_t n);
		bool progressbar=true;
		void set_steps(size_t n);
		size_t get_steps();
		void set_ncopies(size_t n);
		size_t get_ncopies();

		void set_offset(std::vector<double> d);
		std::vector<double> get_offset();
		void set_scale(std::vector<double> d);
		std::vector<double> get_scale();

		SpatMessages msg;
		bool hasWarning() {return msg.has_warning;}
		bool hasError() {return msg.has_error;}
		std::vector<std::string> getWarnings() { return msg.getWarnings();}
		std::string getError() { return msg.getError();}

};



class SpatExtent {
	public:
		double xmin, xmax, ymin, ymax;
		SpatExtent() {xmin = -180; xmax = 180; ymin = -90; ymax = 90;}
		SpatExtent(double _xmin, double _xmax, double _ymin, double _ymax) {xmin = _xmin; xmax = _xmax; ymin = _ymin; ymax = _ymax;}
		virtual ~SpatExtent(){}

		SpatExtent deepCopy() {return *this;}
		std::string show();
		SpatExtent align(double d, std::string snap);

		bool intersects(SpatExtent e) { 
			if ((xmin > e.xmax) || (xmax < e.xmin) || (ymin > e.ymax) || (ymax < e.ymin)) {
				return false;
			}
			return true;
		}

		SpatExtent intersect(SpatExtent e) { // check first if intersects?
			SpatExtent out;
			out.xmin = std::max(xmin, e.xmin);
			out.xmax = std::min(xmax, e.xmax);
			out.ymin = std::max(ymin, e.ymin);
			out.ymax = std::min(ymax, e.ymax);
			return out;
		}


		void unite(SpatExtent e) {
			if (std::isnan(xmin)) {
				xmin = e.xmin; 
				xmax = e.xmax;
				ymin = e.ymin;
				ymax = e.ymax;
			} else {
				xmin = std::min(xmin, e.xmin);
				xmax = std::max(xmax, e.xmax);
				ymin = std::min(ymin, e.ymin);
				ymax = std::max(ymax, e.ymax);
			}
		}

		std::vector<double> asVector() {
			std::vector<double> e = { xmin, xmax, ymin, ymax };
			return(e);
		}

		std::vector<std::vector<double>> asPoints() {
			std::vector<std::vector<double>> pts(2, std::vector<double>(4));
			pts[0][0] = xmin;
			pts[1][0] = ymin;
			pts[0][1] = xmin;
			pts[1][1] = ymax;
			pts[0][2] = xmax;
			pts[1][2] = ymax;
			pts[0][3] = xmax;
			pts[1][3] = ymin;
			return(pts);
		}

		bool valid() {
			return ((xmax >= xmin) && (ymax >= ymin));
		}
		bool valid_notempty() {
			return ((xmax > xmin) && (ymax > ymin));
		}
		bool empty() {
			return ((xmax <= xmin) || (ymax <= ymin));
		}

		bool compare(SpatExtent e, std::string oper, double tolerance);

		SpatExtent round(int n);
		SpatExtent floor();
		SpatExtent ceil();
		
		std::vector<size_t> test_sample(size_t size, size_t N, bool replace, std::vector<double> w, unsigned seed);
		std::vector<std::vector<double>> sampleRegular(size_t size, bool lonlat);
		std::vector<std::vector<double>> sampleRandom(size_t size, bool lonlat, unsigned seed);
		
};



class SpatSRS {
	public:
		virtual ~SpatSRS(){}

//		SpatSRS(std::string s);
		std::string proj4, wkt;
		bool set(std::string txt, std::string &msg);
/*
#ifdef useGDAL	
		bool set(OGRSpatialReference *poSRS, std::string &msg);
#endif		
*/
		double to_meter();
		bool m_dist(double &m, bool lonlat, std::string unit);

		std::string get(std::string x) {
			return (x == "proj4" ? proj4 : wkt); 
		}

		std::string get_prj() {
			return proj4;
		}

		bool is_equal(SpatSRS x) {
			return (proj4 == x.proj4);
		}

		bool is_empty() {
			return wkt.empty();
		}

		bool is_same(std::string other, bool ignoreempty);
		bool is_same(SpatSRS other, bool ignoreempty);


		bool is_lonlat_text() {
			bool b1 = proj4.find("longlat") != std::string::npos;
			bool b2 = proj4.find("epsg:4326") != std::string::npos;
			return (b1 | b2);
		}

		bool is_lonlat(); // as above, but using GDAL
		bool is_not_lonlat();


		bool could_be_lonlat(SpatExtent e) {
			bool b = is_lonlat();
			if ((!b) & is_empty()) {
				if ((e.xmin >= -180.1) & (e.xmax <= 180.1) & (e.ymin >= -90.1) & (e.ymax <= 90.1)) {
					b = true;
				}
			}
			return b;
		}

		bool is_global_lonlat(SpatExtent e) {
			if (is_lonlat()) {
                return (std::abs(e.xmax - e.xmin - 360) < 0.001);
				//double halfres = xres()/ 180;
				//if (((e.xmin - halfres) <= -180) && ((e.xmax + halfres) >= 180)) {
				//	return true;
				//}
			}
			return false;
		}
};


class SpatProgress {
	public:
		virtual ~SpatProgress(){}		
		size_t nstep;
		size_t step;
		std::vector<int> steps;
		void init(size_t n, int nmin);
		bool show = false;
		void stepit();
		void finish();
		void interrupt();
};


#endif
