/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <activemq/commands/ActiveMQMapMessage.h>
#include <activemq/wireformat/openwire/marshal/PrimitiveTypesMarshaller.h>
#include <activemq/util/CMSExceptionSupport.h>

#include <decaf/lang/exceptions/UnsupportedOperationException.h>

#include <decaf/io/ByteArrayInputStream.h>
#include <decaf/io/ByteArrayOutputStream.h>
#include <decaf/io/BufferedInputStream.h>
#include <decaf/io/DataInputStream.h>
#include <decaf/io/DataOutputStream.h>
#include <decaf/util/zip/DeflaterOutputStream.h>
#include <decaf/util/zip/InflaterInputStream.h>

using namespace std;
using namespace decaf;
using namespace decaf::io;
using namespace decaf::lang;
using namespace decaf::lang::exceptions;
using namespace decaf::util;
using namespace decaf::util::zip;
using namespace activemq;
using namespace activemq::util;
using namespace activemq::exceptions;
using namespace activemq::commands;
using namespace activemq::wireformat;
using namespace activemq::wireformat::openwire;
using namespace activemq::wireformat::openwire::marshal;

////////////////////////////////////////////////////////////////////////////////
const unsigned char ActiveMQMapMessage::ID_ACTIVEMQMAPMESSAGE = 25;

////////////////////////////////////////////////////////////////////////////////
ActiveMQMapMessage::ActiveMQMapMessage() : ActiveMQMessageTemplate<cms::MapMessage>(), map() {
}

////////////////////////////////////////////////////////////////////////////////
ActiveMQMapMessage::~ActiveMQMapMessage() throw() {
}

////////////////////////////////////////////////////////////////////////////////
unsigned char ActiveMQMapMessage::getDataStructureType() const {
    return ActiveMQMapMessage::ID_ACTIVEMQMAPMESSAGE;
}

////////////////////////////////////////////////////////////////////////////////
ActiveMQMapMessage* ActiveMQMapMessage::cloneDataStructure() const {
    ActiveMQMapMessage* message = new ActiveMQMapMessage();
    message->copyDataStructure( this );
    return message;
}

