/*
 * Copyright (c) 2004 Nokia. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of Nokia nor the names of its contributors may be
 * used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "KWQLoader.h"

#include "KWQKJobClasses.h"
#include "KWQLogging.h"
#include "KWQResourceLoader.h"
#include "KWQFoundationExtras.h"
#include "WebCoreBridge.h"
#include "khtml_part.h"
#include "loader.h"

#include "KWIQResponse.h"

using khtml::Cache;
using khtml::CachedObject;
using khtml::CachedImage;
using khtml::DocLoader;
using khtml::Loader;
using khtml::Request;
using KIO::TransferJob;

extern "C" {
static void _collect_pairs(gpointer key, gpointer value, gpointer user_data)
{
    const gchar* k =  (const gchar*) key;
    const gchar* v =  (const gchar*) value;
    QString* str = static_cast<QString*>(user_data);    
    str->append(QString::fromLatin1(k));
    str->append(QString::fromLatin1(": "));
    str->append(QString::fromLatin1(v));
    str->append(QString::fromLatin1("\n"));
}
}


static QString KWQHeaderStringFromHashTable(GHashTable *headers, int statusCode)
{
    QString result;
    gchar* str;
    str = g_strdup_printf("HTTP/1.0 %d OK\n", statusCode);
    result = QString::fromLatin1(str);
    g_free(str);

    g_hash_table_foreach(headers, _collect_pairs, &result);

    return result;
}


bool KWQServeRequest(Loader *loader, Request *request, TransferJob *job)
{
    LOG(Loading, "Serving request for base %s, url %s", 
        request->m_docLoader->part()->baseURL().url().latin1(),
        request->object->url().string().latin1());

    return KWQServeRequest(loader, request->m_docLoader, job);
}

extern "C"{
void headers_value_key_destroy(gpointer data)
{
    gchar *c = static_cast<gchar *>(data);
    g_free(c);
}
}

void fillHashTableWithHeaderStrings(GHashTable* headers, const QString& headerString) 
{
    QStringList l = QStringList::split(QString("\n"), headerString);
    QValueList<QString>::Iterator it = l.begin();
    QValueList<QString>::Iterator end = l.end();
    QString str, key, value;
    int pos;
    while (it != end) {
	str = (*it);
	pos = str.find(":");
	if (pos != -1) {
	    key = str.left(pos).stripWhiteSpace();
	    value = str.right(pos+1).stripWhiteSpace();
		g_hash_table_insert(headers, g_strdup(key.utf8()), g_strdup(value.utf8()));
	}
	++it;
    }
}

bool KWQServeRequest(Loader *loader, DocLoader *docLoader, TransferJob *job)
{
    KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(docLoader->part());
    WebCoreBridge *bridge = part->bridge();

    part->didTellBridgeAboutLoad(job->url().url());
    
    KWQResourceLoader *resourceLoader = new KWQResourceLoader(job);
    
    GHashTable *headers = 0; 

    QString headerString = job->queryMetaData("customHTTPHeader");
    
    if (!headerString.isEmpty()) {
	headers = g_hash_table_new_full(g_str_hash, g_str_equal,
					headers_value_key_destroy,
					headers_value_key_destroy);

	fillHashTableWithHeaderStrings(headers, headerString);
    }

    WebCoreResourceHandle *handle;

    if (job->method() == "POST") {
	GByteArray* postData = 0;	
        const int size = job->postData().size();
	if (size) {
	    GByteArray* postData = g_byte_array_sized_new(size);
	    g_byte_array_append(postData, reinterpret_cast<guint8 *>(g_strdup(job->postData().data())), size);
	}

	handle = bridge->startLoadingResource(resourceLoader,
					      job->url().url().utf8(),
					      headers,
					      postData);
	if (postData)
	    g_byte_array_free(postData, true);
    } else {
	handle = bridge->startLoadingResource(resourceLoader,
					      job->url().url().utf8(),
					      headers );

    }
    if (headers) g_hash_table_destroy(headers);
    
    resourceLoader->setHandle(handle);
   
    return handle != 0;    
}

QByteArray KWQServeSynchronousRequest(Loader *loader, DocLoader *docLoader, TransferJob *job, KURL &finalURL, QString &responseHeaders)
{
    KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(docLoader->part());
    WebCoreBridge *bridge = part->bridge();

    part->didTellBridgeAboutLoad(job->url().url());

    GHashTable* requestHeaderDict = 0;
    QString headerString = job->queryMetaData("customHTTPHeader");
    if (!headerString.isEmpty()) {
	requestHeaderDict = g_hash_table_new_full(g_str_hash, g_str_equal,
					       headers_value_key_destroy,
					       headers_value_key_destroy);

	fillHashTableWithHeaderStrings(requestHeaderDict, headerString);
    }

    GByteArray *postData = g_byte_array_new();

    // no copy, a bit unorthodox way to use gbytearray
    postData->data = 0;
    postData->len = 0;
    if (job->method() == "POST") {
	postData->data = (guint8*) job->postData().data();
	postData->len = job->postData().size();
    }

    gchar* returnedFinalURL = 0;

    GHashTable *responseHeaderDict = g_hash_table_new_full(g_str_hash, g_str_equal,
							headers_value_key_destroy,
							headers_value_key_destroy);
    int statusCode = 0;

    GByteArray *resultData = g_byte_array_new();
    bridge->syncLoadResourceWithURL(job->url().url().latin1(), 
				    requestHeaderDict, 
				    postData,
				    resultData,
				    &returnedFinalURL,
				    responseHeaderDict,
				    &statusCode);
    job->kill();

    if (returnedFinalURL){
	finalURL = KURL(returnedFinalURL);
	g_free(returnedFinalURL);
    }

    responseHeaders = KWQHeaderStringFromHashTable(responseHeaderDict, statusCode);

    QByteArray results(resultData->len);

    memcpy(results.data(), resultData->data, resultData->len);

    g_byte_array_free(postData, true);
    g_byte_array_free(resultData, true);

    if (requestHeaderDict)
	g_hash_table_destroy(requestHeaderDict);
    g_hash_table_destroy(responseHeaderDict);

    return results;
}

int KWQNumberOfPendingOrLoadingRequests(khtml::DocLoader *dl)
{
    return Cache::loader()->numRequests(dl);
}

bool KWQCheckIfReloading(DocLoader *loader)
{
    return static_cast<KWQKHTMLPart *>(loader->part())->bridge()->isReloading();
}

void KWQCheckCacheObjectStatus(DocLoader *loader, CachedObject *cachedObject)
{
    // Return from the function for objects that we didn't load from the cache.
    if (!cachedObject)
        return;
    switch (cachedObject->status()) {
    case CachedObject::Persistent:
    case CachedObject::Cached:
    case CachedObject::Uncacheable:
        break;
    case CachedObject::NotCached:
    case CachedObject::Unknown:
    case CachedObject::New:
    case CachedObject::Pending:
        return;
    }
    
    ASSERT(cachedObject->response());
    
    // Notify the caller that we "loaded".
    KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(loader->part());

    QString urlString = cachedObject->url().string();

    if (!part->haveToldBridgeAboutLoad(urlString)) {
	WebCoreBridge *bridge = part->bridge();
	// FIXME: KWIQ: Apple has here cachedimage specific code, which
	// uses dynamic cast to get cachedImage->dataSize() instead of
	// cachedObject->size() 
	// why is that?
	bridge->objectLoadedFromCacheWithURL(cachedObject->url().string().utf8(),
					     cachedObject->response(),
					     cachedObject->size());
	part->didTellBridgeAboutLoad(urlString);	
    }
	
}

void KWQRetainResponse(KWIQResponse *r)
{
    KWQRetain(r);
}

void KWQReleaseResponse(KWIQResponse *r)
{
    KWQRelease(r);
}


QString KWQResponseMIMEType(KWIQResponse *r)
{
    ASSERT(r);
    return r->mimeType();
}

QString KWQResponseHeaderString(KWIQResponse *r)
{
    ASSERT(r);    

    return KWQHeaderStringFromHashTable(r->allHeaderFields(), r->statusCode());
}

time_t KWQCacheObjectExpiresTime(khtml::DocLoader *docLoader, KWIQResponse *response)
{
    return response->expiresTime();
}


KWQLoader::KWQLoader(Loader *loader)
    : _requestStarted(loader,SIGNAL(requestStarted(khtml::DocLoader *, khtml::CachedObject *)))
    , _requestDone(loader, SIGNAL(requestDone(khtml::DocLoader *, khtml::CachedObject *)))
    , _requestFailed(loader, SIGNAL(requestFailed(khtml::DocLoader *, khtml::CachedObject *)))
{
}
