/*
 * This file implements the atc::Compressor methods that are not strongly related to compression or decompression alone.
 */

#include <memory>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <thread>
#include <Eigen/Core>
#ifdef ATC_FFTW
	#include <fftw3.h>
#endif

#include "Compressor.hpp"
#include "RawIO.hpp"
#include "util.hpp"

namespace atc {
	
	template<typename ftype>
	Compressor<ftype>::Compressor(const Options& options) {
		this->options = options;
		if (this->options.threads < 0)
			throw std::invalid_argument("Number of threads cannot be strictly negative");
		#ifdef ATC_OPENMP
			if (this->options.threads == 0) {
				this->options.threads = std::thread::hardware_concurrency();
				print_info("Number of threads was not specified (or set to 0), defaulting to " + std::to_string(this->options.threads) + ".");
			} else {
				print_info("Number of threads set to " + std::to_string(this->options.threads) + ".");
			}
			#ifdef ATC_FFTW
				fftw_init_threads();
				fftwf_init_threads();
			#endif
			Eigen::setNbThreads(this->options.threads);
		#else
			if (this->options.threads > 1)
				warn("Number of threads is set to value larger than 1 but ATC was compiled without OpenMP support, so no multi-threading will be performed.");
			this->options.threads = 1;
		#endif
	}
	
	template<typename ftype>
	Compressor<ftype>::~Compressor() {
		#ifdef ATC_OPENMP
			#ifdef ATC_FFTW
				fftw_cleanup_threads();
				fftwf_cleanup_threads();
			#endif
			Eigen::setNbThreads(0);
		#endif
	}
	
	template<typename ftype>
	void Compressor<ftype>::print_core() {
		std::cout << "[Info]\t\tcore = [";
		for (size_t i = 0; i < std::min(size_t(100), core.size()); i++) {
			std::cout << core[i];
			if (i < core.size() - 1)
				std::cout << ", ";
		}
		std::cout << "]" << std::endl;
	}
	
	template<typename ftype>
	void Compressor<ftype>::resize_core(const std::vector<size_t>& current_sizes, const std::vector<size_t>& new_sizes, const std::string& timer_label) {
		
		if (new_sizes != current_sizes) {
			
			// Reshape core in-place by always reading from fields ahead of the writing index
			
			if (timer_label != "")
				start_timer(timer_label);
			
			// Preliminaries
			size_t current_size = product(current_sizes);
			size_t new_size = product(new_sizes);
			bool shrink = new_size < current_size;
			// Calculate strides using stored mode order
			std::vector<size_t> transposed_strides(modes);
			for (uint8_t mode = 0; mode < modes; mode++) {
				if (mode == 0)
					transposed_strides[mode] = 1;
				else
					transposed_strides[mode] = transposed_strides[mode - 1]*(shrink ? current_sizes : new_sizes)[current_mode_order[mode - 1]];
			}
			
			std::vector<size_t> read_indices(modes, 0);
			if (shrink) {
				
				// Core will be shrunk
				size_t read_index = 0;
				for (size_t write_index = 0; write_index < new_size; write_index++) {
					
					core[write_index] = core[read_index];
					
					// Increment index
					read_indices[0]++;
					read_index++;
					
					// We update all necessary indices in cascade, left to right, by checking for overflow
					for (uint8_t mode = 0; (read_indices[mode] >= new_sizes[current_mode_order[mode]]) && (mode < modes - 1); mode++) {
						// This index overflowed, so loop it around (back to 0) and check the next index
						read_index += (current_sizes[current_mode_order[mode]] - new_sizes[current_mode_order[mode]])*transposed_strides[mode]; // We may need to jump if this mode is cropped
						read_indices[mode] = 0;
						read_indices[mode + 1]++;
					}
					
				}
				
			} else {
				
				// Core will be padded
				// read_indices can stay initialized at [0, ..., 0] because in the first step underflow will occur in all modes and the right padding will be computed automatically
				size_t read_index = current_size;
				for (size_t write_index = new_size; write_index-- > 0;) {
					
					// The reading index will always be decremented by one
					// The writing index may be decremented more in the case of an index underflow
					// We update all necessary indices in cascade, left to right, by checking for potential underflow
					uint8_t mode = 0;
					if (read_indices[0] == 0) {
						size_t padding = 0;
						while (mode < modes && read_indices[mode] == 0) {
							// This index will underflow, so loop it around and check the next index
							read_indices[mode] = current_sizes[mode] - 1;
							// Count padding for skipped elements
							if (current_mode_order[mode] >= modes) {
								print_vector(current_mode_order);
								exit(1);
							}
							padding += (new_sizes[current_mode_order[mode]] - current_sizes[current_mode_order[mode]])*transposed_strides[mode];
							mode++;
						}
						// Set skipped elements to 0
						for (size_t i = 0; i < padding; i++)
							core[write_index--] = 0;
					}
					if (mode < modes) {
						// Decrement the first index where underflow wouldn't occur
						// If all indices were decremented, no further decrements are necessary
						read_indices[mode]--;
					}
					read_index--;
					
					if (write_index >= core.size() || read_index >= core.size() || write_index >= new_size || read_index >= current_size) {
						std::cout << write_index << " " << read_index << " " << core.size() << " " << new_size << " " << current_size << std::endl;
						exit(1);
					}
					core[write_index] = core[read_index];
					
				}
				
			}
			
			if (timer_label != "")
				stop_timer();
			
		}
		
	}
	
	#include "explicit_instantiations/Compressor.txt"
	
}
