// 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 com.cloud.hypervisor.vmware.util.VmwareContext;
import com.vmware.vim25.CustomFieldStringValue;
import com.vmware.vim25.DatastoreInfo;
import com.vmware.vim25.DynamicProperty;
import com.vmware.vim25.HostNasVolumeSpec;
import com.vmware.vim25.HostResignatureRescanResult;
import com.vmware.vim25.HostScsiDisk;
import com.vmware.vim25.HostUnresolvedVmfsResignatureSpec;
import com.vmware.vim25.HostUnresolvedVmfsVolume;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.NasDatastoreInfo;
import com.vmware.vim25.ObjectContent;
import com.vmware.vim25.ObjectSpec;
import com.vmware.vim25.PropertyFilterSpec;
import com.vmware.vim25.PropertySpec;
import com.vmware.vim25.RetrieveOptions;
import com.vmware.vim25.RetrieveResult;
import com.vmware.vim25.SelectionSpec;
import com.vmware.vim25.TraversalSpec;
import com.vmware.vim25.VmfsDatastoreCreateSpec;
import com.vmware.vim25.VmfsDatastoreExpandSpec;
import com.vmware.vim25.VmfsDatastoreOption;

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

public class HostDatastoreSystemMO extends BaseMO {

    public HostDatastoreSystemMO(VmwareContext context, ManagedObjectReference morHostDatastore) {
        super(context, morHostDatastore);
    }

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

    public ManagedObjectReference findDatastore(String name) throws Exception {
        ManagedObjectReference morDatastore = findSpecificDatastore(name);
        if (morDatastore == null) {
            morDatastore = findDatastoreCluster(name);
        }
        return morDatastore;
    }

