/*******************************************************************************
 * Copyright 2015 Netflix
 *
 * Licensed 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 com.netflix.dyno.contrib;

import com.netflix.config.DynamicStringProperty;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.dyno.connectionpool.ErrorRateMonitorConfig;
import com.netflix.dyno.connectionpool.RetryPolicy.RetryPolicyFactory;
import com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl;
import com.netflix.dyno.connectionpool.impl.RetryNTimes;
import com.netflix.dyno.connectionpool.impl.RunOnce;

public class ArchaiusConnectionPoolConfiguration extends ConnectionPoolConfigurationImpl {

    private static final Logger Logger = LoggerFactory.getLogger(ArchaiusConnectionPoolConfiguration.class);

    private static final String DynoPrefix = "dyno.";

    //private final DynamicIntProperty port;
    private final DynamicIntProperty maxConnsPerHost;
    private final DynamicIntProperty maxTimeoutWhenExhausted;
    private final DynamicIntProperty maxFailoverCount;
    private final DynamicIntProperty connectTimeout;
    private final DynamicIntProperty socketTimeout;
    private final DynamicBooleanProperty localZoneAffinity;
    private final DynamicIntProperty resetTimingsFrequency;
    private final DynamicStringProperty configPublisherConfig;
    private final DynamicIntProperty compressionThreshold;

    private final LoadBalancingStrategy loadBalanceStrategy;
    private final CompressionStrategy compressionStrategy;
    private final ErrorRateMonitorConfig errorRateConfig;
    private final RetryPolicyFactory retryPolicyFactory;
    private final DynamicBooleanProperty failOnStartupIfNoHosts;
    private final DynamicIntProperty lockVotingSize;

    private DynamicBooleanProperty isDualWriteEnabled;
    private DynamicStringProperty dualWriteClusterName;
    private DynamicIntProperty dualWritePercentage;

    public ArchaiusConnectionPoolConfiguration(String name) {
        super(name);

        String propertyPrefix = DynoPrefix + name;

        maxConnsPerHost = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".connection.maxConnsPerHost", super.getMaxConnsPerHost());
        maxTimeoutWhenExhausted = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".connection.maxTimeoutWhenExhausted", super.getMaxTimeoutWhenExhausted());
        maxFailoverCount = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".connection.maxFailoverCount", super.getMaxFailoverCount());
        connectTimeout = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".connection.connectTimeout", super.getConnectTimeout());
        socketTimeout = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".connection.socketTimeout", super.getSocketTimeout());
        localZoneAffinity = DynamicPropertyFactory.getInstance().getBooleanProperty(propertyPrefix + ".connection.localZoneAffinity", super.localZoneAffinity());
        resetTimingsFrequency = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".connection.metrics.resetFrequencySeconds", super.getTimingCountersResetFrequencySeconds());
        configPublisherConfig = DynamicPropertyFactory.getInstance().getStringProperty(propertyPrefix + ".config.publisher.address", super.getConfigurationPublisherConfig());
        failOnStartupIfNoHosts = DynamicPropertyFactory.getInstance().getBooleanProperty(propertyPrefix + ".config.startup.failIfNoHosts", super.getFailOnStartupIfNoHosts());
        compressionThreshold = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".config.compressionThreshold", super.getValueCompressionThreshold());
        lockVotingSize = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".config.lock.votingSize", super.getLockVotingSize());

        loadBalanceStrategy = parseLBStrategy(propertyPrefix);
        errorRateConfig = parseErrorRateMonitorConfig(propertyPrefix);
        retryPolicyFactory = parseRetryPolicyFactory(propertyPrefix);
        compressionStrategy = parseCompressionStrategy(propertyPrefix);

        isDualWriteEnabled = DynamicPropertyFactory.getInstance().getBooleanProperty(propertyPrefix + ".dualwrite.enabled", super.isDualWriteEnabled());
        dualWriteClusterName = DynamicPropertyFactory.getInstance().getStringProperty(propertyPrefix + ".dualwrite.cluster", super.getDualWriteClusterName());
        dualWritePercentage = DynamicPropertyFactory.getInstance().getIntProperty(propertyPrefix + ".dualwrite.percentage", super.getDualWritePercentage());
    }


    @Override
    public String getName() {
        return super.getName();
    }


    @Override
    public int getMaxConnsPerHost() {
        return maxConnsPerHost.get();
    }

    @Override
    public int getMaxTimeoutWhenExhausted() {
        return maxTimeoutWhenExhausted.get();
    }

    @Override
    public int getMaxFailoverCount() {
        return maxFailoverCount.get();
    }


    @Override
    public int getConnectTimeout() {
        return connectTimeout.get();
    }

    @Override
    public int getSocketTimeout() {
        return socketTimeout.get();
    }

    @Override
    public RetryPolicyFactory getRetryPolicyFactory() {
        return retryPolicyFactory;
    }

    @Override
    public boolean localZoneAffinity() {
        return localZoneAffinity.get();
    }

    @Override
    public LoadBalancingStrategy getLoadBalancingStrategy() {
        return loadBalanceStrategy;
    }

    @Override
    public CompressionStrategy getCompressionStrategy() {
        return compressionStrategy;
    }

    @Override
    public int getValueCompressionThreshold() {
        return compressionThreshold.get();
    }

    @Override
    public int getTimingCountersResetFrequencySeconds() {
        return resetTimingsFrequency.get();
    }

    @Override
    public String getConfigurationPublisherConfig() {
        return configPublisherConfig.get();
    }

    @Override
    public boolean getFailOnStartupIfNoHosts() {
        return failOnStartupIfNoHosts.get();
    }

    @Override
    public boolean isDualWriteEnabled() {
        return isDualWriteEnabled.get();
    }

    @Override
    public String getDualWriteClusterName() {
        return dualWriteClusterName.get();
    }

    @Override
    public int getDualWritePercentage() {
        return dualWritePercentage.get();
    }

    @Override
    public int getLockVotingSize() {
        return lockVotingSize.get();
    }

    public void setIsDualWriteEnabled(DynamicBooleanProperty booleanProperty) {
        this.isDualWriteEnabled = booleanProperty;
    }

    public void setDualWriteClusterName(DynamicStringProperty stringProperty) {
        this.dualWriteClusterName = stringProperty;
    }

    public void setDualWritePercentage(DynamicIntProperty intProperty) {
        this.dualWritePercentage = intProperty;
    }

    @Override
    public String toString() {
        return "ArchaiusConnectionPoolConfiguration{" +
                "name=" + getName() +
                ", maxConnsPerHost=" + maxConnsPerHost +
                ", maxTimeoutWhenExhausted=" + maxTimeoutWhenExhausted +
                ", maxFailoverCount=" + maxFailoverCount +
                ", connectTimeout=" + connectTimeout +
                ", socketTimeout=" + socketTimeout +
                ", localZoneAffinity=" + localZoneAffinity +
                ", resetTimingsFrequency=" + resetTimingsFrequency +
                ", configPublisherConfig=" + configPublisherConfig +
                ", compressionThreshold=" + compressionThreshold +
                ", loadBalanceStrategy=" + loadBalanceStrategy +
                ", compressionStrategy=" + compressionStrategy +
                ", errorRateConfig=" + errorRateConfig +
                ", retryPolicyFactory=" + retryPolicyFactory +
                ", failOnStartupIfNoHosts=" + failOnStartupIfNoHosts +
                ", isDualWriteEnabled=" + isDualWriteEnabled +
                ", dualWriteClusterName=" + dualWriteClusterName +
                ", dualWritePercentage=" + dualWritePercentage +
                '}';
    }

    private LoadBalancingStrategy parseLBStrategy(String propertyPrefix) {

        LoadBalancingStrategy defaultConfig = super.getLoadBalancingStrategy();

        String cfg =
                DynamicPropertyFactory.getInstance().getStringProperty(propertyPrefix + ".lbStrategy", defaultConfig.name()).get();

        LoadBalancingStrategy lb = null;
        try {
            lb = LoadBalancingStrategy.valueOf(cfg);
        } catch (Exception e) {
            Logger.warn("Unable to parse LoadBalancingStrategy: " + cfg + ", switching to default: " + defaultConfig.name());
            lb = defaultConfig;
        }

        return lb;
    }

    private CompressionStrategy parseCompressionStrategy(String propertyPrefix) {

        CompressionStrategy defaultCompStrategy = super.getCompressionStrategy();

        String cfg = DynamicPropertyFactory
                .getInstance()
                .getStringProperty(propertyPrefix + ".compressionStrategy", defaultCompStrategy.name()).get();

        CompressionStrategy cs = null;
        try {
            cs = CompressionStrategy.valueOf(cfg);
            Logger.info("Dyno configuration: CompressionStrategy = " + cs.name());
        } catch (IllegalArgumentException ex) {
            Logger.warn("Unable to parse CompressionStrategy: " + cfg + ", switching to default: " + defaultCompStrategy.name());
            cs = defaultCompStrategy;
        }

        return cs;

    }

    private ErrorRateMonitorConfig parseErrorRateMonitorConfig(String propertyPrefix) {
        String errorRateConfig = DynamicPropertyFactory.getInstance().getStringProperty(propertyPrefix + ".errorRateConfig", null).get();
        try {
            if (errorRateConfig == null) {
                return null;
            }

            // Classic format that is supported is json.

            JSONObject json = new JSONObject(errorRateConfig);

            int window = json.getInt("window");
            int frequency = json.getInt("frequency");
            int suppress = json.getInt("suppress");

            ErrorRateMonitorConfigImpl configImpl = new ErrorRateMonitorConfigImpl(window, frequency, suppress);

            JSONArray thresholds = json.getJSONArray("thresholds");
            for (int i = 0; i < thresholds.length(); i++) {

                JSONObject tConfig = thresholds.getJSONObject(i);

                int rps = tConfig.getInt("rps");
                int seconds = tConfig.getInt("seconds");
                int coverage = tConfig.getInt("coverage");

                configImpl.addThreshold(rps, seconds, coverage);
            }

            return configImpl;

        } catch (Exception e) {
            Logger.warn("Failed to parse error rate config: " + errorRateConfig, e);
        }

        return new ErrorRateMonitorConfigImpl();
    }

    private RetryPolicyFactory parseRetryPolicyFactory(String propertyPrefix) {

        String retryPolicy = DynamicPropertyFactory.getInstance().getStringProperty(propertyPrefix + ".retryPolicy", "RunOnce").get();

        if (retryPolicy.equals("RunOnce")) {
            return new RunOnce.RetryFactory();
        }

        if (retryPolicy.startsWith("RetryNTimes")) {

            String[] parts = retryPolicy.split(":");

            if (parts.length < 2) {
                return new RunOnce.RetryFactory();
            }

            try {

                int n = Integer.parseInt(parts[1]);
                boolean allowFallback = false;
                if (parts.length == 3) {
                    allowFallback = Boolean.parseBoolean(parts[2]);
                }
                return new RetryNTimes.RetryFactory(n, allowFallback);

            } catch (Exception e) {
                return new RunOnce.RetryFactory();
            }
        }

        return new RunOnce.RetryFactory();
    }


}