/*
 * $Id$
 *
 * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
 * Copyright (c) 2002-2014, Professor Benoit Macq
 * Copyright (c) 2010-2011, Kaori Hagihara
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "auxtrans_manager.h"

#ifdef _WIN32
#include <process.h>
#else
#include <pthread.h>
#endif

#ifdef SERVER
#include "fcgi_stdio.h"
#define logstream FCGI_stdout
#else
#define FCGI_stdout stdout
#define FCGI_stderr stderr
#define logstream stderr
#endif /*SERVER */

auxtrans_param_t init_aux_transport( int tcp_auxport, int udp_auxport)
{
  auxtrans_param_t auxtrans;

  auxtrans.tcpauxport = tcp_auxport;
  auxtrans.udpauxport = udp_auxport;

  if( 49152 <= tcp_auxport && tcp_auxport <= 65535)
    auxtrans.tcplistensock = open_listeningsocket( (uint16_t)tcp_auxport);
  else
    auxtrans.tcplistensock = -1;

  auxtrans.udplistensock = -1;
  /* open listening socket for udp later */

  return auxtrans;
}

void close_aux_transport( auxtrans_param_t auxtrans)
{
  if( auxtrans.tcplistensock != -1)
    if( close_socket( auxtrans.tcplistensock) != 0)
      perror("close");

  if( auxtrans.udplistensock != -1)
    if( close_socket( auxtrans.udplistensock) != 0)
      perror("close");
}


/*!< auxiliary response parameters */
typedef struct aux_response_param{
  char *cid;            /*!< channel ID */
  unsigned char *data;  /*!< sending data */
  OPJ_SIZE_T datalen;          /*!< length of data */
  OPJ_SIZE_T maxlenPerFrame;   /*!< maximum data length to send per frame */
  SOCKET listensock;    /*!< listeing socket */
#ifdef _WIN32
  HANDLE hTh;           /*!< thread handle */
#endif
} aux_response_param_t;

aux_response_param_t * gene_auxresponse( OPJ_BOOL istcp, auxtrans_param_t auxtrans, const char cid[], void *data, OPJ_SIZE_T datalen, OPJ_SIZE_T maxlenPerFrame);

void delete_auxresponse( aux_response_param_t **auxresponse);


#ifdef _WIN32
unsigned __stdcall aux_streaming( void *arg);
#else
void * aux_streaming( void *arg);
#endif

void send_responsedata_on_aux( OPJ_BOOL istcp, auxtrans_param_t auxtrans, const char cid[], void *data, OPJ_SIZE_T datalen, OPJ_SIZE_T maxlenPerFrame)
{
  aux_response_param_t *auxresponse;
#ifdef _WIN32
  unsigned int threadId;
#else
  pthread_t thread;
  int status;
#endif
  
  if( istcp){
    if( auxtrans.tcplistensock == -1){
      fprintf( FCGI_stderr, "Error: error in send_responsedata_on_aux(), tcp listening socket no open\n");
      return;
    }

    auxresponse = gene_auxresponse( istcp, auxtrans, cid, data, datalen, maxlenPerFrame);

#ifdef _WIN32
    auxresponse->hTh = (HANDLE)_beginthreadex( NULL, 0, &aux_streaming, auxresponse, 0, &threadId);
    if( auxresponse->hTh == 0)
      fprintf( FCGI_stderr,"ERRO: pthread_create() %s", strerror( (int)auxresponse->hTh));
#else
    status = pthread_create( &thread, NULL, &aux_streaming, auxresponse);
    if( status != 0)
      fprintf( FCGI_stderr,"ERROR: pthread_create() %s",strerror(status));
#endif   
  }
  else
    fprintf( FCGI_stderr, "Error: error in send_responsedata_on_aux(), udp not implemented\n");
}

