/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 09/18/2009
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/

#include "NetIO.h"
#include <string.h>
#include "MediaClientImpl.h"

namespace videonext { namespace media {

/* this function is really slow, but I don't know how to easily emulate buffered stdio under windowz */
int recvline(int fd, char *vptr, int maxlen) 
{
    int n, rc;
    char c, *ptr;

    ptr = vptr;

    for (n = 1; n < maxlen - 1; n++)
    {
       if ( (rc = recv(fd, &c, 1, 0)) == 1 ) 
        {
            *ptr++ = c;
            if (c == '\n')
                break;
        } 
        else if (rc == 0)
        {
            if (n == 1) return (0);
            else break;
        }
        else return (-1);
    }

    *ptr++ = 0;
    return (n++);
}

/* non blocking connect is fully described by Richard Stevens. Here is sort of his implementation */
static int connect_nonb(int sockfd,  struct sockaddr *saptr, socklen_t salen, int nsec)
{
   int     n, error;
#if defined(__WIN32__) || defined(WIN32)
   int len;
#else
   socklen_t len;
#endif   
   fd_set rset, wset;
   struct timeval tval;

#if defined(__WIN32__) || defined(WIN32) || defined(IMN_PIM)                                                                            
    unsigned long arg = 1;                                                                                                               
    ioctlsocket(sockfd, FIONBIO, &arg);
#else     
   int flags = fcntl(sockfd, F_GETFL, 0);
   fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
#endif

   error = 0;
   if ( (n = connect(sockfd, saptr, salen)) < 0)
   {
       if (__get_errno() != EINPROGRESS && __get_errno() != ETIMEDOUT && __get_errno() != EWOULDBLOCK
#ifdef WIN32           
           && __get_errno() != WSAEWOULDBLOCK
#endif       
           )
       {
           fprintf(stderr, "Error: %d (%d)\n", __get_errno(), errno);
           return -1;
       }
   }
      
   /* Do whatever we want while the connect is taking place. */
   if (n == 0)
      goto done;               /* connect completed immediately */

   FD_ZERO(&rset);
   FD_SET(sockfd, &rset);
   wset = rset;
   tval.tv_sec = nsec;
   tval.tv_usec = 0;

   if ( (n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0)
   {
      close(sockfd);          /* timeout */
      __set_errno(ETIMEDOUT);
      return -1;
   }

   if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) 
   {
      len = sizeof(error);
      if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) < 0)
         return -1; 
   }

done: /* restore file status flags */
#if defined(__WIN32__) || defined(WIN32) || defined(IMN_PIM)
   arg = 0;                                                                                                               
   ioctlsocket(sockfd, FIONBIO, &arg);
#else
   fcntl(sockfd, F_SETFL, flags);  
#endif

   if (error) 
   {
      close(sockfd);           /* just in case */
      __set_errno(error);
      return -1;
   }

   return 0;
}


int connect_socket(ResultStatus *result, const std::string &host, int port)
{
   int sockfd = -1;
   struct  sockaddr_in sa;
   char    **pptr;
   struct  hostent *hptr;
   int error_code;
   char error_str[256];
   result->errorCode = 0;

   do
   {   
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd  < 0)
      {
         error_code = __get_errno();
         if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
             fprintf(stderr, "strerror_r failed\n");
         SET_RESULT_STATUS(result, error_code, "socket() error: %s", error_str);
         break;    
      } 

#if defined(__MINGW32CE__) || defined (WIN32)
      int socktv = 20000;
#else
      struct timeval socktv;
      socktv.tv_sec  = 20;
      socktv.tv_usec = 0;
#endif
      if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&socktv, sizeof(socktv)) == -1)
      {
         error_code = __get_errno();
         if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
             fprintf(stderr, "strerror_r failed\n");
         SET_RESULT_STATUS(result, error_code, "setsockopt() error: %s", error_str);
         break;
      }

      int snd_size = 65536 * 8;
      if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*)&snd_size, sizeof(int)) < 0)
      {
         error_code = __get_errno();
         if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
             fprintf(stderr, "strerror_r failed\n");
         SET_RESULT_STATUS(result, error_code, "setsockopt() error: %s", error_str);
      }
      
      if ((hptr = gethostbyname(host.c_str())) == NULL)
      {
#if defined(__MINGW32CE__) || defined (WIN32)
         error_code = __get_errno();
         strerror_r(error_code, error_str, sizeof(error_str));
         SET_RESULT_STATUS(result, error_code, "Could not resolve host '%s': %s", host.c_str(), error_str);
#else
         SET_RESULT_STATUS(result, h_errno, "Could not resolve host '%s': %s", host.c_str(), hstrerror(h_errno));
#endif

         break;
      }
        
      pptr = hptr->h_addr_list;
      for (; *pptr != NULL; pptr++)
      {          
         bzero(&sa, sizeof(sa));
         
         sa.sin_family = AF_INET;
         sa.sin_port = htons(port);  
            
         char addr[INET6_ADDRSTRLEN];
         if (inet_ntop(hptr->h_addrtype, *pptr, addr, sizeof(addr)) == NULL)
         {
            error_code = __get_errno();
            if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
                fprintf(stderr, "strerror_r failed\n");
            SET_RESULT_STATUS(result, error_code, "inet_ntop() error: %s", error_str);
            break;
         }                                
         sa.sin_addr.s_addr = inet_addr(addr);
            
            
         if (connect_nonb(sockfd, (struct sockaddr *)&sa, sizeof(sa), 20) < 0)
         {
            error_code = __get_errno();
            if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
                fprintf(stderr, "strerror_r failed\n");
            SET_RESULT_STATUS(result, errno, "Could not connect to '%s:%d': %s", host.c_str(), port, error_str);
         }
         else
         {
            result->errorCode = 0; result->errorString = "";
            fprintf(stderr, "Connected\n");
            break;            
         }
      }
        
      if (result->errorCode != 0) 
      {
         break; // appropriate error message is set already
      }

      return sockfd;
   
   } while (0);

   if (sockfd != -1)
   {
       fprintf(stderr, "Closed\n");
       close(sockfd);
   }

   return -1;
}

