#include <exception>
#include <iomanip>
#include <iostream>
#include "array.hpp"

template<typename T>
void test_operation(unsigned int test, std::string description,
      T&& operation) {
   std::cout << "Test " << std::setw(2) << std::right << test << ": ";
   std::cout << std::setw(30) << std::left << std::move(description) << " ";
   try {
      operation();
      std::cout << "ok" << std::endl;
   } catch (std::exception& e) {
      std::cout << "exception: " << e.what() << std::endl;
   } catch (...) {
      std::cout << "unknown exception" << std::endl;
   }
}

struct FailingObject {
   class Exception: public std::exception {
      const char* what() const noexcept override {
	 return "FailingObject exception";
      }
   };
   FailingObject() {
      throw Exception();
   }
   FailingObject(const FailingObject& other) {
      throw Exception();
   }
   FailingObject(int i) {
      if (i < 0) throw Exception();
   }
   FailingObject& operator=(const FailingObject& other) {
      throw Exception();
   }
};

int main() {
   unsigned int test = 0;
   test_operation(++test, "Array()", []() { Array<FailingObject>(); });
   test_operation(++test, "Array(0)", []() { Array<FailingObject>(0); });
   test_operation(++test, "Array(1)", []() { Array<FailingObject>(1); });
   test_operation(++test, "Array(1, FailingObject(1))",
      []() { Array<FailingObject>(1, FailingObject(1)); });
   test_operation(++test, "Array(it1, it2) from {1, 2}", []() {
      int values[] = {1, 2, 3};
      Array<FailingObject>(values, values + sizeof(values)/sizeof(values[0]));
   });
   test_operation(++test, "Array(it1, it2) from {1, -2}", []() {
      int values[] = {1, -2};
      Array<FailingObject>(values, values + sizeof(values)/sizeof(values[0]));
   });
   test_operation(++test, "Array{1, 2}", []() {
      Array<FailingObject>{1, 2};
   });
   test_operation(++test, "Array{1, -2}", []() {
      Array<FailingObject>{1, -2};
   });
   test_operation(++test, "Array copy constructor", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject> array2(array);
   });
   test_operation(++test, "Array move constructor", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject>(std::move(array));
   });
   test_operation(++test, "Array swap", []() {
      int values[] = {1, 2};
      Array<FailingObject> array1(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject> array2(values,
	 values + sizeof(values)/sizeof(values[0]));
      using std::swap;
      swap(array1, array2);
   });
   test_operation(++test, "Array assignment", []() {
      int values[] = {1, 2};
      Array<FailingObject> array1(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject> array2;
      array2 = std::move(array1);
   });
   test_operation(++test, "Array get_size", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      array.get_size();
   });
   test_operation(++test, "Array access by index", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      array(1);
   });
   test_operation(++test, "Array access by index const", []() {
      int values[] = {1, 2};
      const Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      array(1);
   });
   test_operation(++test, "Array iterators", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      for (auto& object: array) {
      }
   });
   test_operation(++test, "Array const iterators", []() {
      int values[] = {1, 2};
      const Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      for (auto& object: array) {
      }
   });
   std::cout << test << " tests survived." << std::endl;
}
