/*
 * 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.rocketmq.tools.command.message;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.tools.command.SubCommand;
import org.apache.rocketmq.tools.command.SubCommandException;

import java.util.Set;

public class ConsumeMessageCommand implements SubCommand {

    private String topic = null;
    private long messageCount = 128;
    private DefaultMQPullConsumer defaultMQPullConsumer;


    public enum ConsumeType {
        /**
         * Topic only
         */
        DEFAULT,
        /**
         * Topic brokerName queueId set
         */
        BYQUEUE,
        /**
         * Topic brokerName queueId offset set
         */
        BYOFFSET
    }

    private static long timestampFormat(final String value) {
        long timestamp;
        try {
            timestamp = Long.parseLong(value);
        } catch (NumberFormatException e) {
            timestamp = UtilAll.parseDate(value, UtilAll.YYYY_MM_DD_HH_MM_SS_SSS).getTime();
        }

        return timestamp;
    }
    @Override
    public String commandName() {
        return "consumeMessage";
    }

    @Override
    public String commandDesc() {
        return "Consume message.";
    }

    @Override
    public Options buildCommandlineOptions(final Options options) {
        Option opt = new Option("t", "topic", true, "Topic name");
        opt.setRequired(true);
        options.addOption(opt);

        opt = new Option("b", "brokerName", true, "Broker name");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("i", "queueId", true, "Queue Id");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("o", "offset", true, "Queue offset");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("g", "consumerGroup", true, "Consumer group name");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("s", "beginTimestamp ", true,
                "Begin timestamp[currentTimeMillis|yyyy-MM-dd#HH:mm:ss:SSS]");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("e", "endTimestamp ", true,
                "End timestamp[currentTimeMillis|yyyy-MM-dd#HH:mm:ss:SSS]");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("c", "MessageNumber", true, "Number of message to be consumed");
        opt.setRequired(false);
        options.addOption(opt);


        return options;

    }

    @Override
    public void execute(final CommandLine commandLine, final Options options, RPCHook rpcHook) throws SubCommandException {
        if (defaultMQPullConsumer == null) {
            defaultMQPullConsumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook);
        }
        defaultMQPullConsumer.setInstanceName(Long.toString(System.currentTimeMillis()));

        long offset = 0;
        long timeValueEnd = 0;
        long timeValueBegin = 0;
        String queueId = null;
        String brokerName = null;
        ConsumeType consumeType = ConsumeType.DEFAULT;

        try {
            /* Group name must be set before consumer start */
            if (commandLine.hasOption('g')) {
                String consumerGroup = commandLine.getOptionValue('g').trim();
                defaultMQPullConsumer.setConsumerGroup(consumerGroup);
            }

            defaultMQPullConsumer.start();

            topic = commandLine.getOptionValue('t').trim();

            if (commandLine.hasOption('c')) {
                messageCount = Long.parseLong(commandLine.getOptionValue('c').trim());
                if (messageCount <= 0) {
                    System.out.print("Please input a positive messageNumber!");
                    return;
                }
            }
            if (commandLine.hasOption('b')) {
                brokerName = commandLine.getOptionValue('b').trim();

            }
            if (commandLine.hasOption('i')) {
                if (!commandLine.hasOption('b')) {
                    System.out.print("Please set the brokerName before queueId!");
                    return;
                }
                queueId = commandLine.getOptionValue('i').trim();

                consumeType = ConsumeType.BYQUEUE;
            }
            if (commandLine.hasOption('o')) {
                if (consumeType != ConsumeType.BYQUEUE) {
                    System.out.print("Please set queueId before offset!");
                    return;
                }
                offset = Long.parseLong(commandLine.getOptionValue('o').trim());
                consumeType = ConsumeType.BYOFFSET;
            }

            long now = System.currentTimeMillis();
            if (commandLine.hasOption('s')) {
                String timestampStr = commandLine.getOptionValue('s').trim();
                timeValueBegin = timestampFormat(timestampStr);
                if (timeValueBegin > now) {
                    System.out.print("Please set the beginTimestamp before now!");
                    return;
                }
            }
            if (commandLine.hasOption('e')) {
                String timestampStr = commandLine.getOptionValue('e').trim();
                timeValueEnd = timestampFormat(timestampStr);
                if (timeValueEnd > now) {
                    System.out.print("Please set the endTimestamp before now!");
                    return;
                }
                if (timeValueBegin > timeValueEnd) {
                    System.out.print("Please make sure that the beginTimestamp is less than or equal to the endTimestamp");
                    return;
                }
            }

            switch (consumeType) {
                case DEFAULT:
                    executeDefault(timeValueBegin, timeValueEnd);
                    break;
                case BYOFFSET:
                    executeByCondition(brokerName, queueId, offset, timeValueBegin, timeValueEnd);
                    break;
                case BYQUEUE:
                    executeByCondition(brokerName, queueId, 0, timeValueBegin, timeValueEnd);
                    break;
                default:
                    System.out.print("Unknown type of consume!");
                    break;
            }

        } catch (Exception e) {
            throw new SubCommandException(this.getClass().getSimpleName() + " command failed", e);
        } finally {
            defaultMQPullConsumer.shutdown();
        }
    }

    private void pullMessageByQueue(MessageQueue mq, long minOffset, long maxOffset) {
        READQ:
        for (long offset = minOffset; offset <= maxOffset; ) {
            PullResult pullResult = null;
            try {
                pullResult = defaultMQPullConsumer.pull(mq, "*", offset, (int)(maxOffset - offset + 1));
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
            if (pullResult != null) {
                offset = pullResult.getNextBeginOffset();
                switch (pullResult.getPullStatus()) {
                    case FOUND:
                        System.out.print("Consume ok\n");
                        PrintMessageByQueueCommand.printMessage(pullResult.getMsgFoundList(), "UTF-8",
                            true, true);
                        break;
                    case NO_MATCHED_MSG:
                        System.out.printf("%s no matched msg. status=%s, offset=%s\n", mq, pullResult.getPullStatus(),
                            offset);
                        break;
                    case NO_NEW_MSG:
                    case OFFSET_ILLEGAL:
                        System.out.printf("%s print msg finished. status=%s, offset=%s\n", mq,
                            pullResult.getPullStatus(), offset);
                        break READQ;
                    default:
                        break;
                }
            }
        }
    }

    private void executeDefault(long timeValueBegin, long timeValueEnd) {
        try {
            Set<MessageQueue> mqs = defaultMQPullConsumer.fetchSubscribeMessageQueues(topic);
            long countLeft = messageCount;
            for (MessageQueue mq : mqs) {
                if (countLeft == 0) {
                    return;
                }
                long minOffset = defaultMQPullConsumer.minOffset(mq);
                long maxOffset = defaultMQPullConsumer.maxOffset(mq);
                if (timeValueBegin > 0) {
                    minOffset = defaultMQPullConsumer.searchOffset(mq, timeValueBegin);
                }
                if (timeValueEnd > 0) {
                    maxOffset = defaultMQPullConsumer.searchOffset(mq, timeValueEnd);
                }
                if (maxOffset - minOffset > countLeft) {
                    System.out.printf("The older %d message of the %d queue will be provided\n", countLeft, mq.getQueueId());
                    maxOffset = minOffset + countLeft - 1;
                    countLeft = 0;
                } else {
                    countLeft = countLeft - (maxOffset - minOffset) - 1;
                }

                pullMessageByQueue(mq, minOffset, maxOffset);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void executeByCondition(String brokerName, String queueId, long offset, long timeValueBegin, long timeValueEnd) {
        MessageQueue mq = new MessageQueue(topic, brokerName, Integer.parseInt(queueId));
        try {
            long minOffset = defaultMQPullConsumer.minOffset(mq);
            long maxOffset = defaultMQPullConsumer.maxOffset(mq);
            if (timeValueBegin > 0) {
                minOffset = defaultMQPullConsumer.searchOffset(mq, timeValueBegin);
            }
            if (timeValueEnd > 0) {
                maxOffset = defaultMQPullConsumer.searchOffset(mq, timeValueEnd);
            }
            if (offset > maxOffset) {
                System.out.printf("%s no matched msg, offset=%s\n", mq, offset);
                return;
            }
            minOffset = minOffset > offset ? minOffset : offset;
            if (maxOffset - minOffset > messageCount) {
                System.out.printf("The oldler %d message will be provided\n", messageCount);
                maxOffset = minOffset + messageCount - 1;
            }

            pullMessageByQueue(mq, minOffset, maxOffset);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}