package org.ovirt.engine.core.bll.validator;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.ovirt.engine.core.bll.ValidationResult;
import org.ovirt.engine.core.common.businessentities.DiskImage;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainDynamic;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.VolumeFormat;
import org.ovirt.engine.core.common.businessentities.VolumeType;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.VdcBllMessages;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;

public class StorageDomainValidator {

    private static final double QCOW_OVERHEAD_FACTOR = 1.1;
    private static final long INITIAL_BLOCK_ALLOCATION_SIZE = 1024L * 1024L * 1024L;
    private static final long EMPTY_QCOW_HEADER_SIZE = 1024L * 1024L;

    private StorageDomain storageDomain;

    public StorageDomainValidator(StorageDomain domain) {
        storageDomain = domain;
    }

    public ValidationResult isDomainExistAndActive() {
        if (storageDomain == null) {
            return new ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_STORAGE_DOMAIN_NOT_EXIST);
        }
        if (storageDomain.getStatus() != StorageDomainStatus.Active) {
            return new ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_STORAGE_DOMAIN_STATUS_ILLEGAL2,
                    storageDomain.getStatus().name());
        }
        return ValidationResult.VALID;
    }

    public ValidationResult domainIsValidDestination() {
        if (storageDomain.getStorageDomainType().isIsoOrImportExportDomain()) {
            return new ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_STORAGE_DOMAIN_TYPE_ILLEGAL);
        }
        return ValidationResult.VALID;
    }

    public ValidationResult isDomainWithinThresholds() {
        StorageDomainDynamic dynamic = storageDomain.getStorageDynamicData();
        if (dynamic != null && dynamic.getfreeDiskInGB() < getLowDiskSpaceThreshold()) {
            return new ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_DISK_SPACE_LOW_ON_TARGET_STORAGE_DOMAIN,
                    storageName());
        }
        return ValidationResult.VALID;
    }

    private String storageName() {
        return String.format("$%1$s %2$s", "storageName", storageDomain.getStorageName());
    }

    public ValidationResult isDomainHasSpaceForRequest(final long requestedSize) {
        return isDomainHasSpaceForRequest(requestedSize, true);
    }

    public ValidationResult isDomainHasSpaceForRequest(final long requestedSize, final boolean useThresHold) {
        long size = useThresHold ? getLowDiskSpaceThreshold() : 0L;
        if (storageDomain.getAvailableDiskSize() != null &&
                storageDomain.getAvailableDiskSize() - requestedSize < size) {
            return new ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_DISK_SPACE_LOW_ON_TARGET_STORAGE_DOMAIN,
                    storageName());
        }
        return ValidationResult.VALID;
    }

    private static Integer getLowDiskSpaceThreshold() {
        return Config.<Integer> getValue(ConfigValues.FreeSpaceCriticalLowInGB);
    }

    public ValidationResult hasSpaceForNewDisks(Collection<DiskImage> diskImages) {
        double availableSize = storageDomain.getAvailableDiskSizeInBytes();
        double totalSizeForDisks = 0.0;

        for (DiskImage diskImage : diskImages) {
            double sizeForDisk = diskImage.getSize();

            if (diskImage.getVolumeFormat() == VolumeFormat.COW) {
                if (storageDomain.getStorageType().isFileDomain()) {
                    sizeForDisk = EMPTY_QCOW_HEADER_SIZE;
                } else {
                    sizeForDisk = INITIAL_BLOCK_ALLOCATION_SIZE;
                }
            } else if (diskImage.getVolumeType() == VolumeType.Sparse) {
                sizeForDisk = EMPTY_QCOW_HEADER_SIZE;
            }
            totalSizeForDisks += sizeForDisk;
        }

        return validateRequiredSpace(availableSize, totalSizeForDisks);
    }

    public ValidationResult hasSpaceForClonedDisks(Collection<DiskImage> diskImages) {
        double availableSize = storageDomain.getAvailableDiskSizeInBytes();
        double totalSizeForDisks = 0.0;

        for (DiskImage diskImage : diskImages) {
            double diskCapacity = diskImage.getSize();
            double sizeForDisk = diskCapacity;

            if ((storageDomain.getStorageType().isFileDomain() && diskImage.getVolumeType() == VolumeType.Sparse) ||
                    storageDomain.getStorageType().isBlockDomain() && diskImage.getVolumeFormat() == VolumeFormat.COW) {
                double usedSapce = diskImage.getActualDiskWithSnapshotsSizeInBytes();
                sizeForDisk = Math.min(diskCapacity, usedSapce);
            }

            if (diskImage.getVolumeFormat() == VolumeFormat.COW) {
                sizeForDisk = Math.ceil(QCOW_OVERHEAD_FACTOR * sizeForDisk);
            }
            totalSizeForDisks += sizeForDisk;
        }

        return validateRequiredSpace(availableSize, totalSizeForDisks);
    }

    public ValidationResult hasSpaceForClonedDisk(DiskImage diskImage) {
        return hasSpaceForClonedDisks(Collections.singleton(diskImage));
    }

    public ValidationResult hasSpaceForNewDisk(DiskImage diskImage) {
        return hasSpaceForNewDisks(Collections.singleton(diskImage));
    }

    private ValidationResult validateRequiredSpace(double availableSize, double requiredSize) {
        if (availableSize >= requiredSize) {
            return ValidationResult.VALID;
        }

        return new ValidationResult(VdcBllMessages.ACTION_TYPE_FAILED_DISK_SPACE_LOW_ON_TARGET_STORAGE_DOMAIN,
                storageName());
    }

    public static Map<StorageDomain, Integer> getSpaceRequirementsForStorageDomains(Collection<DiskImage> images,
            Map<Guid, StorageDomain> storageDomains, Map<Guid, DiskImage> imageToDestinationDomainMap) {
        Map<DiskImage, StorageDomain> spaceMap = new HashMap<DiskImage, StorageDomain>();
        for (DiskImage image : images) {
            Guid storageId = imageToDestinationDomainMap.get(image.getId()).getStorageIds().get(0);
            StorageDomain domain = storageDomains.get(storageId);
            if (domain == null) {
                domain = DbFacade.getInstance().getStorageDomainDao().get(storageId);
            }
            spaceMap.put(image, domain);
        }
        return StorageDomainValidator.getSpaceRequirementsForStorageDomains(spaceMap);
    }

    public static Map<StorageDomain, Integer> getSpaceRequirementsForStorageDomains(Map<DiskImage, StorageDomain> imageToDomainMap) {
        Map<StorageDomain, Integer> map = new HashMap<StorageDomain, Integer>();
        if (!imageToDomainMap.isEmpty()) {
            for (Map.Entry<DiskImage, StorageDomain> entry : imageToDomainMap.entrySet()) {
                StorageDomain domain = entry.getValue();
                int size = (int) entry.getKey().getActualSize();
                if (map.containsKey(domain)) {
                    map.put(domain, map.get(domain) + size);
                } else {
                    map.put(domain, size);
                }
            }
        }
        return map;
    }
}

@@ -1658,32 +1658,70 @@
                 
+ String.format(%22$%251$s %252$s%22, %22status%22,
  storageDomain.g
@@ -1734,24 +1734,25 @@
 us().name())
+)
 ;%0A        %7D%0A

Fix CDA message to include status