/*
 * 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.flink.connector.pulsar.source.config;

import org.apache.flink.annotation.PublicEvolving;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.connector.base.source.reader.RecordsWithSplitIds;
import org.apache.flink.connector.pulsar.common.config.PulsarConfiguration;
import org.apache.flink.connector.pulsar.source.enumerator.cursor.CursorPosition;
import org.apache.flink.connector.pulsar.source.enumerator.cursor.StartCursor;

import org.apache.pulsar.client.api.ConsumerBuilder;
import org.apache.pulsar.client.api.Schema;
import org.apache.pulsar.client.api.SubscriptionInitialPosition;
import org.apache.pulsar.client.api.SubscriptionMode;
import org.apache.pulsar.client.api.SubscriptionType;

import java.time.Duration;
import java.util.Objects;

import static org.apache.flink.connector.base.source.reader.SourceReaderOptions.ELEMENT_QUEUE_CAPACITY;
import static org.apache.flink.connector.pulsar.common.config.PulsarOptions.PULSAR_STATS_INTERVAL_SECONDS;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_ALLOW_KEY_SHARED_OUT_OF_ORDER_DELIVERY;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_AUTO_COMMIT_CURSOR_INTERVAL;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_ENABLE_AUTO_ACKNOWLEDGE_MESSAGE;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_ENABLE_SOURCE_METRICS;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_FETCH_ONE_MESSAGE_TIME;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_INITIAL_CURSOR;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_MAX_FETCH_RECORDS;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_MAX_FETCH_TIME;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_PARTITION_DISCOVERY_INTERVAL_MS;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_READ_SCHEMA_EVOLUTION;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_RESET_SUBSCRIPTION_CURSOR;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_SUBSCRIPTION_MODE;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_SUBSCRIPTION_NAME;
import static org.apache.flink.connector.pulsar.source.PulsarSourceOptions.PULSAR_VERIFY_INITIAL_OFFSETS;

/** The configuration class for pulsar source. */
@PublicEvolving
public class SourceConfiguration extends PulsarConfiguration {
    private static final long serialVersionUID = 8488507275800787580L;

    private final int messageQueueCapacity;
    private final long partitionDiscoveryIntervalMs;
    private final boolean enableAutoAcknowledgeMessage;
    private final long autoCommitCursorInterval;
    private final long fetchOneMessageTime;
    private final Duration maxFetchTime;
    private final int maxFetchRecords;
    private final CursorVerification verifyInitialOffsets;
    private final String subscriptionName;
    private final SubscriptionMode subscriptionMode;
    private final boolean allowKeySharedOutOfOrderDelivery;
    private final boolean enableSchemaEvolution;
    private final boolean enableMetrics;
    private final boolean resetSubscriptionCursor;
    private final SubscriptionInitialPosition subscriptionInitialPosition;

    public SourceConfiguration(Configuration configuration) {
        super(configuration);

        this.messageQueueCapacity = get(ELEMENT_QUEUE_CAPACITY);
        this.partitionDiscoveryIntervalMs = get(PULSAR_PARTITION_DISCOVERY_INTERVAL_MS);
        this.enableAutoAcknowledgeMessage = get(PULSAR_ENABLE_AUTO_ACKNOWLEDGE_MESSAGE);
        this.autoCommitCursorInterval = get(PULSAR_AUTO_COMMIT_CURSOR_INTERVAL).toMillis();
        this.fetchOneMessageTime =
                getOptional(PULSAR_FETCH_ONE_MESSAGE_TIME).map(Duration::toMillis).orElse(0L);
        this.maxFetchTime = get(PULSAR_MAX_FETCH_TIME);
        this.maxFetchRecords = get(PULSAR_MAX_FETCH_RECORDS);
        this.verifyInitialOffsets = get(PULSAR_VERIFY_INITIAL_OFFSETS);
        this.subscriptionName = get(PULSAR_SUBSCRIPTION_NAME);
        this.subscriptionMode = get(PULSAR_SUBSCRIPTION_MODE);
        this.allowKeySharedOutOfOrderDelivery = get(PULSAR_ALLOW_KEY_SHARED_OUT_OF_ORDER_DELIVERY);
        this.enableSchemaEvolution = get(PULSAR_READ_SCHEMA_EVOLUTION);
        this.enableMetrics =
                get(PULSAR_ENABLE_SOURCE_METRICS) && get(PULSAR_STATS_INTERVAL_SECONDS) > 0;
        this.resetSubscriptionCursor = get(PULSAR_RESET_SUBSCRIPTION_CURSOR);
        this.subscriptionInitialPosition = get(PULSAR_INITIAL_CURSOR);
    }

    /** The capacity of the element queue in the source reader. */
    public int getMessageQueueCapacity() {
        return messageQueueCapacity;
    }

    /**
     * We would override the interval into a negative number when we set the connector with bounded
     * stop cursor.
     */
    public boolean isEnablePartitionDiscovery() {
        return getPartitionDiscoveryIntervalMs() > 0;
    }

    /** The interval in millis for flink querying topic partition information. */
    public long getPartitionDiscoveryIntervalMs() {
        return partitionDiscoveryIntervalMs;
    }

