//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "httpObject.h"

#include "platform/platform.h"
#include "platform/event.h"
#include "io/fileStream.h"
#include "sim/simBase.h"
#include "console/consoleInternal.h"

IMPLEMENT_CONOBJECT(HTTPObject);

//--------------------------------------

HTTPObject::HTTPObject()
{
   mHostName = 0;
   mPath = 0;
   mQuery = 0;
   mPost = 0;
   mBufferSave = 0;
}

HTTPObject::~HTTPObject()
{
   dFree(mHostName);
   dFree(mPath);
   dFree(mQuery);
   dFree(mPost);

   mHostName = 0;
   mPath = 0;
   mQuery = 0;
   mPost = 0;
   dFree(mBufferSave);
}

//--------------------------------------
//--------------------------------------
void HTTPObject::get(const char *host, const char *path, const char *query)
{
   if(mHostName)
      dFree(mHostName);
   if(mPath)
      dFree(mPath);
   if(mQuery)
      dFree(mQuery);
   if(mPost)
      dFree(mPost);
   if(mBufferSave)
      dFree(mBufferSave);

   mBufferSave = 0;
   mHostName = dStrdup(host);
   mPath = dStrdup(path);
   if(query)
      mQuery = dStrdup(query);
   else
      mQuery = NULL;
   mPost = NULL;

   connect(host);
}

void HTTPObject::post(const char *host, const char *path, const char *query, const char *post)
{
   if(mHostName)
      dFree(mHostName);
   if(mPath)
      dFree(mPath);
   if(mQuery)
      dFree(mQuery);
   if(mPost)
      dFree(mPost);
   if(mBufferSave)
      dFree(mBufferSave);

   mBufferSave = 0;
   mHostName = dStrdup(host);
   mPath = dStrdup(path);
   if(query && query[0])
      mQuery = dStrdup(query);
   else
      mQuery = NULL;
   mPost = dStrdup(post);
   connect(host);
}

static char getHex(char c)
{
   if(c <= 9)
      return c + '0';
   return c - 10 + 'A';
}

static S32 getHexVal(char c)
{
   if(c >= '0' && c <= '9')
      return c - '0';
   else if(c >= 'A' && c <= 'Z')
      return c - 'A' + 10;
   else if(c >= 'a' && c <= 'z')
      return c - 'a' + 10;
   return -1;
}

void HTTPObject::expandPath(char *dest, const char *path, U32 destSize)
{
   static bool asciiEscapeTableBuilt = false;
   static bool asciiEscapeTable[256];
   if(!asciiEscapeTableBuilt)
   {
      asciiEscapeTableBuilt = true;
      U32 i;
      for(i = 0; i <= ' '; i++)
         asciiEscapeTable[i] = true;
      for(;i <= 0x7F; i++)
         asciiEscapeTable[i] = false;
      for(;i <= 0xFF; i++)
         asciiEscapeTable[i] = true;
      asciiEscapeTable['\"'] = true;
      asciiEscapeTable['_'] = true;
      asciiEscapeTable['\''] = true;
      asciiEscapeTable['#'] = true;
      asciiEscapeTable['$'] = true;
      asciiEscapeTable['%'] = true;
      asciiEscapeTable['&'] = true;
      asciiEscapeTable['+'] = true;
      asciiEscapeTable['-'] = true;
      asciiEscapeTable['~'] = true;
   }

   U32 destIndex = 0;
   U32 srcIndex = 0;
   while(path[srcIndex] && destIndex < destSize - 3)
   {
      char c = path[srcIndex++];
      if(asciiEscapeTable[c])
      {
         dest[destIndex++] = '%';
         dest[destIndex++] = getHex((c >> 4) & 0xF);
         dest[destIndex++] = getHex(c & 0xF);
      }
      else
         dest[destIndex++] = c;
   }
   dest[destIndex] = 0;
}

