/*
 * This file defines the Options struct as well as all required enums.
 * Note that the research/debugging options are not meant for public use,
 * but were instead used for performing experiments or debugging ATC
 * and are kept in for now as they may prove useful in case future
 * debugging/experiments are necessary.
 */

#pragma once

#include <vector>

#include "atc/Slice.hpp"

namespace atc {
	
	// Options enums
	
	/// Represents the interpretation of mode orders provided by the user and shown to the user, such the the requested ST-HOSVD mode processing order.
	enum class StorageOrder {
		MSI, ///< Most-signficant-index first, also known as C ordering (row-major for matrices)
		LSI  ///< Least-signficant-index first, also known as Fortran ordering (column-major for matrices)
	};
	/// Represents the precision of the floating-point type used for internal numerical operations.
	enum class FloatType {
		float32, ///< Single-precision
		float64  ///< Double-precision
	};
	/// Represents the error metric used. ``norm`` refers to the Frobenius/Euclidean norm, i.e. the square root of the sum of the squared values, ``size`` refers to the number of elements in the tensor.
	enum class ErrorMetric {
		RelativeError, ///< ``norm(decompressed_data - original_data)/norm(original_data)``
		RMSE, ///< ``norm(decompressed_data - original_data)/sqrt(size(original_data))``
		PSNR ///< ``20*log10((max(original_data) - min(original_data))/RMSE(decompressed_data - original_data))``
	};
	/// Represents the size of the integer type used for quantization and encoding. 
	enum class IntType {
		uint32, ///< 32-bit
		uint64 ///< 64-bit
	};
	/// Represents the core flattening/vectorization method used to convert the core to a one-dimensional array for quantization and encoding. For more information, see <a href="https://arxiv.org/abs/2107.01384">our paper</a>.
	enum class CoreFlattening {
		Unfold, ///< Also known as lexicographic ordering
		Zigzag,
		ZOrder
	};
	/// Represents the entropy coding method used for compressing run-lengths during the encoding process.
	enum class EntropyCodingMethod {
		Arithmetic, ///< Arithmetic coding
		ANS ///< Asymmetric numeral systems
	};
	
	// Research options
	enum class HouseholderWeightingMethod {Exact, Approximation, CoreSliceNorms, Uniform};
	
	// Options themselves
	
	/**
	 * This struct defines a series of compression and/or decompression options.
	 * Almost all ATC interface functions accept an optional `atc::Options` object.
	 * When not given, a new `atc::Options` object will be constructed using the default constructor,
	 * initializing all options to their default values shown below.
	 */
	struct Options {
		
		// GENERAL OPTIONS
		
		bool verbose = false; ///< If true, ATC will print various status and progress information. If false, warnings will still be printed.
		bool suppress_warnings = false; ///< If true, ATC will not print warnings anymore.
		bool timings = false; ///< Tracks the time (normally real-time) taken by various sections of the ATC pipeline. The results are stored in the ``timings`` field inside `atc::Results`.
		/**
		 * The amount of threads that should be used for DCT's, tensor transpositions, linear algebra as well as quantization and encoding.
		 * 0 means ATC will attempt to determine the number of available cores automatically.
		 */
		int threads = 0;
		/// Determines the interpretation of mode orders provided by the user and shown to the user, such the the requested ST-HOSVD mode processing order or the order of the displayed sizes when ``verbose`` is true.
		StorageOrder storage_order = StorageOrder::MSI;
		FloatType float_type = FloatType::float64; ///< Floating-point type used for data arrays. Single-precision might suffice in certain high error cases, while decreasing execution time and memory usage.
		
		
		
		// COMPRESSION OPTIONS
		
		// General
		size_t skip_bytes = 0; ///< The number of bytes that should be skipped when reading the original data file, e.g. in case there is a header at the start of the file.
		ErrorMetric error_metric = ErrorMetric::PSNR; ///< The error metric used to express the requested target error and optionally the decompression error calculated by ``calculate_error``.
		
		// T-HODCT
		/** 
		 * If true, apply an HODCT followed by rank truncation (T-HODCT) before the ST-HOSVD.
		 * Theoretically, the T-HODCT might be able to quickly truncate very unimportant (i.e. high-frequency) components from the data,
		 * thus speeding up the subsequent ST-HOSVD and quantization steps, while incurring a limited time cost due to its lower complexity.
		 * However, in practice we did not find such scenarios, which is why this option is disabled by default.
		 */
		bool apply_dct = false; 
		double dct_truncation_max_sse_share = 0; ///< Upper bound to the ratio ``sse_introduced_by_dct_truncation``/``sse_introduced_by_rank_truncation``, should be in the interval [0, 1].
		
		// ST-HOSVD
		double rank_truncation_max_sse_share = 0.5; ///< Upper bound to the ratio ``sse_introduced_by_rank_truncation``/``target_sse``, should be in the interval [0, 1].
		std::vector<uint8_t> mode_processing_order; ///< Mode processing order during ST-HOSVD compression. Should be a permutation of ``[0, 1, ..., modes - 1]``. If not set, the order is chosen by the compressor using the smallest-mode-first heuristic.
		/**
		 * If true, the SVD is calculated using the significantly slower but more precise Eigen::BDCSVD solver instead of using the eigenvalue decomposition of the Gramian matrix.
		 * This may have a slight effect on compression in some cases but typically comes with a significant time penalty.
		 */
		bool precise_svd = false;
		bool print_squared_singular_values = false; ///< If true and verbose is true, prints the list of computed squared singular values (or eigenvalues from the Gramian matrix) for each mode during the ST-HOSVD.
		/// If true, compress the factors using their Householder reflector representation before quantization. This can lead to slight compression improvements at the cost of a bit of speed. For more information, see <a href="https://arxiv.org/abs/2107.01384">our paper</a>.
		bool householder = true;
		
