/*
Copyright (C) 2010, 2014 Srivats P.

This file is part of "Ostinato"

This 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 3 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, see <http://www.gnu.org/licenses/>
*/

#include "userscript.h"

//
// -------------------- UserScriptProtocol --------------------
//

UserScriptProtocol::UserScriptProtocol(StreamBase *stream, AbstractProtocol *parent)
    : AbstractProtocol(stream, parent),
        userProtocol_(this)
{
    isScriptValid_ = false;
    errorLineNumber_ = 0;

    userProtocolScriptValue_ = engine_.newQObject(&userProtocol_);
    engine_.globalObject().setProperty("protocol", userProtocolScriptValue_);

    QScriptValue meta = engine_.newQMetaObject(userProtocol_.metaObject());
    engine_.globalObject().setProperty("Protocol", meta);
}

UserScriptProtocol::~UserScriptProtocol()
{
}

AbstractProtocol* UserScriptProtocol::createInstance(StreamBase *stream,
    AbstractProtocol *parent)
{
    return new UserScriptProtocol(stream, parent);
}

quint32 UserScriptProtocol::protocolNumber() const
{
    return OstProto::Protocol::kUserScriptFieldNumber;
}

void UserScriptProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const
{
    protocol.MutableExtension(OstProto::userScript)->CopyFrom(data);
    protocol.mutable_protocol_id()->set_id(protocolNumber());
}

void UserScriptProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
{
    if (protocol.protocol_id().id() == protocolNumber() &&
            protocol.HasExtension(OstProto::userScript))
        data.MergeFrom(protocol.GetExtension(OstProto::userScript));

    evaluateUserScript();
}

QString UserScriptProtocol::name() const
{
    return QString("%1:{UserScript} [EXPERIMENTAL]").arg(userProtocol_.name());
}

QString UserScriptProtocol::shortName() const
{
    return QString("%1:{Script} [EXPERIMENTAL]").arg(userProtocol_.name());
}

quint32 UserScriptProtocol::protocolId(ProtocolIdType type) const
{
    QScriptValue userFunction;
    QScriptValue userValue;

    if (!isScriptValid_)
        goto _do_default;

    userFunction = userProtocolScriptValue_.property("protocolId");

    if (!userFunction.isValid())
        goto _do_default;

    Q_ASSERT(userFunction.isFunction());

    userValue = userFunction.call(QScriptValue(),
        QScriptValueList() << QScriptValue(&engine_, type));

    Q_ASSERT(userValue.isValid());
    Q_ASSERT(userValue.isNumber());

    return userValue.toUInt32();

_do_default:
    return AbstractProtocol::protocolId(type);
}

int UserScriptProtocol::fieldCount() const
{
    return userScript_fieldCount;
}

QVariant UserScriptProtocol::fieldData(int index, FieldAttrib attrib,
        int streamIndex) const
{
    switch (index)
    {
    case userScript_program:

        switch(attrib)
        {
        case FieldName:            
            return QString("UserProtocol");

        case FieldValue:
        case FieldTextValue:
            return QString().fromStdString(data.program());

        case FieldFrameValue:
        {
            if (!isScriptValid_)
                return QByteArray();

            QScriptValue userFunction = userProtocolScriptValue_.property(
                    "protocolFrameValue");

            Q_ASSERT(userFunction.isValid());
            Q_ASSERT(userFunction.isFunction());

            QScriptValue userValue = userFunction.call(QScriptValue(),
                QScriptValueList() << QScriptValue(&engine_, streamIndex));

            Q_ASSERT(userValue.isValid());
            Q_ASSERT(userValue.isArray());

            QByteArray fv;
            QList<int> pktBuf;
               
            qScriptValueToSequence(userValue, pktBuf); 

            fv.resize(pktBuf.size());
            for (int i = 0; i < pktBuf.size(); i++)
                fv[i] = pktBuf.at(i) & 0xFF;

            return fv;
        }
        default:
            break;
        }
        break;

    default:
        qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
            index);
        break;
    }

    return AbstractProtocol::fieldData(index, attrib, streamIndex);
}

bool UserScriptProtocol::setFieldData(int index, const QVariant &value, 
        FieldAttrib attrib)
{
    bool isOk = false;

    if (attrib != FieldValue)
        goto _exit;

    switch (index)
    {
        case userScript_program:
        {
            data.set_program(value.toString().toStdString());
            evaluateUserScript();
            break;
        }
        default:
            qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
                index);
            break;
    }

_exit:
    return isOk;
}

