/* This file is part of the KDE project
   Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
   Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
   Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
   Copyright (C) 2005 Sebastian Sauer <mail@dipe.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "kexiscriptdesignview.h"
#include "kexiscripteditor.h"

#include <kross/main/manager.h>
#include <kross/main/scriptcontainer.h>
#include <kross/main/scriptaction.h>
#include <kross/api/interpreter.h>

#include <qlayout.h>
#include <qsplitter.h>
#include <qtimer.h>
#include <qdatetime.h>
#include <qdom.h>
#include <qstylesheet.h>
#include <ktextbrowser.h>
#include <kdebug.h>

#include <kexidialogbase.h>
#include <kexidb/connection.h>

/// @internal
class KexiScriptDesignViewPrivate
{
    public:

        /**
         * The \a Kross::Api::ScriptAction instance which provides
         * us access to the scripting framework Kross.
         */
        Kross::Api::ScriptAction* scriptaction;

        /// The \a KexiScriptEditor to edit the scripting code.
        KexiScriptEditor* editor;

        /// The \a KoProperty::Set used in the propertyeditor.
        KoProperty::Set* properties;

        /// Boolean flag to avoid infinite recursion.
        bool updatesProperties;

        /// Used to display statusmessages.
        KTextBrowser* statusbrowser;
};

KexiScriptDesignView::KexiScriptDesignView(KexiMainWindow *mainWin, QWidget *parent, Kross::Api::ScriptAction* scriptaction)
    : KexiViewBase(mainWin, parent, "KexiScriptDesignView")
    , d( new KexiScriptDesignViewPrivate() )
{
    d->scriptaction = scriptaction;
    d->updatesProperties = false;

    QSplitter* splitter = new QSplitter(this);
    splitter->setOrientation(Vertical);
    QHBoxLayout* layout = new QHBoxLayout(this);
    layout->addWidget(splitter);

    d->editor = new KexiScriptEditor(mainWin, splitter, "ScriptEditor");
    splitter->setFocusProxy(d->editor);
    addChildView(d->editor);
    setViewWidget(d->editor);

    d->statusbrowser = new KTextBrowser(splitter, "ScriptStatusBrowser");
    d->statusbrowser->setReadOnly(true);
    d->statusbrowser->setTextFormat(QTextBrowser::RichText);
    //d->browser->setWordWrap(QTextEdit::WidgetWidth);
    d->statusbrowser->installEventFilter(this);
    splitter->setResizeMode(d->statusbrowser, QSplitter::KeepSize);

    plugSharedAction( "script_execute", this, SLOT(execute()) );
    if(KexiEditor::isAdvancedEditor()) // the configeditor is only in advanced mode avaiable.
        plugSharedAction( "script_config_editor", d->editor, SLOT(slotConfigureEditor()) );

    loadData();

    d->properties = new KoProperty::Set(this, "KexiScripting");
    connect(d->properties, SIGNAL( propertyChanged(KoProperty::Set&, KoProperty::Property&) ),
            this, SLOT( slotPropertyChanged(KoProperty::Set&, KoProperty::Property&) ));

    // To schedule the initialize fixes a crasher in Kate.
    QTimer::singleShot(50, this, SLOT( initialize() ));
}

KexiScriptDesignView::~KexiScriptDesignView()
{
    delete d->properties;
    delete d;
}

Kross::Api::ScriptAction* KexiScriptDesignView::scriptAction() const
{
    return d->scriptaction;
}

void KexiScriptDesignView::initialize()
{
    updateProperties();
    d->editor->initialize( d->scriptaction );
}

void KexiScriptDesignView::updateProperties()
{
    if(d->updatesProperties)
        return;
    d->updatesProperties = true;

    Kross::Api::Manager* manager = Kross::Api::Manager::scriptManager();

    QString interpretername = d->scriptaction->getInterpreterName();
    Kross::Api::InterpreterInfo* info = interpretername.isEmpty() ? 0 : manager->getInterpreterInfo(interpretername);

    {
        // if interpreter isn't defined or invalid, try to fallback.
        QStringList list;
        list << "python" << "ruby";
        QStringList::Iterator it( list.begin() );
        while( (! info) && (it != list.end()) ) {
            interpretername = (*it);
            info = manager->getInterpreterInfo(interpretername);
            if(info)
                d->scriptaction->setInterpreterName(interpretername);
            ++it;
        }
    }

    if(info) {
        d->properties->clear();

        QStringList interpreters = manager->getInterpreters();
        KoProperty::Property::ListData* proplist = new KoProperty::Property::ListData(interpreters, interpreters);
        KoProperty::Property* prop = new KoProperty::Property(
            "language", // name
            proplist, // ListData
            d->scriptaction->getInterpreterName(), // value
            i18n("Interpreter"), // caption
            i18n("The used scripting interpreter."), // description
            KoProperty::List // type
        );
        d->properties->addProperty(prop);

        Kross::Api::InterpreterInfo::Option::Map options = info->getOptions();
        Kross::Api::InterpreterInfo::Option::Map::Iterator it( options.begin() );
        for(; it != options.end(); ++it) {
            Kross::Api::InterpreterInfo::Option* option = it.data();
            KoProperty::Property* prop = new KoProperty::Property(
                    it.key().latin1(), // name
                    d->scriptaction->getOption(it.key(), option->value), // value
                    option->name, // caption
                    option->comment, // description
                    KoProperty::Auto // type
            );
            d->properties->addProperty(prop);
        }
    }

    //propertySetSwitched();
    propertySetReloaded(true);
    d->updatesProperties = false;
}

