/*
 *
 */

#include <boost/lexical_cast.hpp>
#include <boost/random.hpp>
#include <boost/assign.hpp>
#include <boost/circular_buffer.hpp>
#include <boost/program_options.hpp>
#include <set>
#include <cstdlib>
#include <limits.h>
#include "SerialNeutronExchanger.hh"
#include "PyRunOnCPP.hh"
//#define DEBUG


////////////////////////////////////////////////

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

  int call_count = 0;
  std::set<std::string> IPlist;
  IPlist.insert("127.0.0.1");
  IPlist.insert("192.168.30.10");
  IPlist.insert("192.168.13.170");
  IPlist.insert("192.168.13.171");
  IPlist.insert("192.168.13.145");
  IPlist.insert("130.87.59.189");
  IPlist.insert("130.87.59.237");
  IPlist.insert("130.87.59.188");
  IPlist.insert("130.87.58.49");
  IPlist.insert("130.87.57.111");
  IPlist.insert("130.87.58.68");

  boost::asio::io_service io;
  boost::asio::ip::tcp::acceptor acc(io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 8888));

  // makes and return new port/key
  boost::mt19937 eng(static_cast<unsigned long>(time(0)));
  boost::variate_generator<boost::mt19937&, boost::uniform_int<> >
	_rndkey(eng, boost::uniform_int<> ( 0, INT_MAX ));

  boost::program_options::options_description opt("options");
  opt.add_options()
	("help,h", "show this help")
	("t,t",    "show the port and remotehost")
	("start", boost::program_options::value<unsigned long>()->default_value(50000), "port range start")
	("range", boost::program_options::value<unsigned long>()->default_value(2000),  "port range");
  boost::program_options::variables_map vm;
  boost::program_options::parsed_options parsed =
    boost::program_options::command_line_parser(argc, argv).options(opt).allow_unregistered().run();
  store(parsed, vm);
  notify(vm);

  if (vm.count("help")) {
	std::cout << opt << std::endl;
	return 0;
  }

  bool displayport = (vm.count("t")) ? true : false;
  unsigned long portrangestart = vm["start"].as<unsigned long>();
  unsigned long portrangesize  = vm["range"].as<unsigned long>();

  portrangesize = std::min(65535-portrangestart, portrangesize);


#if defined(DEBUG)
  std::cerr << "Pstart: " << portrangestart << ", Psize: " << portrangesize << std::endl;
#endif

  boost::circular_buffer<unsigned long> P(portrangesize, 0);
  for (unsigned long l=0;l<portrangesize;++l)
	P[l] = portrangestart + l;
  random_shuffle(P.begin(), P.end());
#if defined(DEBUG)
  for (unsigned int l = 0; l < P.size(); l++) {
	std::cerr << "reserved portnum = " << P[l] << '\n';
  }
#endif

  boost::circular_buffer<unsigned long>::iterator pnumber = P.begin();



	/* XXX
	 * make STL map for the relation from port number to the shmid
	 *
	 * make shared memory, and store its id to above map
	 * for each port number
	 *
	 * write 0 to all these sh_memory
	 */


  while (true) {
	boost::asio::ip::tcp::iostream  h;
	acc.accept(*h.rdbuf());

	// allow/deny the connection if required
	std::string fromhost =
	  h.rdbuf()->remote_endpoint().address().to_string();
#if defined(DEBUG)
	std::cerr << "\n";
	std::cerr << "----------------------------------------------\n";
	std::cerr << "connected from: " << fromhost << std::endl;
	//for (std::set<std::string>::iterator p=IPlist.begin(),
	//       end=IPlist.end();p!=end;++p)
	//  std::cerr << "allowed IP: " << *p << std::endl;
#endif


	if (IPlist.find(fromhost) != IPlist.end()) {

	  // makes key and port before fork()
	  UInt4 key = _rndkey();
	  if ( pnumber == P.end() ) {
		pnumber = P.begin();
	  }
	  UInt4 p   = *pnumber++;
#if defined(DEBUG)
	  std::cerr << "  Call count = " << call_count << '\n';
#endif
	  call_count++;

		/* XXX
		 * obtain shared meomry id from the map, and check it
		 *
		 * while it is 1 increment pnumber
		 */

	  if (fork() == 0) {

		/* XXX
		 * write 1 to the shared memory with the id related to
		 * the port number
		 */



		std::cerr << key << " " << p << std::endl;
		if (displayport) std::cout << "port " << p << " is used to the connection to the " << fromhost << std::endl;

		h << p   << std::endl
		  << key << std::endl;
		//SNE.Transmit(p);
		//SNE.Transmit(key);

		// obtain the script
		std::vector<std::string> pyscript;
		SerialNeutronExchanger SNE(&h);
		sleep(1);
		SNE.Receive(pyscript);

		// preparing new python script
		std::vector<std::string> new_connection;

		using namespace boost::assign;
		new_connection +=
		  "import Manyo",
		  "import Manyo.SerialPySrv as MS",
		  "import sys",
		  "Ar=MS.ASIO_remote(" + boost::lexical_cast<std::string>(p) + ")",
		  "EX=MS.SerialNeutronExchanger(Ar)",
		  "K = EX.ReceiveUInt4()",
		  "if K != " + boost::lexical_cast<std::string>(key) + ": sys.exit()";



		/* XXX
		 * add following procedures into new_connection as python scripts
		 *
		 * write 0 to a shared memory which has specified shmid
		 *
		 * because python has no feature to access to shared memory,
		 * simple C function to write 0 with specified id should be written
		 * and be linked in libManyo
		 * SWIG interface to access this function from python is also needed
		 */




		pyscript.insert(pyscript.begin(), new_connection.begin(), new_connection.end());

		for (unsigned int i=0;i<pyscript.size();++i)
		  std::cout << pyscript[i] << std::endl;

		// perform new script and wait the connection
#ifdef DEBUG
		std::cerr << "execute python script....";
#endif
		PyRunOnCPP L;
		L.PyRun(pyscript);
		sleep(3);
#ifdef DEBUG
		std::cerr << "\n";

		//acc.close();
		std::cerr << "close port for wait and exit fork with Python code excuter\n";
#endif
		exit(0);
	  }
	}
  }

  return 0;
}
