/*
Launchy: Application Launcher
Copyright (C) 2007  Josh Karlin

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; either version 2
of the License, or (at your option) any later version.

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 <QString>

#ifdef Q_OS_WIN
#include "winfiles.h"
#endif

#include "gui.h"
#include "weby.h"
#include "IconCache.h"

int Suggest::currentId = 0;

Suggest::Suggest()
{
}


void Suggest::run(QString url, QString query)
{
    this->query = query;
    url = url.replace("%s", QUrl::toPercentEncoding(query));
    QUrl u = QUrl::fromPercentEncoding(url.toLatin1());

    httpMsg = "";
    reply = qnam.get(QNetworkRequest(u));
    connect(reply, SIGNAL(finished()), this, SLOT(httpFinished()));
    connect(reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead()));
    id = ++currentId;

    /*
    //http.setHost(u.host(), u.port(80));
    //http.get(u.toEncoded(QUrl::RemoveScheme | QUrl::RemoveAuthority));
    */
    loop.exec();
}

void Suggest::httpFinished()
{
    if (id != currentId) return;

    if (!reply->error()) {

        if (!query.isEmpty())
        {
            results << query;
        }

        QRegExp regex("\\[.*\\[(.*)\\]\\]");
        QRegExp rx("\"((?:[^\\\\\"]|\\\\\")*)\"");
        if (regex.indexIn(httpMsg) != -1)
        {
            QString csv = regex.cap(1);

            int pos = 0;
            while ((pos = rx.indexIn(csv, pos)) != -1)
            {
                QString result = rx.cap(1);
                if (result.count() > 0)
                    results << result;

                pos += rx.matchedLength();
            }
        }
    }

    loop.exit();
    reply->deleteLater();
    reply = 0;
}

void Suggest::httpReadyRead()
{
    httpMsg += reply->readAll();
}