int UserScriptProtocol::protocolFrameSize(int streamIndex) const
{
    if (!isScriptValid_)
        return 0;

    QScriptValue userFunction = userProtocolScriptValue_.property(
            "protocolFrameSize");

    Q_ASSERT(userFunction.isValid());
    Q_ASSERT(userFunction.isFunction());

    QScriptValue userValue = userFunction.call(QScriptValue(), 
            QScriptValueList() << QScriptValue(&engine_, streamIndex));

    Q_ASSERT(userValue.isNumber());

    return userValue.toInt32();
}

bool UserScriptProtocol::isProtocolFrameSizeVariable() const
{
    return userProtocol_.isProtocolFrameSizeVariable();
}

int UserScriptProtocol::protocolFrameVariableCount() const
{
    return AbstractProtocol::lcm(
            AbstractProtocol::protocolFrameVariableCount(),
            userProtocol_.protocolFrameVariableCount());
}

quint32 UserScriptProtocol::protocolFrameCksum(int streamIndex,
        CksumType cksumType, CksumFlags cksumFlags) const
{
    QScriptValue userFunction;
    QScriptValue userValue;

    if (!isScriptValid_)
        goto _do_default;

    userFunction = userProtocolScriptValue_.property("protocolFrameCksum");

    qDebug("userscript protoFrameCksum(): isValid:%d/isFunc:%d",
        userFunction.isValid(), userFunction.isFunction());

    if (!userFunction.isValid())
        goto _do_default;

    Q_ASSERT(userFunction.isFunction());

    userValue = userFunction.call(
                    QScriptValue(),
                    QScriptValueList()
                        << QScriptValue(&engine_, streamIndex)
                        << QScriptValue(&engine_, cksumType)
                        << QScriptValue(&engine_, cksumFlags));

    Q_ASSERT(userValue.isValid());
    Q_ASSERT(userValue.isNumber());

    return userValue.toUInt32();

_do_default:
    return AbstractProtocol::protocolFrameCksum(
                                streamIndex, cksumType, cksumFlags);
}

void UserScriptProtocol::evaluateUserScript() const
{
    QScriptValue    userFunction;
    QScriptValue    userValue;
    QString         property;

    isScriptValid_ = false;
    errorLineNumber_ = userScriptLineCount();

    // Reset all properties including the dynamic ones
    userProtocol_.reset();
    userProtocolScriptValue_.setProperty("protocolFrameValue", QScriptValue());
    userProtocolScriptValue_.setProperty("protocolFrameSize", QScriptValue());
    userProtocolScriptValue_.setProperty("protocolFrameCksum", QScriptValue());
    userProtocolScriptValue_.setProperty("protocolId", QScriptValue());

    engine_.evaluate(fieldData(userScript_program, FieldValue).toString());
    if (engine_.hasUncaughtException())
        goto _error_exception;

    // Validate protocolFrameValue()
    property = QString("protocolFrameValue");
    userFunction = userProtocolScriptValue_.property(property);

    qDebug("userscript property %s: isValid:%d/isFunc:%d", 
            qPrintable(property),
            userFunction.isValid(), userFunction.isFunction());

    if (!userFunction.isValid())
    {
        errorText_ = property + QString(" not set");
        goto _error_exit;
    }

    if (!userFunction.isFunction())
    {
        errorText_ = property + QString(" is not a function");
        goto _error_exit;
    }

    userValue = userFunction.call();
    if (engine_.hasUncaughtException())
        goto _error_exception;

    qDebug("userscript property %s return value: isValid:%d/isArray:%d",
            qPrintable(property),
            userValue.isValid(), userValue.isArray());

    if (!userValue.isArray())
    {
        errorText_ = property + QString(" does not return an array");
        goto _error_exit;
    }

    // Validate protocolFrameSize()
    property = QString("protocolFrameSize");
    userFunction = userProtocolScriptValue_.property(property);

    qDebug("userscript property %s: isValid:%d/isFunc:%d", 
            qPrintable(property),
            userFunction.isValid(), userFunction.isFunction());

    if (!userFunction.isValid())
    {
        errorText_ = property + QString(" not set");
        goto _error_exit;
    }

    if (!userFunction.isFunction())
    {
        errorText_ = property + QString(" is not a function");
        goto _error_exit;
    }

    userValue = userFunction.call();
    if (engine_.hasUncaughtException())
        goto _error_exception;

    qDebug("userscript property %s return value: isValid:%d/isNumber:%d",
            qPrintable(property),
            userValue.isValid(), userValue.isNumber());

    if (!userValue.isNumber())
    {
        errorText_ = property + QString(" does not return a number");
        goto _error_exit;
    }

    // Validate protocolFrameCksum() [optional]
    property = QString("protocolFrameCksum");
    userFunction = userProtocolScriptValue_.property(property);

    qDebug("userscript property %s: isValid:%d/isFunc:%d", 
            qPrintable(property),
            userFunction.isValid(), userFunction.isFunction());

    if (!userFunction.isValid())
        goto _skip_cksum;

    if (!userFunction.isFunction())
    {
        errorText_ = property + QString(" is not a function");
        goto _error_exit;
    }

    userValue = userFunction.call();
    if (engine_.hasUncaughtException())
        goto _error_exception;

    qDebug("userscript property %s return value: isValid:%d/isNumber:%d",
            qPrintable(property),
            userValue.isValid(), userValue.isNumber());

    if (!userValue.isNumber())
    {
        errorText_ = property + QString(" does not return a number");
        goto _error_exit;
    }


_skip_cksum:
    // Validate protocolId() [optional]
    property = QString("protocolId");
    userFunction = userProtocolScriptValue_.property(property);

    qDebug("userscript property %s: isValid:%d/isFunc:%d", 
            qPrintable(property),
            userFunction.isValid(), userFunction.isFunction());

    if (!userFunction.isValid())
        goto _skip_protocol_id;

    if (!userFunction.isFunction())
    {
        errorText_ = property + QString(" is not a function");
        goto _error_exit;
    }

    userValue = userFunction.call();
    if (engine_.hasUncaughtException())
        goto _error_exception;

    qDebug("userscript property %s return value: isValid:%d/isNumber:%d",
            qPrintable(property),
            userValue.isValid(), userValue.isNumber());

    if (!userValue.isNumber())
    {
        errorText_ = property + QString(" does not return a number");
        goto _error_exit;
    }


_skip_protocol_id:
    errorText_ = QString("");
    isScriptValid_ = true;
    return;

_error_exception:
    errorLineNumber_ = engine_.uncaughtExceptionLineNumber();
    errorText_ = engine_.uncaughtException().toString();

_error_exit:
    userProtocol_.reset();
    return;
}