////////////////////////////////////////////////////////////////////////////////
cms::MapMessage* ActiveMQMapMessage::clone() const {
    ActiveMQMapMessage* clone = this->cloneDataStructure();
    clone->setReadOnlyBody(false);
    clone->setReadOnlyProperties(false);
    return dynamic_cast<cms::MapMessage*>(clone);
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::copyDataStructure(const DataStructure* src) {
    ActiveMQMessageTemplate<cms::MapMessage>::copyDataStructure(src);

    const ActiveMQMapMessage* srcMap = dynamic_cast<const ActiveMQMapMessage*>(src);

    if (srcMap != NULL && srcMap->map.get() != NULL) {
        this->map.reset(new util::PrimitiveMap(*srcMap->map));
    }
}

////////////////////////////////////////////////////////////////////////////////
std::string ActiveMQMapMessage::toString() const {
    return ActiveMQMessageTemplate<cms::MapMessage>::toString();
}

////////////////////////////////////////////////////////////////////////////////
bool ActiveMQMapMessage::equals(const DataStructure* value) const {
    return ActiveMQMessageTemplate<cms::MapMessage>::equals(value);
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::clearBody() throw (cms::CMSException) {
    ActiveMQMessageTemplate<cms::MapMessage>::clearBody();
    this->getMap().clear();
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::beforeMarshal(WireFormat* wireFormat) {

    try {

        // Let the base class do its thing.
        ActiveMQMessageTemplate<cms::MapMessage>::beforeMarshal(wireFormat);

        if (map.get() != NULL && !map->isEmpty()) {

            ByteArrayOutputStream* bytesOut = new ByteArrayOutputStream();

            OutputStream* os = bytesOut;

            if (this->connection != NULL && this->connection->isUseCompression()) {
                this->compressed = true;

                Deflater* deflator = new Deflater(this->connection->getCompressionLevel());

                os = new DeflaterOutputStream(os, deflator, true, true);
            }

            DataOutputStream dataOut(os, true);
            PrimitiveTypesMarshaller::marshalMap(map.get(), dataOut);
            dataOut.close();

            std::pair<unsigned char*, int> array = bytesOut->toByteArray();
            this->setContent(std::vector<unsigned char>(array.first, array.first + array.second));
            delete[] array.first;
        } else {
            clearBody();
        }
    }
    AMQ_CATCH_RETHROW(decaf::io::IOException)
    AMQ_CATCH_EXCEPTION_CONVERT(Exception, decaf::io::IOException)
    AMQ_CATCHALL_THROW(decaf::io::IOException)
}

////////////////////////////////////////////////////////////////////////////////
PrimitiveMap& ActiveMQMapMessage::getMap() {

    try {
        this->checkMapIsUnmarshalled();
        return *map;
    }
    AMQ_CATCH_RETHROW(NullPointerException)
    AMQ_CATCH_EXCEPTION_CONVERT(Exception, NullPointerException)
    AMQ_CATCHALL_THROW(NullPointerException)
}

////////////////////////////////////////////////////////////////////////////////
const PrimitiveMap& ActiveMQMapMessage::getMap() const {

    try {
        this->checkMapIsUnmarshalled();
        return *map;
    }
    AMQ_CATCH_RETHROW(NullPointerException)
    AMQ_CATCH_EXCEPTION_CONVERT(Exception, NullPointerException)
    AMQ_CATCHALL_THROW(NullPointerException)
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::checkMapIsUnmarshalled() const {

    try {

        if (map.get() == NULL && !getContent().empty()) {

            InputStream* is = new ByteArrayInputStream(getContent());

            if (isCompressed()) {
                is = new InflaterInputStream(is, true);
                is = new BufferedInputStream(is, true);
            }

            DataInputStream dataIn(is, true);

            map.reset(PrimitiveTypesMarshaller::unmarshalMap(dataIn));

            if (map.get() == NULL) {
                throw NullPointerException(
                    __FILE__, __LINE__,
                    "ActiveMQMapMessage::getMap() - All attempts to create a map have failed.");
            }
        } else if (map.get() == NULL) {
            map.reset(new PrimitiveMap());
        }
    }
    AMQ_CATCH_RETHROW(NullPointerException)
    AMQ_CATCH_EXCEPTION_CONVERT(Exception, NullPointerException)
    AMQ_CATCHALL_THROW(NullPointerException)
}

////////////////////////////////////////////////////////////////////////////////
bool ActiveMQMapMessage::isEmpty() const {

    try {
        return getMap().isEmpty();
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
std::vector<std::string> ActiveMQMapMessage::getMapNames() const {

    try {
        return getMap().keySet().toArray();
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
bool ActiveMQMapMessage::itemExists(const std::string& name) const {

    try {
        return getMap().containsKey(name);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
cms::Message::ValueType ActiveMQMapMessage::getValueType(const std::string& key) const {

    try {
        util::PrimitiveValueNode::PrimitiveType type = this->getMap().getValueType(key);

        switch (type) {
            case util::PrimitiveValueNode::NULL_TYPE:
                return cms::Message::NULL_TYPE;
            case util::PrimitiveValueNode::BOOLEAN_TYPE:
                return cms::Message::BOOLEAN_TYPE;
            case util::PrimitiveValueNode::BYTE_TYPE:
                return cms::Message::BYTE_TYPE;
            case util::PrimitiveValueNode::CHAR_TYPE:
                return cms::Message::CHAR_TYPE;
            case util::PrimitiveValueNode::SHORT_TYPE:
                return cms::Message::SHORT_TYPE;
            case util::PrimitiveValueNode::INTEGER_TYPE:
                return cms::Message::INTEGER_TYPE;
            case util::PrimitiveValueNode::LONG_TYPE:
                return cms::Message::LONG_TYPE;
            case util::PrimitiveValueNode::DOUBLE_TYPE:
                return cms::Message::DOUBLE_TYPE;
            case util::PrimitiveValueNode::FLOAT_TYPE:
                return cms::Message::FLOAT_TYPE;
            case util::PrimitiveValueNode::STRING_TYPE:
            case util::PrimitiveValueNode::BIG_STRING_TYPE:
                return cms::Message::STRING_TYPE;
            case util::PrimitiveValueNode::BYTE_ARRAY_TYPE:
                return cms::Message::BYTE_ARRAY_TYPE;
            default:
                break;
        }

        return cms::Message::UNKNOWN_TYPE;
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
bool ActiveMQMapMessage::getBoolean(const std::string& name) const {

    try {
        return getMap().getBool(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setBoolean(const std::string& name, bool value) {

    failIfReadOnlyBody();
    try {
        getMap().setBool(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
unsigned char ActiveMQMapMessage::getByte(const std::string& name) const {

    try {
        return getMap().getByte(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setByte(const std::string& name, unsigned char value) {

    failIfReadOnlyBody();
    try {
        getMap().setByte(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
std::vector<unsigned char> ActiveMQMapMessage::getBytes(const std::string& name) const {

    try {
        return getMap().getByteArray(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setBytes(const std::string& name, const std::vector<unsigned char>& value) {

    failIfReadOnlyBody();
    try {
        getMap().setByteArray(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
char ActiveMQMapMessage::getChar(const std::string& name) const {

    try {
        return getMap().getChar(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setChar(const std::string& name, char value) {

    failIfReadOnlyBody();
    try {
        getMap().setChar(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
double ActiveMQMapMessage::getDouble(const std::string& name) const {

    try {
        return getMap().getDouble(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setDouble(const std::string& name, double value) {

    failIfReadOnlyBody();
    try {
        getMap().setDouble(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
float ActiveMQMapMessage::getFloat(const std::string& name) const {

    try {
        return getMap().getFloat(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setFloat(const std::string& name, float value) {

    failIfReadOnlyBody();
    try {
        getMap().setFloat(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
int ActiveMQMapMessage::getInt(const std::string& name) const {

    try {
        return getMap().getInt(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setInt(const std::string& name, int value) {

    failIfReadOnlyBody();
    try {
        getMap().setInt(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
long long ActiveMQMapMessage::getLong(const std::string& name) const {

    try {
        return getMap().getLong(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setLong(const std::string& name, long long value) {

    failIfReadOnlyBody();
    try {
        getMap().setLong(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
short ActiveMQMapMessage::getShort(const std::string& name) const {

    try {
        return getMap().getShort(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setShort(const std::string& name, short value) {

    failIfReadOnlyBody();
    try {
        getMap().setShort(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
std::string ActiveMQMapMessage::getString(const std::string& name) const {

    try {
        return getMap().getString(name);
    } catch (UnsupportedOperationException& ex) {
        throw CMSExceptionSupport::createMessageFormatException(ex);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}

////////////////////////////////////////////////////////////////////////////////
void ActiveMQMapMessage::setString(const std::string& name, const std::string& value) {

    failIfReadOnlyBody();
    try {
        getMap().setString(name, value);
    }
    AMQ_CATCH_ALL_THROW_CMSEXCEPTION()
}
