package org.cloudbus.cloudsim.container.schedulers;

/**
 * Created by sareh on 14/07/15.
 */
import org.cloudbus.cloudsim.Cloudlet;
import org.cloudbus.cloudsim.Consts;
import org.cloudbus.cloudsim.ResCloudlet;
import org.cloudbus.cloudsim.core.CloudSim;

import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;

public class ContainerCloudletSchedulerDynamicWorkload extends ContainerCloudletSchedulerTimeShared {

        /** The mips. */
        private double mips;

        /** The number of PEs. */
        private int numberOfPes;

        /** The total mips. */
        private double totalMips;

        /** The under allocated mips. */
        private Map<String, Double> underAllocatedMips;

        /** The cache previous time. */
        private double cachePreviousTime;

        /** The cache current requested mips. */
        private List<Double> cacheCurrentRequestedMips;

        /**
         * Instantiates a new vM scheduler time shared.
         *
         * @param mips the mips
         * @param numberOfPes the pes number
         */
        public ContainerCloudletSchedulerDynamicWorkload (double mips, int numberOfPes) {
            super();
            setMips(mips);
            setNumberOfPes(numberOfPes);
            setTotalMips(getNumberOfPes() * getMips());
            setUnderAllocatedMips(new HashMap<String, Double>());
            setCachePreviousTime(-1);
        }

        /**
         * Updates the processing of cloudlets running under management of this scheduler.
         *
         * @param currentTime current simulation time
         * @param mipsShare array with MIPS share of each Pe available to the scheduler
         * @return time predicted completion time of the earliest finishing cloudlet, or 0 if there is
         *         no next events
         * @pre currentTime >= 0
         * @post $none
         */
        @Override
        public double updateContainerProcessing(double currentTime, List<Double> mipsShare) {
            setCurrentMipsShare(mipsShare);

            double timeSpan = currentTime - getPreviousTime();
            double nextEvent = Double.MAX_VALUE;
            List<ResCloudlet> cloudletsToFinish = new ArrayList<>();

            for (ResCloudlet rcl : getCloudletExecList()) {
                rcl.updateCloudletFinishedSoFar((long) (timeSpan
                        * getTotalCurrentAllocatedMipsForCloudlet(rcl, getPreviousTime()) * Consts.MILLION));

                if (rcl.getRemainingCloudletLength() == 0) { // finished: remove from the list
                    cloudletsToFinish.add(rcl);
                } else { // not finish: estimate the finish time
                    double estimatedFinishTime = getEstimatedFinishTime(rcl, currentTime);
                    if (estimatedFinishTime - currentTime < CloudSim.getMinTimeBetweenEvents()) {
                        estimatedFinishTime = currentTime + CloudSim.getMinTimeBetweenEvents();
                    }
                    if (estimatedFinishTime < nextEvent) {
                        nextEvent = estimatedFinishTime;
                    }
                }
            }

            for (ResCloudlet rgl : cloudletsToFinish) {
                getCloudletExecList().remove(rgl);
                cloudletFinish(rgl);
            }

            setPreviousTime(currentTime);

            if (getCloudletExecList().isEmpty()) {
                return 0;
            }

            cloudletsToFinish.clear();

            return nextEvent;
        }

        /**
         * Receives an cloudlet to be executed in the VM managed by this scheduler.
         *
         * @param cl the cl
         * @return predicted completion time
         * @pre _gl != null
         * @post $none
         */
        @Override
        public double cloudletSubmit(Cloudlet cl) {
            return cloudletSubmit(cl, 0);
        }

        /**
         * Receives an cloudlet to be executed in the VM managed by this scheduler.
         *
         * @param cl the cl
         * @param fileTransferTime the file transfer time
         * @return predicted completion time
         * @pre _gl != null
         * @post $none
         */
        @Override
        public double cloudletSubmit(Cloudlet cl, double fileTransferTime) {
            ResCloudlet rcl = new ResCloudlet(cl);
            rcl.setCloudletStatus(Cloudlet.INEXEC);

            for (int i = 0; i < cl.getNumberOfPes(); i++) {
                rcl.setMachineAndPeId(0, i);
            }

            getCloudletExecList().add(rcl);
            return getEstimatedFinishTime(rcl, getPreviousTime());
        }

        /**
         * Processes a finished cloudlet.
         *
         * @param rcl finished cloudlet
         * @pre rgl != $null
         * @post $none
         */
        @Override
        public void cloudletFinish(ResCloudlet rcl) {
            rcl.setCloudletStatus(Cloudlet.SUCCESS);
            rcl.finalizeCloudlet();
            getCloudletFinishedList().add(rcl);
        }

        /**
         * Get utilization created by all cloudlets.
         *
         * @param time the time
         * @return total utilization
         */
        @Override
        public double getTotalUtilizationOfCpu(double time) {
            double totalUtilization = 0;
            for (ResCloudlet rcl : getCloudletExecList()) {
                totalUtilization += rcl.getCloudlet().getUtilizationOfCpu(time);
            }
            return totalUtilization;
        }

