/*
 * This file provides the user with the atc::Slice class,
 * used to define the requested cut-out of the output tensor,
 * and declares its members.
 * 
 * The contents of this file are heavily based on TTHRESH code, with permission from the author, Rafael Ballester-Ripoll.
 * TTHRESH can be found here: https://github.com/rballester/tthresh/blob/
 */

#pragma once

#include <string>
#include <sstream>
#include <limits>

namespace atc {
	
	/**
	 * Represents how a tensor grid should be reduced to a coarser grid along one mode.
	 * Each of these reductions corresponds to a linear operation, i.e. multiplying the tensor along this mode with a pre-defined matrix.
	 */
	enum class Reduction {
		Downsampling, ///< Default reduction, simply takes the value from the index itself and discards the neighboring values.
		Box, ///< Averages out all values across an interval of size ``step`` centered around the index.
		Lanczos ///< Applies <a href="https://en.wikipedia.org/wiki/Lanczos_resampling">Lanczos filtering</a> across an interval of size ``2*step`` centered around the index.
	};
	
	/**
	 * This class represents a slicing operation along a single mode. A slice consists of a ``start``, ``stop`` and ``step`` parameter.
	 * For example, the slice ``1:10:2`` indicates that only the indices ``1, 3, 5, 7, 9`` should be included.
	 * More complex syntax, using undefined implicit parameters and parameters relative to the mode size are also possible.
	 * For more information, see <a href="https://numpy.org/doc/stable/reference/arrays.indexing.html#basic-slicing-and-indexing">NumPy basic slicing and indexing</a>.
	 * 
	 * When reducing the original tensor to a more coarse grid (using a ``step`` parameter greater than 1), you may want to perform this reduction in a smoother way than simply discarding all but 1 out of every ``step`` slices.
	 * To use an alternative linear filter, you can define a set the ``reduction`` parameter as well. In the string format, this is done by appending the corresponding letter to the slice representation, e.g. ``1:10:2b``.
	 */
	class Slice {
	
	public:
		
		/// This constant indicates that a ``start``/``stop``/``step`` value was undefined
		static const int64_t UNDEFINED = std::numeric_limits<int64_t>::min();
		
		/// Create a slice from its raw parameters.
		Slice(int64_t start = UNDEFINED, int64_t stop = UNDEFINED, int64_t step = UNDEFINED, Reduction reduction = Reduction::Downsampling);
		/// Create a slice from its Python description
		Slice(const std::string& description);
		
		const int64_t& get_start() const; ///< Returns the ``start`` parameter.
		const int64_t& get_stop() const; ///< Returns the ``stop`` parameter.
		const int64_t& get_step() const; ///< Returns the ``step`` parameter.
		const Reduction& get_reduction() const; ///< Returns the ``reduction`` parameter.
		
		// The functions below are not meant for users but are public because of their use in the library
		// Therefore they are not extensively documented
		
		void update_mode_size(size_t size); // Sets the size for this mode, allowing us to substitute undefined and negative start/stop values by their absolute values.
		size_t get_size() const; // Number of elements encompassed by the slice. Should only be called after update_mode_size.
		bool is_standard() const; // True if the slice has the same effect as the default slicing (doing nothing). Should only be called after update_mode_size.
		
		friend std::ostream& operator<<(std::ostream& os, const Slice& slice); // Prints a string representation of the slice to the output stream.
		
	private:
		
		int64_t start = UNDEFINED, stop = UNDEFINED, step = UNDEFINED;
		size_t mode_size = 0;
		Reduction reduction = Reduction::Downsampling;
		
	};
	
}
