/***************************************************************************
 *   Copyright (C) 2007 by Ian Reinhart Geiser                             *
 *   geiseri@yahoo.com                                                     *
 *                                                                         *
 *   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.             *
 ***************************************************************************/

/**
	@author Ian Reinhart Geiser <geiseri@yahoo.com>
*/

#include "jsonquery.h"
#include "jsonmapper.h"

#include <QRegExp>
#include <QFile>

#include <stdlib.h>

#include <qdebug.h>

int JSONQuery::countListItems( const QString &path, const QVariant &map )
{
	return JSONQuery::resolveNodeFromPath( path, map ).toList().size();
}

QStringList JSONQuery::queryKeys( const QString &path, const QVariant &map )
{
	QVariant node = JSONQuery::resolveNodeFromPath( path, map );
	if ( node.type() == QVariant::Map )
	    return node.toMap().keys();
	else if ( node.type() == QVariant::List )
	{
	    QStringList keys;
	    for( int idx = 0; idx < node.toList().size(); ++idx )
		keys << QString::number(idx);
	    return keys;
	}
	else
	    return QStringList();
}

QString JSONQuery::queryValue( const QString &path, const QVariant &map )
{
	return JSONQuery::resolveNodeFromPath( path, map ).toString();
}

int JSONQuery::countListItems( const QString &path, const QString &jsonMarkup )
{
	return countListItems( path, JavascriptUtils::fromJSON( jsonMarkup ) );
}

QStringList JSONQuery::queryKeys( const QString &path, const QString &jsonMarkup )
{
	return queryKeys( path, JavascriptUtils::fromJSON( jsonMarkup ) );
}

QString JSONQuery::queryValue( const QString &path, const QString &jsonMarkup, const QMap<QString,QString> &properties  )
{
	QVariant map = JavascriptUtils::fromJSON( jsonMarkup );
	return expandMacros( queryValue( path, map ), map, properties);
}

QString executeShellCommand( const QString &command )
{
	QString result;
	FILE *fs = popen(QFile::encodeName(command).data(), "r");
	if (fs)
	{
		QTextStream ts(fs, QIODevice::ReadOnly);
		result = ts.readAll().trimmed();
		pclose(fs);
	}
	return result;
}

QString JSONQuery::expandMacros( const QString &source, const QVariant &map, const QMap<QString,QString> &properties )
{
	QString result = source;
	QMap<QString,QString> internalPropertyMap = properties;
	QMap<QString,QString> commandOutputMap;

	QRegExp macroRegExp("%\\{([^}]+)\\}");
	int pos = 0;
	while ((pos = macroRegExp.indexIn(source, pos)) != -1)
	{
	    pos += macroRegExp.matchedLength();
	    QString queryPath = macroRegExp.capturedTexts().value(1);
	    if( queryValue( queryPath, map ).isEmpty() )
	    {
		char *env = getenv( queryPath.toUtf8().constData() );
		if( env )
		    internalPropertyMap[queryPath] = env;
	    }
	    else
	      internalPropertyMap[queryPath] = queryValue( queryPath, map );
	}

	QRegExp shellCommandRegExp("%\\(([^)]+)\\)");
	pos = 0;
	while ((pos = shellCommandRegExp.indexIn(source, pos)) != -1)
	{
	    pos += shellCommandRegExp.matchedLength();
	    QString command = shellCommandRegExp.capturedTexts().value(1);
	    QString commandOutput = executeShellCommand( command );
	    if( !commandOutput.isEmpty() )
		commandOutputMap[command] = commandOutput;
	}

	foreach( QString key, internalPropertyMap.keys() )
		result.replace( QString("%{%1}").arg(key), internalPropertyMap[key] );

	foreach( QString key, commandOutputMap.keys() )
		result.replace( QString("%(%1)").arg(key), commandOutputMap[key] );

	return result;
}

QVariant JSONQuery::resolveNodeFromPath( const QString &path, const QVariant &map )
{
	QString item = path.left( path.indexOf('.') );
	QString remainder = path.right( qMax(0, path.size() - item.size() - 1) );

	int offset = -1;
	QString key;

	if( item.contains('[') )
	{
	  key = item.left( item.indexOf('[') );
	  offset = item.right( item.size() - key.size() - 1).remove(']').toInt();
	}
	else
	{
	  key = item;
	}

	QVariant currentVariant;

	if( map.type() == QVariant::Map )
	{
		currentVariant = map.toMap()[key];
	}
	else if( map.type() == QVariant::List )
	{
		currentVariant = map.toList().value(offset);
	}

	if ( currentVariant.type() == QVariant::List && offset != -1)
	{
	    currentVariant = currentVariant.toList().value(offset);
	}

//	qDebug() << key << offset << path << remainder << currentVariant;

	if( remainder.isEmpty() )
	  return currentVariant;
	else if( currentVariant.isValid() )
	  return resolveNodeFromPath( remainder, currentVariant );
	else
	  return QVariant();
}

bool JSONQuery::keyExists( const QString &path, const QVariant &map )
{
    return JSONQuery::resolveNodeFromPath( path, map ).isValid();
}

bool JSONQuery::keyExists( const QString &path, const QString &jsonMarkup  )
{
    return keyExists( path, JavascriptUtils::fromJSON( jsonMarkup ) );
}

void mergeMaps( QVariantMap *map1, const QVariantMap &map2 )
{
	foreach( QString key, map2.keys() )
	{
		if( map2[key].type() == QVariant::Map )
		{
			if ( !map1->contains( key ) )
				(*map1)[key] = map2[key];
			else
			{
			    QVariantMap newSubMap = (*map1)[key].toMap();
			    mergeMaps( &newSubMap, map2[key].toMap() );
			    (*map1)[key] = newSubMap;
			}
		}
		else if( map2[key].type() == QVariant::List )
		{
			if ( map1->contains( key ) )
			{
			    QVariantList mergedList = (*map1)[key].toList();
			    QVariantList newList = map2[key].toList();
			    // Merge list here...
			    foreach( QVariant item, newList )
			    {
				if ( !mergedList.contains( item ) )
				    mergedList << item;
			    }
			    (*map1)[key] = mergedList;
			}
			else
				(*map1)[key] = map2[key];
		}
		else
			(*map1)[key] = map2[key];
	}
}

QVariant mergeMaps( const QStringList &markups )
{
	QVariantMap maps;
	foreach( QString jsonMarkup, markups )
	{
		QVariantMap map = JavascriptUtils::fromJSON( jsonMarkup ).toMap();
		mergeMaps( &maps, map );
	}
	return maps;
}

int JSONQuery::countListItems( const QString &path, const QStringList &jsonMarkups )
{
	QVariant map = mergeMaps( jsonMarkups );
	return countListItems( path, map );
}

QStringList JSONQuery::queryKeys( const QString &path, const QStringList &jsonMarkups )
{
	QVariant map = mergeMaps( jsonMarkups );
	return queryKeys( path, map );
}

QString JSONQuery::queryValue( const QString &path, const QStringList &jsonMarkups, const QMap<QString,QString> &properties )
{
	QVariant map = mergeMaps( jsonMarkups );
	return expandMacros( queryValue( path, map ), map, properties);
}

bool JSONQuery::keyExists( const QString &path, const QStringList &jsonMarkups  )
{
	QVariant map = mergeMaps( jsonMarkups );
	return keyExists( path, map );
}

