/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkHTTPHandler.h"
#include "vtkKWRemoteIOUtilities.h"

#include "Curl/curl/mprintf.h"

vtkStandardNewMacro ( vtkHTTPHandler );
vtkCxxRevisionMacro ( vtkHTTPHandler, "$Revision: 1.10 $" );

// Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
// byte) 
//------------------------------------------------------------------------------
static void time2str(char *r, long t)
{
  long h;
  if(!t) 
    {
    strcpy(r, "--:--:--");
    return;
    }
  h = (t/3600);
  if(h <= 99) 
    {
    long m = (t-(h*3600))/60;
    long s = (t-(h*3600)-(m*60));
    if (h)
      {
      curl_msnprintf(r, 12, "%2ldh %02ldm %02lds",h,m,s);
      }
    else
      {
      curl_msnprintf(r, 8, "%02ldm %02lds",m,s);
      }
    }
  else 
    {
    /* this equals to more than 99 hours, switch to a more suitable output
       format to fit within the limits. */
    if(h/24 <= 999)
      {
      curl_msnprintf(r, 9, "%3ldd %02ldh", h/24, h-(h/24)*24);
      }
    else
      {
      curl_msnprintf(r, 9, "%7ldd", h/24);
      }
    }
}

/*------------------------------------------------------------------------------
vtkHTTPHandler* vtkHTTPHandler::New()
{
  // First try to create the object from the vtkObjectFactory
  vtkObject* ret = vtkObjectFactory::CreateInstance("vtkHTTPHandler");
  if(ret)
    {
    return (vtkHTTPHandler*)ret;
    }
  // If the factory was unable to create the object, then create it here.
  return new vtkHTTPHandler;
}
*/

size_t read_callback(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
  size_t retcode;

  /* in real-world cases, this would probably get this data differently
     as this fread() stuff is exactly what the library already would do
     by default internally */
  retcode = fread(ptr, size, nmemb, stream);

  std::cout << "*** We read " << retcode << " bytes from file\n";

  return retcode;
}

size_t write_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
  if (stream == NULL)
    {
    std::cerr << "write_callback: can't write, stream is null. size = " << size << std::endl;
    return -1;
    }
  size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
  return written;
}

size_t Progresscallback(vtkURIHandler *s, 
                       double dltotal, 
                       double dlnow, 
                       double ultotal, 
                       double ulnow)
{
  vtkHTTPHandler *self = static_cast< vtkHTTPHandler * >(s);

  if(ultotal == 0)
    {
    if(dltotal > 0)
      {

      double dlspeed = 0.0;
      curl_easy_getinfo( self->CurlHandle, CURLINFO_SPEED_DOWNLOAD, &dlspeed );

      long dlestimate = 0;

      if (dlspeed > 0.0)
        {
        dlestimate = (long)((dltotal-dlnow) / dlspeed);
        }

      time2str(self->EstimatedTimeRemaining, dlestimate);

      self->SetProgress( dlnow/dltotal );
      self->SetTotalDownloadSize( dltotal );
      self->SetDownloadedSize( dlnow );
   
      }
    }
  else
    {
    self->SetProgress( ulnow/ultotal );
    }
  return 0;
}

//----------------------------------------------------------------------------
vtkHTTPHandler::vtkHTTPHandler()
{
  this->CurlHandle = NULL;
}


//----------------------------------------------------------------------------
vtkHTTPHandler::~vtkHTTPHandler()
{
  this->CurlHandle = NULL;
}


//----------------------------------------------------------------------------
void vtkHTTPHandler::PrintSelf(ostream& os, vtkIndent indent)
{
  Superclass::PrintSelf ( os, indent );
}



//----------------------------------------------------------------------------
int vtkHTTPHandler::CanHandleURI ( const char *uri )
{
  //--- What's the best way to determine whether this handler
  //--- speaks the correct protocol?
  //--- first guess is to look at the prefix up till the colon.

  size_t index;
  std::string uriString (uri);
  std::string prefix;

  //--- get all characters up to (and not including) the '://'
  if ( ( index = uriString.find ( "://", 0 ) ) != std::string::npos )
    {
    prefix = uriString.substr ( 0, index );
    //--- check to see if any bracketed characters are in
    //--- this part of the string.
    if ( (index = prefix.find ( "]:", 0 ) ) != std::string::npos )
      {
      //--- if so, strip off the leading bracketed characters in case
      //--- we adopt the gwe "[filename.ext]:" prefix.
      prefix = prefix.substr ( index+2 );
      }
    if ( prefix == "http" )
      {
      vtkDebugMacro("vtkHTTPHandler: CanHandleURI: can handle this file: " << uriString.c_str());
      return (1);
      }
    }
  else
    {
    vtkDebugMacro ( "vtkHTTPHandler::CanHandleURI: unrecognized uri format: " << uriString.c_str() );
    }
  return ( 0 );
}



