/* 
 * Copyright (C) 2007-2012 Dmytro Bogovych <dmytro.bogovych@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#ifdef _WIN32
# include <winsock2.h>
# include <windows.h>
#endif

#include <algorithm>

#include "EP_ReliableTunnel.h"
#include "EP_Engine.h"
#include "Log.h"

#include "../ICE/ICECRC32.h"

enum
{
  CONFIRMATION_PT = 1,
  DATA_PT = 2
};

#define CONFIRMATION_TIMEOUT 500
#define LOG_SUBSYSTEM "RT"

ReliableTunnel::ReliableTunnel(const char* streamname)
{
  mStack.setEncryption(this);
  mStreamName = streamname;
  mBandwidth = 0;
  mExitSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
  mDataSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
  
}

ReliableTunnel::~ReliableTunnel()
{
  ::CloseHandle(mDataSignal);
  ::CloseHandle(mExitSignal);
}

std::string ReliableTunnel::streamName()
{
  return mStreamName;
}

std::string ReliableTunnel::streamProfile()
{
  return "RTP/DP";
}

void ReliableTunnel::setDestinationAddress(InternetAddress& addr)
{
  mDestination = addr;
}


void ReliableTunnel::queueData(const void* bufferptr, int buffersize)
{
  assert(bufferptr != NULL);
  assert(buffersize != 0);

  resip::Lock l(mNewQueuedGuard);
  mNewQueued.push_back(std::string((const char*)bufferptr, buffersize));
  
  ::SetEvent(mDataSignal);
}

// This method is called by user agent to send ICE packet from mediasocket
void ReliableTunnel::sendData(InternetAddress& addr, const void* dataBuffer, unsigned int datasize)
{
  switch (addr.type())
  {
  case AF_INET:
    mSocket4.sendDatagram(addr, dataBuffer, datasize);
    return;

  case AF_INET6:
    mSocket4.sendDatagram(addr, dataBuffer, datasize);
    return;
  }
}

void ReliableTunnel::sessionEstablished(int conntype)
{
  // Start worker thread
  if (conntype == EV_ICE)
    run();
}

void ReliableTunnel::sessionTerminated()
{
  // Stop worker thread
  ::SetEvent(mExitSignal);
  shutdown();
  join();
}

void ReliableTunnel::updateSdpOffer(resip::SdpContents::Session::Medium& sdp)
{
  // Get new destination port
  mDestination.setPort((unsigned short)sdp.port());
  
  sdp.addCodec(resip::SdpContents::Session::Codec("rt", 104));
}

void ReliableTunnel::setSocket(DatagramSocket& socket4, DatagramSocket& socket6)
{
  mSocket4 = socket4;
  mSocket6 = socket6;
}
  
DatagramSocket& ReliableTunnel::socket(int family)
{
  switch (family)
  {
  case AF_INET:
    return mSocket4;

  case AF_INET6:
    return mSocket4;

  default:
    assert(0);
  }
}

bool ReliableTunnel::processSdpOffer(const resip::SdpContents::Session::Medium& media)
{
  //check for default port number
  mDestination.setPort(media.port());
  
  return true;
}

void ReliableTunnel::thread()
{
  // Construct event array
  while (true)
  {
    HANDLE eventarray[2] = { mDataSignal, mExitSignal };

    DWORD rescode = ::WaitForMultipleObjects(2, eventarray, FALSE, INFINITE);
    if (rescode == WAIT_OBJECT_0)
    {
      resip::Lock l(mNewQueuedGuard);
      for (unsigned i = 0; i<mNewQueued.size(); i++)
        mStack.queueOutgoing(mNewQueued[i].c_str(), mNewQueued[i].size());
      mNewQueued.clear();
      
      sendOutgoing();
    }
    else
      break;
  }
}

void ReliableTunnel::setBandwidth(unsigned int bytesPerSecond)
{
  mBandwidth = bytesPerSecond;
}

unsigned int ReliableTunnel::bandwidth()
{
  return mBandwidth;
}

void ReliableTunnel::processData(const void* dataptr, int datasize)
{
  resip::Lock l(mStackGuard);
  mStack.processIncoming(dataptr, datasize);
}

bool ReliableTunnel::hasData()
{
  resip::Lock l(mStackGuard);
  
  return mIncomingData.size() || mStack.hasAppData();
}

unsigned ReliableTunnel::getData(void* ptr, unsigned capacity)
{
  resip::Lock l(mStackGuard);
   
  char* dataOut = (char*)ptr;
  
  while (capacity && hasData())
  {
    // Check if mIncomingData is empty
    if (!mIncomingData.size())
    {
      unsigned available = mStack.appData(NULL);
      if (!available)
        return 0;

      mIncomingData.resize(available);
      mIncomingData.rewind();
      mStack.appData(mIncomingData.mutableData());
    }

    if (mIncomingData.size())
    {
      unsigned toCopy = min(capacity, mIncomingData.size());
      mIncomingData.dequeueBuffer(dataOut, toCopy);
      dataOut += toCopy;
      capacity -= toCopy;
    }
  }

  return dataOut - (char*)ptr;
}

// Returns block size for encryption algorythm
int ReliableTunnel::blockSize()
{
  return 8;
}
      
// Encrypts dataPtr buffer inplace. dataSize must be odd to GetBlockSize() returned value.
void ReliableTunnel::encrypt(void* dataPtr, int dataSize)
{
  if (mEncryptionKey.empty())
    return;

#ifdef USE_OPENSSL
  for (unsigned i=0; i<dataSize / blockSize(); i++)
    BF_ecb_encrypt((unsigned char*)dataPtr + i * blockSize(), (unsigned char*)dataPtr + i * blockSize(), &mCipher, BF_ENCRYPT);
#endif

#ifdef USE_CRYPTOPP
  for (unsigned i=0; i<dataSize / blockSize(); i++)
    mEncryptor.ProcessBlock((unsigned char*)dataPtr + i * blockSize());
#endif
}

// Decrypts dataPtr buffer inplace. dataSize must be odd to GetBlockSize() returned value.
void ReliableTunnel::decrypt(void* dataPtr, int dataSize)
{
  if (mEncryptionKey.empty())
    return;
#ifdef USE_OPENSSL
  for (unsigned i=0; i<dataSize / blockSize(); i++)
    BF_ecb_encrypt((unsigned char*)dataPtr + i * blockSize(), (unsigned char*)dataPtr + i * blockSize(), &mCipher, BF_DECRYPT);
#endif
  
#ifdef USE_CRYPTOPP
  for (unsigned i=0; i<dataSize / blockSize(); i++)
    mDecryptor.ProcessBlock((unsigned char*)dataPtr + i * blockSize());
#endif
}

// Calculates CRC
unsigned ReliableTunnel::crc(const void* dataptr, int datasize)
{
  unsigned long result;
  ICEImpl::CRC32 crc;
  crc.fullCrc((const unsigned char*)dataptr, datasize, &result);
  
  return result;
}

void ReliableTunnel::sendOutgoing()
{
  // Check if stack has to send smth
  if (mStack.hasPacketToSend())
  {
    // Get data to send
    char buffer[2048];
    int length = sizeof(buffer);
    mStack.getPacketToSend(buffer, length);
    
    // Send it over UDP
    sendData(this->mDestination, buffer, length);
  }
}

void ReliableTunnel::setEncryptionKey(void* ptr, unsigned length)
{
#ifdef USE_OPENSSL
  BF_set_key(&mCipher, length, (const unsigned char*)ptr);
#endif

#ifdef USE_CRYPTOPP
  mEncryptor.SetKey((unsigned char*)ptr, length);
  mDecryptor.SetKey((unsigned char*)ptr, length);
#endif

  // Save key
  mEncryptionKey = std::string((const char*)ptr, length);
}
