#include <iostream>
#include <cstdlib>
#include <list>
#include <string>
#include <iomanip>
#include <map>
using namespace std;

#include <boost/thread/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <boost/program_options.hpp>
namespace bpo = boost::program_options;

#include "legion.hpp"
#include "cputask.hpp"
#include "cpumaniple.hpp"

// ==============================================================

enum TaskTypeID{ kPlusOne=1, kDivTwo=2 };

class PlusOne : public SciGPU::Legion::CPUtask {
public:
  // Constructor
  PlusOne( void ) : CPUtask(kPlusOne),
		    initial(0),
		    final(0) {} ;

  // Initial and final 'work' values
  int initial, final;

  // Over-ride to perform some work
  virtual bool Perform( void ) {
    final = initial+1;    
    boost::posix_time::milliseconds slp(10);  
    boost::this_thread::sleep( slp );
    return( true );
  }
};


class DivTwo : public SciGPU::Legion::CPUtask {
public:
  // Constructor
  DivTwo( void ) : CPUtask(kDivTwo),
		   input(0),
		   output(0) {} ;

  // Initial and final 'work' values
  int input, output;

  // Over-ride to perform some work
  virtual bool Perform( void ) {
    output = input/2;    
    boost::posix_time::milliseconds slp(10);  
    boost::this_thread::sleep( slp );
    return( true );
  }
};


ostream& operator<<( ostream& os, const PlusOne& task ) {

  os << "PlusOne performed by Maniple " << task.GetManipleID()
     << ": " << setw(6) << task.initial
     << "->" << setw(6) << task.final;
  
  return( os );
}


ostream& operator<<( ostream& os, const DivTwo& task ) {

  os << "DivTwo performed by Maniple " << task.GetManipleID()
     << ": " << setw(6) << task.input
     << "->" << setw(6) << task.output;
  
  return( os );
}


// ==============================================================

class PickyManiple : public SciGPU::Legion::CPUmaniple {

public:
  // Constructor
  PickyManiple( const unsigned int onlyMultiples=1 ) : CPUmaniple(),
						       factor(onlyMultiples) {} ;
						
protected:
  // Print some information at initialisation
  void Initialise( void ) {
    stringstream msg;

    msg << "Picky with factor = " << this->factor;

    this->LogMessage( msg.str() );
  }

  // Method to approve a task
  bool ApproveTask( const SciGPU::Legion::Task& theTask );

private:
  const unsigned int factor;
};


bool PickyManiple::ApproveTask( const SciGPU::Legion::Task& theTask ) {
  bool approved;

  try {
    // Attempt to convert the Task to a PlusOne task
    const PlusOne& p1 = dynamic_cast<const PlusOne&>(theTask);
    
    if( this->factor <= 1 ) {
      // Always approve factors of 0 and 1
      approved = true;
    } else {
      // Perform modulo test
      if( (p1.initial % this->factor) == 0 ) {
	approved = true;
      } else {
	approved = false;
      }
    }
  }
  catch( std::bad_cast ) {
    // Wrong type
    approved = false;
  }
  
  return( approved );
} // End method



// ==============================================================

const unsigned int nManiplesDefault = 2;
const unsigned int nPlusOneDefault = 1000;
const unsigned int nDivTwoDefault = 1000;

unsigned int nManiples;
unsigned int nPlusOne;
unsigned int nDivTwo;


void ReadCommandLine( int ac, char* av[] ) {

  try {
    bpo::options_description generic("Generic options");
    generic.add_options()
      ("help", "Produce help message" )
      ;

    bpo::options_description control("Control options");
    control.add_options()
      ("maniples",bpo::value<unsigned int>(&nManiples)->default_value(nManiplesDefault),"Number of maniples to enlist")
      ("nPlusOne",bpo::value<unsigned int>(&nPlusOne)->default_value(nPlusOneDefault),"Number of PlusOne tasks to perform")
      ("nDivTwo",bpo::value<unsigned int>(&nDivTwo)->default_value(nDivTwoDefault),"Number of DivTwo tasks to perform")
      ;

    bpo::options_description cmdLine;
    cmdLine.add(generic).add(control);

    bpo::variables_map vm;
    bpo::store( bpo::command_line_parser(ac, av).
		options(cmdLine).run(), vm );
    bpo::notify( vm );
    
    if( vm.count( "help" ) ) {
      cout << cmdLine << endl;
      exit( EXIT_SUCCESS );
    }
  }
  catch( exception& e ) {
    cerr << "Error: " << e.what() << endl;
    exit( EXIT_FAILURE );
  }
  catch( ... ) {
    cerr << "Unknown exception" << endl;
    exit( EXIT_FAILURE );
  }

  if( nManiples < 1 ) {
    cerr << "Must have at least one maniple!" << endl;
    exit( EXIT_FAILURE );
  }

}

  



// ==============================================================

int main( int argc, char *argv[] ) {

  ReadCommandLine( argc, argv );

  cout << "Legion Picky" << endl;
  cout << "============" << endl << endl;

  
  // Create the Legion
  SciGPU::Legion::Legion myLegion;

  // Create PickyManiples with various factors
  unsigned int nextFactor = 1;
  for( unsigned int i=1; i<nManiples; i++ ) {
    PickyManiple picky(nextFactor);
    myLegion.AddManiple( picky );
    nextFactor *= (i+2);
  }
  
  // Give the Legion its default maniple
  myLegion.AddManiple( SciGPU::Legion::CPUmaniple() );

  // Declare the list of tasks
  list<SciGPU::Legion::Task*> tasks;


  for( unsigned int i=0; i<nDivTwo; i++ ) {
    DivTwo* nxt = new DivTwo;
    nxt->input = 131072 + ( std::rand() % 65536 );
    tasks.push_back( nxt );
  }

  for( unsigned int i=0; i<nPlusOne; i++ ) {
    PlusOne* nxt = new PlusOne;
    nxt->initial = std::rand() % 65536;
    tasks.push_back( nxt );
  }


  
  // Enqueue them
  list<SciGPU::Legion::Task*>::const_iterator it;
  for( it = tasks.begin(); it != tasks.end(); it++ ) {
    myLegion.Enqueue( *it );
  }

  // Wait for completion
  volatile size_t nComplete;
  do {
    boost::posix_time::seconds slp(10);  
    boost::this_thread::sleep( slp );
    nComplete = myLegion.CountComplete();
    cout << "nComplete = " << nComplete <<endl;
  } while( nComplete < tasks.size() );


  // Print some results
  cout << endl;
  cout << "Results:" << endl;

  cout << endl;

  myLegion.PrintCompletedStats( cout );
  
  cout << endl;

  // Release all the tasks
  while( tasks.size() != 0 ) {
    delete tasks.front();
    tasks.pop_front();
  }

  return( EXIT_SUCCESS );
}