		// Quantization
		
		/** 
		 * Internal type used for integer arrays during quantization. This type will be stored in the compressed file and will also be used during decompression.
		 * Although 64-bit is the default, 32-bit usually suffices for high to medium compression errors while reducing quantization memory usage, which may also provide minor speed-ups due to improved caching (untested).
		 * However, if some ST-HOSVD is applied, peak memory usage will not occur during quantization anyway.
		 */
		IntType int_type = IntType::uint64;
		/// Determines the core flattening/vectorization method used to convert the core to a one-dimensional array for quantization and encoding. For more information, see <a href="https://arxiv.org/abs/2107.01384">our paper</a>.
		CoreFlattening core_flattening = CoreFlattening::Unfold;
		std::vector<uint8_t> mode_storage_order; ///< Mode storage order for core for quantization and encoding purposes. Should be a permutation of ``[0, 1, ..., modes - 1]``. For more information, including the default setting, see <a href="https://arxiv.org/abs/2107.01384">our paper</a>.
		/// The number of blocks used to parallellize the quantization process. 0 (default) means that it is set to the number of threads used. For more information, see <a href="https://arxiv.org/abs/2107.01384">our paper</a>.
		size_t quantization_blocks = 0;
		/// The maximal number of threads that should be used during the last pass of the quantization process. During this pass the SSE reduction is tracked and synchronized in between all threads. To limit this overhead it is best not to use arbitrarily many threads in this step.
		size_t last_pass_max_threads = 8;
		/// This parameter determines how precise ATC should try to be when approximating the target error during the quantization phase. For example, when using a target relative error of 2% and a quantization relative error margin of 1%, ATC will try to keep the final error in between 1.99% and 2.01%. However, this is not any kind of guarantee, it's simply a parameter used to trade increased error control for increased synchronization overhead when parallellizing the quantization phase.
		double quantization_rel_error_margin = 0.001;
		/// If true, ATC will not necessarily encode both leading and trailing bits until the same breakpoint in the last encoded bitplane. This can lead to slight compression improvements at the cost of a bit of speed. For more information, see <a href="https://arxiv.org/abs/2107.01384">our paper</a>.
		bool split_bitplane_truncation = true;
		
		// Encoding
		/// The entropy coding method used for encoding the run-lengths during the quantization and encoding phase.
		EntropyCodingMethod entropy_coding_method = EntropyCodingMethod::Arithmetic;
		
		
		
		// Decompression options
		
		/**
		 * Determines which section of the original tensor should actually be decompressed. Each slice corresponds to one mode.
		 * If ``cutout.size()`` is smaller than the number of modes, the remaining modes are sliced using the default slicing (start: 0, end: ``mode_size`` - 1, step: 1).
		 * For more information, see the `Slice` class.
		 */
		std::vector<Slice> cutout; 
		bool autocrop = false; ///< Crop away first/last rows in factors (i.e. first/last slices in the decompressed tensor) with too little energy. Cannot be used when providing a custom cut-out already.
		double autocrop_threshold = 1e-3; ///< First/last rows in factors whose root mean square value is smaller than or equal to this value will be cropped away.
		
		
		
		// COMPRESS_DECOMPRESS options
		
		/// If true, a copy of the original data to be kept in memory when loading from a file and after decompression the error of the decompressed data will be calculated and returned using the `error` field in `atc::Results`.
		bool calculate_error = false;
		
		
		
		// RESEARCH/DEBUGGING OPTIONS
		// Not meant for users, so not extensively documented
		// Do not use these unless you know what you are doing!
		
		bool rank_truncation = true; // Enables both ST-HOSVD truncation and DCT truncation during preprocessing)
		bool static_rank_truncation_thresholding = false; // Disables dynamic rank truncation thresholding during the ST-HOSVD
		// Eigen used to create an extra temporary when parallellizing matrix multiplication (during the ST-HOSVD), usually increasing ATC's peak memory usage by 50%
		// Using EPCM, this temporary was not allocated, saving a lot of memory
		// However, we don't observe this problem anymore in new experiments, so EPCM should be turned off normally in other to not mess with Eigen's or BLAS's parallellization
		bool explicitly_parallellized_core_multiplications = false;
		std::string factors_path;
		std::string core_path;
		// During dequantization, unknown (unencoded) bits are estimated to be 1000...0 for significant coefficients
		// If the following option is activated, this effect is ignored during error control, like in TTHRESH, overestimating the actual error
		bool ignore_dequantization_correction = false;
		// Use old (TTHRESH) vs new (with adjusted normalization) factor quantization stopping criterion
		bool tthresh_factor_stop_criterion = false;
		bool print_encoding_sse_reduction_bit_cost = false;
		std::string leading_bit_positions_path;
		// Quantized core slice norms are calculated based on the encoded part of the core after quantization (like in TTHRESH)
		// If true, quantized core slice norms are used for factor weighting (in the case of unified quantization: leads to iterative factor reweighting)
		// Quantized core sizes will also be used for storing the factors
		// If false, the core slice norms from the ST-HOSVD are used instead
		// ST-HOSVD-truncated core sizes will also be used for storing the factors
		bool use_quantized_core_slice_norms = true;
		bool print_significant_coefficients_per_slice = false;
		HouseholderWeightingMethod householder_weighting_method = HouseholderWeightingMethod::Approximation;
		// If true, a copy of the unquantized core and factors will be kept for comparison
		bool test_error_approximation = false;
		// If not empty, store error tensor components and factors separately in files so that they can be analyzed by external programs
		std::string error_components_path;
		
	};
	
}
