package org.zstack.storage.backup;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.storage.backup.BackupStorageCapacity;
import org.zstack.header.storage.backup.BackupStorageCapacityUpdaterRunnable;
import org.zstack.header.storage.backup.BackupStorageVO;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;

import static org.zstack.core.Platform.operr;

import javax.persistence.LockModeType;

/**
 * Created by xing5 on 2016/4/28.
 */
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class BackupStorageCapacityUpdater {
    private static CLogger logger = Utils.getLogger(BackupStorageCapacityUpdater.class);

    @Autowired
    private DatabaseFacade dbf;
    @Autowired
    private CloudBus bus;
    @Autowired
    private ErrorFacade errf;

    private String backupStorageUuid;
    private BackupStorageVO capacityVO;
    private BackupStorageVO originalCopy;

    private long totalForLog;
    private long availForLog;

    public BackupStorageCapacityUpdater(String backupStorageUuid) {
        this.backupStorageUuid = backupStorageUuid;
    }

    private void logCapacityChange() {
        if (logger.isTraceEnabled()) {
            StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
            int index = 0;
            String fileName = BackupStorageCapacityUpdater.class.getSimpleName() + ".java";
            for (int i=0; i<stackTraceElements.length; i++) {
                if (fileName.equals(stackTraceElements[i].getFileName())) {
                    index = i;
                }
            }
            StackTraceElement caller = stackTraceElements[index+1];
            logger.trace(String.format("[Backup Storage Capacity] %s:%s:%s changed the capacity of the backup storage[uuid:%s] as:\n" +
                            "total: %s --> %s\n" +
                            "available: %s --> %s\n" , caller.getFileName(), caller.getMethodName(), caller.getLineNumber(), capacityVO.getUuid(),
                    totalForLog, capacityVO.getTotalCapacity(),
                    availForLog, capacityVO.getAvailableCapacity()));
        }
    }

    private boolean lockCapacity() {
        if (backupStorageUuid != null) {
            capacityVO = dbf.getEntityManager().find(BackupStorageVO.class, backupStorageUuid, LockModeType.PESSIMISTIC_WRITE);
        }

        if (capacityVO != null) {
            totalForLog = capacityVO.getTotalCapacity();
            availForLog = capacityVO.getAvailableCapacity();

            originalCopy = new BackupStorageVO();
            originalCopy.setAvailableCapacity(capacityVO.getAvailableCapacity());
            originalCopy.setTotalCapacity(capacityVO.getTotalCapacity());
        }

        return capacityVO != null;
    }

    private void checkResize() {
        if (originalCopy != null && capacityVO != null && originalCopy.getTotalCapacity() != 0 && originalCopy.getTotalCapacity() != capacityVO.getTotalCapacity()) {
            logger.debug(String.format("the capacity of backup storage[uuid:%s] changed from %s to %s, this indicates the backup storage is re-sized." +
                    " We need to recalculate its capacity", capacityVO.getUuid(), originalCopy.getTotalCapacity(), capacityVO.getTotalCapacity()));
            // primary storage re-sized
        }
    }

    private void merge() {
        capacityVO = dbf.getEntityManager().merge(capacityVO);
        logCapacityChange();
    }


    private void logDeletedPrimaryStorage() {
        logger.warn(String.format("[Backup Storage Capacity] unable to update capacity for the backup storage[uuid:%s]. It may have been deleted, cannot find it in database",
                backupStorageUuid));
    }

    @Transactional
    private boolean _increaseAvailableCapacity(long size) {
        if (!lockCapacity()) {
            logDeletedPrimaryStorage();
            return false;
        }

        long n = capacityVO.getAvailableCapacity() + size;
        if (n > capacityVO.getTotalCapacity()) {
            throw new CloudRuntimeException(String.format("invalid primary storage[uuid:%s] capacity, available capacity[%s] > total capacity[%s]",
                    capacityVO.getUuid(), n, capacityVO.getTotalCapacity()));
        }

        capacityVO.setAvailableCapacity(n);
        merge();
        return true;
    }

    public boolean reserveCapacity(long size) {
        return _reserveCapacity(size, true);
    }

    public boolean reserveCapacity(long size, boolean exceptionOnFailure) {
        return _reserveCapacity(size, exceptionOnFailure);
    }

    @Transactional
    private boolean _reserveCapacity(long size, boolean exceptionOnFailure) {
        if (!lockCapacity()) {
            logDeletedPrimaryStorage();
            return false;
        }

        if (capacityVO.getAvailableCapacity() < size) {
            if (!exceptionOnFailure) {
                return false;
            } else {
                throw new OperationFailureException(operr("cannot reserve %s on the backup storage[uuid:%s], it only has %s available",
                                size, backupStorageUuid, capacityVO.getAvailableCapacity()));
            }
        }

        return _decreaseAvailableCapacity(size);
    }

    public boolean increaseAvailableCapacity(long size) {
        boolean ret = _increaseAvailableCapacity(size);
        checkResize();
        return ret;
    }

    @Transactional
    private boolean _decreaseAvailableCapacity(long size) {
        if (!lockCapacity()) {
            logDeletedPrimaryStorage();
            return false;
        }

        capacityVO.setAvailableCapacity(capacityVO.getAvailableCapacity() - size);
        merge();
        return true;
    }

    public boolean decreaseAvailableCapacity(long size) {
        boolean ret = _decreaseAvailableCapacity(size);
        checkResize();
        return ret;
    }

    @Transactional
    private boolean _update(Long total, Long avail) {
        if (!lockCapacity()) {
            logDeletedPrimaryStorage();
            return false;
        }

        if (total != null) {
            capacityVO.setTotalCapacity(total);
        }
        if (avail != null) {
            capacityVO.setAvailableCapacity(avail);
        }

        merge();
        return true;
    }

    public boolean update(Long total, Long avail) {
        boolean ret = _update(total, avail);
        checkResize();
        return ret;
    }

    @Transactional
    private boolean _run(BackupStorageCapacityUpdaterRunnable runnable) {
        if (!lockCapacity()) {
            logDeletedPrimaryStorage();
            return false;
        }

        BackupStorageCapacity cap = new BackupStorageCapacity();
        cap.setUuid(capacityVO.getUuid());
        cap.setAvailableCapacity(capacityVO.getAvailableCapacity());
        cap.setTotalCapacity(capacityVO.getTotalCapacity());

        cap = runnable.call(cap);
        if (cap != null) {
            capacityVO.setTotalCapacity(cap.getTotalCapacity());
            capacityVO.setAvailableCapacity(cap.getAvailableCapacity());
            merge();
            return true;
        }

        return false;
    }

    public boolean run(BackupStorageCapacityUpdaterRunnable runnable) {
        boolean ret = _run(runnable);
        checkResize();
        return ret;
    }
}

@@ -817,85 +817,85 @@
 ort 
-static org.zstack.core.Platform.operr;%0A%0Aimport javax.persistence.LockModeType
+javax.persistence.LockModeType;%0A%0Aimport static org.zstack.core.Platform.operr
 ;%0A%0A/
@@ -3955,39 +3955,38 @@
  void logDeleted
-Primary
+Backup
 Storage() %7B%0A    
@@ -4324,39 +4324,38 @@
       logDeleted
-Primary
+Backup
 Storage();%0A     
@@ -4561,23 +4561,22 @@
 invalid 
-primary
+backup
  storage
@@ -5203,39 +5203,38 @@
       logDeleted
-Primary
+Backup
 Storage();%0A     
@@ -6010,39 +6010,38 @@
       logDeleted
-Primary
+Backup
 Storage();%0A     
@@ -6482,39 +6482,38 @@
       logDeleted
-Primary
+Backup
 Storage();%0A     
@@ -7065,15 +7065,14 @@
 eted
-Primary
+Backup
 Stor

Fix typo in code