/*
 * 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.cassandra.schema;

import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Objects;

import org.apache.cassandra.exceptions.ConfigurationException;

import static java.lang.String.format;

public final class SpeculativeRetryParam
{
    public enum Kind
    {
        NONE, CUSTOM, PERCENTILE, ALWAYS
    }

    public static final SpeculativeRetryParam NONE = none();
    public static final SpeculativeRetryParam ALWAYS = always();
    public static final SpeculativeRetryParam DEFAULT = percentile(99);

    private final Kind kind;
    private final double value;

    // pre-processed (divided by 100 for PERCENTILE), multiplied by 1M for CUSTOM (to nanos)
    private final double threshold;

    private SpeculativeRetryParam(Kind kind, double value)
    {
        this.kind = kind;
        this.value = value;

        if (kind == Kind.PERCENTILE)
            threshold = value / 100;
        else if (kind == Kind.CUSTOM)
            threshold = TimeUnit.MILLISECONDS.toNanos((long) value);
        else
            threshold = value;
    }

    public Kind kind()
    {
        return kind;
    }

    public double threshold()
    {
        return threshold;
    }

    public static SpeculativeRetryParam none()
    {
        return new SpeculativeRetryParam(Kind.NONE, 0);
    }

    public static SpeculativeRetryParam always()
    {
        return new SpeculativeRetryParam(Kind.ALWAYS, 0);
    }

    public static SpeculativeRetryParam custom(double value)
    {
        return new SpeculativeRetryParam(Kind.CUSTOM, value);
    }

    public static SpeculativeRetryParam percentile(double value)
    {
        return new SpeculativeRetryParam(Kind.PERCENTILE, value);
    }

    public static SpeculativeRetryParam fromString(String value)
    {
        if (value.toLowerCase().endsWith("ms"))
        {
            try
            {
                return custom(Double.parseDouble(value.substring(0, value.length() - "ms".length())));
            }
            catch (IllegalArgumentException e)
            {
                throw new ConfigurationException(format("Invalid value %s for option '%s'", value, TableParams.Option.SPECULATIVE_RETRY));
            }
        }

        if (value.toUpperCase().endsWith(Kind.PERCENTILE.toString()))
        {
            double threshold;
            try
            {
                threshold = Double.parseDouble(value.substring(0, value.length() - Kind.PERCENTILE.toString().length()));
            }
            catch (IllegalArgumentException e)
            {
                throw new ConfigurationException(format("Invalid value %s for option '%s'", value, TableParams.Option.SPECULATIVE_RETRY));
            }

            if (threshold >= 0.0 && threshold <= 100.0)
                return percentile(threshold);

            throw new ConfigurationException(format("Invalid value %s for PERCENTILE option '%s': must be between 0.0 and 100.0",
                                                    value,
                                                    TableParams.Option.SPECULATIVE_RETRY));
        }

        if (value.equals(Kind.NONE.toString()))
            return NONE;

        if (value.equals(Kind.ALWAYS.toString()))
            return ALWAYS;

        throw new ConfigurationException(format("Invalid value %s for option '%s'", value, TableParams.Option.SPECULATIVE_RETRY));
    }

    @Override
    public boolean equals(Object o)
    {
        if (!(o instanceof SpeculativeRetryParam))
            return false;
        SpeculativeRetryParam srp = (SpeculativeRetryParam) o;
        return kind == srp.kind && threshold == srp.threshold;
    }

    @Override
    public int hashCode()
    {
        return Objects.hashCode(kind, threshold);
    }

    @Override
    public String toString()
    {
        switch (kind)
        {
            case CUSTOM:
                return format("%sms", value);
            case PERCENTILE:
                return format("%sPERCENTILE", new DecimalFormat("#.#####").format(value));
            default: // NONE and ALWAYS
                return kind.toString();
        }
    }
}
