#ifndef __JNET__JTCPSOCKET__
#define __JNET__JTCPSOCKET__

#include "JLang/JException.hh"

#include "JSystem/JNetwork.hh"

#include "JNet/JSocket.hh"
#include "JNet/JHostname.hh"

#define IPROTO_TCP 6

#if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE)
#define TCP_KEEPIDLE TCP_KEEPALIVE
#endif

/**
 * \author mdejong
 */
namespace JNET {}
namespace JPP { using namespace JNET; }

namespace JNET {

  /**
   * TCP socket.
   */
  class JTCPSocket :
    public JSocket
  {
  public:
    /**
     * Default constructor.
     */
    JTCPSocket() :
      JSocket(AF_INET, SOCK_STREAM)
    {}


    /**
     * Constructor.
     *
     * \param  server         file descriptor of TCP server socket
     */
    JTCPSocket(const int server)
    {
      accept(server);
    }


    /**
     * Set non-blocking of I/O.
     *
     * \param  on             true to enable non-blocking; false to disable
     */
    void setNonBlocking(const bool on)
    {
      const int flags = fcntl(getFileDescriptor(), F_GETFL, -1);
      const int mask  = FNDELAY;

      if (flags == -1) {
	THROW(JSocketException, "Get socket option failed " << errno);
      }

      if (((flags & mask) != mask &&  on) ||
	  ((flags & mask) != 0    && !on) ) {

	if (fcntl(getFileDescriptor(), F_SETFL, flags ^ mask) < 0) {
	  THROW(JSocketException, "Set socket option failed " << errno);
	}
      }
    }


    /**
     * Get non-blocking of I/O.
     *
     * \return                true if enabled non-blocking; else false
     */
    bool getNonBlocking() const
    {
      const int flags = fcntl(getFileDescriptor(), F_GETFL, -1);
      const int mask  = FNDELAY;

      if (flags == -1) {
	THROW(JSocketException, "Get socket option failed " << errno);
      }

      return ((flags & mask) != 0);
    }


    /**
     * Set the TCP idle time.
     *
     * \param  t_s            time [s]
     */
    void setKeepIdle(const int t_s)
    {
      setOption(IPROTO_TCP, TCP_KEEPIDLE, t_s);
    }


    /**
     * Set the TCP idle count.
     *
     * \param  count          count
     */
    void setKeepCnt(const int count)
    {
      setOption(IPROTO_TCP, TCP_KEEPCNT, count);
    }


    /**
     * Set the TCP interval time.
     *
     * \param  t_s            time [s]
     */
    void setKeepIntvl(const int t_s)
    {
      setOption(IPROTO_TCP, TCP_KEEPINTVL, t_s);
    }


    /**
     * Set TCP no-delay.
     *
     * \param  on             true to set TCP no-delay; false to disable
     */
    void setTcpNoDelay(const bool on)
    {
      setOption(IPPROTO_TCP, TCP_NODELAY, int(on ? 1 : 0));
    }


    /**
     * Get TCP no-delay.
     *
     * \return                true if TCP no-delay; else false
     */
    bool getTcpNoDelay() const
    {
      return (getOption<int>(IPPROTO_TCP, TCP_NODELAY) == 1);
    }


    /**
     * Accept connection from a server.
     *
     * \param  server         file descriptor of TCP server socket
     */
    void accept(const int server)
    {
      socklen_t size = sizeof(sockaddr_in);

      fileDescriptor = ::accept(server, getSockaddr(), &size);
    }


    /**
     * Connect to port on local host.
     *
     * \param  port           port number
     */
    void connect(const int port)
    {
      connect(INADDR_ANY, port);
    }


    /**
     * Connect to port on specified host.
     *
     * \param  hostname       host name
     */
    void connect(const JHostname& hostname)
    {
      connect(hostname.hostname, hostname.port);
    }

    
    /**
     * Connect to port on specified host.
     *
     * \param  hostname       host name
     * \param  port           port number
     */
    void connect(const std::string& hostname, const int port)
    {
      connect(JSYSTEM::getIPnumber(hostname), port);
    }

    
    /**
     * Connect to port on specified host.
     *
     * \param  ip_number      IP number
     * \param  port           port number
     */
    void connect(const int ip_number, const int port)
    {
      setIPnumber(ip_number);
      setPort(port);

      if (::connect(getFileDescriptor(), getSockaddr(), sizeof(sockaddr_in)) < 0) {
	THROW(JSocketException, "Socket connection failed " << JSYSTEM::getIPaddress(ip_number) << ":" << port << " / " << getFileDescriptor() << " " << errno);
      }
    }
  };
}

#endif