/*****************************
File:       Packet.CPP
Language:   C++ (header)
Project:    H3DNetworkingUtils
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is H3DNetworkingUtils v1.0.

The Initial Developer of the Original Code is CSIRO.
Portions created by the Initial Developer are Copyright (C) 1009 CSIRO. All Rights Reserved.
Contributor(s):
    Chris Gunn  <Chris.Gunn@csiro.au> <ChrisJGunn@gmail.com>
***************************/
#include "H3DNetworkingUtils/Packet.h"
#include <iostream>
#include "H3DNetworkingUtils/systypes.h"

using namespace H3DNetworkingUtils;

const int Packet::HEART_BEAT_ID = -2;
const char Packet::CR = (char)13;
const char Packet::LF = (char)10;
const int Packet::PACKET_SIZE_SIZE = 4;
const int Packet::SEQ_NUM_SIZE = 2;
const int Packet::ID_SIZE = 4;
const int Packet::HEADER_SIZE = PACKET_SIZE_SIZE + SEQ_NUM_SIZE + ID_SIZE;
const int Packet::PACKET_SIZE_OFFSET = 0;
const int Packet::SEQ_NUM_OFFSET = PACKET_SIZE_SIZE;
const int Packet::ID_OFFSET = SEQ_NUM_OFFSET + SEQ_NUM_SIZE;

// ----------------------------------------------------------------------------
Packet::Packet(int sz) :
max_payload_size(sz),
payload_size(0),
get_pos (0),
buffer_head (0),
buffer_payload (0),
keep_data (false),
buffer_size (max_payload_size + HEADER_SIZE) {
   buffer_head = new char[buffer_size];
   buffer_payload = buffer_head + HEADER_SIZE;
}

// ----------------------------------------------------------------------------
Packet::Packet(Packet const & other) :
max_payload_size (other.max_payload_size),
payload_size (other.payload_size),
get_pos (other.get_pos),
buffer_head (0),
buffer_payload (0),
keep_data (other.keep_data),
buffer_size  (other.buffer_size) {
   buffer_head = new char[buffer_size];
   buffer_payload = buffer_head + HEADER_SIZE;
   memcpy(buffer_head, other.buffer_head, buffer_size);
}

// ----------------------------------------------------------------------------
Packet & Packet::operator=(Packet const & other) {
  if (this == &other) {
      return *this;
   }
   payload_size = other.payload_size;
   buffer_size = other.buffer_size;
   get_pos  = other.get_pos;
   delete [] buffer_head;
   buffer_head = new char[buffer_size];
   buffer_payload = buffer_head + HEADER_SIZE;
   keep_data = other.keep_data;
   memcpy(buffer_head, other.buffer_head, buffer_size);

   return *this;
}

// ----------------------------------------------------------------------------
// readInt16:
//    Extract 16 bit int from buffer and return it.
//
int Packet::readInt16() const {
  // use a union so that we can easily access the bytes within a float
  union {
    short v;
    char sz[2];
  } decoded_int;
  decoded_int.v = 0; // Initialize to zeros.

  // read 2 bytes
  memcpy(&decoded_int.sz[2], &buffer_payload[get_pos], 2);
  get_pos += 2;
  return ntoh32(decoded_int.v);
}

// ----------------------------------------------------------------------------
void Packet::writeInt16(int const & val ) {
  stretchBuffer(4);
  // use a union so that we can easily access the bytes within an int
  union {
    short v;
    char sz[2];
  } enc_int;

  enc_int.v = val;

  // append 2 bytes
  memcpy(&buffer_payload[payload_size], &enc_int.sz[2], 2);
  payload_size += 2;
}

// ----------------------------------------------------------------------------
// Returns true on success.
bool Packet::receive(TCPSock * sockP) {
  int num = sockP->read(&buffer_head[0], PACKET_SIZE_SIZE);
  if (num != PACKET_SIZE_SIZE) {
    get_pos = 0;
    return false;
  }
  memcpy(&dec_int, &buffer_head[PACKET_SIZE_OFFSET], PACKET_SIZE_SIZE);
  int expected_size = ntoh32(dec_int.v);
  stretchBuffer(expected_size);

  // (we've already read PACKET_SIZE_SIZE bytes).
  int received_size = sockP->read(&buffer_head[PACKET_SIZE_SIZE], expected_size);
  payload_size = received_size - (HEADER_SIZE - PACKET_SIZE_SIZE);
  payload_size = (payload_size < 0 ? 0 : payload_size);
  get_pos = 0;
  return true;
}

