package org.apache.cassandra.stress.settings;
/*
 * 
 * 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.
 * 
 */


import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.Uninterruptibles;

import org.apache.cassandra.stress.operations.OpDistributionFactory;
import org.apache.cassandra.stress.util.JavaDriverClient;
import org.apache.cassandra.stress.util.ResultLogger;
import org.apache.cassandra.db.ConsistencyLevel;

// Generic command settings - common to read/write/etc
public abstract class SettingsCommand implements Serializable
{

    public static enum TruncateWhen
    {
        NEVER, ONCE, ALWAYS
    }

    public final Command type;
    public final long count;
    public final long duration;
    public final TimeUnit durationUnits;
    public final boolean noWarmup;
    public final TruncateWhen truncate;
    public final ConsistencyLevel consistencyLevel;
    public final double targetUncertainty;
    public final int minimumUncertaintyMeasurements;
    public final int maximumUncertaintyMeasurements;

    public abstract OpDistributionFactory getFactory(StressSettings settings);

    public SettingsCommand(Command type, GroupedOptions options)
    {
        this(type, (Options) options,
                options instanceof Count ? (Count) options : null,
                options instanceof Duration ? (Duration) options : null,
                options instanceof Uncertainty ? (Uncertainty) options : null
        );
    }

    public SettingsCommand(Command type, Options options, Count count, Duration duration, Uncertainty uncertainty)
    {
        this.type = type;
        this.consistencyLevel = ConsistencyLevel.valueOf(options.consistencyLevel.value().toUpperCase());
        this.noWarmup = options.noWarmup.setByUser();
        this.truncate = TruncateWhen.valueOf(options.truncate.value().toUpperCase());

        if (count != null)
        {
            this.count = OptionDistribution.parseLong(count.count.value());
            this.duration = 0;
            this.durationUnits = null;
            this.targetUncertainty = -1;
            this.minimumUncertaintyMeasurements = -1;
            this.maximumUncertaintyMeasurements = -1;
        }
        else if (duration != null)
        {
            this.count = -1;
            this.duration = Long.parseLong(duration.duration.value().substring(0, duration.duration.value().length() - 1));
            switch (duration.duration.value().toLowerCase().charAt(duration.duration.value().length() - 1))
            {
                case 's':
                    this.durationUnits = TimeUnit.SECONDS;
                    break;
                case 'm':
                    this.durationUnits = TimeUnit.MINUTES;
                    break;
                case 'h':
                    this.durationUnits = TimeUnit.HOURS;
                    break;
                case 'd':
                    this.durationUnits = TimeUnit.DAYS;
                    break;
                default:
                    throw new IllegalStateException();
            }
            this.targetUncertainty = -1;
            this.minimumUncertaintyMeasurements = -1;
            this.maximumUncertaintyMeasurements = -1;
        }
        else
        {
            this.count = -1;
            this.duration = 0;
            this.durationUnits = null;
            this.targetUncertainty = Double.parseDouble(uncertainty.uncertainty.value());
            this.minimumUncertaintyMeasurements = Integer.parseInt(uncertainty.minMeasurements.value());
            this.maximumUncertaintyMeasurements = Integer.parseInt(uncertainty.maxMeasurements.value());
        }
    }

    // Option Declarations

    static abstract class Options extends GroupedOptions
    {
        final OptionSimple noWarmup = new OptionSimple("no-warmup", "", null, "Do not warmup the process", false);
        final OptionSimple truncate = new OptionSimple("truncate=", "never|once|always", "never", "Truncate the table: never, before performing any work, or before each iteration", false);
        final OptionSimple consistencyLevel = new OptionSimple("cl=", "ONE|QUORUM|LOCAL_QUORUM|EACH_QUORUM|ALL|ANY|TWO|THREE|LOCAL_ONE|SERIAL|LOCAL_SERIAL", "LOCAL_ONE", "Consistency level to use", false);
    }