//--------------------------------------
void HTTPObject::onConnected()
{
   Parent::onConnected();
   char expPath[8192];
   char buffer[8192];

   if(mQuery)
   {
      dSprintf(buffer, sizeof(buffer), "%s?%s", mPath, mQuery);
      expandPath(expPath, buffer, sizeof(expPath));
   }
   else
      expandPath(expPath, mPath, sizeof(expPath));

   char *pt = dStrchr(mHostName, ':');
   if(pt)
      *pt = 0;
   dSprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", expPath, mHostName);
   if(pt)
      *pt = ':';

   send((U8*)buffer, dStrlen(buffer));
   mParseState = ParsingStatusLine;
   mChunkedEncoding = false;
}

void HTTPObject::onConnectFailed()
{
   dFree(mHostName);
   dFree(mPath);
   dFree(mQuery);
   mHostName = 0;
   mPath = 0;
   mQuery = 0;
   Parent::onConnectFailed();
}


void HTTPObject::onDisconnect()
{
   dFree(mHostName);
   dFree(mPath);
   dFree(mQuery);
   mHostName = 0;
   mPath = 0;
   mQuery = 0;
   Parent::onDisconnect();
}

bool HTTPObject::processLine(U8 *line)
{
   if(mParseState == ParsingStatusLine)
   {
      mParseState = ParsingHeader;
   }
   else if(mParseState == ParsingHeader)
   {
      if(!dStricmp((char *) line, "transfer-encoding: chunked"))
         mChunkedEncoding = true;
      if(line[0] == 0)
      {
         if(mChunkedEncoding)
            mParseState = ParsingChunkHeader;
         else
            mParseState = ProcessingBody;
         return true;
      }
   }
   else if(mParseState == ParsingChunkHeader)
   {
      if(line[0]) // strip off the crlf if necessary
      {
         mChunkSize = 0;
         S32 hexVal;
         while((hexVal = getHexVal(*line++)) != -1)
         {
            mChunkSize *= 16;
            mChunkSize += hexVal;
         }
         if(mBufferSave)
         {
            mBuffer = mBufferSave;
            mBufferSize = mBufferSaveSize;
            mBufferSave = 0;
         }
         if(mChunkSize)
            mParseState = ProcessingBody;
         else
         {
            mParseState = ProcessingDone;
            finishLastLine();
         }
      }
   }
   else
   {
      return Parent::processLine(line);
   }
   return true;
}

U32 HTTPObject::onDataReceive(U8 *buffer, U32 bufferLen)
{
   U32 start = 0;
   parseLine(buffer, &start, bufferLen);
   return start;
}

//--------------------------------------
U32 HTTPObject::onReceive(U8 *buffer, U32 bufferLen)
{
   if(mParseState == ProcessingBody)
   {
      if(mChunkedEncoding && bufferLen >= mChunkSize)
      {
         U32 ret = onDataReceive(buffer, mChunkSize);
         mChunkSize -= ret;
         if(mChunkSize == 0)
         {
            if(mBuffer)
            {
               mBufferSaveSize = mBufferSize;
               mBufferSave = mBuffer;
               mBuffer = 0;
               mBufferSize = 0;
            }
            mParseState = ParsingChunkHeader;
         }
         return ret;
      }
      else
      {
         U32 ret = onDataReceive(buffer, bufferLen);
         mChunkSize -= ret;
         return ret;
      }
   }
   else if(mParseState != ProcessingDone)
   {
      U32 start = 0;
      parseLine(buffer, &start, bufferLen);
      return start;
   }
   return bufferLen;
}

//--------------------------------------
ConsoleMethod( HTTPObject, get, void, 4, 5, "(TransportAddress addr, string requirstURI, string query=NULL)")
{
   object->get(argv[2], argv[3], argc == 4 ? NULL : argv[4]);
}

ConsoleMethod( HTTPObject, post, void, 6, 6, "(TransportAddress addr, string requestURI, string query, string post)")
{
   object->post(argv[2], argv[3], argv[4], argv[5]);
}
