/*
 * $Id:$
 */

#if !defined(SERIALNEUTRONEXCHANGER)
#define SERIALNEUTRONEXCHANGER


#include <iostream>
#include <string>
#include <vector>

// boost asio
#include <boost/asio.hpp>

// boost serialization
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>
//  boost archive
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

#include "Header.hh"
#include "ElementContainer.hh"
#include "ElementContainerArray.hh"
#include "ElementContainerMatrix.hh"
#include "UInt4Container.hh"
#include "UInt4ContainerArray.hh"
#include "UInt4ContainerMatrix.hh"

//#define DEBUG

/** local side of the Boost ASIO netowrk connection
 *
 *
 */
class ASIO_local {
private:
  boost::asio::ip::tcp::iostream  *h;
  //!< Boost ASIO network stream for the exchange

public:
  /*
   * @param host hostname std::string to send(transmit)
   * @param port port number as a string of the remote host to send(transmit)
   */
  ASIO_local(const std::string &host, const std::string &port) {
    h  = new boost::asio::ip::tcp::iostream (host, port);
  }
  ~ASIO_local() {
    if ( h != NULL ) delete h;
  }

  /** return the status of Boost ASIO's tcp::iostream
   *
   * @return  false for bad (not connected)
   */
  //bool stat(void) const { return *h; };
  bool stat(void) {
    if ( h != NULL ) return !h->fail();
    return false;
  }

  friend class SerialNeutronExchanger;
  friend class SerialPythonServerClient;
};


/** remote side of the Boost ASIO netowrk connection
 *
 *
 */
class ASIO_remote {
private:
  boost::asio::io_service io;
  boost::asio::ip::tcp::acceptor *acc;

  boost::asio::ip::tcp::iostream  *h;
  //!< Boost ASIO network stream for the exchange

public:
  /*
   * @param port port number to listen to
   */
  ASIO_remote(const UInt4 port) {
#if defined(DEBUG)
    std::cerr << "ASIO_remote -> start constructer\n";
#endif
    h  = new boost::asio::ip::tcp::iostream;
    acc = new boost::asio::ip::tcp::acceptor
      (io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
#if defined(DEBUG)
    std::cerr << "made boost::asio::io::tcp::acceptor instance\n";
#endif

    start_accept();
#if defined(DEBUG)
    std::cerr << "ASIO_remote -> constructed\n";
#endif
  }

  void start_accept() {
#if defined(DEBUG)
    std::cerr << "start_accept -> start\n";
#endif
    boost::system::error_code error;
    //acc.accept(socket, error);
    acc->accept( *h->rdbuf(), error );

#if defined(DEBUG)
    if (error) {
      std::cout << "accept failed: " << error.message() << std::endl;
    } else {
      std::cout << "accept correct!" << std::endl;
    }
#endif

  }
  ~ASIO_remote() {
    acc->close();
    delete h;
    delete acc;
#if defined(DEBUG)
    std::cerr << "ASIO_remote <- destructed\n";
#endif
  }
  std::string from_host(void) const { /*
    boost::asio::ip::tcp::endpoint T  = h->rdbuf()->remote_endpoint();
    boost::asio::ip::address A = T.address();
    return A.to_string(); */
    return h->rdbuf()->remote_endpoint().address().to_string();
  }

  friend class SerialNeutronExchanger;
};

/** basic class for the network exchange of Manyo-lib data containers with Boost serialization
 *
 * SerialNeutronExchanger needs ASIO_local or ASIO_remote for the boost asio
 * iostream.
 */
class SerialNeutronExchanger {
private:
  boost::asio::ip::tcp::iostream  *h;
  //!< Boost ASIO network stream for the exchange

public:
  SerialNeutronExchanger(ASIO_local &Al) {
    h = Al.h;
  }
  SerialNeutronExchanger(ASIO_remote &Ar) {
    h = Ar.h;
  }
  /*
   * @param *_h pointer to the boost::asio::ip::tcp::iostream in ASIO_local or ASIO_remote
   */
  SerialNeutronExchanger(boost::asio::ip::tcp::iostream  *_h) : h(_h) {
  }



  /** Transmit any type of object with boost::serialization (binary) and boost::asio
   *
   * @param val reference to the object to transmit
   * (mainly UInt4, Int4, std::string, Double and there std::vector,
   *  and Manyo's object HeaderBase, {Element, UInt4}Container{, Array, Matrix}
   */
  template <typename T>
  void Transmit(const T &val) {
    boost::archive::binary_oarchive *oa = new boost::archive::binary_oarchive (*h);
    *oa << val;
    delete oa;
  }

  /** Receive any type of object with boost::serialization (binary) and boost::asio
   *
   * @param val reference to the object to receive and write to
   * (mainly UInt4, Int4, std::string, Double and there std::vector,
   *  and Manyo's object HeaderBase, {Element, UInt4}Container{, Array, Matrix}
   */
  template <typename T>
  bool Receive(T &val) {
    try {
      boost::archive::binary_iarchive *ia = new boost::archive::binary_iarchive (*h);
      *ia >> val;
      delete ia;
    }
    catch (boost::archive::archive_exception &N) {
      std::cerr << "SerialNeutronExchanger CAUGHT Exception!: Receive Failed" << std::endl;
      T nulval;
      val = nulval;
      return false;
    }
    return true;
  }


  UInt4 ReceiveUInt4(void) {
    UInt4 data;
    Receive(data);
    return data;
  }
  Int4  ReceiveInt4(void) {
    Int4 data;
    Receive(data);
    return data;
  }
  std::string  ReceiveString(void) {
    std::string data;
    Receive(data);
    return data;
  }
  Double ReceiveDouble(void) {
    Double data;
    Receive(data);
    return data;
  }

};


#endif /* SERIALNEUTRONEXCHANGER */
