// 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.vmware.mo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;

import com.vmware.vim25.CustomFieldStringValue;
import com.vmware.vim25.DatacenterConfigInfo;
import com.vmware.vim25.DVPortgroupConfigInfo;
import com.vmware.vim25.DistributedVirtualSwitchPortConnection;
import com.vmware.vim25.DynamicProperty;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.ObjectContent;
import com.vmware.vim25.ObjectSpec;
import com.vmware.vim25.PropertyFilterSpec;
import com.vmware.vim25.PropertySpec;
import com.vmware.vim25.SelectionSpec;
import com.vmware.vim25.TraversalSpec;
import com.vmware.vim25.VirtualEthernetCardDistributedVirtualPortBackingInfo;

import com.cloud.hypervisor.vmware.util.VmwareContext;
import com.cloud.utils.Pair;

public class DatacenterMO extends BaseMO {

    public DatacenterMO(VmwareContext context, ManagedObjectReference morDc) {
        super(context, morDc);
    }

    public DatacenterMO(VmwareContext context, String morType, String morValue) {
        super(context, morType, morValue);
    }

    public DatacenterMO(VmwareContext context, String dcName) throws Exception {
        super(context, null);

        _mor = _context.getVimClient().getDecendentMoRef(_context.getRootFolder(), "Datacenter", dcName);
        if (_mor == null) {
            logger.error("Unable to locate DC {}", dcName);
        }
    }

    @Override
    public String getName() throws Exception {
        return (String)_context.getVimClient().getDynamicProperty(_mor, "name");
    }