    static class Count extends Options
    {
        final OptionSimple count = new OptionSimple("n=", "[0-9]+[bmk]?", null, "Number of operations to perform", true);
        @Override
        public List<? extends Option> options()
        {
            return Arrays.asList(count, noWarmup, truncate, consistencyLevel);
        }
    }

    static class Duration extends Options
    {
        final OptionSimple duration = new OptionSimple("duration=", "[0-9]+[smhd]", null, "Time to run in (in seconds, minutes, hours or days)", true);
        @Override
        public List<? extends Option> options()
        {
            return Arrays.asList(duration, noWarmup, truncate, consistencyLevel);
        }
    }

    static class Uncertainty extends Options
    {
        final OptionSimple uncertainty = new OptionSimple("err<", "0\\.[0-9]+", "0.02", "Run until the standard error of the mean is below this fraction", false);
        final OptionSimple minMeasurements = new OptionSimple("n>", "[0-9]+", "30", "Run at least this many iterations before accepting uncertainty convergence", false);
        final OptionSimple maxMeasurements = new OptionSimple("n<", "[0-9]+", "200", "Run at most this many iterations before accepting uncertainty convergence", false);
        @Override
        public List<? extends Option> options()
        {
            return Arrays.asList(uncertainty, minMeasurements, maxMeasurements, noWarmup, truncate, consistencyLevel);
        }
    }

    public abstract void truncateTables(StressSettings settings);

    protected void truncateTables(StressSettings settings, String ks, String ... tables)
    {
        JavaDriverClient client = settings.getJavaDriverClient(false);
        assert settings.command.truncate != SettingsCommand.TruncateWhen.NEVER;
        for (String table : tables)
        {
            String cql = String.format("TRUNCATE %s.%s", ks, table);
            client.execute(cql, org.apache.cassandra.db.ConsistencyLevel.ONE);
        }
        System.out.println(String.format("Truncated %s.%s. Sleeping %ss for propagation.",
                                         ks, Arrays.toString(tables), settings.node.nodes.size()));
        Uninterruptibles.sleepUninterruptibly(settings.node.nodes.size(), TimeUnit.SECONDS);
    }

    // CLI Utility Methods

    public void printSettings(ResultLogger out)
    {
        out.printf("  Type: %s%n", type.toString().toLowerCase());
        out.printf("  Count: %,d%n", count);
        if (durationUnits != null)
        {
            out.printf("  Duration: %,d %s%n", duration, durationUnits.toString());
        }
        out.printf("  No Warmup: %s%n", noWarmup);
        out.printf("  Consistency Level: %s%n", consistencyLevel.toString());
        if (targetUncertainty != -1)
        {
            out.printf("  Target Uncertainty: %.3f%n", targetUncertainty);
            out.printf("  Minimum Uncertainty Measurements: %,d%n", minimumUncertaintyMeasurements);
            out.printf("  Maximum Uncertainty Measurements: %,d%n", maximumUncertaintyMeasurements);
        } else {
            out.printf("  Target Uncertainty: not applicable%n");
        }
    }


    static SettingsCommand get(Map<String, String[]> clArgs)
    {
        for (Command cmd : Command.values())
        {
            if (cmd.category == null)
                continue;

            for (String name : cmd.names)
            {
                final String[] params = clArgs.remove(name);
                if (params == null)
                    continue;

                switch (cmd.category)
                {
                    case BASIC:
                        return SettingsCommandPreDefined.build(cmd, params);
                    case MIXED:
                        return SettingsCommandPreDefinedMixed.build(params);
                    case USER:
                        return SettingsCommandUser.build(params);
                }
            }
        }
        return null;
    }

    static void printHelp(Command type)
    {
        printHelp(type.toString().toLowerCase());
    }

    static void printHelp(String type)
    {
        GroupedOptions.printOptions(System.out, type.toLowerCase(), new Uncertainty(), new Count(), new Duration());
    }
}