        /**
         * Gets the current mips.
         *
         * @return the current mips
         */
        @Override
        public List<Double> getCurrentRequestedMips() {
            if (getCachePreviousTime() == getPreviousTime()) {
                return getCacheCurrentRequestedMips();
            }
            List<Double> currentMips = new ArrayList<>();
            double totalMips = getTotalUtilizationOfCpu(getPreviousTime()) * getTotalMips();
            double mipsForPe = totalMips / getNumberOfPes();

            for (int i = 0; i < getNumberOfPes(); i++) {
                currentMips.add(mipsForPe);
            }

            setCachePreviousTime(getPreviousTime());
            setCacheCurrentRequestedMips(currentMips);

            return currentMips;
        }

        /**
         * Gets the current mips.
         *
         * @param rcl the rcl
         * @param time the time
         * @return the current mips
         */
        @Override
        public double getTotalCurrentRequestedMipsForCloudlet(ResCloudlet rcl, double time) {
            return rcl.getCloudlet().getUtilizationOfCpu(time) * getTotalMips();
        }

        /**
         * Gets the total current mips for the clouddlet.
         *
         * @param rcl the rcl
         * @param mipsShare the mips share
         * @return the total current mips
         */
        @Override
        public double getTotalCurrentAvailableMipsForCloudlet(ResCloudlet rcl, List<Double> mipsShare) {
            double totalCurrentMips = 0.0;
            if (mipsShare != null) {
                int neededPEs = rcl.getNumberOfPes();
                for (double mips : mipsShare) {
                    totalCurrentMips += mips;
                    neededPEs--;
                    if (neededPEs <= 0) {
                        break;
                    }
                }
            }
            return totalCurrentMips;
        }

        /**
         * Gets the current mips.
         *
         * @param rcl the rcl
         * @param time the time
         * @return the current mips
         */
        @Override
        public double getTotalCurrentAllocatedMipsForCloudlet(ResCloudlet rcl, double time) {
            double totalCurrentRequestedMips = getTotalCurrentRequestedMipsForCloudlet(rcl, time);
            double totalCurrentAvailableMips = getTotalCurrentAvailableMipsForCloudlet(rcl, getCurrentMipsShare());
            if (totalCurrentRequestedMips > totalCurrentAvailableMips) {
                return totalCurrentAvailableMips;
            }
            return totalCurrentRequestedMips;
        }

        /**
         * Update under allocated mips for cloudlet.
         *
         * @param rcl the rgl
         * @param mips the mips
         */
        public void updateUnderAllocatedMipsForCloudlet(ResCloudlet rcl, double mips) {
            if (getUnderAllocatedMips().containsKey(rcl.getUid())) {
                mips += getUnderAllocatedMips().get(rcl.getUid());
            }
            getUnderAllocatedMips().put(rcl.getUid(), mips);
        }

        /**
         * Get estimated cloudlet completion time.
         *
         * @param rcl the rcl
         * @param time the time
         * @return the estimated finish time
         */
        public double getEstimatedFinishTime(ResCloudlet rcl, double time) {
            return time
                    + ((rcl.getRemainingCloudletLength()) / getTotalCurrentAllocatedMipsForCloudlet(rcl, time));
        }

        /**
         * Gets the total current mips.
         *
         * @return the total current mips
         */
        public int getTotalCurrentMips() {
            int totalCurrentMips = 0;
            for (double mips : getCurrentMipsShare()) {
                totalCurrentMips += mips;
            }
            return totalCurrentMips;
        }

        /**
         * Sets the total mips.
         *
         * @param mips the new total mips
         */
        public void setTotalMips(double mips) {
            totalMips = mips;
        }

        /**
         * Gets the total mips.
         *
         * @return the total mips
         */
        public double getTotalMips() {
            return totalMips;
        }

        /**
         * Sets the pes number.
         *
         * @param pesNumber the new pes number
         */
        public void setNumberOfPes(int pesNumber) {
            numberOfPes = pesNumber;
        }

        /**
         * Gets the pes number.
         *
         * @return the pes number
         */
        public int getNumberOfPes() {
            return numberOfPes;
        }

        /**
         * Sets the mips.
         *
         * @param mips the new mips
         */
        public void setMips(double mips) {
            this.mips = mips;
        }

        /**
         * Gets the mips.
         *
         * @return the mips
         */
        public double getMips() {
            return mips;
        }

        /**
         * Sets the under allocated mips.
         *
         * @param underAllocatedMips the under allocated mips
         */
        public void setUnderAllocatedMips(Map<String, Double> underAllocatedMips) {
            this.underAllocatedMips = underAllocatedMips;
        }

        /**
         * Gets the under allocated mips.
         *
         * @return the under allocated mips
         */
        public Map<String, Double> getUnderAllocatedMips() {
            return underAllocatedMips;
        }

        /**
         * Gets the cache previous time.
         *
         * @return the cache previous time
         */
        protected double getCachePreviousTime() {
            return cachePreviousTime;
        }

        /**
         * Sets the cache previous time.
         *
         * @param cachePreviousTime the new cache previous time
         */
        protected void setCachePreviousTime(double cachePreviousTime) {
            this.cachePreviousTime = cachePreviousTime;
        }

        /**
         * Gets the cache current requested mips.
         *
         * @return the cache current requested mips
         */
        protected List<Double> getCacheCurrentRequestedMips() {
            return cacheCurrentRequestedMips;
        }

        /**
         * Sets the cache current requested mips.
         *
         * @param cacheCurrentRequestedMips the new cache current requested mips
         */
        protected void setCacheCurrentRequestedMips(List<Double> cacheCurrentRequestedMips) {
            this.cacheCurrentRequestedMips = cacheCurrentRequestedMips;
        }

    }


