// 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.cloudstack.ha;

import com.cloud.api.response.SimulatorHAStateResponse;
import com.cloud.host.Host;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.utils.fsm.StateListener;
import com.cloud.utils.fsm.StateMachine2;
import org.apache.cloudstack.ha.provider.HACheckerException;
import org.apache.cloudstack.ha.provider.HAFenceException;
import org.apache.cloudstack.ha.provider.HAProvider;
import org.apache.cloudstack.ha.provider.HARecoveryException;
import org.apache.cloudstack.ha.provider.host.HAAbstractHostProvider;
import org.joda.time.DateTime;

import javax.inject.Inject;
import java.security.InvalidParameterException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SimulatorHAProvider extends HAAbstractHostProvider implements HAProvider<Host>, StateListener<HAConfig.HAState, HAConfig.Event, HAConfig> {

    @Inject
    private HAManager haManager;

    private final Map<Long, SimulatorHAState> hostHAStateMap = new ConcurrentHashMap<>();

    public SimulatorHAProvider() {
        HAConfig.HAState.getStateMachine().registerListener(this);
    }

    public void setHAStateForHost(final Long hostId, final SimulatorHAState state) {
        hostHAStateMap.put(hostId, state);
        haManager.purgeHACounter(hostId, HAResource.ResourceType.Host);
    }

    public List<SimulatorHAStateResponse> listHAStateTransitions(final Long hostId) {
        final SimulatorHAState haState = hostHAStateMap.get(hostId);
        if (haState == null) {
            return Collections.emptyList();
        }
        return haState.listRecentStateTransitions();
    }

    @Override
    public HAResource.ResourceType resourceType() {
        return HAResource.ResourceType.Host;
    }

    @Override
    public HAResource.ResourceSubType resourceSubType() {
        return HAResource.ResourceSubType.Simulator;
    }

    @Override
    public boolean isEligible(final Host host) {
        if (host == null) {
            return false;
        }
        final SimulatorHAState haState = hostHAStateMap.get(host.getId());
        return !isInMaintenanceMode(host) && !isDisabled(host) && haState != null
                && Hypervisor.HypervisorType.Simulator.equals(host.getHypervisorType());
    }

    @Override
    public boolean isHealthy(final Host host) throws HACheckerException {
        final SimulatorHAState haState = hostHAStateMap.get(host.getId());
        return haState != null && haState.isHealthy();
    }

    @Override
    public boolean hasActivity(final Host host, final DateTime afterThis) throws HACheckerException {
        final SimulatorHAState haState = hostHAStateMap.get(host.getId());
        return haState != null && haState.hasActivity();
    }

    @Override
    public boolean recover(final Host host) throws HARecoveryException {
        final SimulatorHAState haState = hostHAStateMap.get(host.getId());
        return haState != null && haState.canRecover();
    }

    @Override
    public boolean fence(final Host host) throws HAFenceException {
        final SimulatorHAState haState = hostHAStateMap.get(host.getId());
        return haState != null && haState.canFenced();
    }

    @Override
    public Object getConfigValue(final HAProvider.HAProviderConfig name, final Host host) {
        switch (name) {
            case HealthCheckTimeout:
                return 5L;
            case ActivityCheckTimeout:
                return 5L;
            case RecoveryTimeout:
                return 5L;
            case FenceTimeout:
                return 5L;
            case MaxActivityCheckInterval:
                return 1L;
            case MaxActivityChecks:
                return 3L;
            case ActivityCheckFailureRatio:
                final SimulatorHAState haState = hostHAStateMap.get(host.getId());
                return (haState != null && haState.hasActivity()) ? 1.0 : 0.0;
            case MaxDegradedWaitTimeout:
                return 1L;
            case MaxRecoveryAttempts:
                return 2L;
            case RecoveryWaitTimeout:
                return 1L;
            default:
                throw new InvalidParameterException("Unknown HAProviderConfig " + name.toString());
        }
    }

    private boolean addStateTransition(final HAConfig vo, final boolean status,
                                       final HAConfig.HAState oldState, final HAConfig.HAState newState, final HAConfig.Event event) {
        if (vo.getResourceType() != HAResource.ResourceType.Host) {
            return false;
        }
        final SimulatorHAState haState = hostHAStateMap.get(vo.getResourceId());
        if (haState == null || !status) {
            return false;
        }
        final HAResourceCounter counter = haManager.getHACounter(vo.getResourceId(), vo.getResourceType());
        return haState.addStateTransition(newState, oldState, event, counter);
    }

    @Override
    public boolean preStateTransitionEvent(final HAConfig.HAState oldState, final HAConfig.Event event,
                                           final HAConfig.HAState newState, final HAConfig vo, final boolean status, final Object opaque) {
        return addStateTransition(vo, status, oldState, newState, event);
    }

    @Override
    public boolean postStateTransitionEvent(final StateMachine2.Transition<HAConfig.HAState, HAConfig.Event> transition,
                                            final HAConfig vo, final boolean status, final Object opaque) {
        return addStateTransition(vo, status, transition.getCurrentState(), transition.getToState(), transition.getEvent());
    }
}