    public void registerTemplate(ManagedObjectReference morHost, String datastoreName, String templateName, String templateFileName) throws Exception {

        ManagedObjectReference morFolder = (ManagedObjectReference)_context.getVimClient().getDynamicProperty(_mor, "vmFolder");
        assert (morFolder != null);

        ManagedObjectReference morTask =
            _context.getService()
                .registerVMTask(morFolder, String.format("[%s] %s/%s", datastoreName, templateName, templateFileName), templateName, true, null, morHost);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (!result) {
            throw new Exception("Unable to register template due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        } else {
            _context.waitForTaskProgressDone(morTask);
        }
    }

    public VirtualMachineMO findVm(String vmName) throws Exception {
        int key = getCustomFieldKey("VirtualMachine", CustomFieldConstants.CLOUD_VM_INTERNAL_NAME);
        if (key == 0) {
            logger.warn("Custom field " + CustomFieldConstants.CLOUD_VM_INTERNAL_NAME + " is not registered ?!");
        }
        String instanceNameCustomField = "value[" + key + "]";
        List<ObjectContent> ocs = getVmPropertiesOnDatacenterVmFolder(new String[] {"name", instanceNameCustomField});
        return HypervisorHostHelper.findVmFromObjectContent(_context, ocs.toArray(new ObjectContent[0]), vmName, instanceNameCustomField);
    }

    public List<VirtualMachineMO> findVmByNameAndLabel(String vmLabel) throws Exception {
        CustomFieldsManagerMO cfmMo = new CustomFieldsManagerMO(_context, _context.getServiceContent().getCustomFieldsManager());
        int key = cfmMo.getCustomFieldKey("VirtualMachine", CustomFieldConstants.CLOUD_UUID);
        assert (key != 0);

        List<VirtualMachineMO> list = new ArrayList<>();

        List<ObjectContent> ocs = getVmPropertiesOnDatacenterVmFolder(new String[] {"name", String.format("value[%d]", key)});
        if (ocs != null && ocs.size() > 0) {
            for (ObjectContent oc : ocs) {
                List<DynamicProperty> props = oc.getPropSet();
                if (props != null) {
                    for (DynamicProperty prop : props) {
                        if (prop.getVal() != null) {
                            if (prop.getName().equalsIgnoreCase("name")) {
                                if (prop.getVal().toString().equals(vmLabel)) {
                                    list.add(new VirtualMachineMO(_context, oc.getObj()));
                                    break;        // break out inner loop
                                }
                            } else if (prop.getVal() instanceof CustomFieldStringValue) {
                                String val = ((CustomFieldStringValue)prop.getVal()).getValue();
                                if (val.equals(vmLabel)) {
                                    list.add(new VirtualMachineMO(_context, oc.getObj()));
                                    break;        // break out inner loop
                                }
                            }
                        }
                    }
                }
            }
        }
        return list;
    }

    public VirtualMachineMO checkIfVmAlreadyExistsInVcenter(String vmNameOnVcenter, String vmNameInCS) throws Exception {
        int key = getCustomFieldKey("VirtualMachine", CustomFieldConstants.CLOUD_VM_INTERNAL_NAME);
        if (key == 0) {
            logger.warn("Custom field " + CustomFieldConstants.CLOUD_VM_INTERNAL_NAME + " is not registered ?!");
        }

        List<ObjectContent> ocs = getVmPropertiesOnDatacenterVmFolder(new String[] {"name", String.format("value[%d]", key)});
        if (ocs != null && ocs.size() > 0) {
            for (ObjectContent oc : ocs) {
                List<DynamicProperty> props = oc.getPropSet();
                if (props != null) {
                    String vmVcenterName = null;
                    String vmInternalCSName = null;
                    for (DynamicProperty prop : props) {
                        if (prop.getName().equals("name")) {
                            vmVcenterName = prop.getVal().toString();
                        }
                        if (prop.getName().startsWith("value[") && prop.getVal() != null) {
                            vmInternalCSName = ((CustomFieldStringValue)prop.getVal()).getValue();
                        }
                    }
                    if (vmNameOnVcenter.equals(vmVcenterName)) {
                        if (vmInternalCSName != null && !vmInternalCSName.isEmpty() && !vmNameInCS.equals(vmInternalCSName)) {
                            return (new VirtualMachineMO(_context, oc.getObj()));
                        }
                    }
                }
            }
        }
        return null;
    }

    public List<UnmanagedInstanceTO> getAllVmsOnDatacenter(String keyword) throws Exception {
        List<ObjectContent> ocs = getVmPropertiesOnDatacenterVmFolder(propertyPathsForUnmanagedVmsThinListing);
        return convertVmsObjectContentsToUnmanagedInstances(ocs, keyword);
    }

    public List<HostMO> getAllHostsOnDatacenter() throws Exception {
        List<HostMO> hosts = new ArrayList<>();

        List<ObjectContent> ocs = getHostPropertiesOnDatacenterHostFolder(new String[] {"name"});
        if (CollectionUtils.isNotEmpty(ocs)) {
            for (ObjectContent oc : ocs) {
                hosts.add(new HostMO(getContext(), oc.getObj()));
            }
        }

        return hosts;
    }

    public ManagedObjectReference findDatastore(String name) throws Exception {
        assert (name != null);

        List<ObjectContent> ocs = getDatastorePropertiesOnDatacenter(new String[] {"name"});
        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                if (oc.getPropSet().get(0).getVal().toString().equals(name)) {
                    return oc.getObj();
                }
            }
        }
        return null;
    }

    public ManagedObjectReference listDatastore(String name) throws Exception {
        assert (name != null);

        List<ObjectContent> ocs = getDatastorePropertiesOnDatacenter(new String[] {"name"});
        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                if (oc.getPropSet().get(0).getVal().toString().equals(name)) {
                    return oc.getObj();
                }
            }
        }
        return null;
    }

    public ManagedObjectReference findHost(String name) throws Exception {
        List<ObjectContent> ocs = getHostPropertiesOnDatacenterHostFolder(new String[] {"name"});

        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                if (oc.getPropSet().get(0).getVal().toString().equals(name)) {
                    return oc.getObj();
                }
            }
        }
        return null;
    }

    public ManagedObjectReference getVmFolder() throws Exception {
        return (ManagedObjectReference)_context.getVimClient().getDynamicProperty(_mor, "vmFolder");
    }

    public List<ObjectContent> getHostPropertiesOnDatacenterHostFolder(String[] propertyPaths) throws Exception {
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("HostSystem");
        pSpec.getPathSet().addAll(Arrays.asList(propertyPaths));

        TraversalSpec computeResource2HostTraversal = new TraversalSpec();
        computeResource2HostTraversal.setType("ComputeResource");
        computeResource2HostTraversal.setPath("host");
        computeResource2HostTraversal.setName("computeResource2HostTraversal");

        SelectionSpec recurseFolders = new SelectionSpec();
        recurseFolders.setName("folder2childEntity");

        TraversalSpec folder2childEntity = new TraversalSpec();
        folder2childEntity.setType("Folder");
        folder2childEntity.setPath("childEntity");
        folder2childEntity.setName(recurseFolders.getName());
        folder2childEntity.getSelectSet().add(recurseFolders);
        folder2childEntity.getSelectSet().add(computeResource2HostTraversal);

        TraversalSpec dc2HostFolderTraversal = new TraversalSpec();
        dc2HostFolderTraversal.setType("Datacenter");
        dc2HostFolderTraversal.setPath("hostFolder");
        dc2HostFolderTraversal.setName("dc2HostFolderTraversal");
        dc2HostFolderTraversal.getSelectSet().add(folder2childEntity);

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(dc2HostFolderTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        return _context.getService().retrieveProperties(_context.getPropertyCollector(), pfSpecArr);

    }

    public List<ObjectContent> getDatastorePropertiesOnDatacenter(String[] propertyPaths) throws Exception {

        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("Datastore");
        pSpec.getPathSet().addAll(Arrays.asList(propertyPaths));

        TraversalSpec dc2DatastoreTraversal = new TraversalSpec();
        dc2DatastoreTraversal.setType("Datacenter");
        dc2DatastoreTraversal.setPath("datastore");
        dc2DatastoreTraversal.setName("dc2DatastoreTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(dc2DatastoreTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        return _context.getService().retrieveProperties(_context.getPropertyCollector(), pfSpecArr);

    }

    public List<ObjectContent> getVmPropertiesOnDatacenterVmFolder(String[] propertyPaths) throws Exception {
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("VirtualMachine");
        pSpec.getPathSet().addAll(Arrays.asList(propertyPaths));

        TraversalSpec dc2VmFolderTraversal = new TraversalSpec();
        dc2VmFolderTraversal.setType("Datacenter");
        dc2VmFolderTraversal.setPath("vmFolder");
        dc2VmFolderTraversal.setName("dc2VmFolderTraversal");

        SelectionSpec recurseFolders = new SelectionSpec();
        recurseFolders.setName("folder2childEntity");

        TraversalSpec folder2childEntity = new TraversalSpec();
        folder2childEntity.setType("Folder");
        folder2childEntity.setPath("childEntity");
        folder2childEntity.setName(recurseFolders.getName());
        folder2childEntity.getSelectSet().add(recurseFolders);
        dc2VmFolderTraversal.getSelectSet().add(folder2childEntity);

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(dc2VmFolderTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        return _context.getService().retrieveProperties(_context.getPropertyCollector(), pfSpecArr);
    }

    public static Pair<DatacenterMO, String> getOwnerDatacenter(VmwareContext context, ManagedObjectReference morEntity) throws Exception {

        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("Datacenter");
        pSpec.getPathSet().add("name");

        TraversalSpec entityParentTraversal = new TraversalSpec();
        entityParentTraversal.setType("ManagedEntity");
        entityParentTraversal.setPath("parent");
        entityParentTraversal.setName("entityParentTraversal");
        SelectionSpec selSpec = new SelectionSpec();
        selSpec.setName("entityParentTraversal");
        entityParentTraversal.getSelectSet().add(selSpec);

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(morEntity);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(entityParentTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = context.getService().retrieveProperties(context.getPropertyCollector(), pfSpecArr);

        assert (ocs != null && ocs.size() > 0);
        assert (ocs.get(0).getObj() != null);
        assert (ocs.get(0).getPropSet().get(0) != null);
        assert (ocs.get(0).getPropSet().get(0).getVal() != null);

        String dcName = ocs.get(0).getPropSet().get(0).getVal().toString();
        return new Pair<>(new DatacenterMO(context, ocs.get(0).getObj()), dcName);
    }

    public ManagedObjectReference getDvPortGroupMor(String dvPortGroupName) throws Exception {
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("DistributedVirtualPortgroup");
        pSpec.getPathSet().add("name");

        TraversalSpec datacenter2DvPortGroupTraversal = new TraversalSpec();
        datacenter2DvPortGroupTraversal.setType("Datacenter");
        datacenter2DvPortGroupTraversal.setPath("network");
        datacenter2DvPortGroupTraversal.setName("datacenter2DvPortgroupTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(datacenter2DvPortGroupTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(), pfSpecArr);

        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                List<DynamicProperty> props = oc.getPropSet();
                if (props != null) {
                    for (DynamicProperty prop : props) {
                        if (prop.getVal().equals(dvPortGroupName))
                            return oc.getObj();
                    }
                }
            }
        }
        return null;
    }

    public boolean hasDvPortGroup(String dvPortGroupName) throws Exception {
        ManagedObjectReference morNetwork = getDvPortGroupMor(dvPortGroupName);
        if (morNetwork != null)
            return true;
        return false;
    }

    public DVPortgroupConfigInfo getDvPortGroupSpec(String dvPortGroupName) throws Exception {
        DVPortgroupConfigInfo configSpec = null;
        String nameProperty = null;
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("DistributedVirtualPortgroup");
        pSpec.getPathSet().add("name");
        pSpec.getPathSet().add("config");

        TraversalSpec datacenter2DvPortGroupTraversal = new TraversalSpec();
        datacenter2DvPortGroupTraversal.setType("Datacenter");
        datacenter2DvPortGroupTraversal.setPath("network");
        datacenter2DvPortGroupTraversal.setName("datacenter2DvPortgroupTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(datacenter2DvPortGroupTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(), pfSpecArr);

        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                List<DynamicProperty> props = oc.getPropSet();
                if (props != null) {
                    assert (props.size() == 2);
                    for (DynamicProperty prop : props) {
                        if (prop.getName().equals("config")) {
                            configSpec = (DVPortgroupConfigInfo)prop.getVal();
                        } else {
                            nameProperty = prop.getVal().toString();
                        }
                    }
                    if (nameProperty.equalsIgnoreCase(dvPortGroupName)) {
                        return configSpec;
                    }
                }
            }
        }
        return null;
    }

    public ManagedObjectReference getDvSwitchMor(ManagedObjectReference dvPortGroupMor) throws Exception {
        String dvPortGroupKey = null;
        ManagedObjectReference dvSwitchMor = null;
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("DistributedVirtualPortgroup");
        pSpec.getPathSet().add("key");
        pSpec.getPathSet().add("config.distributedVirtualSwitch");

        TraversalSpec datacenter2DvPortGroupTraversal = new TraversalSpec();
        datacenter2DvPortGroupTraversal.setType("Datacenter");
        datacenter2DvPortGroupTraversal.setPath("network");
        datacenter2DvPortGroupTraversal.setName("datacenter2DvPortgroupTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(datacenter2DvPortGroupTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(), pfSpecArr);

        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                List<DynamicProperty> props = oc.getPropSet();
                if (props != null) {
                    assert (props.size() == 2);
                    for (DynamicProperty prop : props) {
                        if (prop.getName().equals("key")) {
                            dvPortGroupKey = (String)prop.getVal();
                        } else {
                            dvSwitchMor = (ManagedObjectReference)prop.getVal();
                        }
                    }
                    if ((dvPortGroupKey != null) && dvPortGroupKey.equals(dvPortGroupMor.getValue())) {
                        return dvSwitchMor;
                    }
                }
            }
        }
        return null;
    }

    public String getDvSwitchUuid(ManagedObjectReference dvSwitchMor) throws Exception {
        assert (dvSwitchMor != null);
        return (String)_context.getVimClient().getDynamicProperty(dvSwitchMor, "uuid");
    }

    public VirtualEthernetCardDistributedVirtualPortBackingInfo getDvPortBackingInfo(Pair<ManagedObjectReference, String> networkInfo) throws Exception {
        assert (networkInfo != null);
        assert (networkInfo.first() != null && networkInfo.first().getType().equalsIgnoreCase("DistributedVirtualPortgroup"));
        final VirtualEthernetCardDistributedVirtualPortBackingInfo dvPortBacking = new VirtualEthernetCardDistributedVirtualPortBackingInfo();
        final DistributedVirtualSwitchPortConnection dvPortConnection = new DistributedVirtualSwitchPortConnection();
        ManagedObjectReference dvsMor = getDvSwitchMor(networkInfo.first());
        String dvSwitchUuid = getDvSwitchUuid(dvsMor);
        dvPortConnection.setSwitchUuid(dvSwitchUuid);
        dvPortConnection.setPortgroupKey(networkInfo.first().getValue());
        dvPortBacking.setPort(dvPortConnection);
        System.out.println("Plugging NIC device into network " + networkInfo.second() + " backed by dvSwitch: " + dvSwitchUuid);
        return dvPortBacking;
    }

    public ManagedObjectReference getDvSwitchMor(String dvSwitchName) throws Exception {
        ManagedObjectReference dvSwitchMor = null;
        ManagedObjectReference networkFolderMor = null;
        networkFolderMor = _context.getVimClient().getMoRefProp(_mor, "networkFolder");
        dvSwitchMor = _context.getVimClient().getDecendentMoRef(networkFolderMor, "VmwareDistributedVirtualSwitch", dvSwitchName);
        return dvSwitchMor;
    }

    public boolean ensureCustomFieldDef(String fieldName) throws Exception {
        CustomFieldsManagerMO cfmMo = new CustomFieldsManagerMO(_context, _context.getServiceContent().getCustomFieldsManager());
        return cfmMo.ensureCustomFieldDef("Datacenter", fieldName) > 0;
    }

    public DatacenterConfigInfo getDatacenterConfigInfo() throws Exception {
        DatacenterConfigInfo configInfo = (DatacenterConfigInfo)_context.getVimClient().getDynamicProperty(_mor, "configuration");
        return configInfo;
    }
}
