/*
 * download.cpp by Matthias Braun <matze@braunis.de>
 *
 * Copyright (C) 2002 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation (version 2 of the License)
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
#include <psconfig.h>

#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>
#include <iutil/databuff.h>
#include <iutil/vfs.h>

#include "download.h"
#include "registry.h"
#include "paws/pawswidget.h"
#include "paws/pawsmanager.h"
#include "paws/pawsprogressbar.h"
#include "pawsupdaterwindow.h"

#include "updaterglobals.h"
#include "updaterconfig.h"
#include "fileutil.h"

size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
  size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
  return written;
}

namespace updater
{

Downloader::Downloader ()
    : callback (0)
{

    eng = psupdaterengine;
    curl = curl_easy_init();
    if (!curl)
    psupdaterengine->CriticalError("Couldn't initialize libcurl downloader."); 

    curlerror = new char[CURL_ERROR_SIZE];

    curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, curlerror);

    curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0);

    curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, ProgressCallback);

    curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, this);
}

Downloader::~Downloader ()
{
    delete[] curlerror;
    curl_easy_cleanup(curl);
}

void Downloader::SetProxy (bool active, const char* host, int port)
{
    if (!active)
    {
#ifdef WIN32
        curl_easy_setopt (curl, CURLOPT_PROXY, "");
#endif
    }
    else
    {
        curl_easy_setopt (curl, CURLOPT_PROXY, host);
        curl_easy_setopt (curl, CURLOPT_PROXYPORT, port);
    }
}

void Downloader::SetCallback (StatusCallback* newcallback)
{
    callback = newcallback;
}

bool Downloader::DownloadFile (const char* dest, const char* url,
                   bool doprogress,bool changemirrors)
{
    char * destpath;
    // Set passwords
    curl_easy_setopt (curl, CURLOPT_USERPWD,psupdaterengine->GetUpdater()->GetConfig()->GetMirror()->GetPassphrase());

    csRef<iVFS> vfs = eng->GetVFS();
    if (vfs)
    {
        csRef<iDataBuffer> prefixpath = vfs->GetRealPath("/this/");
        destpath = (char *)malloc(strlen(dest) + prefixpath->GetSize()+1); //not sure if the +1 is necessary, but paranoid.
        strncpy(destpath,prefixpath->GetData(),prefixpath->GetSize());
        strcat(destpath,dest);
    }
    else
    {
        printf("No VFS in object registry!?\n");
        return false;
    }
    FILE* file;
    file = fopen (destpath, "wb");
    
    if (!file)
    {
        printf("Couldn't write to file! (%s)\n", dest);
        return false;
    }
    
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);

    // do we want a progressbar?
    progress = doprogress;
    lastprogress = 0;

    start = csGetTicks();
    CURLcode result = curl_easy_perform(curl);

    fclose (file);
    free(destpath);
    destpath = 0;
    
    long curlhttpcode;
    curl_easy_getinfo (curl, CURLINFO_HTTP_CODE, &curlhttpcode);

    csString error;

    if (result != CURLE_OK)
    {
        if (result == CURLE_COULDNT_CONNECT || result == CURLE_COULDNT_RESOLVE_HOST)
            error = "Couldn't connect to mirror";
        else
            error.Format("Error while downloading: %s, file %s",curlerror,url);
    }

    // Tell the user that we failed
    if (curlhttpcode != 200 || error != "")
    {
        if(error == "")
            printf ("Server error %d (%s)\n",curlhttpcode,url);
        else
            printf ("Server error: %s (%d)\n",error.GetData(),curlhttpcode);

        if(changemirrors)
            return NextMirror(dest,url,progress);
        else
            return false;
    }

    return true;
}

int Downloader::ProgressCallback (void* clientp, 
    double total, double now, double, double)
{
    Downloader* me = (Downloader*)clientp;

    if (!psupdaterengine)
        psupdaterengine=me->GetEngine();

    //Still null?
    if (!psupdaterengine || !psupdaterengine->GetUpdater() || !psupdaterengine->GetUpdater()->IsRunning())
        return 0;

    int secs = (int)ceil(double(csGetTicks() - me->GetStartTick()) / 1000 );
    double kb     = ceil((now/double(secs)) / 1024);

    //Silent progress
    if (psupdaterengine->IsSilent())
    {
        if (total>0 && now>0)
        {
            float percent = now/total*100;
            if (me->callback)
                me->callback->StatusUpdate (percent, "");
    
            if (me->progress)
            {
                while ( (((int) percent+.001) - me->lastprogress > 5)  && me->lastprogress<=100)
                {

                    if (me->lastprogress % 25 == 0)
                    {
                        printf ("%d%%", me->lastprogress);
                    }
                    else if (me->lastprogress % 5 == 0)
                    {
                        printf (".");
                    }
                    
                    fflush(stdout);
                    me->lastprogress += 1;
                }
            }
        }

        int percent = (int)(now/total*100);
        if (percent == 100 && me->lastprogress != 100)
        {
            if(me->progress)
                printf("100%% (%d KB/s)\n",(int)kb);
            me->lastprogress = 100;
        }

        return 0;
    }

    //Active progress

    //Get PAWS window
    pawsUpdaterWindow* updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");
    if (!updWnd)
        return false;

    if (total>0 && now>0)
    {
        psupdaterengine->LockStatusMutex();
        int percent = (int)(now/total*100);
        updWnd->SetProgressValue(percent);
        updWnd->SetSpeed(kb);
        psupdaterengine->UnlockStatusMutex();
    }

    return 0;
}

bool Downloader::NextMirror(const char* dest, const char* url, bool progress)
{
    unsigned int oldVersion = psupdaterengine->GetUpdater()->GetVersionFromFile("/this/version.dat");
    Mirror* oldMirror = psupdaterengine->GetUpdater()->GetConfig()->GetMirror();

    csString newURL = url;
    newURL = newURL.Slice(strlen( csString(oldMirror->GetBaseURL()) + oldMirror->GetFilesDirectory()),newURL.Length());

    // Inform
    psupdaterengine->OutToScreen(
        psupdaterengine->FindRGB(255,0,0),
        "Couldn't connect to mirror %s!",
        oldMirror->GetName()
        );

    // Action
    oldMirror->SetWorking(false); // set this mirror as non working so we don't enter an infinitve loop
    bool loop= true;
    while(loop)
    {
        Mirror* newMirror;
        Config* config = psupdaterengine->GetUpdater()->GetConfig();
        if(config->SetNextWorkingMirror())
        {
            // Get the new mirror
            newMirror = config->GetMirror();
            psupdaterengine->OutToScreen("Trying mirror %s..",newMirror->GetName());
        }
        else
        {
            // No mirrors left :(
            psupdaterengine->OutToScreen("Ran out of mirrors! Game cannot be updated.");
            psupdaterengine->GetUpdater()->EndUpdate();
            return false;
        }

        // Compare the versions between the mirrors
        if (!newMirror->NoVersion())
        {
            csString verFile;
            verFile.Format("updatertemp/%s.dat",newMirror->GetName());
            MakeDirectory("updatertemp");
            
            csString verFileURL = newMirror->GetBaseURL();
            verFileURL += newMirror->GetVersion();

            bool downloaded = DownloadFile(verFile,verFileURL,false,false);

            unsigned int newVersion = psupdaterengine->GetUpdater()->GetVersionFromFile("/this/" + verFile);

            if ( !downloaded || newVersion == 0 )
            {
                newMirror->SetWorking(false);
                psupdaterengine->OutToScreen(psupdaterengine->FindRGB(255,0,0),"Mirror lacks a version file; can't use it");
                continue; // Next mirror
            }

            if (oldVersion > newVersion)
            {
                // Different version
                newMirror->SetWorking(false);
                psupdaterengine->OutToScreen(psupdaterengine->FindRGB(255,0,0),"Mirror '%s' has old data", newMirror->GetName());
                continue; // Next mirror
            }
        }

        // Make new url
        if(!strcmp(url, csString(oldMirror->GetBaseURL()) + oldMirror->GetRepository())) // Was this a file or the repository?
        {
            newURL =  psupdaterengine->GetUpdater()->GetConfig()->GetMirror()->GetBaseURL();
            newURL += psupdaterengine->GetUpdater()->GetConfig()->GetMirror()->GetRepository();
        }
        else
        {
            newURL =
                csString(psupdaterengine->GetUpdater()->GetConfig()->GetMirror()->GetBaseURL()) +
                psupdaterengine->GetUpdater()->GetConfig()->GetMirror()->GetFilesDirectory() +
                newURL;
        }

        // Try again
        bool suc = DownloadFile(dest,newURL,progress,false);

        if(suc)
            break;
        else
            newMirror->SetWorking(false);

        // Next mirror if not working
    }

    return true;
}

} // end of namespace updater
