/*************************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright 2000, 2011 Oracle and/or its affiliates.
*
* OpenOffice.org - a multi-platform office productivity suite
*
* This file is part of OpenOffice.org.
*
* OpenOffice.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenOffice.org 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 Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenOffice.org.  If not, see
* <http://www.openoffice.org/license.html>
* for a copy of the LGPLv3 License.
*
************************************************************************/

#include "sal/config.h"

#include <exception>
#include <vector>

#include "com/sun/star/connection/XConnection.hpp"
#include "com/sun/star/lang/WrappedTargetRuntimeException.hpp"
#include "com/sun/star/uno/XCurrentContext.hpp"
#include "cppuhelper/exc_hlp.hxx"
#include "osl/mutex.hxx"
#include "rtl/memory.h"
#include "uno/dispatcher.hxx"

#include "binaryany.hxx"
#include "bridge.hxx"
#include "currentcontext.hxx"
#include "specialfunctionids.hxx"
#include "writer.hxx"

namespace binaryurp {

namespace {

namespace css = com::sun::star;

bool isProtocolPropertyMessage(rtl::OUString const & oid) {
    return oid.equalsAsciiL(
        RTL_CONSTASCII_STRINGPARAM("UrpProtocolProperties"));
}

}

Writer::Item::Item() {}

Writer::Item::Item(
    rtl::ByteSequence const & theTid, rtl::OUString const & theOid,
    css::uno::TypeDescription const & theType,
    css::uno::TypeDescription const & theMember,
    std::vector< BinaryAny > const & inArguments,
    css::uno::UnoInterfaceReference const & theCurrentContext):
    request(true), tid(theTid), oid(theOid), type(theType), member(theMember),
    arguments(inArguments), currentContext(theCurrentContext)
{}

Writer::Item::Item(
    rtl::ByteSequence const & theTid,
    css::uno::TypeDescription const & theMember, bool theSetter,
    bool theException, BinaryAny const & theReturnValue,
    std::vector< BinaryAny > const & outArguments,
    bool theSetCurrentContextMode):
    request(false), tid(theTid), member(theMember), setter(theSetter),
    arguments(outArguments), exception(theException),
    returnValue(theReturnValue), setCurrentContextMode(theSetCurrentContextMode)
{}

Writer::Writer(rtl::Reference< Bridge > const  & bridge):
    bridge_(bridge), marshal_(bridge, state_), stop_(false)
{
    OSL_ASSERT(bridge.is());
    acquire();
}

void Writer::sendDirectRequest(
    rtl::ByteSequence const & tid, rtl::OUString const & oid,
    css::uno::TypeDescription const & type,
    css::uno::TypeDescription const & member,
    std::vector< BinaryAny > const & inArguments)
{
    OSL_ASSERT(!unblocked_.check());
    sendRequest(
        tid, oid, type, member, inArguments, false,
        css::uno::UnoInterfaceReference());
}

void Writer::sendDirectReply(
    rtl::ByteSequence const & tid, css::uno::TypeDescription const & member,
    bool exception, BinaryAny const & returnValue,
    std::vector< BinaryAny > const & outArguments)
{
    OSL_ASSERT(!unblocked_.check());
    sendReply(tid, member, false, exception, returnValue,outArguments);
}

void Writer::queueRequest(
    rtl::ByteSequence const & tid, rtl::OUString const & oid,
    css::uno::TypeDescription const & type,
    css::uno::TypeDescription const & member,
    std::vector< BinaryAny > const & inArguments)
{
    css::uno::UnoInterfaceReference cc(current_context::get());
    osl::MutexGuard g(mutex_);
    queue_.push_back(Item(tid, oid, type, member, inArguments, cc));
    items_.set();
}

void Writer::queueReply(
    rtl::ByteSequence const & tid,
    com::sun::star::uno::TypeDescription const & member, bool setter,
    bool exception, BinaryAny const & returnValue,
    std::vector< BinaryAny > const & outArguments, bool setCurrentContextMode)
{
    osl::MutexGuard g(mutex_);
    queue_.push_back(
        Item(
            tid, member, setter, exception, returnValue, outArguments,
            setCurrentContextMode));
    items_.set();
}

void Writer::unblock() {
    // Assumes that osl::Condition::set works as a memory barrier, so that
    // changes made by preceeding sendDirectRequest/Reply calls are visible to
    // subsequent sendRequest/Reply calls:
    unblocked_.set();
}

void Writer::stop() {
    {
        osl::MutexGuard g(mutex_);
        stop_ = true;
    }
    unblocked_.set();
    items_.set();
}

Writer::~Writer() {}

void Writer::run() {
    setName("binaryurpWriter");
    try {
        unblocked_.wait();
        for (;;) {
            items_.wait();
            Item item;
            {
                osl::MutexGuard g(mutex_);
                if (stop_) {
                    return;
                }
                OSL_ASSERT(!queue_.empty());
                item = queue_.front();
                queue_.pop_front();
                if (queue_.empty()) {
                    items_.reset();
                }
            }
            if (item.request) {
                sendRequest(
                    item.tid, item.oid, item.type, item.member, item.arguments,
                    (!item.oid.equalsAsciiL(
                        RTL_CONSTASCII_STRINGPARAM("UrpProtocolProperties")) &&
                     !item.member.equals(
                         css::uno::TypeDescription(
                             rtl::OUString(
                                 RTL_CONSTASCII_USTRINGPARAM(
                                     "com.sun.star.uno.XInterface::"
                                     "release")))) &&
                     bridge_->isCurrentContextMode()),
                    item.currentContext);
            } else {
                sendReply(
                    item.tid, item.member, item.setter, item.exception,
                    item.returnValue, item.arguments);
                if (item.setCurrentContextMode) {
                    bridge_->setCurrentContextMode();
                }
            }
        }
    } catch (css::uno::Exception & e) {
        OSL_TRACE(
            OSL_LOG_PREFIX "caught UNO exception '%s'",
            rtl::OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr());
    } catch (std::exception & e) {
        OSL_TRACE(OSL_LOG_PREFIX "caught C++ exception '%s'", e.what());
    }
    bridge_->terminate();
}

void Writer::onTerminated() {
    release();
}

void Writer::sendRequest(
    rtl::ByteSequence const & tid, rtl::OUString const & oid,
    css::uno::TypeDescription const & type,
    css::uno::TypeDescription const & member,
    std::vector< BinaryAny > const & inArguments, bool currentContextMode,
    css::uno::UnoInterfaceReference const & currentContext)
{
    OSL_ASSERT(tid.getLength() != 0 && oid.getLength() != 0 && member.is());
    css::uno::TypeDescription t(type);
    sal_Int32 functionId = 0;
    bool forceSynchronous = false;
    member.makeComplete();
    switch (member.get()->eTypeClass) {
    case typelib_TypeClass_INTERFACE_ATTRIBUTE:
        {
            typelib_InterfaceAttributeTypeDescription * atd =
                reinterpret_cast< typelib_InterfaceAttributeTypeDescription * >(
                    member.get());
            OSL_ASSERT(atd->pInterface != 0);
            if (!t.is()) {
                t = css::uno::TypeDescription(&atd->pInterface->aBase);
            }
            t.makeComplete();
            functionId = atd->pInterface->pMapMemberIndexToFunctionIndex[
                atd->aBase.nPosition];
            if (!inArguments.empty()) { // setter
                ++functionId;
            }
            break;
        }
    case typelib_TypeClass_INTERFACE_METHOD:
        {
            typelib_InterfaceMethodTypeDescription * mtd =
                reinterpret_cast< typelib_InterfaceMethodTypeDescription * >(
                    member.get());
            OSL_ASSERT(mtd->pInterface != 0);
            if (!t.is()) {
                t = css::uno::TypeDescription(&mtd->pInterface->aBase);
            }
            t.makeComplete();
            functionId = mtd->pInterface->pMapMemberIndexToFunctionIndex[
                mtd->aBase.nPosition];
            forceSynchronous = mtd->bOneWay &&
                functionId != SPECIAL_FUNCTION_ID_RELEASE;
            break;
        }
    default:
        OSL_ASSERT(false); // this cannot happen
        break;
    }
    OSL_ASSERT(functionId >= 0);
    if (functionId > SAL_MAX_UINT16) {
        throw css::uno::RuntimeException(
            rtl::OUString(
                RTL_CONSTASCII_USTRINGPARAM("function ID too large for URP")),
            css::uno::Reference< css::uno::XInterface >());
    }
    std::vector< unsigned char > buf;
    bool newType = !(lastType_.is() && t.equals(lastType_));
    bool newOid = oid != lastOid_;
    bool newTid = tid != lastTid_;
    if (newType || newOid || newTid || forceSynchronous || functionId > 0x3FFF)
        // > 14 bit function ID
    {
        Marshal::write8(
            &buf,
            (0xC0 | (newType ? 0x20 : 0) | (newOid ? 0x10 : 0) |
             (newTid ? 0x08 : 0) | (functionId > 0xFF ? 0x04 : 0) |
             (forceSynchronous ? 0x01 : 0)));
            // bit 7: LONGHEADER, bit 6: REQUEST, bit 5: NEWTYPE, bit 4: NEWOID,
            // bit 3: NEWTID, bit 2: FUNCTIONID16, bit 0: MOREFLAGS
        if (forceSynchronous) {
            Marshal::write8(&buf, 0xC0); // bit 7: MUSTREPLY, bit 6: SYNCHRONOUS
        }
        if (functionId <= 0xFF) {
            Marshal::write8(&buf, static_cast< sal_uInt8 >(functionId));
        } else {
            Marshal::write16(&buf, static_cast< sal_uInt16 >(functionId));
        }
        if (newType) {
            marshal_.writeType(&buf, t);
        }
        if (newOid) {
            marshal_.writeOid(&buf, oid);
        }
        if (newTid) {
            marshal_.writeTid(&buf, tid);
        }
    } else if (functionId <= 0x3F) { // <= 6 bit function ID
        Marshal::write8(&buf, static_cast< sal_uInt8 >(functionId));
            // bit 7: !LONGHEADER, bit 6: !FUNCTIONID14
    } else {
        Marshal::write8(
            &buf, static_cast< sal_uInt8 >(0x40 | (functionId >> 8)));
            // bit 7: !LONGHEADER, bit 6: FUNCTIONID14
        Marshal::write8(&buf, functionId & 0xFF);
    }
    if (currentContextMode) {
        css::uno::UnoInterfaceReference cc(currentContext);
        marshal_.writeValue(
            &buf,
            css::uno::TypeDescription(
                cppu::UnoType<
                    css::uno::Reference< css::uno::XCurrentContext > >::get()),
            BinaryAny(
                css::uno::TypeDescription(
                    cppu::UnoType<
                        css::uno::Reference<
                            css::uno::XCurrentContext > >::get()),
                &cc.m_pUnoI));
    }
    switch (member.get()->eTypeClass) {
    case typelib_TypeClass_INTERFACE_ATTRIBUTE:
        if (!inArguments.empty()) { // setter
            OSL_ASSERT(inArguments.size() == 1);
            marshal_.writeValue(
                &buf,
                css::uno::TypeDescription(
                    reinterpret_cast<
                        typelib_InterfaceAttributeTypeDescription * >(
                            member.get())->
                    pAttributeTypeRef),
                inArguments.front());
        }
        break;
    case typelib_TypeClass_INTERFACE_METHOD:
        {
            typelib_InterfaceMethodTypeDescription * mtd =
                reinterpret_cast< typelib_InterfaceMethodTypeDescription * >(
                    member.get());
            std::vector< BinaryAny >::const_iterator i(inArguments.begin());
            for (sal_Int32 j = 0; j != mtd->nParams; ++j) {
                if (mtd->pParams[j].bIn) {
                    marshal_.writeValue(
                        &buf,
                        css::uno::TypeDescription(mtd->pParams[j].pTypeRef),
                        *i++);
                }
            }
            OSL_ASSERT(i == inArguments.end());
            break;
        }
    default:
        OSL_ASSERT(false); // this cannot happen
        break;
    }
    sendMessage(buf);
    lastType_ = t;
    lastOid_ = oid;
    lastTid_ = tid;
}

void Writer::sendReply(
    rtl::ByteSequence const & tid,
    com::sun::star::uno::TypeDescription const & member, bool setter,
    bool exception, BinaryAny const & returnValue,
    std::vector< BinaryAny > const & outArguments)
{
    OSL_ASSERT(tid.getLength() != 0 && member.is() && member.get()->bComplete);
    std::vector< unsigned char > buf;
    bool newTid = tid != lastTid_;
    Marshal::write8(&buf, 0x80 | (exception ? 0x20 : 0) | (newTid ? 0x08 : 0));
        // bit 7: LONGHEADER; bit 6: !REQUEST; bit 5: EXCEPTION; bit 3: NEWTID
    if (newTid) {
        marshal_.writeTid(&buf, tid);
    }
    if (exception) {
        marshal_.writeValue(
            &buf,
            css::uno::TypeDescription(cppu::UnoType< css::uno::Any >::get()),
            returnValue);
    } else {
        switch (member.get()->eTypeClass) {
        case typelib_TypeClass_INTERFACE_ATTRIBUTE:
            if (!setter) {
                marshal_.writeValue(
                    &buf,
                    css::uno::TypeDescription(
                        reinterpret_cast<
                            typelib_InterfaceAttributeTypeDescription * >(
                                member.get())->
                        pAttributeTypeRef),
                    returnValue);
            }
            break;
        case typelib_TypeClass_INTERFACE_METHOD:
            {
                typelib_InterfaceMethodTypeDescription * mtd =
                    reinterpret_cast<
                        typelib_InterfaceMethodTypeDescription * >(
                            member.get());
                marshal_.writeValue(
                    &buf, css::uno::TypeDescription(mtd->pReturnTypeRef),
                    returnValue);
                std::vector< BinaryAny >::const_iterator i(
                    outArguments.begin());
                for (sal_Int32 j = 0; j != mtd->nParams; ++j) {
                    if (mtd->pParams[j].bOut) {
                        marshal_.writeValue(
                            &buf,
                            css::uno::TypeDescription(mtd->pParams[j].pTypeRef),
                            *i++);
                    }
                }
                OSL_ASSERT(i == outArguments.end());
                break;
            }
        default:
            OSL_ASSERT(false); // this cannot happen
            break;
        }
    }
    sendMessage(buf);
    lastTid_ = tid;
    bridge_->decrementCalls();
}

void Writer::sendMessage(std::vector< unsigned char > const & buffer) {
    std::vector< unsigned char > header;
    if (buffer.size() > SAL_MAX_UINT32) {
        throw css::uno::RuntimeException(
            rtl::OUString(
                RTL_CONSTASCII_USTRINGPARAM("message too large for URP")),
            css::uno::Reference< css::uno::XInterface >());
    }
    Marshal::write32(&header, static_cast< sal_uInt32 >(buffer.size()));
    Marshal::write32(&header, 1);
    OSL_ASSERT(!buffer.empty());
    unsigned char const * p = &buffer[0];
    std::vector< unsigned char >::size_type n = buffer.size();
    OSL_ASSERT(header.size() <= SAL_MAX_INT32 && SAL_MAX_INT32 <= SAL_MAX_SIZE);
    sal_Size k = SAL_MAX_INT32 - header.size();
    if (n < k) {
        k = static_cast< sal_Size >(n);
    }
    css::uno::Sequence< sal_Int8 > s(
        static_cast< sal_Int32 >(header.size() + k));
    OSL_ASSERT(!header.empty());
    rtl_copyMemory(
        s.getArray(), &header[0], static_cast< sal_Size >(header.size()));
    for (;;) {
        rtl_copyMemory(s.getArray() + s.getLength() - k, p, k);
        try {
            bridge_->getConnection()->write(s);
        } catch (css::io::IOException & e) {
            css::uno::Any exc(cppu::getCaughtException());
            throw css::lang::WrappedTargetRuntimeException(
                (rtl::OUString(
                    RTL_CONSTASCII_USTRINGPARAM(
                        "Binary URP write raised IO exception: ")) +
                 e.Message),
                css::uno::Reference< css::uno::XInterface >(), exc);
        }
        n = static_cast< std::vector< unsigned char >::size_type >(n - k);
        if (n == 0) {
            break;
        }
        p += k;
        k = SAL_MAX_INT32;
        if (n < k) {
            k = static_cast< sal_Size >(n);
        }
        s.realloc(k);
    }
}

}