    public ManagedObjectReference findSpecificDatastore(String name) throws Exception {
        // added Apache CloudStack specific name convention, we will use custom field "cloud.uuid" as datastore name as well
        CustomFieldsManagerMO cfmMo = new CustomFieldsManagerMO(_context, _context.getServiceContent().getCustomFieldsManager());
        int key = cfmMo.getCustomFieldKey("Datastore", CustomFieldConstants.CLOUD_UUID);
        assert (key != 0);

        List<ObjectContent> ocs = getDatastorePropertiesOnHostDatastoreSystem(new String[] {"name", String.format("value[%d]", key)});
        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                if (oc.getPropSet().get(0).getVal().equals(name))
                    return oc.getObj();

                if (oc.getPropSet().size() > 1) {
                    DynamicProperty prop = oc.getPropSet().get(1);
                    if (prop != null && prop.getVal() != null) {
                        if (prop.getVal() instanceof CustomFieldStringValue) {
                            String val = ((CustomFieldStringValue)prop.getVal()).getValue();
                            if (val.equalsIgnoreCase(name))
                                return oc.getObj();
                        }
                    }
                }
            }
        }
        return null;
    }

    public ManagedObjectReference findDatastoreCluster(String name) throws Exception {
        // added Apache CloudStack specific name convention, we will use custom field "cloud.uuid" as datastore name as well
        CustomFieldsManagerMO cfmMo = new CustomFieldsManagerMO(_context, _context.getServiceContent().getCustomFieldsManager());
        int key = cfmMo.getCustomFieldKey("StoragePod", CustomFieldConstants.CLOUD_UUID);
        assert (key != 0);

        List<ObjectContent> ocs = getDatastoreClusterPropertiesOnHostDatastoreSystem(new String[] {"name", String.format("value[%d]", key)});
        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                if (oc.getPropSet().get(0).getVal().equals(name))
                    return oc.getObj();

                if (oc.getPropSet().size() > 1) {
                    DynamicProperty prop = oc.getPropSet().get(1);
                    if (prop != null && prop.getVal() != null) {
                        if (prop.getVal() instanceof CustomFieldStringValue) {
                            String val = ((CustomFieldStringValue)prop.getVal()).getValue();
                            if (val.equalsIgnoreCase(name))
                                return oc.getObj();
                        }
                    }
                }
            }
        }
        return null;
    }

    public List<HostUnresolvedVmfsVolume> queryUnresolvedVmfsVolumes() throws Exception {
        return _context.getService().queryUnresolvedVmfsVolumes(_mor);
    }

    public List<VmfsDatastoreOption> queryVmfsDatastoreExpandOptions(DatastoreMO datastoreMO) throws Exception {
        return _context.getService().queryVmfsDatastoreExpandOptions(_mor, datastoreMO.getMor());
    }

    public void expandVmfsDatastore(DatastoreMO datastoreMO, VmfsDatastoreExpandSpec vmfsDatastoreExpandSpec) throws Exception {
        _context.getService().expandVmfsDatastore(_mor, datastoreMO.getMor(), vmfsDatastoreExpandSpec);
    }

    // storeUrl in nfs://host/exportpath format
    public ManagedObjectReference findDatastoreByUrl(String storeUrl) throws Exception {
        assert (storeUrl != null);

        List<ManagedObjectReference> datastores = getDatastores();
        if (datastores != null && datastores.size() > 0) {
            for (ManagedObjectReference morDatastore : datastores) {
                NasDatastoreInfo info = getNasDatastoreInfo(morDatastore);
                if (info != null) {
                    URI uri = new URI(storeUrl);
                    String vmwareStyleUrl = "netfs://" + uri.getHost() + "/" + uri.getPath() + "/";
                    if (info.getUrl().equals(vmwareStyleUrl))
                        return morDatastore;
                }
            }
        }

        return null;
    }

    public ManagedObjectReference findDatastoreByName(String datastoreName) throws Exception {
        assert (datastoreName != null);

        List<ManagedObjectReference> datastores = getDatastores();

        if (datastores != null) {
            for (ManagedObjectReference morDatastore : datastores) {
                DatastoreInfo info = getDatastoreInfo(morDatastore);

                if (info != null) {
                    if (info.getName().equals(datastoreName))
                        return morDatastore;
                }
            }
        }

        return null;
    }

    // TODO this is a hacking helper method, when we can pass down storage pool info along with volume
    // we should be able to find the datastore by name
    public ManagedObjectReference findDatastoreByExportPath(String exportPath) throws Exception {
        assert (exportPath != null);

        List<ManagedObjectReference> datastores = getDatastores();
        if (datastores != null && datastores.size() > 0) {
            for (ManagedObjectReference morDatastore : datastores) {
                DatastoreMO dsMo = new DatastoreMO(_context, morDatastore);
                if (dsMo.getInventoryPath().equals(exportPath))
                    return morDatastore;

                NasDatastoreInfo info = getNasDatastoreInfo(morDatastore);
                if (info != null) {
                    String vmwareUrl = info.getUrl();
                    if (vmwareUrl.charAt(vmwareUrl.length() - 1) == '/')
                        vmwareUrl = vmwareUrl.substring(0, vmwareUrl.length() - 1);

                    URI uri = new URI(vmwareUrl);
                    if (uri.getPath().equals("/" + exportPath))
                        return morDatastore;
                }
            }
        }

        return null;
    }

    public List<HostScsiDisk> queryAvailableDisksForVmfs() throws Exception {
        return _context.getService().queryAvailableDisksForVmfs(_mor, null);
    }

    public ManagedObjectReference createVmfsDatastore(String datastoreName, HostScsiDisk hostScsiDisk, Integer vmfsVersion) throws Exception {
        VmfsDatastoreOption vmfsDatastoreOption = _context.getService().queryVmfsDatastoreCreateOptions(_mor, hostScsiDisk.getDevicePath(), vmfsVersion).get(0);
        VmfsDatastoreCreateSpec vmfsDatastoreCreateSpec = (VmfsDatastoreCreateSpec)vmfsDatastoreOption.getSpec();
        vmfsDatastoreCreateSpec.getVmfs().setVolumeName(datastoreName);
        return _context.getService().createVmfsDatastore(_mor, vmfsDatastoreCreateSpec);
    }

    public ManagedObjectReference createVmfs5Datastore(String datastoreName, HostScsiDisk hostScsiDisk) throws Exception {
        return createVmfsDatastore(datastoreName, hostScsiDisk, 5);
    }

    public ManagedObjectReference createVmfs6Datastore(String datastoreName, HostScsiDisk hostScsiDisk) throws Exception {
        return createVmfsDatastore(datastoreName, hostScsiDisk, 6);
    }

    public boolean deleteDatastore(String name) throws Exception {
        ManagedObjectReference morDatastore = findDatastore(name);
        if (morDatastore != null) {
            _context.getService().removeDatastore(_mor, morDatastore);
            return true;
        }
        return false;
    }

    public ManagedObjectReference createNfsDatastore(String host, int port, String exportPath, String uuid) throws Exception {

        HostNasVolumeSpec spec = new HostNasVolumeSpec();
        spec.setRemoteHost(host);
        spec.setRemotePath(exportPath);
        spec.setType("nfs");
        spec.setLocalPath(uuid);

        // readOnly/readWrite
        spec.setAccessMode("readWrite");
        return _context.getService().createNasDatastore(_mor, spec);
    }

    public List<ManagedObjectReference> getDatastores() throws Exception {
        return _context.getVimClient().getDynamicProperty(_mor, "datastore");
    }

    public DatastoreInfo getDatastoreInfo(ManagedObjectReference morDatastore) throws Exception {
        return (DatastoreInfo)_context.getVimClient().getDynamicProperty(morDatastore, "info");
    }

    public NasDatastoreInfo getNasDatastoreInfo(ManagedObjectReference morDatastore) throws Exception {
        DatastoreInfo info = (DatastoreInfo)_context.getVimClient().getDynamicProperty(morDatastore, "info");
        if (info instanceof NasDatastoreInfo)
            return (NasDatastoreInfo)info;
        return null;
    }

    public HostResignatureRescanResult resignatureUnresolvedVmfsVolume(HostUnresolvedVmfsResignatureSpec resolutionSpec) throws Exception {
        ManagedObjectReference task = _context.getService().resignatureUnresolvedVmfsVolumeTask(_mor, resolutionSpec);

        boolean result = _context.getVimClient().waitForTask(task);

        if (result) {
            _context.waitForTaskProgressDone(task);

            TaskMO taskMO = new TaskMO(_context, task);

            return (HostResignatureRescanResult)taskMO.getTaskInfo().getResult();
        } else {
            throw new Exception("Unable to register vm due to " + TaskMO.getTaskFailureInfo(_context, task));
        }
    }

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

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

        TraversalSpec hostDsSys2DatastoreTraversal = new TraversalSpec();
        hostDsSys2DatastoreTraversal.setType("HostDatastoreSystem");
        hostDsSys2DatastoreTraversal.setPath("datastore");
        hostDsSys2DatastoreTraversal.setName("hostDsSys2DatastoreTraversal");

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

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

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

    public List<ObjectContent> getDatastoreClusterPropertiesOnHostDatastoreSystem(String[] propertyPaths) throws Exception {
        ManagedObjectReference retVal = null;
        // Create Property Spec
        PropertySpec propertySpec = new PropertySpec();
        propertySpec.setAll(Boolean.FALSE);
        propertySpec.setType("StoragePod");
        propertySpec.getPathSet().addAll(Arrays.asList(propertyPaths));

        // Now create Object Spec
        ObjectSpec objectSpec = new ObjectSpec();
        objectSpec.setObj(getContext().getRootFolder());
        objectSpec.setSkip(Boolean.TRUE);
        objectSpec.getSelectSet().addAll(
                Arrays.asList(getStorageTraversalSpec()));

        // Create PropertyFilterSpec using the PropertySpec and ObjectPec
        // created above.
        PropertyFilterSpec propertyFilterSpec = new PropertyFilterSpec();
        propertyFilterSpec.getPropSet().add(propertySpec);
        propertyFilterSpec.getObjectSet().add(objectSpec);

        List<PropertyFilterSpec> listpfs = new ArrayList<PropertyFilterSpec>();
        listpfs.add(propertyFilterSpec);
        return retrievePropertiesAllObjects(listpfs);
    }

    private SelectionSpec[] getStorageTraversalSpec() {
        // create a traversal spec that start from root folder

        SelectionSpec ssFolders = new SelectionSpec();
        ssFolders.setName("visitFolders");

        TraversalSpec datacenterSpec = new TraversalSpec();
        datacenterSpec.setName("dcTodf");
        datacenterSpec.setType("Datacenter");
        datacenterSpec.setPath("datastoreFolder");
        datacenterSpec.setSkip(Boolean.FALSE);
        datacenterSpec.getSelectSet().add(ssFolders);

        TraversalSpec visitFolder = new TraversalSpec();
        visitFolder.setType("Folder");
        visitFolder.setName("visitFolders");
        visitFolder.setPath("childEntity");
        visitFolder.setSkip(Boolean.FALSE);

        List<SelectionSpec> ssSpecList = new ArrayList<SelectionSpec>();
        ssSpecList.add(datacenterSpec);
        ssSpecList.add(ssFolders);

        visitFolder.getSelectSet().addAll(ssSpecList);
        return (new SelectionSpec[]{visitFolder});
    }

    private List<ObjectContent> retrievePropertiesAllObjects(
            List<PropertyFilterSpec> listpfs) throws Exception {

        RetrieveOptions propObjectRetrieveOpts = new RetrieveOptions();

        List<ObjectContent> listobjcontent = new ArrayList<ObjectContent>();

        RetrieveResult rslts =
                getContext().getService().retrievePropertiesEx(getContext().getServiceContent().getPropertyCollector(), listpfs,
                        propObjectRetrieveOpts);
        if (rslts != null && rslts.getObjects() != null
                && !rslts.getObjects().isEmpty()) {
            listobjcontent.addAll(rslts.getObjects());
        }
        String token = null;
        if (rslts != null && rslts.getToken() != null) {
            token = rslts.getToken();
        }
        while (token != null && !token.isEmpty()) {
            rslts =
                    getContext().getService().continueRetrievePropertiesEx(getContext().getServiceContent().getPropertyCollector(), token);
            token = null;
            if (rslts != null) {
                token = rslts.getToken();
                if (rslts.getObjects() != null && !rslts.getObjects().isEmpty()) {
                    listobjcontent.addAll(rslts.getObjects());
                }
            }
        }
        return listobjcontent;
    }

}