bool UserScriptProtocol::isScriptValid() const
{
    return isScriptValid_;
}

int UserScriptProtocol::userScriptErrorLineNumber() const
{
    return errorLineNumber_;
}

QString UserScriptProtocol::userScriptErrorText() const
{
    return errorText_;
}

int UserScriptProtocol::userScriptLineCount() const
{
    return fieldData(userScript_program, FieldValue).toString().count(
            QChar('\n')) + 1;
}

//
// -------------------- UserProtocol --------------------
//

UserProtocol::UserProtocol(AbstractProtocol *parent)
    : parent_ (parent)
{
    reset();
}

void UserProtocol::reset()
{
    name_ = QString();
    protocolFrameSizeVariable_ = false;
    protocolFrameVariableCount_ = 1;
}

QString UserProtocol::name() const
{
    return name_;
}

void UserProtocol::setName(QString &name)
{
    name_ = name;
}

bool UserProtocol::isProtocolFrameSizeVariable() const
{
    return protocolFrameSizeVariable_;
}

void UserProtocol::setProtocolFrameSizeVariable(bool variable)
{
    protocolFrameSizeVariable_ = variable;
}

int UserProtocol::protocolFrameVariableCount() const
{
    return protocolFrameVariableCount_;
}

void UserProtocol::setProtocolFrameVariableCount(int count)
{
    protocolFrameVariableCount_ = count;
}

quint32 UserProtocol::payloadProtocolId(UserProtocol::ProtocolIdType type) const
{
    return parent_->payloadProtocolId(
            static_cast<AbstractProtocol::ProtocolIdType>(type));
}

int UserProtocol::protocolFrameOffset(int streamIndex) const
{
    return parent_->protocolFrameOffset(streamIndex);
}

int UserProtocol::protocolFramePayloadSize(int streamIndex) const
{
    return parent_->protocolFramePayloadSize(streamIndex);
}

bool UserProtocol::isProtocolFramePayloadValueVariable() const
{
    return parent_->isProtocolFramePayloadValueVariable();
}

bool UserProtocol::isProtocolFramePayloadSizeVariable() const
{
    return parent_->isProtocolFramePayloadSizeVariable();
}

int UserProtocol::protocolFramePayloadVariableCount() const
{
    return parent_->protocolFramePayloadVariableCount();
}

quint32 UserProtocol::protocolFrameHeaderCksum(int streamIndex,
    AbstractProtocol::CksumType cksumType) const
{
    return parent_->protocolFrameHeaderCksum(streamIndex, cksumType);
}

quint32 UserProtocol::protocolFramePayloadCksum(int streamIndex,
    AbstractProtocol::CksumType cksumType) const
{
    quint32 cksum;

    cksum = parent_->protocolFramePayloadCksum(streamIndex, cksumType);
    qDebug("UserProto:%s = %d", __FUNCTION__, cksum);
    return cksum;
}

/* vim: set shiftwidth=4 tabstop=8 softtabstop=4 expandtab: */
