/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QVariant>
#include <QStringList>
#include <QDebug>
#include <QWriteLocker>
#include <QNetworkProxyFactory>
#include <QNetworkProxy>
#include <gconf/gconf-value.h>
#include <gconf/gconf-client.h>
#include "proxyconf.h"

#define CONF_PROXY "/system/proxy"
#define HTTP_PROXY "/system/http_proxy"


namespace Maemo {

static QString convertKey(const char *key)
{
    return QString::fromUtf8(key);
}

static QVariant convertValue(GConfValue *src)
{
    if (!src) {
        return QVariant();
    } else {
        switch (src->type) {
        case GCONF_VALUE_INVALID:
            return QVariant(QVariant::Invalid);
        case GCONF_VALUE_BOOL:
            return QVariant((bool)gconf_value_get_bool(src));
        case GCONF_VALUE_INT:
            return QVariant(gconf_value_get_int(src));
        case GCONF_VALUE_FLOAT:
            return QVariant(gconf_value_get_float(src));
        case GCONF_VALUE_STRING:
            return QVariant(QString::fromUtf8(gconf_value_get_string(src)));
        case GCONF_VALUE_LIST:
            switch (gconf_value_get_list_type(src)) {
            case GCONF_VALUE_STRING:
                {
                    QStringList result;
                    for (GSList *elts = gconf_value_get_list(src); elts; elts = elts->next)
                        result.append(QString::fromUtf8(gconf_value_get_string((GConfValue *)elts->data)));
                    return QVariant(result);
                }
            default:
                {
                    QList<QVariant> result;
                    for (GSList *elts = gconf_value_get_list(src); elts; elts = elts->next)
                        result.append(convertValue((GConfValue *)elts->data));
                    return QVariant(result);
                }
            }
        case GCONF_VALUE_SCHEMA:
        default:
            return QVariant();
        }
    }
}


/* Fast version of GConfItem, allows reading subtree at a time */
class GConfItemFast {
public:
  GConfItemFast(const QString &k) : key(k) {}
  QHash<QString,QVariant> getEntries() const;

private:
  QString key;
};

#define withClient(c) for (GConfClient *c = gconf_client_get_default(); c; c=0)


QHash<QString,QVariant> GConfItemFast::getEntries() const
{
    QHash<QString,QVariant> children;

    withClient(client) {
        QByteArray k = key.toUtf8();
        GSList *entries = gconf_client_all_entries(client, k.data(), NULL);
        for (GSList *e = entries; e; e = e->next) {
	    char *key_name = strrchr(((GConfEntry *)e->data)->key, '/');
	    if (!key_name)
	        key_name = ((GConfEntry *)e->data)->key;
	    else
	        key_name++;
	    QString key(convertKey(key_name));
	    QVariant value = convertValue(((GConfEntry *)e->data)->value);
	    gconf_entry_unref((GConfEntry *)e->data);
	    //qDebug()<<"key="<<key<<"value="<<value;
	    children.insert(key, value);
        }
        g_slist_free (entries);
    }

    return children;
}



class NetworkProxyFactory : QNetworkProxyFactory
{
    ProxyConf proxy_conf;
    bool proxy_data_read;

public:
    NetworkProxyFactory() : proxy_data_read(false) {  }
    QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query = QNetworkProxyQuery());
};


QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &query)
{
    if (proxy_data_read == false) {
        proxy_data_read = true;
        proxy_conf.readProxyData();
    }

    QList<QNetworkProxy> result = proxy_conf.flush(query);
    if (result.isEmpty())
        result << QNetworkProxy::NoProxy;

    return result;
}


class ProxyConfPrivate {
private:
    // proxy values from gconf
    QString mode;
    bool use_http_host;
    QString autoconfig_url;
    QString http_proxy;
    quint16 http_port;
    QList<QVariant> ignore_hosts;
    QString secure_host;
    quint16 secure_port;
    QString ftp_host;
    quint16 ftp_port;
    QString socks_host;
    quint16 socks_port;
    QString rtsp_host;
    quint16 rtsp_port;

    bool isHostExcluded(const QString &host);

public:
    QString prefix;
    QString http_prefix;

    void readProxyData();
    QList<QNetworkProxy> flush(const QNetworkProxyQuery &query);
};


static QHash<QString,QVariant> getValues(const QString& prefix)
{
    GConfItemFast item(prefix);
    return item.getEntries();
}

static QHash<QString,QVariant> getHttpValues(const QString& prefix)
{
    GConfItemFast item(prefix);
    return item.getEntries();
}

