#include <cassert>
#include <chrono>
#include <iostream>
#include <queue>

#include "dynamic-array.h"
#include "queue.h"
#include "singly-linked-list.h"
#include "doubly-linked-list.h"

namespace
{
   /*
    * run a simple test
    */
   void test_queue(seq::Queue* sqn, int n, int m, std::ostream* os)
   {
      assert((m > 0) && (n > m));
      if(os) *os << "Enqueue even numbers from 0 to " << 2*(n-1) << ".\n";
      for(int i = 0; i < n; i++) sqn->enqueue(2*i);
      for(int i = 0; i < n; i++) sqn->dequeue();
   }
   void test_queue(std::queue<int>* sqn, int n, int m, std::ostream* os)
   {
      assert((m > 0) && (n > m));
      if(os) *os << "Enqueue even numbers from 0 to " << 2*(n-1) << ".\n";
      for(int i = 0; i < n; i++) sqn->push(2*i);
      for(int i = 0; i < n; i++) sqn->pop();
   }
   
   /*
    * return time measurement in units of seconds
    */
   float test_with_time_measurement(seq::Queue* sqn, int iterations)
   {
      int queue_length = 20001;
      int deletions = 10;
      test_queue(sqn, queue_length, deletions, &std::cout);

      int log_entries = 10;
      std::cout << "\nNow repeat the above " << iterations << " times:\n";
      auto t0 = std::chrono::high_resolution_clock::now();
      for(int i = 0; i < iterations; i++)
      {
         test_queue(sqn, queue_length, deletions, nullptr);
         if((i+1) % (iterations/log_entries) == 0)
         {
            std::cout << "\t" << i+1 << "\n";
            std::cout.flush();  // make sure that status output is shown without delay
         }
      }
      auto t1 = std::chrono::high_resolution_clock::now();
      return 1.0e-06 * std::chrono::duration_cast<std::chrono::microseconds>(t1-t0).count();
   }

   float test_with_time_measurement(std::queue<int>* sqn, int iterations)
   {
      int queue_length = 20001;
      int deletions = 10;
      test_queue(sqn, queue_length, deletions, &std::cout);

      int log_entries = 10;
      std::cout << "\nNow repeat the above " << iterations << " times:\n";
      auto t0 = std::chrono::high_resolution_clock::now();
      for(int i = 0; i < iterations; i++)
      {
         test_queue(sqn, queue_length, deletions, nullptr);
         if((i+1) % (iterations/log_entries) == 0)
         {
            std::cout << "\t" << i+1 << "\n";
            std::cout.flush();  // make sure that status output is shown without delay
         }
      }
      auto t1 = std::chrono::high_resolution_clock::now();
      return 1.0e-06 * std::chrono::duration_cast<std::chrono::microseconds>(t1-t0).count();
   }
}

int main()
{
   int iterations = 200;
   
   std::cout << "*** test with dynamic array ***\n";
   seq::DynamicArray dyna;
   float dyna_time = test_with_time_measurement(&dyna, iterations);
   
   std::cout << "\n\n*** test with singly linked list ***\n";
   seq::SinglyLinkedList sll;
   float sll_time = test_with_time_measurement(&sll, iterations);
   
   std::cout << "\n\n*** test with doubly linked list ***\n";
   seq::DoublyLinkedList dll;
   float dll_time = test_with_time_measurement(&dll, iterations);
   
   std::cout << "\n\n*** test with std::queue ***\n";
   std::queue<int> queue;
   float queue_time = test_with_time_measurement(&queue, iterations);
   
   std::cout << "\n\nRuntime for dynamic array:\t" << dyna_time << " s\n";
   std::cout << "Runtime for singly linked list:\t" << sll_time << " s\n";
   std::cout << "Runtime for doubly linked list:\t" << dll_time << " s\n";
   std::cout << "Runtime for std::queue:\t" << queue_time << " s\n";
}
