// src/testsuite/test-history.cc - Copyright 2005, 2006, University
//               of Padova, dept. of Pure and Applied
//               Mathematics
//
// This file is part of SGPEMv2.
//
// This is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// SGPEMv2 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with SGPEMv2; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

/* This executable tests for workingness of the whole History services,
 * and nothing else. */

#include "config.h"
#include "gettext.h"

#include <glibmm/ustring.h>

#include <cassert>
#include <iostream>

#include "concrete_environment.hh"
#include "concrete_history.hh"
#include <sgpemv2/history_observer.hh>

using namespace sgpem;
using namespace std;
using Glib::ustring;


// History observer used to see how many times
// it is updated
class DummyObserver : public HistoryObserver
{
public:
  DummyObserver() : _i(0) {}
  void update(const History& history)
  {
    cout << "Observer.update() : " << ++_i << endl;
  }
  unsigned int _i;
};

struct ProcessCreationData
{
  ustring name;
  time_t arrival_time;
  History::prio_t base_priority;
};

struct ThreadCreationData
{
  ustring name;
  Process* parent;
  time_t cpu_time;
  time_t arrival_time;
  History::prio_t base_priority;
};

struct ResourceCreationData
{
  ustring name;
  bool preemptable;
  size_t places;
  size_t availability;
};

struct RequestCreationData
{
  Thread* owner;
  time_t instant;
};

struct SubRequestCreationData
{
  Request* request;
  History::resource_key_t resource_key;
  time_t duration;
};


DynamicThread&
find_thread(ConcreteEnvironment& env, Thread& _thread)
{
  DynamicThread& thread = (DynamicThread&)_thread;
  Environment::Processes& processes = env.get_processes();

  // please forgive me, I'm sick of using iterators...
  for (unsigned int i = 0; i < processes.size(); ++i)
  {
    Process& p = *processes[i];
    vector<Thread*> threads = p.get_threads();

    for (unsigned int j = 0; j < threads.size(); ++j)
    {
      DynamicThread& t = (DynamicThread&) * threads[j];
      if (t == thread)
        return t;
    }
  }

  return thread;
}

const DynamicThread&
find_thread(const ConcreteEnvironment& env, const Thread& _thread)
{
  const DynamicThread& thread = (DynamicThread&)_thread;
  const Environment::Processes& processes = env.get_processes();

  // please forgive me, I'm sick of using iterators...
  for (unsigned int i = 0; i < processes.size(); ++i)
  {
    const Process& p = *processes[i];
    vector<const Thread*> threads = p.get_threads();

    for (unsigned int j = 0; j < threads.size(); ++j)
    {
      const DynamicThread& t = (const DynamicThread&) * threads[j];
      if (t == thread)
        return t;
    }
  }

  return thread;
}

