package org.cloudbus.cloudsim.power;

import org.apache.commons.math3.analysis.function.Power;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.power.lists.PowerVmList;
import org.cloudbus.cloudsim.util.MathUtil;

import java.util.*;

/**
 * ClassName PowerVmAllocationPolicyNewLocalRegression
 * Description
 * Create by HW
 * Date 2021/3/26 15:02
 */
public class PowerVmAllocationPolicyMigrationNewLocalRegression extends PowerVmAllocationPolicyMigrationAbstract {
    /**
     * Instantiates a new PowerVmAllocationPolicyMigrationAbstract.
     *
     * @param hostList          the host list
     * @param vmSelectionPolicy the vm selection policy
     */
    /** The scheduling interval that defines the periodicity of VM migrations. */
    private double schedulingInterval;
    private double safetyParameter;
    private PowerVmAllocationPolicyMigrationAbstract fallbackVmAllocationPolicy;

    public double getSchedulingInterval() {
        return schedulingInterval;
    }

    public void setSchedulingInterval(double schedulingInterval) {
        this.schedulingInterval = schedulingInterval;
    }

    public double getSafetyParameter() {
        return safetyParameter;
    }

    public void setSafetyParameter(double safetyParameter) {
        this.safetyParameter = safetyParameter;
    }

    public PowerVmAllocationPolicyMigrationAbstract getFallbackVmAllocationPolicy() {
        return fallbackVmAllocationPolicy;
    }

    public void setFallbackVmAllocationPolicy(PowerVmAllocationPolicyMigrationAbstract fallbackVmAllocationPolicy) {
        this.fallbackVmAllocationPolicy = fallbackVmAllocationPolicy;
    }



    protected double[] getParameterEstimates(double[] utilizationHistoryReversed) {
        return MathUtil.getLoessParameterEstimates(utilizationHistoryReversed);
    }

    /**
     * Gets the maximum vm migration time.
     *
     * @param host the host
     * @return the maximum vm migration time
     */
    protected double getMaximumVmMigrationTime(PowerHost host) {
        int maxRam = Integer.MIN_VALUE;
        for (Vm vm : host.getVmList()) {
            int ram = vm.getRam();
            if (ram > maxRam) {
                maxRam = ram;
            }
        }
        return maxRam / ((double) host.getBw() / (2 * 8000));
    }

    public PowerVmAllocationPolicyMigrationNewLocalRegression(
            List<? extends Host> hostList,
            PowerVmSelectionPolicy vmSelectionPolicy,
            double safetyParameter,
            double schedulingInterval,
            PowerVmAllocationPolicyMigrationAbstract fallbackVmAllocationPolicy,
            double utilizationThreshold) {
        super(hostList, vmSelectionPolicy);
        setSafetyParameter(safetyParameter);
        setSchedulingInterval(schedulingInterval);
        setFallbackVmAllocationPolicy(fallbackVmAllocationPolicy);
    }


    public PowerVmAllocationPolicyMigrationNewLocalRegression(
            List<? extends Host> hostList,
            PowerVmSelectionPolicy vmSelectionPolicy,
            double safetyParameter,
            double schedulingInterval,
            PowerVmAllocationPolicyMigrationAbstract fallbackVmAllocationPolicy) {
        super(hostList, vmSelectionPolicy);
        setSafetyParameter(safetyParameter);
        setSchedulingInterval(schedulingInterval);
        setFallbackVmAllocationPolicy(fallbackVmAllocationPolicy);
    }
    //主机过载检测
    @Override
    protected boolean isHostOverUtilized(PowerHost host) {
        PowerHostUtilizationHistory _host = (PowerHostUtilizationHistory) host;
        double[] utilizationHistory = _host.getUtilizationHistory();
        int length =10; // we use 10 to make the regression responsive enough to latest values
        if (utilizationHistory.length < length) {
            return (host.getTotalMips() - host.getAvailableMips()) > 0.9 * host.getTotalMips();
        }
        double[] utilizationHistoryReversed = new double[length];
        for (int i = 1; i <length; i++) {
            utilizationHistoryReversed[i] = utilizationHistory[length - i - 1];
        }
        double predictionUtilizaiton = getPredictionUtilizaiton(host,utilizationHistoryReversed);
        addHistoryEntry(host, predictionUtilizaiton);
        return predictionUtilizaiton >= 1;
    }
    //主机的负载不足检测
    protected  boolean isHostUnderUtilized(PowerHost host){
        PowerHostUtilizationHistory _host = (PowerHostUtilizationHistory) host;
        double[] utilizationHistory = _host.getUtilizationHistory();
        int length = 10; // we use 10 to make the regression responsive enough to latest values
        if (utilizationHistory.length < length) {
            return host.getUtilizationOfCpuMips()<host.getTotalMips()*0.1;
            //return getFallbackVmAllocationPolicy().isHostOverUtilized(host);
        }
        double[] utilizationHistoryReversed = new double[length];

        for (int i = 1; i < length; i++) {
            utilizationHistoryReversed[i] = utilizationHistory[length - i - 1];
        }
        double predictionUtilization=getPredictionUtilizaiton(host,utilizationHistoryReversed);
        double threshold=0.6*(1-getSafetyParameter()*MathUtil.iqr(utilizationHistoryReversed));
        return predictionUtilization<threshold;
    }