aux_response_param_t * gene_auxresponse( OPJ_BOOL istcp, auxtrans_param_t auxtrans, const char cid[], void *data, OPJ_SIZE_T datalen, OPJ_SIZE_T maxlenPerFrame)
{
  aux_response_param_t *auxresponse;

  auxresponse = (aux_response_param_t *)opj_malloc( sizeof(aux_response_param_t));

  auxresponse->cid = strdup( cid);
  auxresponse->data = data;
  auxresponse->datalen = datalen;
  auxresponse->maxlenPerFrame = maxlenPerFrame;
  auxresponse->listensock = istcp ? auxtrans.tcplistensock : auxtrans.udplistensock;

  return auxresponse;
}

void delete_auxresponse( aux_response_param_t **auxresponse)
{
  opj_free( (*auxresponse)->cid);
  opj_free( (*auxresponse)->data);
  opj_free( *auxresponse);
}

/**
 * Identify cid sent from client
 *
 * @param [in] connected_socket file descriptor of the connected socket
 * @param [in] refcid           refenrece channel ID
 * @param [in] fp               file pointer for log of aux stream
 * @return                      true if identified, false otherwise
 */
OPJ_BOOL identify_cid( SOCKET connected_socket, char refcid[], FILE *fp);

OPJ_BOOL recv_ack( SOCKET connected_socket, void *data);

#ifdef _WIN32
unsigned __stdcall aux_streaming( void *arg)
#else
void * aux_streaming( void *arg)
#endif
{
  SOCKET connected_socket;
  unsigned char *chunk, *ptr;
  OPJ_SIZE_T maxLenOfBody, remlen, chunklen;
  const OPJ_SIZE_T headlen = 8;
  
  aux_response_param_t *auxresponse = (aux_response_param_t *)arg;

#ifdef _WIN32
  CloseHandle( auxresponse->hTh);
#else
  pthread_detach( pthread_self());
#endif

  chunk = (unsigned char *)opj_malloc( auxresponse->maxlenPerFrame);
  maxLenOfBody = auxresponse->maxlenPerFrame - headlen;
  remlen = auxresponse->datalen;

  while((connected_socket = accept_socket( auxresponse->listensock)) != -1){
    if( identify_cid( connected_socket, auxresponse->cid, FCGI_stderr)){
      ptr = auxresponse->data;
      while( 0 < remlen){
        memset( chunk, 0, auxresponse->maxlenPerFrame);

        chunklen = remlen<maxLenOfBody?remlen:maxLenOfBody;
        chunklen += headlen;

        chunk[0] = (chunklen >> 8) & 0xff;
        chunk[1] = chunklen & 0xff;

        memcpy( chunk+headlen, ptr, chunklen-headlen);

        do{
          send_stream( connected_socket, chunk, chunklen);
        }while( !recv_ack( connected_socket, chunk));

        remlen -= maxLenOfBody;
        ptr += maxLenOfBody;
      }
      if( close_socket( connected_socket) != 0)
        perror("close");
      break;
    }
  }
  opj_free( chunk);

  delete_auxresponse( &auxresponse);
  
#ifdef _WIN32
  _endthreadex(0);
#else
  pthread_exit(0);
#endif

  return 0;
}


OPJ_BOOL identify_cid( SOCKET connected_socket, char refcid[], FILE *fp)
{
  char *cid;
  OPJ_BOOL succeed;

  if(!(cid = receive_string( connected_socket))){
    fprintf( fp, "Error: error in identify_cid(), while receiving cid from client\n");
    return OPJ_FALSE;
  }
  
  succeed = OPJ_FALSE;
  if( strncmp( refcid, cid, strlen( refcid)) == 0)
    succeed = OPJ_TRUE;
  
  opj_free( cid);

  return succeed;
}

OPJ_BOOL recv_ack( SOCKET connected_socket, void *data)
{
  char *header;
  OPJ_BOOL succeed;
  
  header = receive_stream( connected_socket, 8);
  
  if( memcmp( header, data, 8) != 0)
    succeed = OPJ_FALSE;
  else
    succeed = OPJ_TRUE;
  
  opj_free( header);

  return succeed;
}
