
/* BEGIN_COMMON_COPYRIGHT_HEADER
 *
 * TOra - An Oracle Toolkit for DBA's and developers
 *
 * Shared/mixed copyright is held throughout files in this product
 *
 * Portions Copyright (C) 2000-2001 Underscore AB
 * Portions Copyright (C) 2003-2005 Quest Software, Inc.
 * Portions Copyright (C) 2004-2013 Numerous Other Contributors
 *
 * 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;  only version 2 of
 * the License is valid for this program.
 *
 * 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 as the file COPYING.txt; if not, please see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 *      As a special exception, you have permission to link this program
 *      with the Oracle Client libraries and distribute executables, as long
 *      as you follow the requirements of the GNU GPL in regard to all of the
 *      software in the executable aside from Oracle client libraries.
 *
 * All trademarks belong to their respective owners.
 *
 * END_COMMON_COPYRIGHT_HEADER */

#include "core/tosql.h"
#include "core/utils.h"
#include "core/toconnection.h"

#include <QApplication>
#include <QtCore/QString>

// A little magic to get lrefresh to work and get a check on qApp

#undef QT_TRANSLATE_NOOP
#define QT_TRANSLATE_NOOP(x,y) QTRANS(x,y)

toSQL::sqlMap *toSQL::Definitions;
const char * const toSQL::TOSQL_USERLIST = "Global:UserList";
const char * const toSQL::TOSQL_CREATEPLAN = "Global:CreatePlan";

QString toSQL::defaultVersion("0000");

toSQL::version::version(char const *provider, char const *ver, char const *sql, bool modified)
    : Provider(provider), Version(ver), SQL(sql), Modified(modified)
{
	if(Version.isEmpty())
		Version = "0000";
	if(Version.length() != 4)
		fprintf(stderr, "ERROR:Tried add invalid version(%s) to unknown SQL\n", ver);
}

toSQL::toSQL(char const *name,
             char const *sql,
             char const *description,
             char const *ver,
             char const *provider)
    : Name(name)
{
    updateSQL(name, sql, description, ver, provider, false);
}

toSQL::toSQL(const QString &name)
    : Name(name)
{}

void toSQL::allocCheck(void)
{
    if (!Definitions)
        Definitions = new sqlMap;
}

bool toSQL::updateSQL(char const *_name,
                      char const *_sql,
                      char const *_description,
                      char const *_ver,
                      char const *_provider,
                      bool modified)
{
	QString description(_description);
	version def(_provider, _ver, _sql, modified);

    allocCheck();
    sqlMap::iterator i = Definitions->find(_name);
    if (i == Definitions->end())
    {
        if (description.isEmpty())
        {
            fprintf(stderr, "ERROR:Tried add new version to unknown SQL (%s)\n", _name);
            return false;
        }
        definition newDef;
        newDef.Modified = modified;
        newDef.Description = _description;
        if (!def.SQL.isNull())
        {
            std::list<version> &cl = newDef.Versions;
            cl.insert(cl.end(), def);
        }
        (*Definitions)[_name] = newDef;
        return true;
    }

    if (!description.isEmpty())
    {
    	if ((*i).second.Description != description)
    	{
    		(*i).second.Description = description;
    		(*i).second.Modified = modified;
    	}
    	if (!modified)
    		fprintf(stderr, "ERROR:Overwrite description of nonmodified (%s)\n", _name);
    }

    std::list<version> &cl = (*i).second.Versions;
    for (std::list<version>::iterator j = cl.begin(); j != cl.end(); j++)
    {
    	if (j->Provider == def.Provider && j->Version == def.Version)
    	{
    		if (!def.SQL.isNull())
    		{
    			if (def.SQL != j->SQL)
    				def.Modified = modified;
    			(*j) = def;
    		}
    		return false;
    	}
    	else if (j->Provider > def.Provider || (j->Provider == def.Provider && j->Version > def.Version))
    	{
    		if (!def.SQL.isNull())
    			cl.insert(j, def);
    		return true;
    	}
    }
    cl.insert(cl.end(), def);
    return true;
}

bool toSQL::deleteSQL(QString const& name,
                      QString const& ver,
                      QString const& provider)
{
    allocCheck();
    sqlMap::iterator i = Definitions->find(name);
    if (i == Definitions->end())
    {
        return false;
    }
    else
    {
        std::list<version> &cl = (*i).second.Versions;
        for (std::list<version>::iterator j = cl.begin(); j != cl.end(); j++)
        {
            if (j->Version == ver && j->Provider == provider)
            {
                cl.erase(j);
                if ( cl.empty() )
                    Definitions->erase(i);
                return true;
            }
            else if (j->Provider > provider ||
                     (j->Provider == provider && ! j->Version.isNull()))
            {
                return false;
            }
        }
        return false;
    }
}

toSQL toSQL::sql(const QString &name)
{
    allocCheck();
    sqlMap::iterator i = Definitions->find(name);
    if (i != Definitions->end())
        return name;
    throw qApp->translate("toSQL", "01: Tried to get unknown SQL (%1)").arg(QString(name));
}

