package org.ovirt.engine.core.utils.ovf;

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

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.businessentities.BootSequence;
import org.ovirt.engine.core.common.businessentities.DiskImage;
import org.ovirt.engine.core.common.businessentities.DisplayType;
import org.ovirt.engine.core.common.businessentities.OriginType;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotStatus;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
import org.ovirt.engine.core.common.businessentities.UsbPolicy;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmNetworkInterface;
import org.ovirt.engine.core.common.businessentities.VmOsType;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.businessentities.VmType;
import org.ovirt.engine.core.common.utils.VmDeviceCommonUtils;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.StringHelper;
import org.ovirt.engine.core.compat.backendcompat.XmlDocument;
import org.ovirt.engine.core.compat.backendcompat.XmlNode;
import org.ovirt.engine.core.compat.backendcompat.XmlNodeList;
import org.ovirt.engine.core.utils.linq.LinqUtils;
import org.ovirt.engine.core.utils.linq.Predicate;

public class OvfVmReader extends OvfReader {
    private static final String EXPORT_ONLY_PREFIX = "exportonly_";
    protected VM _vm;

    public OvfVmReader(XmlDocument document,
            VM vm,
            ArrayList<DiskImage> images,
            ArrayList<VmNetworkInterface> interfaces) {
        super(document, images, interfaces);
        _vm = vm;
        _vm.setInterfaces(interfaces);
    }

    @Override
    protected void ReadOsSection(XmlNode section) {
        _vm.getStaticData().setId(new Guid(section.Attributes.get("ovf:id").getValue()));
        XmlNode node = section.SelectSingleNode("Description");
        if (node != null) {
            _vm.getStaticData().setos(VmOsType.valueOf(node.InnerText));
        } else {
            _vm.getStaticData().setos(VmOsType.Unassigned);
        }
    }