#define GET(var, type)				\
  do {						\
    QVariant v = values.value(#var);		\
    if (v.isValid())				\
      var = v.to##type ();			\
  } while(0)

#define GET_HTTP(var, name, type)		\
  do {						\
    QVariant v = httpValues.value(#name);	\
    if (v.isValid())				\
      var = v.to##type ();			\
  } while(0)


void ProxyConfPrivate::readProxyData()
{
    QHash<QString,QVariant> values = getValues(prefix);
    QHash<QString,QVariant> httpValues = getHttpValues(http_prefix);

    //qDebug()<<"values="<<values;

    /* Read the proxy settings from /system/proxy* */
    GET_HTTP(http_proxy, host, String);
    GET_HTTP(http_port, port, Int);
    GET_HTTP(ignore_hosts, ignore_hosts, List);

    GET(mode, String);
    GET(autoconfig_url, String);
    GET(secure_host, String);
    GET(secure_port, Int);
    GET(ftp_host, String);
    GET(ftp_port, Int);
    GET(socks_host, String);
    GET(socks_port, Int);
    GET(rtsp_host, String);
    GET(rtsp_port, Int);

    if (http_proxy.isEmpty())
        use_http_host = false;
    else
        use_http_host = true;
}


bool ProxyConfPrivate::isHostExcluded(const QString &host)
{
    if (host.isEmpty())
        return true;

    if (ignore_hosts.isEmpty())
        return false;

    QHostAddress ipAddress;
    bool isIpAddress = ipAddress.setAddress(host);

    foreach (QVariant h, ignore_hosts) {
        QString entry = h.toString();
        if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
            return true;  // excluded
        } else {
            // do wildcard matching
            QRegExp rx(entry, Qt::CaseInsensitive, QRegExp::Wildcard);
            if (rx.exactMatch(host))
                return true;
        }
    }

    // host was not excluded
    return false;
}


QList<QNetworkProxy> ProxyConfPrivate::flush(const QNetworkProxyQuery &query)
{
    QList<QNetworkProxy> result;

#if 0
    qDebug()<<"http_proxy" << http_proxy;
    qDebug()<<"http_port" << http_port;
    qDebug()<<"ignore_hosts" << ignore_hosts;
    qDebug()<<"use_http_host" << use_http_host;
    qDebug()<<"mode" << mode;
    qDebug()<<"autoconfig_url" << autoconfig_url;
    qDebug()<<"secure_host" << secure_host;
    qDebug()<<"secure_port" << secure_port;
    qDebug()<<"ftp_host" << ftp_host;
    qDebug()<<"ftp_port" << ftp_port;
    qDebug()<<"socks_host" << socks_host;
    qDebug()<<"socks_port" << socks_port;
    qDebug()<<"rtsp_host" << rtsp_host;
    qDebug()<<"rtsp_port" << rtsp_port;
#endif

    if (isHostExcluded(query.peerHostName()))
        return result;          // no proxy for this host

    if (mode == QLatin1String("AUTO")) {
        // TODO: pac currently not supported, fix me
        return result;
    }

    if (mode == QLatin1String("MANUAL")) {
        bool isHttps = false;
	QString protocol = query.protocolTag().toLower();

	// try the protocol-specific proxy
	QNetworkProxy protocolSpecificProxy;

	if (protocol == QLatin1String("ftp")) {
	    if (!ftp_host.isEmpty()) {
	        protocolSpecificProxy.setType(QNetworkProxy::FtpCachingProxy);
		protocolSpecificProxy.setHostName(ftp_host);
		protocolSpecificProxy.setPort(ftp_port);
	    }
	} else if (protocol == QLatin1String("http")) {
	    if (!http_proxy.isEmpty()) {
	        protocolSpecificProxy.setType(QNetworkProxy::HttpProxy);
		protocolSpecificProxy.setHostName(http_proxy);
		protocolSpecificProxy.setPort(http_port);
	    }
	} else if (protocol == QLatin1String("https")) {
	    isHttps = true;
	    if (!secure_host.isEmpty()) {
	        protocolSpecificProxy.setType(QNetworkProxy::HttpProxy);
		protocolSpecificProxy.setHostName(secure_host);
		protocolSpecificProxy.setPort(secure_port);
	    }
	}

	if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
	    result << protocolSpecificProxy;


        if (!socks_host.isEmpty()) {
	    QNetworkProxy proxy;
	    proxy.setType(QNetworkProxy::Socks5Proxy);
	    proxy.setHostName(socks_host);
	    proxy.setPort(socks_port);
	    result << proxy;
	}


	// Add the HTTPS proxy if present (and if we haven't added yet)
	if (!isHttps) {
	    QNetworkProxy https;
	    if (!secure_host.isEmpty()) {
	        https.setType(QNetworkProxy::HttpProxy);
		https.setHostName(secure_host);
		https.setPort(secure_port);
	    }

	    if (https.type() != QNetworkProxy::DefaultProxy &&
		https != protocolSpecificProxy)
	        result << https;
	}
    }

    return result;
}


ProxyConf::ProxyConf()
    : d_ptr(new ProxyConfPrivate)
{
    g_type_init();
    d_ptr->prefix = CONF_PROXY;
    d_ptr->http_prefix = HTTP_PROXY;
}

ProxyConf::~ProxyConf()
{
    delete d_ptr;
}

void ProxyConf::readProxyData()
{
    d_ptr->readProxyData();
}

QList<QNetworkProxy> ProxyConf::flush(const QNetworkProxyQuery &query)
{
    return d_ptr->flush(query);
}


static int refcount = 0;
static QReadWriteLock lock;

void ProxyConf::update()
{
    QWriteLocker locker(&lock);
    NetworkProxyFactory *factory = new NetworkProxyFactory();
    QNetworkProxyFactory::setApplicationProxyFactory((QNetworkProxyFactory*)factory);
    refcount++;
}


void ProxyConf::clear(void)
{
    QWriteLocker locker(&lock);
    refcount--;
    if (refcount == 0)
        QNetworkProxyFactory::setApplicationProxyFactory(NULL);

    if (refcount<0)
        refcount = 0;
}


} // namespace Maemo