int
main(int argc, char** argv)
{
  ostream& info = cout;
  ostream& test = cerr;

  using namespace sgpem;

  ConcreteHistory h;

  info << "Created the ConcreteHistory instance\n";

  test << "Checking if it contains only one Environment... ";

  if (h.get_size() == 1)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  const ConcreteEnvironment& environment = h.get_last_environment();

  info << "Obtained the only contained environment\n";

  test << "Checking if the environment is empty... ";

  if (environment.get_processes().size() == 0 &&
      environment.get_resources().size() == 0 &&
      environment.get_sorted_queue().size() == 0)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  DummyObserver observer;

  info << "Created the observer\n";

  h.attach(observer);

  info << "Attached to the history\n";

  const ProcessCreationData processes[2] =
    {
      { "p1", 0, 3 },
      { "p2", 3, 1 }
    };

  DynamicProcess& p1 = h.add_process(processes[0].name,
                                     processes[0].arrival_time,
                                     processes[0].base_priority);

  DynamicProcess& p2 = h.add_process(processes[1].name,
                                     processes[1].arrival_time,
                                     processes[1].base_priority);

  const ThreadCreationData threads[4] =
    {
      { "p2_1", &p2, 3, 0, 0 },
      { "p1_1", &p1, 2, 0, 2 },
      { "p1_2", &p1, 1, 1, 0 },
      { "p2_2", &p2, 5, 3, 1 }
    };

  DynamicThread& p2_1 = h.add_thread(threads[0].name,
                                     *threads[0].parent,
                                     threads[0].cpu_time,
                                     threads[0].arrival_time,
                                     threads[0].base_priority);

  DynamicThread& p1_1 = h.add_thread(threads[1].name,
                                     *threads[1].parent,
                                     threads[1].cpu_time,
                                     threads[1].arrival_time,
                                     threads[1].base_priority);

  DynamicThread& p1_2 = h.add_thread(threads[2].name,
                                     *threads[2].parent,
                                     threads[2].cpu_time,
                                     threads[2].arrival_time,
                                     threads[2].base_priority);

  DynamicThread& p2_2 = h.add_thread(threads[3].name,
                                     *threads[3].parent,
                                     threads[3].cpu_time,
                                     threads[3].arrival_time,
                                     threads[3].base_priority);

  const ResourceCreationData resources[2] =
    {
      { "res1", true, 2, 0 },
      { "res2", false, 1, 0 }
    };

  const History::ResourcePair res1 = h.add_resource(resources[0].name,
                                     resources[0].preemptable,
                                     resources[0].places,
                                     resources[0].availability);

  const History::ResourcePair res2 = h.add_resource(resources[1].name,
                                     resources[1].preemptable,
                                     resources[1].places,
                                     resources[1].availability);

  const RequestCreationData requests[2] =
    {
      { &p1_2, 0 },
      { &p2_1, 1 }
    };

  DynamicRequest& req1 = h.add_request(*requests[0].owner, requests[0].instant);

  DynamicRequest& req2 = h.add_request(*requests[1].owner, requests[1].instant);

  const SubRequestCreationData subrequests[3] =
    {
      { &req1, res1.first, 1 },
      { &req2, res2.first, 2 },
      { &req2, res1.first, 1 }
    };

  const DynamicSubRequest& sreq1 = h.add_subrequest(*subrequests[0].request,
                                   subrequests[0].resource_key,
                                   subrequests[0].duration);

  const DynamicSubRequest& sreq2 = h.add_subrequest(*subrequests[1].request,
                                   subrequests[1].resource_key,
                                   subrequests[1].duration);

  const DynamicSubRequest& sreq3 = h.add_subrequest(*subrequests[2].request,
                                   subrequests[2].resource_key,
                                   subrequests[2].duration);

  info << "Done adding required data by using the History factory interface\n";

  // FIXME
  // const Environment::SubRequestQueue res1_queue = environment.get_request_queue(res1.first);
  // const Environment::SubRequestQueue res2_queue = environment.get_request_queue(res2.first);

  test << "Checking if the environment contains the correct request queues... ";

  typedef Environment::SubRequestQueue::const_iterator ReqIterator;

  // *************  FIXME  *************** :

//   if(res1_queue.size() == 2 && res2_queue.size() == 1)
//   {
//     bool res1_req1_match = false;
//     bool res1_req2_match = false;
//     bool res2_req1_match = false;
//     bool bad_match = false;

//     for(ReqIterator it = res1_queue.begin(); it != res1_queue.end(); ++it)
//     {
//       if(!res1_req1_match && *it == &req1)
//  res1_req1_match = true;
//       else if(!res1_req2_match && *it == &req2)
//  res1_req2_match == true;
//       else
//  bad_match = true;
//     }

//     if(!res2_req1_match && *res2_queue.begin() == &req1)
//       res2_req1_match = true;
//     else
//       bad_match = true;

//     if(!bad_match && res1_req1_match && res1_req2_match && res2_req1_match)
//       test << "PASS";
//     else
//       test << "FAIL";
//   }
//   else
//     test << "FAIL";
  test << endl;


  ConcreteEnvironment* environment1 = new ConcreteEnvironment(environment);
  ConcreteEnvironment* environment2 = new ConcreteEnvironment(environment);
  ConcreteEnvironment* environment3 = new ConcreteEnvironment(environment);
  ConcreteEnvironment* environment4 = new ConcreteEnvironment(environment);
  ConcreteEnvironment* environment5 = new ConcreteEnvironment(environment);

  info << "Created 5 copies of inital environment\n";

  const History::prio_t priorities[5] = { 3, 1, 4, 5, 2 };

  find_thread(*environment1, p1_1).set_priority_push(priorities[0]);
  find_thread(*environment2, p1_1).set_priority_push(priorities[1]);
  find_thread(*environment3, p1_1).set_priority_push(priorities[2]);
  find_thread(*environment4, p1_1).set_priority_push(priorities[3]);
  find_thread(*environment5, p1_1).set_priority_push(priorities[4]);

  info << "Updated dynamic priority of threads\n";

  h.append_new_environment(environment1);
  h.append_new_environment(environment2);
  h.append_new_environment(environment3);
  h.append_new_environment(environment4);
  h.append_new_environment(environment5);

  info << "Done appending the environment copies to the history\n";

  test << "Checking if history size is 6... ";

  if (h.get_size() == 6)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  const DynamicThread& t1 = find_thread(h.get_environment_at(1), p1_1);
  const DynamicThread& t5 = find_thread(h.get_environment_at(5), p1_1);

  test << "Checking if dynamic priority of thread at instant 1 and 5 is different... ";

  if (t1.get_priority_push() != t5.get_priority_push())
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  const DynamicThread& t4 = find_thread(h.get_environment_at(4), p1_1);

  test << "Checking if dynamic priority of thread at instant 4, is different ";
  test << "from that at instant 1 and 5, and equal to the one expected... ";

  if (t4.get_priority_push() != t1.get_priority_push() &&
      t4.get_priority_push() != t5.get_priority_push() &&
      t4.get_priority_push() == priorities[3])
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  test << "Checking whether all subrequests in the last environment ";
  test << "refer to valid resources... ";

  const ConcreteEnvironment& last_env = h.get_last_environment();
  const Environment::Processes& le_processes = last_env.get_processes();
  const Environment::Resources& le_resources = last_env.get_resources();

  bool subreq_pass = true;

  for (unsigned int i = 0; i < le_processes.size(); ++i)
  {
    vector<Thread*> threads = le_processes[i]->get_threads();

    for (unsigned int j = 0; j < threads.size(); ++j)
    {
      vector<Request*> requests = threads[j]->get_requests();

      for (unsigned int k = 0; k < requests.size(); ++k)
      {
        vector<SubRequest*> subrequests = requests[k]->get_subrequests();

        for (unsigned int x = 0; x < subrequests.size(); ++x)
        {
          if (le_resources.find(subrequests[x]->get_resource_key()) == le_resources.end())
            subreq_pass = false;
        }
      }
    }
  }

  if (subreq_pass)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  test << "Checking if history throws an exception on accessing a nonexistent environment... ";

  try
  {
    h.get_environment_at(6);
    test << "FAIL";
  }
  catch (out_of_range)
  {
    test << "PASS";
  }
  test << endl;

  ThreadCreationData p1_3_d = { "p1_3", &p1, 3, 4, 0 };

  Thread& p1_3 = h.add_thread(p1_3_d.name,
                              *p1_3_d.parent,
                              p1_3_d.cpu_time,
                              p1_3_d.arrival_time,
                              p1_3_d.base_priority);

  info << "Added new thread, child of process 1\n";

  test << "Checking if history has size 1 after adding a thread... ";

  if (h.get_size() == 1)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  ConcreteEnvironment* environment6 = new ConcreteEnvironment(h.get_last_environment());

  h.append_new_environment(environment6);

  info << "Added a new environment to the history as a clear-cut copy of the last" << endl;

  h.remove(res1.first);

  test << "Checking if history has size 1 after removing a resource... ";

  if (h.get_size() == 1)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;


  test << "Checking whether objects inside the history are the same as the beginning, ";
  test << "except for the missing thread and associated requests... ";

  const Environment::Processes& final_processes = h.get_last_environment().get_processes();
  const Environment::Resources& final_resources = h.get_last_environment().get_resources();

  bool final_p1_match = false;
  bool final_p2_match = false;
  bool final_bad_match = false;
  bool final_sreq2_match = false;

  for (unsigned int i = 0; i < final_processes.size(); ++i)
  {
    DynamicProcess& p = (DynamicProcess&) * final_processes[i];

    if (!final_p1_match && p == p1)
      final_p1_match = true;
    else if (!final_p2_match && p == p2)
      final_p2_match = true;
    else
      final_bad_match = true;

    vector<DynamicThread*>& threads = p.get_dynamic_threads();

    for (unsigned int j = 0; j < threads.size(); ++j)
    {
      vector<DynamicRequest*> requests = threads[j]->get_dynamic_requests();

      for (unsigned int k = 0; k < requests.size(); ++k)
      {
        vector<DynamicSubRequest*> subrequests = requests[k]->get_dynamic_subrequests();

        for (unsigned int x = 0; x < subrequests.size(); ++x)
        {
          // NOTE this will of course fail if the subrequests which should not be here
          // are still in the environment
          if (!final_sreq2_match && *subrequests[x] == sreq2)
            final_sreq2_match = true;
          else
            final_bad_match = true;
        }
      }
    }
  }

  typedef Environment::Resources::const_iterator ResourceIt;

  if (final_resources.size() == 1)
  {
    DynamicResource& r = (DynamicResource&) * (final_resources.begin()->second);
    if (!(r == *res2.second))
      final_bad_match = true;
  }
  else
    final_bad_match = true;

  if (!final_bad_match && final_p1_match && final_p2_match && final_sreq2_match)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;


  test << "Checking whether ConcreteHistory throws an exception if we try to use ";
  test << "a negative index to access an environment... ";

  try
  {
    // FIXME This causes a warning because we force a conversion of a negative number
    // to an unsigned integer. What did the designers wanted to test by doing this?
    // The compiler?
    h.get_environment_at(-17);
    test << "FAIL";
  }
  catch (out_of_range)
  {
    test << "PASS";
  }
  test << endl;

  test << "Internal observer's counter should be 21... ";

  if (observer._i == 21)
    test << "PASS";
  else
    test << "FAIL";
  test << endl;

  return 0;
}
