// 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 com.cloud.hypervisor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.hypervisor.xenserver.XenserverConfigs;
import org.apache.cloudstack.resourcedetail.dao.GuestOsDetailsDao;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.DettachCommand;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.cloud.agent.api.Command;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.GuestOSHypervisorVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.GuestOSHypervisorDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.template.VirtualMachineTemplate.BootloaderType;
import com.cloud.utils.Pair;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.dao.UserVmDao;

public class XenServerGuru extends HypervisorGuruBase implements HypervisorGuru, Configurable {

    private Logger logger = Logger.getLogger(getClass());

    @Inject
    private GuestOSDao guestOsDao;
    @Inject
    private GuestOSHypervisorDao guestOsHypervisorDao;
    @Inject
    private VolumeDao volumeDao;
    @Inject
    private PrimaryDataStoreDao storagePoolDao;
    @Inject
    private VolumeDataFactory volFactory;
    @Inject
    private UserVmDao userVmDao;
    @Inject
    private GuestOsDetailsDao guestOsDetailsDao;

    private static final ConfigKey<Integer> MaxNumberOfVCPUSPerVM = new ConfigKey<Integer>("Advanced", Integer.class, "xen.vm.vcpu.max", "16",
            "Maximum number of VCPUs that VM can get in XenServer.", true, ConfigKey.Scope.Cluster);

    @Override
    public HypervisorType getHypervisorType() {
        return HypervisorType.XenServer;
    }