void performHTTPGET(ResultStatus *result,
                    const std::string &host, 
                    unsigned port,
                    const std::string &request, 
                    const DynaParams  *headers,
                    std::string *sessionID_, 
                    std::string *outputData)
{
   int error_code;
   char error_str[256];

   result->errorCode = 0;
   if (outputData) outputData->clear();

   // building a GET string
   std::string get 
      = "GET " + request + " HTTP/1.0\r\n"
          + "Host: " + host + "\r\n";
 
   std::string *sessionID = sessionID_;
   if (!sessionID)
   {
      sessionID = &MediaClient::instance()->impl()->sessionID;
   }

   if (!port)
   {
      port = MediaClient::instance()->impl()->masterPort;
   }

   if (sessionID && !sessionID->empty())
   {
      get += "Cookie: PHPSESSID=" + *sessionID + "; path=/\r\n";
   }

   if (headers)
   {
      DynaParams::const_iterator iter = headers->begin();
      for (; iter != headers->end(); ++iter)
      {
         get +=  iter->first + ": " + iter->second + "\r\n";
      }
   }

   get += "\r\n";                

   int sockfd = -1;
   FILE *fsock = 0;

   do
   {   
      char buffer[768];
     
//      fprintf(stderr, "Connecting to %s:%d\n", host.c_str(), port);
      sockfd = connect_socket(result, host, port);

      if (result->errorCode != 0) break;
                  
//      fprintf(stderr, "Request string:\n%s", get.c_str());
    
      if (send(sockfd, get.c_str(), get.length(), 0) <= 0)
      {
         error_code = __get_errno();
         if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
             fprintf(stderr, "strerror_r failed\n");
         SET_RESULT_STATUS(result, error_code, "Failed to write into socket: %s", error_str);
         break;
      }

      // Using buffered input (if possible)
#ifndef WIN32
      fsock = fdopen(sockfd, "r");
      setvbuf(fsock, 0, _IOFBF, 12000);
#endif
//      fprintf(stderr, "Check main HTTP-state header\n");

      // check main HTTP-state header
#ifndef WIN32
      if (fgets(buffer, sizeof buffer, fsock))
#else
      if (recvline(sockfd, buffer, sizeof buffer) > 0)   
#endif
      {
         if (strstr(buffer, "HTTP/1.0 200") != buffer
             && strstr(buffer, "HTTP/1.1 200") != buffer)
         {
            SET_RESULT_STATUS(result, EINTERNALERROR, "Request failed. Server response: %s", buffer);
            break;
         }
      }
      else 
      {
         error_code = __get_errno();
         if (strerror_r(error_code, error_str, sizeof(error_str)) != 0)
             fprintf(stderr, "strerror_r failed\n");
         SET_RESULT_STATUS(result, EINTERNALERROR, "Request for '%s' failed: %s", host.c_str(), error_str);
         break;
      }

      // handling headers
      //     fprintf(stderr, "-- headers begin\n");
#ifndef WIN32
      while (fgets(buffer, sizeof buffer, fsock))
#else
      while (recvline(sockfd, buffer, sizeof buffer) > 0)   
#endif
      {
//         fprintf(stderr, "%s", buffer);

         if (strcmp(buffer, "\r\n") == 0) break;

         char *sessid, *sessid_end;
         if (/*sessionID->empty() &&*/ (sessid = strstr(buffer, "Set-Cookie: PHPSESSID=")) != 0)
         {
            sessid += sizeof("Set-Cookie: PHPSESSID=") - 1;
            
            if ((sessid_end = strchr(sessid, ';')) != 0) *sessid_end = 0;
            
            if (sessionID) sessionID->assign(sessid, strlen(sessid));
         }
      }
//      fprintf(stderr, "-- headers end\n");
//      fprintf(stderr, "PHPSESSID=%s\n", sessionID->c_str());

      // handling data
      if (outputData)
      {
         int bytesRead;
#ifndef WIN32
         while((bytesRead = fread(buffer, 1, sizeof(buffer), fsock)) > 0) 
#else
         while((bytesRead = recv(sockfd, buffer, sizeof(buffer), 0)) > 0)    
#endif
         {
            outputData->append(buffer, bytesRead);
         }
//           fprintf(stderr, "output data:\n %s\n", outputData->c_str());
      }

   } while (0);
           
   if (fsock) fclose(fsock);
   else if (sockfd != -1) close(sockfd);
        
}  


}}
