// ---------------------------------------------------------------------------
// - UdpSocket.cpp                                                           -
// - afnix:net module - udp socket socket implementation                     -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2006 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Ascii.hpp"
#include "Vector.hpp"
#include "UdpSocket.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "cnet.hpp"
#include "csio.hpp"
#include "cerr.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // the maximum datagram size
  static const long UDP_BUFFER_SIZE = 65508;

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a default udp socket

  UdpSocket::UdpSocket (void) {
    d_sid  = c_ipsockudp ();
    if (d_sid < 0) throw Exception ("udp-error", c_errmsg (d_sid));
    p_buf  = new char[UDP_BUFFER_SIZE];
    d_port = 0;
  }

  // create a udp socket by socket id

  UdpSocket::UdpSocket (const int sid) {
    d_sid  = sid;
    if (d_sid < 0) throw Exception ("udp-error", "invalid upd socket");
    p_buf  = new char[UDP_BUFFER_SIZE];
    d_port = 0;
  }

  // return the class name

  String UdpSocket::repr (void) const {
    return "UdpSocket";
  }

  // destroy this upd client

  UdpSocket::~UdpSocket (void) {
    delete [] p_buf;
  }

  // join a multicast group by address

  bool UdpSocket::join (const Address& addr) {
    wrlock ();
    addr.rdlock ();
    bool result = c_ipjoin (d_sid, addr.p_addr);
    addr.unlock ();
    unlock ();
    return result;
  }

  // drop from multicast group by address

  bool UdpSocket::drop (const Address& addr) {
    wrlock ();
    addr.rdlock ();
    bool result = c_ipdrop (d_sid, addr.p_addr);
    addr.unlock ();
    unlock ();
    return result;
  }

  // read one character from a udp message

  char UdpSocket::read (void) {
    wrlock ();
    // check the pushback buffer first
    if (d_buffer.length () != 0) {
      unlock ();
      return d_buffer.read ();
    }
    // read the datagram
    long count  = 0;
    if (d_addr.p_addr == nilp)
      count = c_iprecv (d_sid, p_buf, UDP_BUFFER_SIZE);
    else
      count = c_iprecvfr (d_sid, d_port, d_addr.p_addr, p_buf, 
			  UDP_BUFFER_SIZE);
    if (count < 0) {
      unlock ();
      throw Exception ("read-error", c_errmsg (count));
    }
    if (count == 0) {
      unlock ();
      throw Exception ("read-error", "cannot read udp datagram");
    }
    // update the buffer with the data
    d_buffer.add (p_buf, count);
    char result = d_buffer.read ();
    unlock ();
    return result;
  }

  // read a buffer by size from a udp message

  Buffer* UdpSocket::read (const long size) {
    wrlock ();
    Buffer* result = new Buffer;
    // check the pushback buffer first
    long blen = d_buffer.length ();
    long rlen = size;
    if (blen > 0) {
      if (blen < size) {
	for (long i = 0; i < blen; i++) {
	  result->add (d_buffer.read ());
	  rlen--;
	}
      }
      if (blen > size) {
	for (long i = 0; i < size; i++) result->add (d_buffer.read ());
	unlock ();
	return result;
      }
    }
    // now fill the result buffer by reading
    if (rlen <= 0) {
      unlock ();
      return result;
    }
    long count = 0;
    if (d_addr.p_addr == nilp)
      count = c_iprecv (d_sid, p_buf, UDP_BUFFER_SIZE);
    else
      count = c_iprecvfr (d_sid, d_port, d_addr.p_addr, p_buf, 
			  UDP_BUFFER_SIZE);
    if (count < 0) {
      unlock ();
      throw Exception ("read-error", c_errmsg (count));
    }
    // place the remaining in the result buffer
    rlen = (rlen <= count) ? rlen : count;
    for (long i = 0; i < rlen; i++) result->add (p_buf[i]);
    // place the rest of the datagram in the socket buffer
    for (long i = rlen; i < count; i++) d_buffer.add (p_buf[i]);
    unlock ();
    return result;
  }

  // send one character in a udp message
  
  void UdpSocket::write (const char value) {
    wrlock ();
    long count = 0;
    if (d_addr.p_addr == nilp)
      count = c_ipsend (d_sid, &value, 1);
    else
      count = c_ipsendto (d_sid, d_port, d_addr.p_addr, &value, 1);
    unlock ();
    if (count < 0) throw Exception ("write-error", c_errmsg (count));
  }

  // send a buffer in a udp message
  
  void UdpSocket::write (const char* value) {
    wrlock ();
    long size  = Ascii::strlen (value);
    long count = 0;
    if (d_addr.p_addr == nilp)
      count = c_ipsend (d_sid, value, size);
    else
      count = c_ipsendto (d_sid, d_port, d_addr.p_addr, value, size);
    unlock ();
    if (count < 0) throw Exception ("write-error", c_errmsg (count));
  }

  // return true if the udp client buffer is empty

  bool UdpSocket::iseof (void) const {
    rdlock ();
    bool result = (d_buffer.length () == 0);
    unlock ();
    return result;
  }

  // check if we can read a character

  bool UdpSocket::valid (const long tout) const {
    rdlock ();
    try {
      // first check in the buffer
      if (d_buffer.length () != 0) {
	unlock ();
	return true;
      }
      // check if we can read one character
      bool result = c_rdwait (d_sid, tout);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // accept a new datagram from this udp socket

  Datagram* UdpSocket::accept (void) const {
    t_byte* daddr = nilp;
    rdlock ();
    try {
      // get the socket address length
      long alen = isipv6 () ? 16 : 4;
      // create a new address 
      daddr = new t_byte[alen+1];
      daddr[0]      = alen;
      t_word  dport = 0;
      // get the receiving buffer
      long result = c_iprecvfr (d_sid, dport, daddr, p_buf, UDP_BUFFER_SIZE);
      if (result < 0) {
	delete [] daddr;
	throw Exception ("accept-error", c_errmsg (result));
      }
      // create a new datagram
      Datagram* dg = new Datagram (d_sid, dport, daddr, p_buf, result);
      delete [] daddr;
      unlock ();
      return dg;
    } catch (...) {
      delete [] daddr;
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 2;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_WRITE  = zone.intern ("write");
  static const long QUARK_ACCEPT = zone.intern ("accept");

  // create a new object in a generic way

  Object* UdpSocket::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    if (argc != 0) 
      throw Exception ("argument-error", "too many arguments with udp socket");
    return new UdpSocket;
  }

  // return true if the given quark is defined

  bool UdpSocket::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }
    bool result = hflg ? Socket::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark

  Object* UdpSocket::apply (Runnable* robj, Nameset* nset, const long quark,
			    Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_ACCEPT) return accept ();
      if (quark == QUARK_WRITE) {
	write ((char*) nilp);
	return nilp;
      }
    }
    // call the socket method
    return Socket::apply (robj, nset, quark, argv);
  }
}