    //获取主机的历史利用率
    protected  double[] getUtilizationHistory(PowerHost host){
    PowerHostUtilizationHistory _host=(PowerHostUtilizationHistory)host;
        double[] utilizationHistory = _host.getUtilizationHistory();
        return utilizationHistory;
    }
//获取主机的预测利用率
    protected  double getPredictionUtilizaiton(PowerHost host,double[] utilizationHistory){
        //获取历史利用率
        int n=utilizationHistory.length;
        double sumX = 0.0;
        double sumY=0.0;
        double[] x=new double[n];
        double[] y=new double[n];
        double yDividex=0.0;
        double average_x=0;
        double  average_y=0;
        double OneDivideX=0.0;
        for(int i=1;i<n;i++){
            x[i]=i+1;
            y[i]=utilizationHistory[i];
            sumX+=x[i];
            sumY+=y[i];
            yDividex+=  y[i]/x[i];
            average_x+=x[i]/n;
            average_y+=y[i]/n;
            OneDivideX+=1/x[i];
        }
        double beta1=(yDividex-average_y*OneDivideX)/(n-average_x*OneDivideX);
        double beta0=average_y-beta1*average_x;
        //此时应该指明主机的
        double MigrationInterval=getMaximumVmMigrationTime(host)/getSchedulingInterval();
        double predictUtilization=beta0+beta1*(n+MigrationInterval);
        double finalUtilization=getSafetyParameter()*predictUtilization;
        Log.printLine("打印最终得到利用率为："+finalUtilization);
        return finalUtilization;
    }


//重写虚拟机迁移之后的虚拟机分配的逻辑
/**
 * 在这里首先进行虚拟机的利用率进行排序，以虚拟机内存利用率进行排序，然后，选择合适的虚拟机进行迁移
 * 在迁移的过程中，其实我们可以选择相应的迁移过程进行迁移
 */
//重写虚拟机迁移之后的虚拟机分配的逻辑
    /**
     * 在这里首先进行虚拟机的利用率进行排序，以虚拟机内存利用率进行排序，然后，选择合适的虚拟机进行迁移
     * 在迁移的过程中，其实我们可以选择相应的迁移过程进行迁移
     */
    @Override
    protected List<Map<String, Object>> getNewVmPlacement(List<? extends Vm> vmsToMigrate, Set<? extends Host> excludedHosts) {
        List<Map<String, Object>> migrationMap = new LinkedList<Map<String, Object>>();
        ArrayList<? extends Host> hostlist = new ArrayList<>(excludedHosts);
        PowerVmList.sortByCpuUtilization(vmsToMigrate);
        for (Vm vm:vmsToMigrate) {
            double minPower=Double.MAX_VALUE;
            Host allocatedHost=null;
            for (Host host:hostlist){
                if (isHostOverUtilized((PowerHost) host)){
                    continue;
                }
                if (isHostUnderUtilized((PowerHost)host)){
                    continue;
                }
                if (host.isSuitableForVm(vm)){
                    double power=getPowerAfterAllocation((PowerHost) host,vm);
                    double powerDiff=power-((PowerHost)host).getPower();
                    if (powerDiff<minPower){
                        minPower=powerDiff;
                        allocatedHost=host;
                    }
                }
                if (allocatedHost !=null){
                    Log.printConcatLine("VM #", vm.getId(), " allocated to host #", allocatedHost.getId());
                    Map<String, Object> migrate = new HashMap<String, Object>();
                    migrate.put("vm", vm);
                    migrate.put("host", allocatedHost);
                    migrationMap.add(migrate);
                }
            }
        }
        return migrationMap;
    }
}