KoProperty::Set* KexiScriptDesignView::propertySet()
{
    return d->properties;
}

void KexiScriptDesignView::slotPropertyChanged(KoProperty::Set& /*set*/, KoProperty::Property& property)
{
    if(property.isNull()) 
        return;

    if(property.name() == "language") {
        QString language = property.value().toString();
        kdDebug() << QString("KexiScriptDesignView::slotPropertyChanged() language=%1").arg(language) << endl;
        d->scriptaction->setInterpreterName( language );
        // We assume Kross and the HighlightingInterface are using same
        // names for the support languages...
        d->editor->setHighlightMode( language );
        // Update the properties. We need to call it delayed cause
        // else we may crash whyever...
        QTimer::singleShot(50, this, SLOT( updateProperties() ));
    }
    else {
        bool ok = d->scriptaction->setOption( property.name(), property.value() );
        if(! ok) {
            kdWarning() << QString("KexiScriptDesignView::slotPropertyChanged() unknown property '%1'.").arg(property.name()) << endl;
            return;
        }
    }

    setDirty(true);
}

void KexiScriptDesignView::execute()
{
    d->statusbrowser->clear();
    QTime time;
    time.start();
    d->statusbrowser->append( i18n("Execution of the script \"%1\" started.").arg(d->scriptaction->name()) );

    d->scriptaction->activate();
    if( d->scriptaction->hadException() ) {
        QString errormessage = d->scriptaction->getException()->getError();
        d->statusbrowser->append(QString("<b>%2</b><br>").arg(QStyleSheet::escape(errormessage)) );

        QString tracedetails = d->scriptaction->getException()->getTrace();
        d->statusbrowser->append( QStyleSheet::escape(tracedetails) );

        long lineno = d->scriptaction->getException()->getLineNo();
        if(lineno >= 0)
            d->editor->setLineNo(lineno);
    }
    else {
        d->statusbrowser->append( i18n("Successfully executed. Time elapsed: %1ms").arg(time.elapsed()) );
    }
}

bool KexiScriptDesignView::loadData()
{
    QString data;
    if(! loadDataBlock(data)) {
        kexipluginsdbg << "KexiScriptDesignView::loadData(): no DataBlock" << endl;
        return false;
    }

    QString errMsg;
    int errLine;
    int errCol;

    QDomDocument domdoc;
    bool parsed = domdoc.setContent(data, false, &errMsg, &errLine, &errCol);

    if(! parsed) {
        kexipluginsdbg << "KexiScriptDesignView::loadData() XML parsing error line: " << errLine << " col: " << errCol << " message: " << errMsg << endl;
        return false;
    }

    QDomElement scriptelem = domdoc.namedItem("script").toElement();
    if(scriptelem.isNull()) {
        kexipluginsdbg << "KexiScriptDesignView::loadData(): script domelement is null" << endl;
        return false;
    }

    QString interpretername = scriptelem.attribute("language");
    Kross::Api::Manager* manager = Kross::Api::Manager::scriptManager();
    Kross::Api::InterpreterInfo* info = interpretername.isEmpty() ? 0 : manager->getInterpreterInfo(interpretername);
    if(info) {
        d->scriptaction->setInterpreterName(interpretername);

        Kross::Api::InterpreterInfo::Option::Map options = info->getOptions();
        Kross::Api::InterpreterInfo::Option::Map::Iterator it = options.begin();
        for(; it != options.end(); ++it) {
            QString value = scriptelem.attribute( it.data()->name );
            if(! value.isNull()) {
                QVariant v(value);
                if( v.cast( it.data()->value.type() ) ) // preserve the QVariant's type
                    d->scriptaction->setOption(it.data()->name, v);
            }
        }
    }

    d->scriptaction->setCode( scriptelem.text() );

    return true;
}

KexiDB::SchemaData* KexiScriptDesignView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
{
    KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel);
    kexipluginsdbg << "KexiScriptDesignView::storeNewData(): new id:" << s->id() << endl;

    if(!s || cancel) {
        delete s;
        return 0;
    }

    if(! storeData()) {
        kdWarning() << "KexiScriptDesignView::storeNewData Failed to store the data." << endl;
        //failure: remove object's schema data to avoid garbage
        KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
        conn->removeObject( s->id() );
        delete s;
        return 0;
    }

    return s;
}

tristate KexiScriptDesignView::storeData(bool /*dontAsk*/)
{
    kexipluginsdbg << "KexiScriptDesignView::storeData(): " << parentDialog()->partItem()->name() << " [" << parentDialog()->id() << "]" << endl;

    QDomDocument domdoc("script");
    QDomElement scriptelem = domdoc.createElement("script");
    domdoc.appendChild(scriptelem);

    QString language = d->scriptaction->getInterpreterName();
    scriptelem.setAttribute("language", language);

    Kross::Api::InterpreterInfo* info = Kross::Api::Manager::scriptManager()->getInterpreterInfo(language);
    if(info) {
        Kross::Api::InterpreterInfo::Option::Map defoptions = info->getOptions();
        QMap<QString, QVariant>& options = d->scriptaction->getOptions();
        for(QMap<QString, QVariant>::Iterator it = options.begin(); it != options.end(); ++it) {
            if( defoptions.contains(it.key()) ) { // only remember options which the InterpreterInfo knows about...
                scriptelem.setAttribute(it.key(), it.data().toString());
            }
        }
    }

    QDomText scriptcode = domdoc.createTextNode(d->scriptaction->getCode());
    scriptelem.appendChild(scriptcode);

    return storeDataBlock( domdoc.toString() );
}

#include "kexiscriptdesignview.moc"