    @Override
    protected void ReadHardwareSection(XmlNode section) {
        XmlNodeList list = section.SelectNodes("Item");
        for (XmlNode node : list) {
            String resourceType = node.SelectSingleNode("rasd:ResourceType", _xmlNS).InnerText;

            if (StringHelper.EqOp(resourceType, OvfHardware.CPU)) {
                _vm.getStaticData().setnum_of_sockets(
                        Integer.parseInt(node.SelectSingleNode("rasd:num_of_sockets", _xmlNS).InnerText));
                _vm.getStaticData().setcpu_per_socket(
                        Integer.parseInt(node.SelectSingleNode("rasd:cpu_per_socket", _xmlNS).InnerText));
            } else if (StringHelper.EqOp(resourceType, OvfHardware.Memory)) {
                _vm.getStaticData().setmem_size_mb(
                        Integer.parseInt(node.SelectSingleNode("rasd:VirtualQuantity", _xmlNS).InnerText));
            } else if (StringHelper.EqOp(resourceType, OvfHardware.DiskImage)) {
                final Guid guid = new Guid(node.SelectSingleNode("rasd:InstanceId", _xmlNS).InnerText);

                DiskImage image = LinqUtils.firstOrNull(_images, new Predicate<DiskImage>() {
                    @Override
                    public boolean eval(DiskImage diskImage) {
                        return diskImage.getImageId().equals(guid);
                    }
                });
                image.setId(OvfParser.GetImageGrupIdFromImageFile(node.SelectSingleNode(
                        "rasd:HostResource", _xmlNS).InnerText));
                if (!StringHelper.isNullOrEmpty(node.SelectSingleNode("rasd:Parent", _xmlNS).InnerText)) {
                    image.setParentId(new Guid(node.SelectSingleNode("rasd:Parent", _xmlNS).InnerText));
                }
                if (!StringHelper.isNullOrEmpty(node.SelectSingleNode("rasd:Template", _xmlNS).InnerText)) {
                    image.setit_guid(new Guid(node.SelectSingleNode("rasd:Template", _xmlNS).InnerText));
                }
                image.setappList(node.SelectSingleNode("rasd:ApplicationList", _xmlNS).InnerText);

                XmlNode storageNode = node.SelectSingleNode("rasd:StorageId", _xmlNS);
                if (storageNode != null &&
                        !StringHelper.isNullOrEmpty(storageNode.InnerText)) {
                    image.setstorage_ids(new ArrayList<Guid>(Arrays.asList(new Guid(storageNode.InnerText))));
                }
                if (!StringHelper.isNullOrEmpty(node.SelectSingleNode("rasd:StoragePoolId", _xmlNS).InnerText)) {
                    image.setstorage_pool_id(new Guid(node.SelectSingleNode("rasd:StoragePoolId", _xmlNS).InnerText));
                }
                final Date creationDate = OvfParser.UtcDateStringToLocaDate(
                        node.SelectSingleNode("rasd:CreationDate", _xmlNS).InnerText);
                if (creationDate == null) {
                    image.setcreation_date(creationDate);
                }
                final Date lastModified = OvfParser.UtcDateStringToLocaDate(
                        node.SelectSingleNode("rasd:LastModified", _xmlNS).InnerText);
                if (lastModified != null) {
                    image.setlastModified(lastModified);
                }
                final Date last_modified_date = OvfParser.UtcDateStringToLocaDate(
                        node.SelectSingleNode("rasd:last_modified_date", _xmlNS).InnerText);
                if (last_modified_date != null) {
                    image.setlast_modified_date(last_modified_date);
                }
                readVmDevice(node, _vm.getStaticData(), image.getId(), Boolean.TRUE);
            } else if (StringHelper.EqOp(resourceType, OvfHardware.Network)) {
                VmNetworkInterface iface = getNetwotkInterface(node);
                updateSingleNic(node, iface);
                _vm.getInterfaces().add(iface);
                readVmDevice(node, _vm.getStaticData(), iface.getId(), Boolean.TRUE);
            } else if (StringHelper.EqOp(resourceType, OvfHardware.USB)) {
                _vm.getStaticData().setusb_policy(
                        UsbPolicy.forStringValue(node.SelectSingleNode("rasd:UsbPolicy", _xmlNS).InnerText));
            } else if (StringHelper.EqOp(resourceType, OvfHardware.Monitor)) {
                _vm.getStaticData().setnum_of_monitors(
                        Integer.parseInt(node.SelectSingleNode("rasd:VirtualQuantity", _xmlNS).InnerText));
                readVmDevice(node, _vm.getStaticData(), Guid.NewGuid(), Boolean.TRUE);
            } else if (StringHelper.EqOp(resourceType, OvfHardware.CD)) {
                readVmDevice(node, _vm.getStaticData(), Guid.NewGuid(), Boolean.TRUE);
            } else if (StringHelper.EqOp(resourceType, OvfHardware.OTHER)) {
                if (node.SelectSingleNode(OvfProperties.VMD_TYPE, _xmlNS) != null
                        && !StringHelper.isNullOrEmpty(node.SelectSingleNode(OvfProperties.VMD_TYPE, _xmlNS).InnerText)) {
                    String type = String.valueOf(node.SelectSingleNode(OvfProperties.VMD_TYPE, _xmlNS).InnerText);
                    String device = String.valueOf(node.SelectSingleNode(OvfProperties.VMD_DEVICE, _xmlNS).InnerText);
                    // special devices are treated as managed devices but still have the OTHER OVF ResourceType
                    if (VmDeviceCommonUtils.isSpecialDevice(device, type)) {
                        readVmDevice(node, _vm.getStaticData(), Guid.NewGuid(), Boolean.TRUE);
                    } else {
                        readVmDevice(node, _vm.getStaticData(), Guid.NewGuid(), Boolean.FALSE);
                    }
                } else {
                    readVmDevice(node, _vm.getStaticData(), Guid.NewGuid(), Boolean.FALSE);
                }
            }
        }
    }

