/*
 *
 * 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.bookkeeper.verifier;

import org.apache.bookkeeper.client.BookKeeper;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;

/**
 * Performs a configurable IO stream against a bookkeeper client while
 * validating results.
 */
public class BookkeeperVerifierMain {

    private static void printHelpAndExit(Options options, String header, int code) {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(
                "BookkeeperVerifierMain",
                header,
                options, "", true);
        System.exit(code);
    }

    public static void main(String[] args) throws Exception {

        Options options = new Options();
        options.addOption(
                "ledger_path", true, "Hostname or IP of bookie to benchmark");
        options.addOption(
                "zookeeper",
                true,
                "Zookeeper ensemble, (default \"localhost:2181\")");
        options.addOption(
                "ensemble_size", true, "Bookkeeper client ensemble size");
        options.addOption(
                "write_quorum", true, "Bookkeeper client write quorum size");
        options.addOption("ack_quorum", true, "Bookkeeper client ack quorum size");
        options.addOption("duration", true, "Run duration in seconds");
        options.addOption("drain_timeout", true, "Seconds to wait for in progress ops to end");
        options.addOption(
                "target_concurrent_ledgers",
                true,
                "target number ledgers to write to concurrently");
        options.addOption(
                "target_concurrent_writes",
                true,
                "target number of concurrent writes per ledger");
        options.addOption(
                "target_write_group",
                true,
                "target number of entries to write at a time");
        options.addOption(
                "target_read_group",
                true,
                "target number of entries to read at a time");
        options.addOption("target_ledgers", true, "Target number of ledgers");
        options.addOption("target_ledger_size", true, "Target size per ledger");
        options.addOption("target_entry_size", true, "Target size per entry");
        options.addOption(
                "target_concurrent_reads", true, "Number of reads to maintain");
        options.addOption(
                "cold_to_hot_ratio", true, "Ratio of reads on open ledgers");
        options.addOption("help", false, "Print this help message");

        CommandLineParser parser = new PosixParser();
        CommandLine cmd = null;
        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            printHelpAndExit(options, "Unable to parse command line", 1);
        }

        if (cmd.hasOption("help")) {
            printHelpAndExit(options, "Help:", 0);
        }

        String ledgerPath = cmd.getOptionValue("ledger_path", "/ledgers");
        String zkString = cmd.getOptionValue("zookeeper", "localhost:2181");
        int ensembleSize = 0;
        int writeQuorum = 0;
        int ackQuorum = 0;
        int duration = 0;
        int drainTimeout = 0;
        int targetConcurrentLedgers = 0;
        int targetConcurrentWrites = 0;
        int targetWriteGroup = 0;
        int targetReadGroup = 0;
        int targetLedgers = 0;
        long targetLedgerSize = 0;
        int targetEntrySize = 0;
        int targetConcurrentReads = 0;
        double coldToHotRatio = 0;

        try {
            ensembleSize = Integer.parseInt(cmd.getOptionValue("ensemble_size", "3"));
            writeQuorum = Integer.parseInt(cmd.getOptionValue("write_quorum", "3"));
            ackQuorum = Integer.parseInt(cmd.getOptionValue("ack_quorum", "2"));
            duration = Integer.parseInt(cmd.getOptionValue("duration", "600"));
            drainTimeout = Integer.parseInt(cmd.getOptionValue("drain_timeout", "10"));
            targetConcurrentLedgers =
                    Integer.parseInt(cmd.getOptionValue("target_concurrent_ledgers", "4"));
            targetConcurrentWrites =
                    Integer.parseInt(cmd.getOptionValue("target_concurrent_writes", "12"));
            targetWriteGroup =
                    Integer.parseInt(cmd.getOptionValue("target_write_group", "4"));
            targetReadGroup =
                    Integer.parseInt(cmd.getOptionValue("target_read_group", "4"));
            targetLedgers = Integer.parseInt(cmd.getOptionValue("target_ledgers", "32"));
            targetLedgerSize = Long.parseLong(cmd.getOptionValue(
                    "target_ledger_size",
                    "33554432"));
            targetEntrySize = Integer.parseInt(cmd.getOptionValue(
                    "target_entry_size",
                    "16384"));
            targetConcurrentReads = Integer.parseInt(cmd.getOptionValue(
                    "target_concurrent_reads",
                    "16"));
            coldToHotRatio = Double.parseDouble(
                    cmd.getOptionValue("cold_to_hot_ratio", "0.5"));
        } catch (NumberFormatException e) {
            printHelpAndExit(options, "Invalid argument", 0);
        }

        ClientConfiguration conf = new ClientConfiguration();
        conf.setMetadataServiceUri("zk://" + zkString + ledgerPath);
        BookKeeper bkclient = new BookKeeper(conf);

        BookkeeperVerifier verifier = new BookkeeperVerifier(
                new DirectBookkeeperDriver(bkclient),
                ensembleSize,
                writeQuorum,
                ackQuorum,
                duration,
                drainTimeout,
                targetConcurrentLedgers,
                targetConcurrentWrites,
                targetWriteGroup,
                targetReadGroup,
                targetLedgers,
                targetLedgerSize,
                targetEntrySize,
                targetConcurrentReads,
                coldToHotRatio);
        try {
            verifier.run();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            bkclient.close();
        }
    }
}
