#include "AudioVideoPlaybackController.h"

#include "HTTPMediaStreamProducerImpl.h"

#include "MPEG4LATMAudioRTPSource.hh" // for parseGeneralConfigStr()
#include "BasicUsageEnvironment.hh"
#include "Base64.hh"
#include "MediaClientImpl.h"


#include <stdlib.h>
#include <stdio.h>          /* Basic I/O routines          */
#include <sys/types.h>      /* standard system types       */
#include <netinet/in.h>     /* Internet address structures */
#include <arpa/inet.h>
#include <sys/socket.h>     /* socket interface functions  */
#include <netdb.h>          /* host to IP resolution       */
#include <unistd.h>
#include <sys/time.h>

namespace videonext { namespace media {

#define BUF_SIZE 12000

#define GET_BYTE(byte, fh)\
{\
    byte = fgetc(fh);\
}                                                                    

#define GET_NBYTES(ptr, n, fh)\
{\
      if (fread(ptr, 1, n, fh) != n)\
      {\
         fprintf(stderr, "CRT-5001 Error to read from socket"); \
          return -1;\
      }\
}

int readline(int fd, char *vptr, int maxlen) 
{
    int n, rc;
    char c, *ptr;

    ptr = vptr;

    for (n = 1; n < maxlen - 1; n++)
    {
        if ( (rc = read(fd, &c, 1)) == 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++);
}

int HTTPMediaStreamProducerImpl::read_jpg(FILE *fh, uint8_t *buf, uint32_t maxlen)
{
    uint8_t byte1 = 0, byte2 = 0;
    uint8_t *ptr = buf;
    uint8_t *end_ptr = buf + maxlen;
    uint32_t data_length = 0;
    int     counter = 0;
    
    if (!fgets((char*)buf, maxlen, fh)) // \r\n (after frame)
        return -1;
    if (!fgets((char*)buf, maxlen, fh)) // --boundary_name
        return -1;
    
    while (fgets((char*)buf, maxlen, fh) != NULL)
    {
        if (sscanf((char*)buf, "Content-Length: %d", &data_length) == 1
            || sscanf((char*)buf, "Content-length: %d", &data_length) == 1)
        {          
            counter++;
        }
	
	if(counter > 1
	   || strcmp((char*)buf, "\r\n") == 0 
    	   || strcmp((char*)buf, "\n") == 0)
	{
	    break;
	}
    }

    do // skiping until meet 0xFFD8 (Start of image)
    {
        GET_BYTE(byte1, fh);
        if (byte1 == 0xFF) 
        {            
            GET_BYTE(byte2, fh);
            
            if (byte2 == 0xFF) // again 0xFF? means EOF marker was set
            {
                fprintf(stderr, "Error to read from socket");
                return -1;
            }
        }
    }
    while (byte1 != 0xFF && byte2 != 0xD8);
                
    // Ok, here's jpeg starts            
    *ptr++ = byte1; *ptr++ = byte2;
    
    if (data_length != 0 && data_length <= maxlen)
    {
       GET_NBYTES(ptr, data_length-2, fh);
       return data_length;
    }
    
    // here's jpeg parsing if data_length unknown
    maxlen-=2;
#ifndef PARSE_JPEG_HEADERS
    GET_NBYTES(ptr, 623, fh); // skip header
    ptr+=623; maxlen-=623;
#endif
    
    while (ptr <= end_ptr)
    {
        GET_BYTE(*ptr, fh);
        if (*ptr == 0xFF)
        {
            GET_BYTE(*(++ptr), fh);
#ifdef PARSE_JPEG_HEADERS   // not necessary for now         
            if (*ptr == 0xFE || *ptr == 0xC4
                || *ptr == 0xC0 || *ptr == 0xDB
                || *ptr == 0xDB || *ptr == 0xDA)
            {
                ptr++;
                GET_BYTE(byte1, fh); *ptr++ = byte1; 
                GET_BYTE(byte2, fh); *ptr++ = byte2;
                uint16_t size = (byte1<<8)|byte2 - 2;
                GET_NBYTES(ptr, size, fh);
                ptr+=size;
                continue;
            }
            else
#endif                
            if (*ptr == 0xD9) // end of jpeg
            {
                break;
            }
        }
        
        ++ptr;
    }   
            
    if (*(ptr-1) == 0xFF && *ptr == 0xD9) 
    {
        return (ptr+1)-buf;
    }    
    else if (*(ptr-1) == 0xFF && *(ptr-2) == 0xFF
             && *(ptr-3) == 0xFF && *(ptr-4) == 0xFF) // EOF? 
    {        
       fprintf(stderr, "Error to read from socket. Seems server closed connection");
       return -1;
    }
    else
    {        
       fprintf(stderr, "Frame has been truncated. Please increase SINK_BUFFER_SIZE");
       return -1;
    }
}

HTTPMediaStreamProducerImpl::HTTPMediaStreamProducerImpl(MediaStreamHandler *mediaStreamHandler, const std::string &url, 
                                                         unsigned cacheSize, unsigned streamId)
    : MediaStreamProducerImpl(mediaStreamHandler, url, cacheSize, streamId, 1, 0),
     env_(0), scheduler_(0), stopped_(false)
{
    // Begin by setting up our usage environment:
    scheduler_ = BasicTaskScheduler::createNew();  
    env_ = AdaptiveUsageEnvironment::createNew(*scheduler_, mediaStreamHandler_);

    playbackController_ = new AudioVideoPlaybackController(*env_, 0, 0, 0, 0);

}

HTTPMediaStreamProducerImpl::~HTTPMediaStreamProducerImpl()
{
   Medium::close(playbackController_);
}

/*virtual*/void HTTPMediaStreamProducerImpl::changeState(STREAM_STATE state, int errorCode)
{
   if (state == PLAY_FROM_SERVER && previousState_ == PLAY_FROM_SERVER) return;
   mediaStreamHandler_->stateChanged(state, ResultStatus(errorCode?errorCode:env_->getErrno(), env_->getResultMsg()));   
   previousState_ = state;
}

/*virtual*/void HTTPMediaStreamProducerImpl::open()
{       
    changeState(OPENING);
    changeState(OPENED);
}
        
/*virtual*/void HTTPMediaStreamProducerImpl::play()
{       

    // building a GET string
    char proto[16] = {0};
    char authorization[128] = {0};
    char hostname[128] = {0};
    int  port = 0;
    char path[128] = {0};
    url_split(proto, sizeof proto,
              authorization, sizeof authorization,
              hostname, sizeof hostname,
              &port, 
              path, sizeof path, url_.c_str());
 

    std::string basic_auth_header;    
    if (authorization[0])
    {        
       char *b64 = base64Encode(authorization);
       basic_auth_header.append("Authorization: Basic ");
       basic_auth_header.append(b64);
       basic_auth_header.append("\r\n");
       delete[] b64;
    }
      
    std::string request
      = "GET " + std::string(path) + " HTTP/1.0\r\n"
       + "Host: " + std::string(hostname) + "\r\n" 
       + basic_auth_header 
       + "\r\n";                


    ResultStatus result;

    if (port <= 0) port = 80;

    int sockfd = connect_socket(&result, hostname, port);

    if (result.errorCode)
    {
       env_->setResultMsg(result.errorString.c_str());
       changeState(STOPPED, EINTERNALERROR);
       return;
    }

    char buffer[2048];
    char error_str[256];

    if (write(sockfd, request.c_str(), request.length()) <= 0)
    {
       if (strerror_r(errno, error_str, sizeof(error_str)) != 0)
           fprintf(stderr, "strerror_r failed\n");
       
       env_->setResultMsg("Failed write into socket: %s", error_str);
       changeState(STOPPED, EINTERNALERROR);
       
       return;
    }

    // check main HTTP-state header
    if (readline(sockfd, buffer, sizeof(buffer)) > 0)
    {
       if (strstr(buffer, "HTTP/1.0 200") != buffer
           && strstr(buffer, "HTTP/1.1 200") != buffer)
       {
          env_->setResultMsg("Request failed. Server response: %s", buffer);
          changeState(STOPPED, EINTERNALERROR);
          
          return;
          
       }
    }
    else 
    {
       env_->setResultMsg("Request failed. Server response: %s", buffer);
       changeState(STOPPED, EINTERNALERROR);    
    }

    receive_mjpg(sockfd);
    close(sockfd);
       
}

#define JPEG_BUFFER_SIZE 65536

void HTTPMediaStreamProducerImpl::receive_mjpg(int sockfd)
{
    int n = 0;
    struct timeval tv;

    StreamInfo *streamInfo = new StreamInfo;
    streamInfo->streamId = streamId_;
    streamInfo->RTPCodecStr = "JPEG";
    streamInfo->configData = 0;
    streamInfo->configDataSize = 0;

    frameSink_  = VideoFrameConsumer::createNew(*env_, playbackController_,
                                          1000000,
                                          decoderType_, preferedPixelFormat_,
                                          streamInfo, false);
          
    playbackController_->addSink(frameSink_, AudioVideoPlaybackController::VIDEO);
               

    FILE *fin = fdopen(sockfd, "r");
    setvbuf(fin, 0, _IOFBF, BUF_SIZE);
    
    unsigned char *buffer = new unsigned char[JPEG_BUFFER_SIZE];

    while (!stopped_ 
            && ((n = read_jpg(fin, buffer, JPEG_BUFFER_SIZE)) > 0))
    {           
        gettimeofday(&tv, NULL);
    
        // 1) creating extra header
        char extraHeaderTpl[] =  "VNTime: %d.%02d;Size: %d;ObjsSize: %d";
    
        
        char extraHeader[sizeof(extraHeaderTpl)+33];
        snprintf(extraHeader, sizeof(extraHeader), extraHeaderTpl, 
                 (uint)tv.tv_sec,
                 (uint)tv.tv_usec / 10000, 
                 n,
        	 0);
    	
        uint extraHeaderSize = strlen(extraHeader) + 1 /*1 - last zero-byte*/;
 
    
        u_int8_t *ptr = buffer + n - 2 /*2 - EOI*/;
    
        // 2) creating comment section
        *ptr++ = 0xFF; *ptr++ = 0xFE; // jpg comment marker
        *ptr++ = (extraHeaderSize + 2) >> 8;
        *ptr++ = (extraHeaderSize + 2);
    
        // 3) copying extra header
        memcpy(ptr, extraHeader, extraHeaderSize); ptr+=extraHeaderSize;
    
        // 6) adding EOI (0xff 0xd9)
        *ptr++ = 0xFF; *ptr++ = 0xD9;

        // That's all       
        n = ptr - buffer;

        // adding frame to sink
        frameSink_->addData(buffer, n, tv);                                       
        
    }

    if (n < 0 && !stopped_)
    {
       env_->setResultMsg("Error for retrieving stream. Server closed connection unexpected");
       changeState(STOPPED, EINTERNALERROR);
    }
    
    fclose(fin);
}

bool HTTPMediaStreamProducerImpl::startRecording(const char* file_name, const std::map<std::string, std::string>& metadata)
{
	return playbackController_->startRecording(file_name, metadata);
}

bool HTTPMediaStreamProducerImpl::endRecording(bool stop)
{
	return playbackController_->endRecording(stop);
}


}}