QString toSQL::string(const QString &name, const toConnection &conn)
{
    allocCheck();
    QString SessionVersion = conn.version();
    QString SessionProvider = conn.provider();

    bool quit = false;

    sqlMap::iterator i = Definitions->find(name);
    if (i == Definitions->end())
        goto fail;

    // Loop over our connections' provider queries, if nothing is found also loop over "ANY" providers' queries
    do
    {
    	if (SessionProvider == "Any")
    		quit = true;
    	QString retval;
    	QString SqlVersion = defaultVersion;
    	std::list<version> &cl = (*i).second.Versions;
    	for (std::list<version>::iterator j = cl.begin(); j != cl.end(); j++)
    	{
    		if (j->Provider != SessionProvider)
    			continue;

    		QString const& QueryVersion = j->Version;
    		QString const& QueryProvider = j->Provider;
    		if (SqlVersion <= QueryVersion && QueryVersion <= SessionVersion)
    		{
    			retval = j->SQL;
    			SqlVersion = QueryVersion;
    		}
    	}
    	if (!retval.isEmpty())
    		return retval;

    	SessionProvider = "Any";
    }
    while (!quit);

fail:
    throw qApp->translate("toSQL", "02: Tried to get unknown SQL (%1)").arg(QString(name));
}

bool toSQL::saveSQL(const QString &filename, bool all)
{
    allocCheck();
    QString data;

    QRegExp backslash(QString::fromLatin1("\\"));
    QRegExp newline(QString::fromLatin1("\n"));
    for (sqlMap::iterator i = Definitions->begin(); i != Definitions->end(); i++)
    {
        QString str;
        definition &def = (*i).second;
        QString name = (*i).first;
        if (def.Modified || all)
        {
            QString line = name;
            line += "=";
            QString t = def.Description;
            t.replace(backslash, QString::fromLatin1("\\\\"));
            t.replace(newline, QString::fromLatin1("\\n"));
            line += t;
            str = line;
            str += "\n";
        }
        for (std::list<version>::iterator j = def.Versions.begin(); j != def.Versions.end(); j++)
        {
            version &ver = (*j);
            if (ver.Modified || all)
            {
                QString line = name;
                line += "[";
                line += ver.Version;
                line += "][";
                line += ver.Provider;
                line += "]=";
                QString t(ver.SQL);
                t.replace(backslash, QString::fromLatin1("\\\\"));
                t.replace(newline, QString::fromLatin1("\\n"));
                line += t;
                str += line;
                str += "\n";
            }
        }
        data += str;
    }

    // TODO: data shouldn't be a QString
    //       if we use QByteArray, there would be no need to re-encode

    // save as UTF8 encoded file
    return Utils::toWriteFile(filename, data.toUtf8());
}

void toSQL::loadSQL(const QString &filename)
{
    allocCheck();
    // read UTF8 encoded file as byte array
    QByteArray data = Utils::toReadFileB(filename);

    int size = data.length();
    int pos = 0;
    int bol = 0;
    int endtag = -1;
    int provtag = -1;
    int vertag = -1;
    int wpos = 0;
    while (pos < size)
    {
        switch (data[pos])
        {
            case '\n':
                if (endtag == -1)
                    throw QT_TRANSLATE_NOOP("toSQL", "Malformed tag in config file. Missing = on row.");
                data[wpos] = 0;
                {
                    char const* nam = ((const char *)data) + bol;
					char const* val = ((const char *)data) + endtag + 1;
					char const* ver;
					char const* prov(NULL);
                    if (vertag >= 0)
                    {
                        ver = ((const char *)data) + vertag + 1;
                        if (provtag >= 0)
                            prov = ((const char *)data) + provtag + 1;
                        updateSQL(nam, val, "", ver, prov, true);
                    }
                    else
                        updateSQL(nam, "", val, "", "", true);
                }
                bol = pos + 1;
                provtag = vertag = endtag = -1;
                wpos = pos;
                break;
            case '=':
                if (endtag == -1)
                {
                    endtag = pos;
                    data[wpos] = 0;
                    wpos = pos;
                }
                else
                    data[wpos] = data[pos];
                break;
            case '[':
                if (endtag == -1)
                {
                    if (vertag >= 0)
                    {
                        if (provtag >= 0)
                            throw QT_TRANSLATE_NOOP("toSQL", "Malformed line in SQL dictionary file. Two '[' before '='");
                        provtag = pos;
                    }
                    else
                        vertag = pos;
                    data[wpos] = 0;
                    wpos = pos;
                }
                else
                    data[wpos] = data[pos];
                break;
            case ']':
                if (endtag == -1)
                {
                    data[wpos] = 0;
                    wpos = pos;
                }
                else
                    data[wpos] = data[pos];
                break;
            case '\\':
                pos++;
                switch (data[pos])
                {
                    case 'n':
                        data[wpos] = '\n';
                        break;
                    case '\\':
                        if (endtag >= 0)
                            data[wpos] = '\\';
                        else
                            data[wpos] = ':';
                        break;
                    default:
                        throw QT_TRANSLATE_NOOP("toSQL", "Unknown escape character in string (Only \\\\ and \\n recognised)");
                }
                break;
            default:
                data[wpos] = data[pos];
                break;
        }
        wpos++;
        pos++;
    }
}

QList<QString> toSQL::range(const QString &startWith)
{
    QList<QString> ret;
    for (sqlMap::iterator i = Definitions->begin(); i != Definitions->end(); i++)
    {
        if ((*i).first > startWith || startWith.isNull())
        {
            if ((*i).first.mid(0, startWith.length()) == startWith || startWith.isNull())
                ret.append((*i).first);
            else
                return ret;
        }
    }
    return ret;
}

QString toSQL::description(const QString &name)
{
    allocCheck();
    sqlMap::iterator i = Definitions->find(name);
    if (i != Definitions->end())
        return (*i).second.Description;
    throw qApp->translate("toSQL", "03: Tried to get unknown SQL (%1)").arg(QString(name));
}