// ----------------------------------------------------------------------------
int Packet::receive(UDPSock * sockP, InetAddr * remote_end) {
  // With UDP we don't need to worry about the packet size part of the header.
  int received_size = sockP->receive(buffer_head, max_payload_size + HEADER_SIZE, remote_end);

  payload_size = received_size - HEADER_SIZE;
  payload_size = (payload_size < 0 ? 0 : payload_size);
  get_pos = 0;
  return received_size;
}
// Returns size received.

// ----------------------------------------------------------------------------
bool Packet::send(TCPSock * sockP) {
  // For TCP we need to send a header with the size of the packet.
  // This is so that when we read, we can immediately return from the read when
  // we have read in the correct size of bytes.
  // Otherwise it hangs waiting for the receiving buffer to fill.
  // The first PACKET_SIZE_SIZE bytes are the size, the payload directly follows.
  int size_to_send = payload_size + HEADER_SIZE - PACKET_SIZE_SIZE;
  enc_int.v = hton32(size_to_send);
  memcpy(&buffer_head[PACKET_SIZE_OFFSET], enc_int.sz, PACKET_SIZE_SIZE);
  int num = sockP->write(buffer_head, HEADER_SIZE + payload_size);

  bool ret = (num == (HEADER_SIZE + payload_size));
  if (!keep_data){
    clear();
  }
  return ret;
}
// Returns true on success.

// ----------------------------------------------------------------------------
bool Packet::send(UDPSock * sockP, InetAddr * remote_end) {
  // With UDP we don't need to worry about the packet size part of the header.
  int num = sockP->send(buffer_head, HEADER_SIZE + payload_size, remote_end);
  bool ret = (num == HEADER_SIZE + payload_size);
  if (!keep_data) {
    clear();
  }
  return ret;
}
// Returns true on success.

// ----------------------------------------------------------------------------
void Packet::setHeartBeat() {
  setId(HEART_BEAT_ID);
  keep_data = true;
}

// ----------------------------------------------------------------------------
int Packet::getId() const {
  // The id is in the header
  union {
    int v;
    char sz[4];
  } decoded_int;
  decoded_int.v = 0; // Initialize to zeros.

  // read 4 bytes
  memcpy(&decoded_int.sz[0], &buffer_head[ID_OFFSET], ID_SIZE);
  return ntoh32(decoded_int.v);
}

// ----------------------------------------------------------------------------
short Packet::getSeqNum() const {
  // The seq num is the first SEQ_NUM_SIZE bytes in the header
  union {
    short v;
    char sz[2];
  } decoded_int;
  decoded_int.v = 0; // Initialize to zeros.

  // read SEQ_NUM_SIZE bytes
  memcpy(&decoded_int.sz[0], &buffer_head[SEQ_NUM_OFFSET], SEQ_NUM_SIZE);
  return ntoh16(decoded_int.v);
}

// ----------------------------------------------------------------------------
void Packet::setId(int id) {
  // use a union so that we can easily access the bytes within an int
  union {
    int v;
    char sz[4];
  } enc_int;

  enc_int.v = hton32(id);

  // append 4 bytes
  memcpy(&buffer_head[ID_OFFSET], &enc_int.sz[0], ID_SIZE);
}

// ----------------------------------------------------------------------------
void Packet::setSeqNum(short seq) {
  // use a union so that we can easily access the bytes within an int
  // The seq num is the first 2 bytes in the payload
  union {
    short v;
    char sz[2];
  } enc_int;

  enc_int.v = hton16(seq);

  // append 2 bytes
  memcpy(&buffer_head[SEQ_NUM_OFFSET], &enc_int.sz[0], SEQ_NUM_SIZE);
}

// ----------------------------------------------------------------------------
void Packet::stretchBuffer(u_int extra_size) {
  if (payload_size + (int)extra_size > max_payload_size) {
    // Create a new, bigger buffer.
    char * old_buffer_head = &buffer_head[0];
    u_int old_buffer_size = buffer_size;

    u_int increase = (u_int)((float)extra_size * 1.5f);
    u_int test_amt = (u_int)((float)max_payload_size * 0.2f);

    increase = increase > test_amt ? increase : test_amt;
    max_payload_size += increase;
    buffer_size = max_payload_size + HEADER_SIZE;
    buffer_head = new char[buffer_size];
    buffer_payload = buffer_head + HEADER_SIZE;
    memcpy(buffer_head, old_buffer_head, old_buffer_size);
    delete [] old_buffer_head;
  }
}

