text: #include "deconv.hh"
  #include "vanilla.hh"
  #include <algorithm> // min, max
  #include <cmath>
  #include <cstdlib>
  #include <ctime>
  #include <iomanip> // setw
  #include <iostream>
  
  template <class Tp, int N>
  void clear(Tp (&innermost_array)[N])
  {
  	for (auto &e : innermost_array) {
  		e = {};
  	}
  }
  
  template <class Tp, int N, int M>
  void clear(Tp (&degenerate_array)[N][M])
  {
  	for (auto &subarray : degenerate_array) {
  		clear(subarray);
  	}
  }
  
  template <class Tp, int N>
  void randomize(Tp (&innermost_array)[N])
  {
  	for (auto &e : innermost_array) {
  		e = rand() % 100 + 1; // magic number
  	}
  }
  
  template <class Tp, int N, int M>
  void randomize(Tp (&degenerate_array)[N][M])
  {
  	for (auto &subarray : degenerate_array) {
  		randomize(subarray);
  	}
  }
  
  template <class Tp, int N>
  bool are_same(const Tp (&A)[N], const Tp (&B)[N])
  {
  	for (int i{}; i != N; ++i) {
  		if (A[i] != B[i]) {
  			return false;
  		}
  	}
  	return true;
  }
  
  template <class Tp, int N, int M>
  bool are_same(const Tp (&A)[N][M], const Tp (&B)[N][M])
  {
  	bool same{true};
  	for (int i{}; i != N; ++i) {
  		same = same && are_same(A[i], B[i]);
  	}
  	return same;
  }
  
  template <class Tp, int N>
  void copy(Tp (&A)[N], const Tp (&B)[N])
  {
  	for (int i{}; i != N; ++i) {
  		A[i] = B[i];
  	}
  }
  
  template <class Tp, int N, int M>
  void copy(Tp (&A)[N][M], const Tp (&B)[N][M])
  {
  	for (int i{}; i != N; ++i) {
  		copy(A[i], B[i]);
  	}
  }
  
  template <class Tp> // must be integral type
  auto representation_length(const Tp n)
  {
  	if (n == Tp{}) {
  		return 1;
  	}
  	const auto length = 1 + static_cast<int>(std::floor(std::log10(std::abs(n))));
  	return n < 0 ? length + 1 : length;
  }
  
  template <class Tp, int Channels, int Height, int Width>
  std::ostream &operator<<(std::ostream &os,
  						 const Tp (&block)[Height][Width][Channels])
  {
  	for (int c{}; c != Channels; ++c) {
  		auto m = block[0][0][0], M = m;
  		for (int w{}; w != Width; ++w) {
  			for (int h{}; h != Height; ++h) {
  				m = std::min(m, block[w][h][c]);
  				M = std::max(M, block[w][h][c]);
  			}
  		}
  		const auto cell_width = 1 + std::max(representation_length(m), representation_length(M));
  
  		os << "channel = " << c << '\n'
  		   << std::right;
  		for (int h{}; h != Height; ++h) {
  			for (int w{}; w != Width; ++w) {
  				// os << std::setw(3) << block[c][h][w]; // magic number
  				os << std::setw(cell_width) << block[h][w][c];
  			}
  			os << '\n';
  		}
  	}
  	return os << std::left;
  }
  
  template <class Tp, int OutChannels, int InChannels, int KerSize>
  std::ostream &operator<<(std::ostream &os,
  						 const Tp (&kernel)[KerSize][KerSize][OutChannels][InChannels])
  {
  	for (int oc{}; oc != OutChannels; ++oc) {
  		for (int ic{}; ic != InChannels; ++ic) {
  			os << "channel = " << oc << ',' << ic << '\n'
  			   << std::right;
  			for (int h{}; h != KerSize; ++h) {
  				for (int w{}; w != KerSize; ++w) {
  					os << std::setw(3) << kernel[h][w][oc][ic]; // magic number
  				}
  				os << '\n';
  			}
  		}
  	}
  	return os << std::left;
  }
  
  void test_big()
  {
  	constexpr int OutChannels = 3, InChannels = 3, KerSize = 10;
  	using DataType = int;
  	using OutType = DataType[400][400][OutChannels];
  	using InType = DataType[200][200][InChannels];
  	using KerType = DataType[KerSize][KerSize][OutChannels][InChannels];
  
  	srand(time({}));
  	InType in;
  	randomize(in);
  	KerType ker;
  	randomize(ker);
  	OutType out, rout;
  
  	clear(out);
  	clear(rout);
  	deconvolution<1, 0>(out, in, ker);
  	vanilla_deconvolution<1, 0>(rout, in, ker);
  	std::cout << std::boolalpha << are_same(out, rout) << '\n';
  
  	clear(out);
  	clear(rout);
  	deconvolution<6, 0>(out, in, ker);
  	vanilla_deconvolution<6, 0>(rout, in, ker);
  	std::cout << std::boolalpha << are_same(out, rout) << '\n';
  
  	clear(out);
  	clear(rout);
  	deconvolution<6, 3>(out, in, ker);
  	vanilla_deconvolution<6, 3>(rout, in, ker);
  	std::cout << std::boolalpha << are_same(out, rout) << '\n';
  	// std::cout << out << '\n'
  	// 		  << rout;
  
  	// std::cout << (-5 % 3) << ' ' << (-5 % 7) << '\n';
  
  	/*
  	// std::cout << "input:\n"
  	// 		  << in << '\n'
  	// 		  << "kernel:\n"
  	// 		  << ker << '\n';
  	OutType out0;
  	clear(out0);
  	deconvolution0<1, 0>(out0, in, ker);
  	// int dup[OutChannels][6][6];
  	// copy(dup, out0);
  	// std::cout << std::boolalpha << are_same(dup, out0) << '\n';
  
  	// assert that deconv0 == deconv1
  	OutType out1;
  	clear(out1);
  	deconvolution1<1, 0>(out1, in, ker);
  	std::cout << std::boolalpha << are_same(out0, out1) << '\n';
  
  	// I believe that deconv2 should be correct
  	OutType out2;
  	clear(out2);
  	deconvolution2<1, 0>(out2, in, ker);
  	std::cout << std::boolalpha << are_same(out1, out2) << '\n';
  
  	// a different stride
  	// this raised the out-of-bound problem
  	OutType out3{};
  	deconvolution3<2, 0>(out3, in, ker);
  	std::cout << out3;
  
  	// the above with non-zero padding
  	OutType out4{};
  	deconvolution3<2, 2>(out4, in, ker);
  	std::cout << "\nPadding = 2\n"
  			  << out4;
  
  	// already unsafe ?
  	OutType rout0{};
  	reverse_deconvolution0<1, 0>(rout0, in, ker);
  	std::cout << std::boolalpha << are_same(out0, rout0) << '\n';
  	std::cout << rout0;
      
  	// safe
  	OutType rout1{};
  	reverse_deconvolution1<1, 0>(rout1, in, ker);
  	std::cout << std::boolalpha << are_same(out2, rout1) << '\n';
  	std::cout << rout1;
  	// std::cout << "output:\n"
  	// 		  << out;
      */
  }
  
  void test_small()
  {
  	constexpr int OutChannels = 3, InChannels = 3, KerSize = 2;
  	using DataType = int;
  	using OutType = DataType[6][6][OutChannels];
  	using InType = DataType[3][3][InChannels];
  	using KerType = DataType[KerSize][KerSize][OutChannels][InChannels];
  
  	srand(time({}));
  	InType in;
  	randomize(in);
  	KerType ker;
  	randomize(ker);
  	OutType out, rout;
  
  	clear(out);
  	clear(rout);
  	deconvolution<1, 0>(out, in, ker);
  	vanilla_deconvolution<1, 0>(rout, in, ker);
  	std::cout << std::boolalpha << are_same(out, rout) << '\n';
  
  	clear(out);
  	clear(rout);
  	deconvolution<2, 0>(out, in, ker);
  	vanilla_deconvolution<2, 0>(rout, in, ker);
  	std::cout << std::boolalpha << are_same(out, rout) << '\n';
  
  	clear(out);
  	clear(rout);
  	deconvolution<2, 2>(out, in, ker);
  	vanilla_deconvolution<2, 2>(rout, in, ker);
  	std::cout << std::boolalpha << are_same(out, rout) << '\n';
  	// std::cout << out << '\n'
  	// 		  << rout;
  }
  
  // convention: (height, width, channel), (out, in, ker)
  
  int main()
  {
  	test_small();
  	test_big();
  }
  