//----------------------------------------------------------------------------
void vtkHTTPHandler::InitTransfer( )
{
  curl_global_init(CURL_GLOBAL_ALL);
  vtkDebugMacro("vtkHTTPHandler: InitTransfer: initialising CurlHandle");
  this->CurlHandle = curl_easy_init();
  if (this->CurlHandle == NULL)
    {
    vtkErrorMacro("InitTransfer: unable to initialise");
    }
}

//----------------------------------------------------------------------------
int vtkHTTPHandler::CloseTransfer( )
{
  curl_easy_cleanup(this->CurlHandle);
  return EXIT_SUCCESS;      
}


//----------------------------------------------------------------------------
void vtkHTTPHandler::StageFileRead(const char * source, const char * destination)
{
  // NOTE: Do not throw an vtkErrorMacro here. Our download could potentially
  // be on a seperate thread. KWWidget's error dialog is only designed to 
  // receive messages from the main thread.  

  if (source == NULL || destination == NULL)
    {
    cerr << "StageFileRead: source or dest is null!" << endl;
    return;
    }

  // Rename the destination file with a .tmp postfix and rename after download.
  // This should take care of partial downloads.
  std::string tmpDestination = destination;
  tmpDestination += ".partialDownload";

  this->InitTransfer( );
  
  curl_easy_setopt(this->CurlHandle, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(this->CurlHandle, CURLOPT_HTTPGET, 1);
  curl_easy_setopt(this->CurlHandle, CURLOPT_URL, source);
  curl_easy_setopt(this->CurlHandle, CURLOPT_NOPROGRESS, false);
  curl_easy_setopt(this->CurlHandle, CURLOPT_FOLLOWLOCATION, true);
  // use the default curl write call back
  curl_easy_setopt(this->CurlHandle, CURLOPT_WRITEFUNCTION, NULL); // write_callback);
  this->LocalFile = fopen(tmpDestination.c_str(), "wb");
  if (!this->LocalFile)
    {
    cerr << "Failed creating LocalFile (fopen)!" << endl;
    return;
    }

  // output goes into LocalFile, must be  FILE*
  curl_easy_setopt(this->CurlHandle, CURLOPT_WRITEDATA, this->LocalFile);
  curl_easy_setopt(this->CurlHandle, CURLOPT_PROGRESSDATA, this);
  curl_easy_setopt(this->CurlHandle, CURLOPT_PROGRESSFUNCTION, Progresscallback);
  vtkDebugMacro( "StageFileRead: about to do the curl download... source = " 
                 << source << ", dest = " << tmpDestination.c_str() );

  CURLcode retval = curl_easy_perform(this->CurlHandle);

  if (retval == CURLE_OK)
    {
    vtkDebugMacro("StageFileRead: successful return from curl");
    }
  else
    {
    cerr << "Network error: " << curl_easy_strerror(retval) << endl;
    }

  this->CloseTransfer();

  if (fflush(this->LocalFile))
    {
    cerr << "Failed flushing LocalFile (fflush)!" << endl;
    }

  if (fclose(this->LocalFile))
    {
    cerr << "Failed closing LocalFile (fclose)!" << endl;
    }

  if (!vtkKWRemoteIOUtilities::RenameFile( tmpDestination.c_str(), destination ))
    {
    cerr << "Failed to rename the file " 
         << tmpDestination << " to " << destination << endl;
    }
}


//----------------------------------------------------------------------------
void vtkHTTPHandler::StageFileWrite(const char * source, const char * destination)
{
  //--- check these arguments...
  /*
  if (this->LocalFile)
    {
    this->LocalFile->close();
    delete this->LocalFile;
    this->LocalFile = NULL;
    }
  this->LocalFile = new std::ofstream(destination, std::ios::binary);
  */
  this->LocalFile = fopen(source, "r");

  this->InitTransfer( );
  
  curl_easy_setopt(this->CurlHandle, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(this->CurlHandle, CURLOPT_PUT, 1);
  curl_easy_setopt(this->CurlHandle, CURLOPT_URL, source);
//  curl_easy_setopt(this->CurlHandle, CURLOPT_NOPROGRESS, false);
  curl_easy_setopt(this->CurlHandle, CURLOPT_FOLLOWLOCATION, true);
  curl_easy_setopt(this->CurlHandle, CURLOPT_READFUNCTION, read_callback);
  curl_easy_setopt(this->CurlHandle, CURLOPT_READDATA, this->LocalFile);
//  curl_easy_setopt(this->CurlHandle, CURLOPT_PROGRESSDATA, NULL);
  //curl_easy_setopt(this->CurlHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
  CURLcode retval = curl_easy_perform(this->CurlHandle);

   if (retval == CURLE_OK)
    {
    vtkDebugMacro("StageFileWrite: successful return from curl");
    }
   else
    {
    //const char *stringError = curl_easy_strerror(retval);
    //vtkErrorMacro("StageFileWrite: error running curl: " << stringError);
    }
   
  this->CloseTransfer();
  
  fclose(this->LocalFile);
  /*
  this->LocalFile->close();
  delete this->LocalFile;
  this->LocalFile = NULL;
  */
}