void WebyPlugin::init()
{
    // get config / settings directory (base for 'temporary' icon cache dir)
    QString iniFilename = _settings->fileName();
    QFileInfo info(iniFilename);
    iconCachePath = info.absolutePath() + "/weby-icon-cache/";
    iconCache = new IconCache(iconCachePath);
    iconCache->setParent(this);
    connect(iconCache, SIGNAL(findIcon(QUrl)), iconCache, SLOT(query(QUrl)));

    double version = _settings->value("weby/version",0.0).toDouble();

    if ( version == 0.0 )
    {
        int i = 0;
        _settings->beginWriteArray("weby/sites");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Google");
        _settings->setValue("query", "http://www.google.com/search?source=launchy&q=%1");
        //_settings->setValue("suggest", "http://suggestqueries.google.com/complete/search?output=firefox&q=%1");
        //_settings->setValue("default", true);

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Bing");
        _settings->setValue("query", "http://www.bing.com/search?q=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Yahoo");
        _settings->setValue("query", "http://search.yahoo.com/search?p=%1");
        //_settings->setValue("suggest", "http://ff.search.yahoo.com/gossip?output=fxjson&command=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Weather");
        _settings->setValue("query", "http://www.weather.com/weather/local/%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Amazon");
        _settings->setValue("query", "http://www.amazon.com/gp/search?keywords=%1&index=blended");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "YouTube");
        _settings->setValue("query", "http://www.youtube.com/results?search_query=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Wikipedia");
        _settings->setValue("query", "http://en.wikipedia.org/wiki/Special:Search?search=%1&fulltext=Search");
        //_settings->setValue("suggest", "http://en.wikipedia.org/w/api.php?action=opensearch&search=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Dictionary");
        _settings->setValue("query", "http://dictionary.reference.com/browse/%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Thesaurus");
        _settings->setValue("query", "http://thesaurus.reference.com/browse/%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Netflix");
        _settings->setValue("query", "http://www.netflix.com/Search?v1=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "MSDN");
        _settings->setValue("query", "http://search.msdn.microsoft.com/search/default.aspx?siteId=0&tab=0&query=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "E-Mail");
        _settings->setValue("query", "mailto:%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "IMDB");
        _settings->setValue("query", "http://www.imdb.com/find?s=all&q=%1");

        _settings->setArrayIndex(i++);
        _settings->setValue("name", "Maps");
        _settings->setValue("query", "http://maps.google.com/maps?f=q&hl=en&geocode=&q=%1&ie=UTF8&z=12&iwloc=addr&om=1");

        _settings->endArray();
    }

    // Read in the array of websites
    sites.clear();

    int count = _settings->beginReadArray("weby/sites");
    for (int i = 0; i < count; ++i)
    {
        _settings->setArrayIndex(i);
        WebySite s;
        s.name = _settings->value("name").toString();
        s.query = _settings->value("query").toString();
        s.suggest = _settings->value("suggest").toString();
        s.def = _settings->value("default", false).toBool();

        // Ditched the 'base' value between 1.0 and 2.0
        // Also replaced %s with %1,%2,%3...
        if (version == 2.0) {
            s.query.replace("%s","%1");
            s.query = _settings->value("base").toString() + s.query;
/*
            Out of scope for 2.2
            if (s.name == "Google")
                s.suggest = "http://suggestqueries.google.com/complete/search?output=firefox&q=%1";
            else if (s.name == "Yahoo")
                s.suggest = "http://ff.search.yahoo.com/gossip?output=fxjson&command=%1";
            else if (s.name == "Wikipedia")
                s.suggest = "http://en.wikipedia.org/w/api.php?action=opensearch&search=%1";
*/
        }
        
        sites.push_back(s);
    }
    _settings->endArray();

    // Save any upgrades we made from 2.0
    if (version == 2.0) {
        _settings->beginWriteArray("weby/sites");
        for(int i = 0; i < sites.count(); i++) {
            _settings->setArrayIndex(i);
            _settings->setValue("name", sites[i].name);
            _settings->setValue("query", sites[i].query);
            _settings->setValue("suggest", sites[i].suggest);
            _settings->setValue("default", sites[i].def);
        }
        _settings->endArray();
    }
    
    _settings->setValue("weby/version", 2.2);
}

void WebyPlugin::getID(uint* id)
{
    *id = HASH_WEBY;
}

void WebyPlugin::getName(QString* str)
{
    *str = "Weby";
}

void WebyPlugin::getLabels(QList<InputData>* inputData)
{
    if (inputData->count() > 1)
        return;

    // Apply a "website" label if we think it's a website
    const QString & text = inputData->last().getText();

    QString defaultMatchExpression = "^(http|https|ftp)://|^www.|.com|.co.[a-z]{2,}|.net|.org";
    QString matchExpression = _settings->value("weby/UrlRegExp", defaultMatchExpression).toString();
    QRegExp regex(matchExpression);
    if (!regex.isValid())
    {
        qDebug() << QString("Settings match expression \"%1\" is invalid. Using default.").arg(matchExpression);
        regex = QRegExp(defaultMatchExpression);
    }
    if (regex.indexIn(text) != -1)
    {
        inputData->last().setLabel(HASH_WEBSITE);
    }
}



void WebyPlugin::getResults(QList<InputData>* inputData, QList<CatItem>* results)
{
    if (inputData->last().hasLabel(HASH_WEBSITE))
    {
        const QString & text = inputData->last().getText();
        // This is a website, create an entry for it
        if (!text.trimmed().isEmpty())
        {
            results->push_front(CatItem(text + ".weby", text, HASH_WEBY, getIcon()));
        }
    }

    if (inputData->count() > 1 && (unsigned int)inputData->first().getTopResult().id == HASH_WEBY)
    {
        const QString & text = inputData->last().getText();
        // This is user search text, create an entry for it
        QString suggestUrl;
        CatItem* item = &inputData->first().getTopResult();

        foreach(WebySite site, sites)
        {
            if (item->shortName == site.name)
            {
                suggestUrl = site.suggest;
                break;
            }
        }

        if (!suggestUrl.isEmpty() && !text.trimmed().isEmpty())
        {
            // query the web
            Suggest suggest;
            suggest.run(suggestUrl, text);

            foreach(QString res, suggest.results)
            {
                results->push_back(CatItem(res + ".weby", res, HASH_WEBY, item->icon));
            }
        }
        else
        {
            results->push_front(CatItem(text + ".weby", text, HASH_WEBY, item->icon));
        }
    }

    // If we don't have any results, add default
    if (results->size() == 0 && inputData->count() <= 1)
    {
        const QString & text = inputData->last().getText();
        if (!text.trimmed().isEmpty())
        {
            QString name = getDefault().name;
            if (name != "")
            {
                inputData->first().setLabel(HASH_DEFAULTSEARCH);
                results->push_back(CatItem(text + ".weby", name, HASH_WEBY, getIcon()));
            }
        }
    }
}


#ifdef Q_OS_WIN

QString GetShellDirectory(int type)
{
    wchar_t buffer[_MAX_PATH];
    SHGetFolderPath(NULL, type, NULL, 0, buffer);
    return QString::fromWCharArray(buffer);
}


void WebyPlugin::indexIE(QString path, QList<CatItem>* items)
{
    QDir qd(path);
    QString dir = qd.absolutePath();
    QStringList dirs = qd.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);

    for (int i = 0; i < dirs.count(); ++i)
    {
        QString cur = dirs[i];
        if (cur.contains(".lnk"))
            continue;
        indexIE(dir + "/" + dirs[i],items);
    }	

    QStringList files = qd.entryList(QStringList("*.url"), QDir::Files, QDir::Unsorted);
    for (int i = 0; i < files.count(); ++i)
    {
        items->push_back(CatItem(dir + "/" + files[i], files[i].mid(0,files[i].size()-4)));
    }
}

#endif


QString WebyPlugin::getFirefoxPath()
{
    QString path;
    QString osPath;

#ifdef Q_OS_WIN
    osPath = GetShellDirectory(CSIDL_APPDATA) + "/Mozilla/Firefox/";
#endif

#if (defined Q_OS_BSD4 || defined Q_OS_LINUX)
	osPath = QDir::homePath() + "/.mozilla/firefox/";
#endif

#ifdef Q_OS_MAC
    osPath = QDir::homePath() + "/Library/Application Support/Firefox/";
#endif
    QString iniPath = osPath + "profiles.ini";

    QFile file(iniPath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return "";
    bool isRel = false;
    while (!file.atEnd())
    {
        QString line = file.readLine();
        if (line.contains("IsRelative"))
        {
            QStringList spl = line.split("=");
            isRel = spl[1].toInt() != 0;
        }
        if (line.contains("Path"))
        {
            QStringList spl = line.split("=");
            if (isRel)
                path = osPath;
            path += spl[1].mid(0,spl[1].count()-1) + "/bookmarks.html";
            break;
        }
    }

    return path;
}


QString WebyPlugin::getIcon()
{
    return libPath + "/icons/weby.png";
}


void WebyPlugin::indexFirefox(QString path, QList<CatItem>* items)
{
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return;

    marks.clear();

    QRegExp regex_url("<a href=\"([^\"]*)\"", Qt::CaseInsensitive);
    QRegExp regex_urlname("\">([^<]*)</A>", Qt::CaseInsensitive);
    QRegExp regex_shortcut("SHORTCUTURL=\"([^\"]*)\"");
    QRegExp regex_postdata("POST_DATA", Qt::CaseInsensitive);

    while (!file.atEnd())
    {
        QString line = QString::fromUtf8(file.readLine());
        if (regex_url.indexIn(line) != -1)
        {
            Bookmark mark;
            mark.url = regex_url.cap(1);

            if (regex_urlname.indexIn(line) != -1)
            {
                mark.name = regex_urlname.cap(1);

                if (regex_postdata.indexIn(line) != -1)
                    continue;
                if (regex_shortcut.indexIn(line) != -1)
                {
                    mark.shortcut = regex_shortcut.cap(1);
                    marks.push_back(mark);
                    items->push_back(CatItem(mark.url + ".shortcut", mark.shortcut, HASH_WEBY, getIcon()));
                }
                else
                {
                    items->push_back(CatItem(mark.url, mark.name, 0, getIcon()));
                }
            }
        }
    }
}


WebySite WebyPlugin::getDefault()
{
    foreach(WebySite site, sites)
    {
        if (site.def)
        {
            return site;
        }
    }
    return WebySite();
}


void WebyPlugin::getCatalog(QList<CatItem>* items)
{
    foreach(WebySite site, sites)
    {
        QString iconName = iconCache->getIconPath(site.query);
        items->push_back(CatItem(site.name + ".weby", site.name, HASH_WEBY,
            iconName.length() > 0 ? iconName : getIcon()));
    }

#ifdef Q_OS_WIN
    if (_settings->value("weby/ie", true).toBool())
    {
        QString path = GetShellDirectory(CSIDL_FAVORITES);
        indexIE(path, items);
    }
#endif
    if (_settings->value("weby/firefox", true).toBool())
    {
        QString path = getFirefoxPath();
        indexFirefox(path, items);
    }
}


void WebyPlugin::launchItem(QList<InputData>* inputData, CatItem* item)
{
    QString file = "";
    QStringList args;
    
    int i = inputData->count() == 1 && inputData->first().hasLabel(HASH_DEFAULTSEARCH) ? 0 : 1;
    for (; i < inputData->count(); i++)
    {
        QString txt = inputData->at(i).getText();
        args.push_back(QUrl::toPercentEncoding(txt));
    }

    // Is it a Firefox shortcut?
    if (item->fullPath.contains(".shortcut"))
    {
        file = item->fullPath.mid(0, item->fullPath.count()-9);
        file.replace("%s",args[0]);
    }
    else
    {
        // It's a user-specific site
        bool found = false;
        foreach(WebySite site, sites)
        {
            if (item->shortName == site.name)
            {
                found = true;
                file = site.query;
                if (args.count() == 0)
                {
                    // if no addition parameters have been entered and the weby URL has placeholders to take parameters,
                    // strip the URL down to its root and launch that
                    if (file.contains("%1"))
                    {
                        QRegExp regex("^(([a-z]*://)?([^/]*))");
                        if (regex.indexIn(file) != -1)
                        {
                            file = regex.cap(0);
                        }
                    }
                }
                else
                {
                    // Build the new string
                    QString out;
                    int curArg = 0;
                    for(int i = 0; i < file.size()-1; i++) {
                        QChar curchar = file[i];
                        QChar nextchar = file[i+1];
                        if (curchar == '%' && nextchar >= '0' && nextchar <= '9') {							
                            i += 1;
                            if (curArg < args.size()) {
                                out += args[curArg++];
                            }
                        } else {
                            out += curchar;
                        }
                    }
                    file = out;
                }
                break;
            }
        }

        if (!found)
        {
            file = item->shortName;
            // Make sure we have a protocol
            if (!file.startsWith("http://") && !file.startsWith("https://") && !file.startsWith("ftp://"))
            {
                file = "http://" + file;
            }
        }
    }

    QUrl url(file);
    runProgram(url.toString(), "", false);
}


void WebyPlugin::doDialog(QWidget* parent, QWidget** newDlg)
{
    if (gui != NULL)
        return;
    gui.reset(new Gui(parent, _settings));
    *newDlg = gui.data();
}


void WebyPlugin::endDialog(bool accept)
{
    if (accept)
    {
        gui->writeOptions();
        init();
    }
    gui.reset();
}


void WebyPlugin::setPath(QString * path)
{
    libPath = *path;
}


int WebyPlugin::msg(int msgId, void* wParam, void* lParam)
{
    bool handled = false;
    switch (msgId)
    {		
    case MSG_INIT:
        init();
        handled = true;
        break;
    case MSG_GET_LABELS:
        getLabels((QList<InputData>*) wParam);
        handled = true;
        break;
    case MSG_GET_ID:
        getID((uint*) wParam);
        handled = true;
        break;
    case MSG_GET_NAME:
        getName((QString*) wParam);
        handled = true;
        break;
    case MSG_GET_RESULTS:
        getResults((QList<InputData>*) wParam, (QList<CatItem>*) lParam);
        handled = true;
        break;
    case MSG_GET_CATALOG:
        getCatalog((QList<CatItem>*) wParam);
        handled = true;
        break;
    case MSG_LAUNCH_ITEM:
        launchItem((QList<InputData>*) wParam, (CatItem*) lParam);
        handled = true;
        break;
    case MSG_HAS_DIALOG:
        handled = true;
        break;
    case MSG_DO_DIALOG:
        doDialog((QWidget*) wParam, (QWidget**) lParam);
        break;
    case MSG_END_DIALOG:
        endDialog(wParam != 0);
        break;
    case MSG_PATH:
        setPath((QString *) wParam);
    default:
        break;
    }

    return handled;
}
