// Benchmark main program.

#include <iostream>
#include <vector>

#include <pthread.h>

#include "sharedobj.hh"

int increment(int i);  // in extern.cc to avoid inlining.


using namespace std;


// These "shared objects" are created for the threads to fight over:

int num_objs;
vector<sharedobj> objs;


// Struct to pass parameters to one of the threads:

struct run_thread_info {
  int thread_num;         // Incrementing thread number.
  int thread_iterations;  // How many operations this thread should do.
};


void* run_thread(void* arg)
{
  run_thread_info* info = reinterpret_cast<run_thread_info*>(arg);

  // Each threads does info->thread_iterations increments on the data fields in the
  // shared objects, locking and unlocking before and after.  It steps over the
  // objects incrementing by info->thread_num; if num_objs is prime then this will
  // result in an even distribution of accesses across the objects.

  int n = 0;
  for (int i=0; i<info->thread_iterations; ++i) {
    sharedobj& s = objs[n];
    s.lock();
    //s.data++;  Hmm, that's a single instruction on x86
    s.data = increment(s.data);  // That's in another file, so the compiler
                                 // can't inline it and we'll get a window during
                                 // which the other thread could get in.
    s.unlock();
    n = (n+info->thread_num+1)%num_objs;
  }

  return NULL;
}


int main(int argc, char* argv[])
{
  if (argc!=4) {
    cerr << "usage: " << argv[0] << " num_objs num_threads num_ops\n";
    exit(1);
  }

  num_objs = atoi(argv[1]);
  int num_threads = atoi(argv[2]);
  int num_ops = atoi(argv[3]);

  int iterations_left = num_ops;

  objs.resize(num_objs);

  vector<pthread_t> threads(num_threads);

  for (int i=0; i<num_threads; ++i) {
    run_thread_info* info = new run_thread_info;
    info->thread_num = i;
    info->thread_iterations = iterations_left / (num_threads-i);
    iterations_left -= info->thread_iterations;
    pthread_create(&threads[i],NULL,&run_thread,info);
  }

  for (int i=0; i<num_threads; ++i) {
    pthread_join(threads[i],NULL);
  }

  // All being well, the sum of the data values in all of the shared objects will
  // equal the requested total number of operations.  If it doesn't then something
  // went wrong with the locking.

  int total=0;
  for (int i=0; i<num_objs; ++i) {
    total += objs[i].data;
  }

  if (total != num_ops) {
    //cerr << "Error in totalisation, expecting " << num_ops << ", got " << total << "!\n";
    // (Don't print the message, so that run.sh doesn't have to filter it out.)
    exit(1);
  }

  exit(0);
}



