/**
 * 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.
 */
#ifndef MESSAGE_HPP_
#define MESSAGE_HPP_

#include <pulsar/EncryptionContext.h>
#include <pulsar/defines.h>

#include <map>
#include <memory>
#include <optional>
#include <string>

#include "KeyValue.h"
#include "MessageId.h"

namespace pulsar {
namespace proto {
class CommandMessage;
class BrokerEntryMetadata;
class MessageMetadata;
class SingleMessageMetadata;
}  // namespace proto

class SharedBuffer;
class MessageBuilder;
class MessageImpl;
class PulsarWrapper;

class PULSAR_PUBLIC Message {
   public:
    typedef std::map<std::string, std::string> StringMap;

    Message();

    /**
     * Return the properties attached to the message.
     * Properties are application defined key/value pairs that will be attached to the message
     *
     * @return an unmodifiable view of the properties map
     */
    const StringMap& getProperties() const;

    /**
     * Check whether the message has a specific property attached.
     *
     * @param name the name of the property to check
     * @return true if the message has the specified property
     * @return false if the property is not defined
     */
    bool hasProperty(const std::string& name) const;

    /**
     * Get the value of a specific property
     *
     * @param name the name of the property
     * @return the value of the property or null if the property was not defined
     */
    const std::string& getProperty(const std::string& name) const;

    /**
     * Get the content of the message
     *
     *
     * @return the pointer to the message payload
     */
    const void* getData() const;

    /**
     * Get the length of the message
     *
     * @return the length of the message payload
     */
    std::size_t getLength() const;

    /**
     * Get string representation of the message
     *
     * @return the string representation of the message payload
     *
     * NOTE: For MSVC with debug mode, return a thread local std::string object to avoid memory allocation
     * across DLLs and applications, which could lead to a crash.
     */
#if defined(_MSC_VER) && !defined(NDEBUG)
    const std::string& getDataAsString() const;
#else
    std::string getDataAsString() const;
#endif

    /**
     * Get key value message.
     *
     * @return key value message.
     */
    KeyValue getKeyValueData() const;

    /**
     * Get the unique message ID associated with this message.
     *
     * The message id can be used to univocally refer to a message without having to keep the entire payload
     * in memory.
     *
     * Only messages received from the consumer will have a message id assigned.
     *
     */
    const MessageId& getMessageId() const;

    /**
     * Set the unique message ID.
     *
     */
    void setMessageId(const MessageId& messageId) const;

    /**
     * Get the index of this message, if it doesn't exist, return -1
     * @return
     */
    int64_t getIndex() const;

    /**
     * Get the partition key for this message
     * @return key string that is hashed to determine message's topic partition
     */
    const std::string& getPartitionKey() const;

    /**
     * @return true if the message has a partition key
     */
    bool hasPartitionKey() const;

    /**
     * Get the ordering key of the message
     *
     * @return the ordering key of the message
     */
    const std::string& getOrderingKey() const;

    /**
     * Check whether the message has a ordering key
     *
     * @return true if the ordering key was set while creating the message
     *         false if the ordering key was not set while creating the message
     */
    bool hasOrderingKey() const;

    /**
     * Check if the message has a null value.
     *
     * Messages with null values are used as tombstones on compacted topics
     * to delete the message for a specific key.
     *
     * @return true if the message has a null value (tombstone)
     *         false if the message has actual payload data
     */
    bool hasNullValue() const;

    /**
     * Get the UTC based timestamp in milliseconds referring to when the message was published by the client
     * producer
     */
    uint64_t getPublishTimestamp() const;

    /**
     * Get the event timestamp associated with this message. It is set by the client producer.
     */
    uint64_t getEventTimestamp() const;

    /**
     * Get the topic Name from which this message originated from
     */
    const std::string& getTopicName() const;

    /**
     * Get the redelivery count for this message
     */
    int getRedeliveryCount() const;

    /**
     * Check if schema version exists
     */
    bool hasSchemaVersion() const;

    /**
     * Get the schema version.
     *
     * @return the the schema version on success or -1 if the message does not have the schema version
     */
    int64_t getLongSchemaVersion() const;

    /**
     * Get the schema version of the raw bytes.
     */
    const std::string& getSchemaVersion() const;

    /**
     * Set the schema version of the message.
     */
    void setSchemaVersion(const std::string& schemaVersion);

    /**
     * Get the producer name which produced this message.
     *
     * @return the producer name or empty string if not available
     */
    const std::string& getProducerName() const noexcept;

    /**
     * Get the source cluster from which the message was replicated.
     *
     * @return the optional pointer to the source cluster name if the message was replicated, the pointer is
     * valid as the Message instance is alive
     */
    std::optional<const std::string*> getReplicatedFrom() const;

    /**
     * @return the optional encryption context that is present when the message is encrypted, the pointer is
     * valid as the Message instance is alive
     */
    std::optional<const EncryptionContext*> getEncryptionContext() const;

    bool operator==(const Message& msg) const;

   protected:
    typedef std::shared_ptr<MessageImpl> MessageImplPtr;
    MessageImplPtr impl_;

    Message(MessageImplPtr& impl);
    Message(const MessageId& messageId, proto::BrokerEntryMetadata& brokerEntryMetadata,
            proto::MessageMetadata& metadata, SharedBuffer& payload);
    /// Used for Batch Messages
    Message(const MessageId& messageId, proto::BrokerEntryMetadata& brokerEntryMetadata,
            proto::MessageMetadata& metadata, SharedBuffer& payload,
            proto::SingleMessageMetadata& singleMetadata, const std::shared_ptr<std::string>& topicName);
    friend class PartitionedProducerImpl;
    friend class MultiTopicsConsumerImpl;
    friend class MessageBuilder;
    friend class ConsumerImpl;
    friend class ProducerImpl;
    friend class Commands;
    friend class BatchMessageContainerBase;
    friend class BatchAcknowledgementTracker;
    friend class PulsarWrapper;
    friend class MessageBatch;
    friend struct OpSendMsg;

    friend PULSAR_PUBLIC std::ostream& operator<<(std::ostream& s, const StringMap& map);
    friend PULSAR_PUBLIC std::ostream& operator<<(std::ostream& s, const Message& msg);
    friend class PulsarFriend;
};
}  // namespace pulsar

#endif /* MESSAGE_HPP_ */