    /**
     * This is used for all subscription type. If you don't enable the flink checkpoint, please make
     * sure this option is set to true.
     *
     * <p>{@link SubscriptionType#Failover} and {@link SubscriptionType#Exclusive} would perform an
     * incremental acknowledgment in a fixed {@link #getAutoCommitCursorInterval}.
     */
    public boolean isEnableAutoAcknowledgeMessage() {
        return enableAutoAcknowledgeMessage;
    }

    /**
     * The interval in millis for acknowledge message when you enable {@link
     * #isEnableAutoAcknowledgeMessage} and use {@link SubscriptionType#Failover} or {@link
     * SubscriptionType#Exclusive} as your consuming subscription type.
     */
    public long getAutoCommitCursorInterval() {
        return autoCommitCursorInterval;
    }

    /**
     * The fetch time for polling one message. We would stop polling messages and return the
     * messages in {@link RecordsWithSplitIds} when meet this timeout and no message consumed.
     */
    public int getFetchOneMessageTime() {
        return (int) fetchOneMessageTime;
    }

    /**
     * The fetch time for flink split reader polling message. We would stop polling message and
     * return the message in {@link RecordsWithSplitIds} when timeout or exceed the {@link
     * #getMaxFetchRecords}.
     */
    public Duration getMaxFetchTime() {
        return maxFetchTime;
    }

    /**
     * The fetch counts for a split reader. We would stop polling message and return the message in
     * {@link RecordsWithSplitIds} when timeout {@link #getMaxFetchTime} or exceed this value.
     */
    public int getMaxFetchRecords() {
        return maxFetchRecords;
    }

    /** Validate the {@link CursorPosition} generated by {@link StartCursor}. */
    public CursorVerification getVerifyInitialOffsets() {
        return verifyInitialOffsets;
    }

    /**
     * The pulsar's subscription name for this flink source. All the readers would share this
     * subscription name.
     *
     * @see ConsumerBuilder#subscriptionName
     */
    public String getSubscriptionName() {
        return subscriptionName;
    }

    /**
     * The pulsar's subscription mode for this flink source. All the readers would share this
     * subscription mode.
     *
     * @see SubscriptionMode
     */
    public SubscriptionMode getSubscriptionMode() {
        return subscriptionMode;
    }

    /** Whether to enable the out-of-order delivery in Key Shared subscription. */
    public boolean isAllowKeySharedOutOfOrderDelivery() {
        return allowKeySharedOutOfOrderDelivery;
    }

    /**
     * If we need to deserialize the message with a specified Pulsar {@link Schema} instead the
     * default {@link Schema#BYTES}. This switch is only used for {@code PulsarSchemaWrapper}.
     */
    public boolean isEnableSchemaEvolution() {
        return enableSchemaEvolution;
    }

    /** Whether to expose the metrics from Pulsar Consumer. */
    public boolean isEnableMetrics() {
        return enableMetrics;
    }

    /** Whether to reset the start cursor in subscription. */
    public boolean isResetSubscriptionCursor() {
        return resetSubscriptionCursor;
    }

    /** Convert the subscription into a readable str. */
    public String getSubscriptionDesc() {
        return getSubscriptionName() + "(Exclusive," + getSubscriptionMode() + ")";
    }

    /** The initial position for the subscription. */
    public SubscriptionInitialPosition getInitialPosition() {
        return subscriptionInitialPosition;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        SourceConfiguration that = (SourceConfiguration) o;
        return messageQueueCapacity == that.messageQueueCapacity
                && partitionDiscoveryIntervalMs == that.partitionDiscoveryIntervalMs
                && enableAutoAcknowledgeMessage == that.enableAutoAcknowledgeMessage
                && autoCommitCursorInterval == that.autoCommitCursorInterval
                && fetchOneMessageTime == that.fetchOneMessageTime
                && Objects.equals(maxFetchTime, that.maxFetchTime)
                && maxFetchRecords == that.maxFetchRecords
                && verifyInitialOffsets == that.verifyInitialOffsets
                && Objects.equals(subscriptionName, that.subscriptionName)
                && subscriptionMode == that.subscriptionMode
                && allowKeySharedOutOfOrderDelivery == that.allowKeySharedOutOfOrderDelivery
                && enableSchemaEvolution == that.enableSchemaEvolution
                && enableMetrics == that.enableMetrics
                && resetSubscriptionCursor == that.resetSubscriptionCursor
                && subscriptionInitialPosition == that.subscriptionInitialPosition;
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                super.hashCode(),
                messageQueueCapacity,
                partitionDiscoveryIntervalMs,
                enableAutoAcknowledgeMessage,
                autoCommitCursorInterval,
                fetchOneMessageTime,
                maxFetchTime,
                maxFetchRecords,
                verifyInitialOffsets,
                subscriptionName,
                subscriptionMode,
                allowKeySharedOutOfOrderDelivery,
                enableSchemaEvolution,
                enableMetrics,
                resetSubscriptionCursor,
                subscriptionInitialPosition);
    }
}