    @Override
    public VirtualMachineTO implement(VirtualMachineProfile vm) {
        BootloaderType bt = BootloaderType.PyGrub;
        if (vm.getBootLoaderType() == BootloaderType.CD) {
            bt = vm.getBootLoaderType();
        }
        VirtualMachineTO to = toVirtualMachineTO(vm);
        UserVmVO userVmVO = userVmDao.findById(vm.getId());
        if (userVmVO != null) {
            HostVO host = hostDao.findById(userVmVO.getHostId());
            if (host != null) {
                to.setVcpuMaxLimit(MaxNumberOfVCPUSPerVM.valueIn(host.getClusterId()));
            }
        }

        to.setBootloader(bt);

        // Determine the VM's OS description
        GuestOSVO guestOS = guestOsDao.findByIdIncludingRemoved(vm.getVirtualMachine().getGuestOSId());

        Map<String, String> guestOsDetails = guestOsDetailsDao.listDetailsKeyPairs(vm.getVirtualMachine().getGuestOSId());

        to.setGuestOsDetails(guestOsDetails);

        to.setOs(guestOS.getDisplayName());
        HostVO host = hostDao.findById(vm.getVirtualMachine().getHostId());
        GuestOSHypervisorVO guestOsMapping = null;
        if (host != null) {
            guestOsMapping = guestOsHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), getHypervisorType().toString(), host.getHypervisorVersion());
        }
        if (guestOsMapping == null || host == null) {
            to.setPlatformEmulator(null);
        } else {
            to.setPlatformEmulator(guestOsMapping.getGuestOsName());
        }

        return to;
    }

    @Override
    public boolean trackVmHostChange() {
        return true;
    }

    @Override
    public List<Command> finalizeExpungeVolumes(VirtualMachine vm) {
        List<Command> commands = new ArrayList<Command>();

        List<VolumeVO> volumes = volumeDao.findByInstance(vm.getId());

        // it's OK in this case to send a detach command to the host for a root volume as this
        // will simply lead to the SR that supports the root volume being removed
        if (volumes != null) {
            for (VolumeVO volume : volumes) {
                StoragePoolVO storagePool = storagePoolDao.findById(volume.getPoolId());

                // storagePool should be null if we are expunging a volume that was never
                // attached to a VM that was started (the "trick" for storagePool to be null
                // is that none of the VMs this volume may have been attached to were ever started,
                // so the volume was never assigned to a storage pool)
                if (storagePool != null && storagePool.isManaged()) {
                    DataTO volTO = volFactory.getVolume(volume.getId()).getTO();
                    DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getPath(), volume.getVolumeType());

                    DettachCommand cmd = new DettachCommand(disk, vm.getInstanceName());

                    cmd.setManaged(true);

                    cmd.setStorageHost(storagePool.getHostAddress());
                    cmd.setStoragePort(storagePool.getPort());

                    cmd.set_iScsiName(volume.get_iScsiName());

                    commands.add(cmd);
                }
            }
        }

        return commands;
    }

    @Override
    public Pair<Boolean, Long> getCommandHostDelegation(long hostId, Command cmd) {
        boolean isCopyCommand = cmd instanceof CopyCommand;
        Pair<Boolean, Long> defaultHostToExecuteCommands = super.getCommandHostDelegation(hostId, cmd);
        if (!isCopyCommand) {
            logger.debug("We are returning the default host to execute commands because the command is not of Copy type.");
            return defaultHostToExecuteCommands;
        }
        CopyCommand copyCommand = (CopyCommand)cmd;
        DataTO srcData = copyCommand.getSrcTO();
        DataTO destData = copyCommand.getDestTO();

        boolean isSourceDataHypervisorXenServer = srcData.getHypervisorType() == HypervisorType.XenServer;
        if (!isSourceDataHypervisorXenServer) {
            logger.debug("We are returning the default host to execute commands because the target hypervisor of the source data is not XenServer.");
            return defaultHostToExecuteCommands;
        }
        // only now can we decide, now we now we're only deciding for ourselves
        if (cmd instanceof StorageSubSystemCommand) {
            if (s_logger.isTraceEnabled()) {
                s_logger.trace(String.format("XenServer StrorageSubSystemCommand re always executed in sequence (command of type %s to host %l).", cmd.getClass(), hostId));
            }
            StorageSubSystemCommand c = (StorageSubSystemCommand)cmd;
            c.setExecuteInSequence(true);
        }
        DataStoreTO srcStore = srcData.getDataStore();
        DataStoreTO destStore = destData.getDataStore();
        boolean isSourceAndDestinationNfsObjects = srcStore instanceof NfsTO && destStore instanceof NfsTO;
        if (!isSourceAndDestinationNfsObjects) {
            logger.debug("We are returning the default host to execute commands because the source and destination objects are not NFS type.");
            return defaultHostToExecuteCommands;
        }
        boolean isSourceObjectSnapshotTypeAndDestinationObjectTemplateType = srcData.getObjectType() == DataObjectType.SNAPSHOT
                && destData.getObjectType() == DataObjectType.TEMPLATE;
        if (!isSourceObjectSnapshotTypeAndDestinationObjectTemplateType) {
            logger.debug("We are returning the default host to execute commands because the source and destination objects are not snapshot and template respectively.");
            return defaultHostToExecuteCommands;
        }
        HostVO defaultHostToExecuteCommand = hostDao.findById(hostId);

        HostVO hostCandidateToExecutedCommand = hostDao.findHostInZoneToExecuteCommand(defaultHostToExecuteCommand.getDataCenterId(), srcData.getHypervisorType());
        hostDao.loadDetails(hostCandidateToExecutedCommand);
        String hypervisorVersion = hostCandidateToExecutedCommand.getHypervisorVersion();
        if (StringUtils.isBlank(hypervisorVersion)) {
            logger.debug("We are returning the default host to execute commands because the hypervisor version is blank.");
            return defaultHostToExecuteCommands;
        }
        boolean isXenServer610 = StringUtils.equals(hypervisorVersion, "6.1.0");
        if (isXenServer610) {
            logger.debug("We are returning the default host to execute commands because the hypervisor version is 6.1.0.");
            return defaultHostToExecuteCommands;
        }
        String snapshotHotFixVersion = hostCandidateToExecutedCommand.getDetail(XenserverConfigs.XS620HotFix);
        boolean isXenServer620 = StringUtils.equals(hypervisorVersion, "6.2.0");
        if (isXenServer620 && !StringUtils.equalsIgnoreCase(XenserverConfigs.XSHotFix62ESP1004, snapshotHotFixVersion)) {
            logger.debug(String.format("We are returning the default host to execute commands because the hypervisor version is not 6.2.0 with hotfix ESP1004 [hypervisorVersion=%s, hotfixVersion=%s]", hypervisorVersion, snapshotHotFixVersion));
            return defaultHostToExecuteCommands;
        }
        logger.debug(String.format("We are changing the hostId to executed command from %d to %d.", hostId, hostCandidateToExecutedCommand.getId()));
        return new Pair<Boolean, Long>(Boolean.TRUE, new Long(hostCandidateToExecutedCommand.getId()));
    }

    @Override
    public String getConfigComponentName() {
        return XenServerGuru.class.getSimpleName();
    }

    @Override
    public ConfigKey<?>[] getConfigKeys() {
        return new ConfigKey<?>[] {MaxNumberOfVCPUSPerVM};
    }
}