    @Override
    protected void ReadGeneralData() {
        // General Vm
        XmlNode content = _document.SelectSingleNode("//*/Content");

        XmlNode node = content.SelectSingleNode("Name");
        if (node != null) {
            _vm.getStaticData().setvm_name(node.InnerText);
            name = _vm.getStaticData().getvm_name();
        }
        node = content.SelectSingleNode("TemplateId");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.getStaticData().setvmt_guid(new Guid(node.InnerText));
            }
        }
        node = content.SelectSingleNode("TemplateName");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setvmt_name(node.InnerText);
            }
        }
        node = content.SelectSingleNode("Description");
        if (node != null) {
            _vm.getStaticData().setdescription(node.InnerText);
        }
        node = content.SelectSingleNode("Domain");
        if (node != null) {
            _vm.getStaticData().setdomain(node.InnerText);
        }
        node = content.SelectSingleNode("CreationDate");
        if (node != null) {
            final Date creationDate = OvfParser.UtcDateStringToLocaDate(node.InnerText);
            if (creationDate != null) {
                _vm.getStaticData().setcreation_date(creationDate);
            }
        }
        node = content.SelectSingleNode("ExportDate");
        if (node != null) {
            final Date exportDate = OvfParser.UtcDateStringToLocaDate(node.InnerText);
            if (exportDate != null) {
                _vm.getStaticData().setExportDate(exportDate);
            }
        }
        node = content.SelectSingleNode("IsInitilized");
        if (node != null) {
            _vm.getStaticData().setis_initialized(Boolean.parseBoolean(node.InnerText));
        }
        node = content.SelectSingleNode("IsAutoSuspend");
        if (node != null) {
            _vm.getStaticData().setis_auto_suspend(Boolean.parseBoolean(node.InnerText));
        }
        node = content.SelectSingleNode("TimeZone");
        if (node != null) {
            _vm.getStaticData().settime_zone(node.InnerText);
        }
        node = content.SelectSingleNode("IsStateless");
        if (node != null) {
            _vm.getStaticData().setis_stateless(Boolean.parseBoolean(node.InnerText));
        }
        node = content.SelectSingleNode("quota_id");
        if (node != null) {
            _vm.getStaticData().setQuotaId(new Guid(node.InnerText));
        }

        XmlNodeList list = content.SelectNodes("Section");
        for (XmlNode section : list) {
            String value = section.Attributes.get("xsi:type").getValue();

            if (StringHelper.EqOp(value, "ovf:OperatingSystemSection_Type")) {
                ReadOsSection(section);

            }
            else if (StringHelper.EqOp(value, "ovf:VirtualHardwareSection_Type")) {
                ReadHardwareSection(section);
            } else if (StringUtils.equals(value, "ovf:SnapshotsSection_Type")) {
                readSnapshotsSection(section);
            }
        }

        node = content.SelectSingleNode("Origin");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setorigin(OriginType.forValue(Integer.parseInt(node.InnerText)));
            }
        }
        node = content.SelectSingleNode("initrd_url");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setinitrd_url((node.InnerText));
            }
        }
        node = content.SelectSingleNode("default_boot_sequence");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setdefault_boot_sequence(BootSequence.forValue(Integer.parseInt(node.InnerText)));
            }
        }

        node = content.SelectSingleNode("kernel_url");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setkernel_url((node.InnerText));
            }
        }
        node = content.SelectSingleNode("kernel_params");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setkernel_params((node.InnerText));
            }
        }

        OvfLogEventHandler<VmStatic> handler = new VMStaticOvfLogHandler(_vm.getStaticData());

        // Gets a list of all the aliases of the fields that should be logged in
        // ovd For each one of these fields, the proper value will be read from
        // the ovf and field in vm static
        List<String> aliases = handler.getAliases();

        for (String alias : aliases) {

            String value = readEventLogValue(content, alias);
            if (!StringHelper.isNullOrEmpty(value)) {
                handler.addValueForAlias(alias, value);

            }
        }

        node = content.SelectSingleNode("app_list");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setapp_list(node.InnerText);
            }
        }
        // if no app list in VM, get it from one of the leafs
        else if(_images != null && _images.size() > 0) {
            int root = GetFirstImage(_images, _images.get(0));
            if (root != -1) {
                for(int i=0; i<_images.size(); i++) {
                    int x = GetNextImage(_images, _images.get(i));
                    if (x == -1) {
                        _vm.setapp_list(_images.get(i).getappList());
                    }
                }
            } else {
                _vm.setapp_list(_images.get(0).getappList());
            }
        }
        node = content.SelectSingleNode("VmType");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setvm_type(VmType.forValue(Integer.parseInt(node.InnerText)));
            }
        }
        node = content.SelectSingleNode("DefaultDisplayType");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setdefault_display_type(DisplayType.forValue(Integer.parseInt(node.InnerText)));
            }
        }

        node = content.SelectSingleNode("MinAllocatedMem");
        if (node != null) {
            if (!StringHelper.isNullOrEmpty(node.InnerText)) {
                _vm.setMinAllocatedMem(Integer.parseInt(node.InnerText));
            }
        }

    }

    // function returns the index of the image that has no parent
    private static int GetFirstImage(java.util.ArrayList<DiskImage> images, DiskImage curr) {
        for (int i = 0; i < images.size(); i++) {
            if (curr.getParentId().equals(images.get(i).getImageId())) {
                return i;
            }
        }
        return -1;
    }

    // function returns the index of image that is it's child
    private static int GetNextImage(java.util.ArrayList<DiskImage> images, DiskImage curr) {
        for (int i = 0; i < images.size(); i++) {
            if (images.get(i).getParentId().equals(curr.getImageId())) {
                return i;
            }
        }
        return -1;
    }

    private String readEventLogValue(XmlNode content, String name) {
        StringBuilder fullNameSB = new StringBuilder(EXPORT_ONLY_PREFIX);
        fullNameSB.append(name);
        XmlNode node = content.SelectSingleNode(fullNameSB.toString());
        if (node != null) {
            return node.InnerText;
        }
        return null;
    }

    private void readSnapshotsSection(XmlNode section) {
        XmlNodeList list = section.SelectNodes("Snapshot");
        ArrayList<Snapshot> snapshots = new ArrayList<Snapshot>();
        _vm.setSnapshots(snapshots);

        for (XmlNode node : list) {
            XmlNode vmConfiguration = node.SelectSingleNode("VmConfiguration", _xmlNS);
            Snapshot snapshot = new Snapshot(vmConfiguration != null);
            snapshot.setId(new Guid(node.Attributes.get("ovf:id").getValue()));
            snapshot.setVmId(_vm.getId());
            snapshot.setType(SnapshotType.valueOf(node.SelectSingleNode("Type", _xmlNS).InnerText));
            snapshot.setStatus(SnapshotStatus.OK);
            snapshot.setDescription(node.SelectSingleNode("Description", _xmlNS).InnerText);

            final Date creationDate = OvfParser.UtcDateStringToLocaDate(node.SelectSingleNode("CreationDate", _xmlNS).InnerText);
            if (creationDate != null) {
                snapshot.setCreationDate(creationDate);
            }

            snapshot.setVmConfiguration(vmConfiguration == null
                    ? null : new String(Base64.decodeBase64(vmConfiguration.InnerText)));

            XmlNode appList = node.SelectSingleNode("ApplicationList", _xmlNS);
            if (appList != null) {
                snapshot.setAppList(appList.InnerText);
            }

            snapshots.add(snapshot);
        }
    }

    @Override
    protected void buildNicReference() {
    }
}
