/*
 * 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.
 */
package org.apache.pulsar.client.impl.conf;

import com.fasterxml.jackson.annotation.JsonIgnore;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import lombok.Data;
import org.apache.pulsar.client.api.ConsumerCryptoFailureAction;
import org.apache.pulsar.client.api.CryptoKeyReader;
import org.apache.pulsar.client.api.MessageCrypto;
import org.apache.pulsar.client.api.MessageId;
import org.apache.pulsar.client.api.Range;
import org.apache.pulsar.client.api.ReaderInterceptor;
import org.apache.pulsar.client.api.ReaderListener;
import org.apache.pulsar.client.api.SubscriptionInitialPosition;
import org.apache.pulsar.client.api.SubscriptionMode;

@Data
public class ReaderConfigurationData<T> implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(
            name = "topicNames",
            required = true,
            value = "Topic name"
    )
    private Set<String> topicNames = new HashSet<>();

    @JsonIgnore
    private MessageId startMessageId;

    @JsonIgnore
    private long startMessageFromRollbackDurationInSec;

    @ApiModelProperty(
            name = "receiverQueueSize",
            value = "Size of a consumer's receiver queue.\n"
                    + "\n"
                    + "For example, the number of messages that can be accumulated by a consumer before an "
                    + "application calls `Receive`.\n"
                    + "\n"
                    + "A value higher than the default value increases consumer throughput, though at the expense of "
                    + "more memory utilization."
    )
    private int receiverQueueSize = 1000;

    @ApiModelProperty(
            name = "readerListener",
            value = "A listener that is called for message received."
    )
    private ReaderListener<T> readerListener;

    @ApiModelProperty(
            name = "readerName",
            value = "Reader name"
    )
    private String readerName = null;

    @ApiModelProperty(
            name = "subscriptionRolePrefix",
            value = "Prefix of subscription role."
    )
    private String subscriptionRolePrefix = null;

    @ApiModelProperty(
            name = "subscriptionName",
            value = "Subscription name"
    )
    private String subscriptionName = null;

    @ApiModelProperty(
            name = "cryptoKeyReader",
            value = "Interface that abstracts the access to a key store."
    )
    private CryptoKeyReader cryptoKeyReader = null;

    @ApiModelProperty(
            name = "cryptoFailureAction",
            value = "Consumer should take action when it receives a message that can not be decrypted.\n"
                    + "* **FAIL**: this is the default option to fail messages until crypto succeeds.\n"
                    + "* **DISCARD**: silently acknowledge and not deliver message to an application.\n"
                    + "* **CONSUME**: deliver encrypted messages to applications. It is the application's"
                    + " responsibility to decrypt the message.\n"
                    + "\n"
                    + "The message decompression fails.\n"
                    + "\n"
                    + "If messages contain batch messages, a client is not be able to retrieve individual messages in"
                    + " batch.\n"
                    + "\n"
                    + "Delivered encrypted message contains {@link EncryptionContext} which contains encryption and "
                    + "compression information in it using which application can decrypt consumed message payload."
    )
    private ConsumerCryptoFailureAction cryptoFailureAction = ConsumerCryptoFailureAction.FAIL;

    @JsonIgnore
    private transient MessageCrypto messageCrypto = null;

    @ApiModelProperty(
            name = "readCompacted",
            value = "If enabling `readCompacted`, a consumer reads messages from a compacted topic rather than a full "
                    + "message backlog of a topic.\n"
                    + "\n"
                    + "A consumer only sees the latest value for each key in the compacted topic, up until reaching "
                    + "the point in the topic message when compacting backlog. Beyond that point, send messages as "
                    + "normal.\n"
                    + "\n"
                    + "`readCompacted` can only be enabled on subscriptions to persistent topics, which have a single "
                    + "active consumer (for example, failure or exclusive subscriptions).\n"
                    + "\n"
                    + "Attempting to enable it on subscriptions to non-persistent topics or on shared subscriptions "
                    + "leads to a subscription call throwing a `PulsarClientException`."
    )
    private boolean readCompacted = false;

    @ApiModelProperty(
            name = "resetIncludeHead",
            value = "If set to true, the first message to be returned is the one specified by `messageId`.\n"
                    + "\n"
                    + "If set to false, the first message to be returned is the one next to the message specified by "
                    + "`messageId`."
    )
    private boolean resetIncludeHead = false;

    private List<Range> keyHashRanges;

    private boolean poolMessages = false;

    private boolean autoUpdatePartitions = true;
    private long autoUpdatePartitionsIntervalSeconds = 60;

    private transient List<ReaderInterceptor<T>> readerInterceptorList;

    // max pending chunked message to avoid sending incomplete message into the queue and memory
    private int maxPendingChunkedMessage = 10;

    private boolean autoAckOldestChunkedMessageOnQueueFull = false;

    private long expireTimeOfIncompleteChunkedMessageMillis = TimeUnit.MINUTES.toMillis(1);

    private SubscriptionMode subscriptionMode = SubscriptionMode.NonDurable;

    private SubscriptionInitialPosition subscriptionInitialPosition = SubscriptionInitialPosition.Latest;

    @JsonIgnore
    public String getTopicName() {
        if (topicNames.size() > 1) {
            throw new IllegalArgumentException("topicNames needs to be = 1");
        }
        return topicNames.iterator().next();
    }

    @JsonIgnore
    public void setTopicName(String topicNames) {
        //Compatible with a single topic
        this.topicNames.clear();
        this.topicNames.add(topicNames);
    }

    @SuppressWarnings("unchecked")
    public ReaderConfigurationData<T> clone() {
        try {
            ReaderConfigurationData<T> clone = (ReaderConfigurationData<T>) super.clone();
            clone.setTopicNames(new HashSet<>(clone.getTopicNames()));
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Failed to clone ReaderConfigurationData");
        }
    }

    @SuppressFBWarnings({"EI_EXPOSE_REP"})
    public MessageCrypto getMessageCrypto() {
        return messageCrypto;
    }

    @SuppressFBWarnings({"EI_EXPOSE_REP2"})
    public void setMessageCrypto(MessageCrypto messageCrypto) {
        this.messageCrypto = messageCrypto;
    }
}
