/*
 * 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.brooklyn.policy.ha;

import org.apache.brooklyn.api.catalog.Catalog;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.BasicConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.sensor.BasicNotificationSensor;
import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.time.Duration;

import com.google.common.net.HostAndPort;

/**
 * Monitors a given {@link HostAndPort}, to emit HASensors.CONNECTION_FAILED and HASensors.CONNECTION_RECOVERED 
 * if the connection is lost/restored.
 */
@Catalog(name="Connection Failure Detector", description="HA policy for monitoring a host:port, "
        + "emitting an event if the connection is lost/restored")
public class ConnectionFailureDetector extends AbstractFailureDetector {

    public static final ConfigKey<HostAndPort> ENDPOINT = ConfigKeys.newConfigKey(
            HostAndPort.class, 
            "connectionFailureDetector.endpoint",
            "The host:port to be checked periodically");

    public static final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newConfigKey(
            Duration.class, 
            "connectionFailureDetector.pollPeriod", 
            "The poll period for checking that the endpoint is reachable", 
            Duration.ONE_SECOND);

    public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = HASensors.CONNECTION_FAILED;

    public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = HASensors.CONNECTION_RECOVERED;

    @SetFromFlag("connectionFailedStabilizationDelay")
    public static final ConfigKey<Duration> CONNECTION_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
            .name("connectionFailureDetector.serviceFailedStabilizationDelay")
            .description("Time period for which the connection must be consistently down for "
                    + "(e.g. doesn't report down-up-down) before concluding failure. "
                    + "Note that long TCP timeouts mean there can be long (e.g. 70 second) "
                    + "delays in noticing a connection refused condition.")
            .defaultValue(Duration.ZERO)
            .build();

    @SetFromFlag("connectionRecoveredStabilizationDelay")
    public static final ConfigKey<Duration> CONNECTION_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
            .name("connectionFailureDetector.serviceRecoveredStabilizationDelay")
            .description("For a failed connection, time period for which the connection must be consistently up for "
                    + "(e.g. doesn't report up-down-up) before concluding recovered")
            .defaultValue(Duration.ZERO)
            .build();

    @Override
    public void init() {
        super.init();
        getRequiredConfig(ENDPOINT); // just to confirm it's set, failing fast
        if (config().getRaw(SENSOR_FAILED).isAbsent()) {
            config().set(SENSOR_FAILED, CONNECTION_FAILED);
        }
        if (config().getRaw(SENSOR_RECOVERED).isAbsent()) {
            config().set(SENSOR_RECOVERED, CONNECTION_RECOVERED);
        }
    }

    @Override
    protected CalculatedStatus calculateStatus() {
        HostAndPort endpoint = getConfig(ENDPOINT);
        boolean isHealthy = Networking.isReachable(endpoint);
        return new BasicCalculatedStatus(isHealthy, "endpoint=" + endpoint);
    }

    //Persistence compatibility overrides
    @Override
    protected Duration getPollPeriod() {
        return getConfig(POLL_PERIOD);
    }

    @Override
    protected Duration getFailedStabilizationDelay() {
        return getConfig(CONNECTION_FAILED_STABILIZATION_DELAY);
    }

    @Override
    protected Duration getRecoveredStabilizationDelay() {
        return getConfig(CONNECTION_RECOVERED_STABILIZATION_DELAY);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Sensor<FailureDescriptor> getSensorFailed() {
        Maybe<Object> sensorFailed = config().getRaw(SENSOR_FAILED);
        if (sensorFailed.isPresent()) {
            return (Sensor<FailureDescriptor>)sensorFailed.get();
        } else {
            return CONNECTION_FAILED;
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Sensor<FailureDescriptor> getSensorRecovered() {
        Maybe<Object> sensorRecovered = config().getRaw(SENSOR_RECOVERED);
        if (sensorRecovered.isPresent()) {
            return (Sensor<FailureDescriptor>)sensorRecovered.get();
        } else {
            return CONNECTION_RECOVERED;
        }
    }

}
