/**
 * This file is part of CloudML [ http://cloudml.org ]
 *
 * Copyright (C) 2012 - SINTEF ICT
 * Contact: Franck Chauvel <franck.chauvel@sintef.no>
 *
 * Module: root
 *
 * CloudML is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * CloudML is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with CloudML. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.cloudml.core;

import com.amazonaws.services.ec2.model.InstanceType;
import org.cloudml.core.samples.DefaultLoadBalancer;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created by Iurii on 4/1/2015.
 */
public class ScalableComponent extends WithResources {

    private static String LB_INSTALL_COMMAND =
            "sudo wget https://dl.dropboxusercontent.com/u/32823955/scripts/lb.sh; sudo sh lb.sh";

    private static String COMPONENT_NAME_DIVIDER = "--";
    private static int START_TIME = 5;
    private static int KILLING_TIME = 3;

    private String id = "";
    private String name = "";

    private String addToLbCommand = "";
    private String removeFromLbCommand = "";

    private String changeDNSAddress = "";

    private LoadBalancerEntity loadBalancer = null;

    private String[] supportedTypesOfInstances = null;
    private int[] killingTypeOfInstancesTypes = null;
    private int[] startTypeOfInstancesTypes = null;

    private List<ExecuteInstance> listOfExecuteInstances = new ArrayList<ExecuteInstance>();
    private List<RelationshipInstance> listOfRegisteredInternalRelationships = new ArrayList<RelationshipInstance>();
    private List<RelationshipInstance> listOfSupportedInternalRelationships = new ArrayList<RelationshipInstance>();

    public ScalableComponent(String name, ExecuteInstance executeInstance, String addCommand, String removeCommand){
        this.name = name;
        this.addToLbCommand = addCommand;
        this.removeFromLbCommand = removeCommand;

        this.listOfExecuteInstances.add(executeInstance);
    }

    /**
     * Find instances that need be killed in the end of the hour.
     *
     * @param type Type of instance to kill
     * @param amount Amount of instance of particular type that need to be killed
     * @return List of instance that need to be marked to kill
     */
    public List<VMInstance> getInstancesOfTypeMarkedToKill(String type, int amount){

        List<VMInstance> instancesMarkedToKill = new ArrayList<VMInstance>();

        int numberOfAliveInstances = getInstancesInComponentOfTypeNotSetToKill(type).size();
        int numberOfInstancesSetToBeKilled = getInstancesInComponentOfTypeSetToKill(type).size();

        if (numberOfAliveInstances - amount > numberOfInstancesSetToBeKilled){
            for(int i = 0; i < numberOfAliveInstances - (amount + numberOfInstancesSetToBeKilled); i++){
                VMInstance instanceMarkedToKill = getLastInstanceToKill(getInstancesInComponentOfTypeNotSetToKill(type));
                instancesMarkedToKill.add(instanceMarkedToKill);
            }
        }else{
            for(int i = 0; i < numberOfInstancesSetToBeKilled - (numberOfAliveInstances - amount); i++ ){
                VMInstance vmInstanceToKeep = getInstanceWithSmallerTimeBagFromSetToBeKilled(type);
                if (vmInstanceToKeep != null) {
                    instancesMarkedToKill.remove(vmInstanceToKeep);
                }
            }
        }

        return instancesMarkedToKill;
    }

    /**
     * Find instances that was marked to be kill, but need to be kept alive because of increased workload.
     *
     * @param type Type of instance that are needed
     * @param amount Amount of instances of this type that need to be created
     * @return List of instances that should be kept alive
     */
    public List<VMInstance> getInstancesOfTypeMarkedToKeep(String type, int amount){

        int numberOfAliveInstances = getInstancesInComponentOfTypeNotSetToKill(type).size();

        List<VMInstance>  instancesMarkedToKeep = new ArrayList<VMInstance>();

        if(numberOfAliveInstances < amount){
            for(int i = 0; i < amount - numberOfAliveInstances; i++){

                List<VMInstance> instancesMarkedToKill = getInstancesInComponentOfTypeSetToKill(type);

                if(instancesMarkedToKill.size() != 0) {
                    // TODO: Restore one with min rbc
                    instancesMarkedToKeep.add(instancesMarkedToKill.get(0));
                }
            }
        }


        return instancesMarkedToKeep;
    }

    /**
     * Remove execute instance and all it's dependencies with particular name
     *
     * @param currentDeployment Current deployment model
     * @param nameOfExecuteInstance Name of Execute instance that need to be removed
     * @return Deployment model that doesn't contain execute instance and all it dependencies
     */
    public Deployment removeInstancesWithName(Deployment currentDeployment, String nameOfExecuteInstance){

        Deployment newDeployment = currentDeployment.clone();

        VMInstance vmToRemove = null;
        String vmToRemoveIP = "";
        ExecuteInstance exToRemove = null;
        InternalComponentInstance iciToRemove = null;

        List<ExecuteInstance> executeInstanceList = new CopyOnWriteArrayList<ExecuteInstance>();
        executeInstanceList.addAll(this.listOfExecuteInstances);

        for (ExecuteInstance exec : executeInstanceList){

            VMInstance currentVm = (VMInstance) exec.getProvidedEnd().getOwner().get();
            InternalComponentInstance currentComponent = (InternalComponentInstance) exec.getRequiredEnd().getOwner().get();

            if (currentVm.getName().equals(nameOfExecuteInstance)){
                vmToRemove = currentVm;
                vmToRemoveIP = currentVm.getPublicAddress();
                exToRemove = exec;
                iciToRemove = currentComponent;
            }
        }

        this.listOfExecuteInstances.remove(exToRemove);

        List<DirectCommand> dcToRemove = new ArrayList<DirectCommand>();

        for (DirectCommand dc : newDeployment.getDirectCommands()){

            // Because in moment of execution VM will be already killed
            if (dc.getClientIP().equals(vmToRemoveIP)){
                dcToRemove.add(dc);
            }
        }

        DirectCommand deleteDcComand = new DirectCommand(
                "remove_" + vmToRemove.getName()+ "_" + generatePrefix(),
                this.removeFromLbCommand,
                this.loadBalancer.getVmInstance(),
                vmToRemove
        );

        List<RelationshipInstance> relToRemove = new ArrayList<RelationshipInstance>();

        for (RelationshipInstance rel : this.listOfSupportedInternalRelationships){

            if (rel.getRequiredEnd().getOwner().get().equals(iciToRemove)){
                relToRemove.add(rel);
            }
        }

        this.listOfSupportedInternalRelationships.removeAll(relToRemove);

        newDeployment.getExecuteInstances().remove(exToRemove);
        newDeployment.getComponentInstances().remove(vmToRemove);
        newDeployment.getComponentInstances().remove(iciToRemove);
        newDeployment.getDirectCommands().removeAll(dcToRemove);
        newDeployment.getDirectCommands().add(deleteDcComand);
        //newDeployment.getRelationshipInstances().removeAll(relToRemove);

        return newDeployment;
    }

    /**
     * Remove all instance that marked to be killed if they close to end of hour
     *
     * @param deployment Current deployment model
     * @return Return new deployment model without instances that should be killed
     */
     public Deployment removeInstancesMarkedToBeKilled(Deployment deployment){

        Deployment newDeployment = deployment.clone();
        Deployment tmp = new Deployment();

        List<ExecuteInstance> executeInstanceList = new CopyOnWriteArrayList<ExecuteInstance>();
        executeInstanceList.addAll(this.listOfExecuteInstances);

        System.out.println("------------------------ Inst to kill timebag -----------------");

        for(String vmType : getSupportedTypesOfInstances()){
            for(VMInstance vmInstance : getInstancesInComponentOfTypeSetToKill(vmType)){
                if(vmInstance.getTimeBag() + deployment.getDelay() + KILLING_TIME > 60){
                    System.out.println(vmInstance.getTimeBag());
                    tmp = removeInstancesWithName(newDeployment, vmInstance.getName());
                    newDeployment = tmp.clone();
                }
            }
        }

        return newDeployment;
    }

    private VMInstance getInstanceWithSmallerTimeBagFromSetToBeKilled(String type){

        int smallestTimeBag = 60;
        VMInstance vmInstanceToSave = null;

        for(VMInstance vmInstance : getInstancesInComponentOfTypeSetToKill(type)){
            if(vmInstance.getTimeBag() < smallestTimeBag){
                smallestTimeBag = vmInstance.getTimeBag();
                vmInstanceToSave = vmInstance;
            }
        }

        return vmInstanceToSave;
    }

    public Deployment removeInstanceFrom(Deployment currentDeployment, String nameOfExecuteInstance){

        Deployment newDeployment = currentDeployment.clone();

        VMInstance vmToRemove = null;
        ExecuteInstance exToRemove = null;
        InternalComponentInstance iciToRemove = null;

        List<ExecuteInstance> executeInstanceList = new CopyOnWriteArrayList<ExecuteInstance>();
        executeInstanceList.addAll(this.listOfExecuteInstances);

        for (ExecuteInstance exec : executeInstanceList){

            VMInstance currentVm = (VMInstance) exec.getProvidedEnd().getOwner().get();
            InternalComponentInstance currentComponent = (InternalComponentInstance) exec.getRequiredEnd().getOwner().get();

            if (currentVm.getName().equals(nameOfExecuteInstance)){
                vmToRemove = currentVm;
                exToRemove = exec;
                iciToRemove = currentComponent;
            }
        }

        this.listOfExecuteInstances.remove(exToRemove);

        newDeployment.getExecuteInstances().remove(exToRemove);
        newDeployment.getComponentInstances().remove(vmToRemove);
        newDeployment.getComponentInstances().remove(iciToRemove);

        return newDeployment;

    }

    public Deployment removeInstancesMarkedToBeKilledInstant(Deployment deployment){

        Deployment newDeployment = deployment.clone();

        List<ExecuteInstance> executeInstanceList = this.getListOfExecuteInstances();

        for(ExecuteInstance executeInstance : executeInstanceList){
            VMInstance currentVm = (VMInstance) executeInstance.getProvidedEnd().getOwner().get();

            // Kill instance only if it's last check before new hour
            if (currentVm.isSetToKill()){
                newDeployment = removeInstancesWithName(newDeployment, currentVm.getName()).clone();
            }
        }

        return newDeployment;
    }

    /**
     * Create all number of new vmInstances depends on new configuration
     *
     * @param configuration Configuration that contains how many new instances of each type should be created
     * @return Deployment configuration that contains all new vmInstances and their dependencies
     */
    public Deployment createNeededInstances(int[] configuration){

        Deployment newDeployment = new Deployment();

        for(int i = 0; i < this.supportedTypesOfInstances.length; i++){
            for (int j = 0; j < configuration[i]; j++){
                newDeployment.merge(createInstanceOfParticularType(this.supportedTypesOfInstances[i]));
            }
        }

        return newDeployment;
    }

    /**
     * Create vmInstance of particular type and all it's dependencies
     *
     * @param type Type of vm to create
     * @return Deployment model that contain new vm and all it's dependencies
     */
    public Deployment createInstanceOfParticularType(String type){

        Deployment newDep = new Deployment();

        VMInstance vmToScale = (VMInstance) this.listOfExecuteInstances.get(0).getProvidedEnd().getOwner().getValue();
        InternalComponentInstance componentToScale =
                (InternalComponentInstance) this.listOfExecuteInstances.get(0).getRequiredEnd().getOwner().getValue();

        VMInstance newVm = scaleVmInstance(vmToScale, type);

        InternalComponentInstance newInternalComponent = scaleInternalComponents(componentToScale);

        ExecuteInstance newExec = scaleExecuteInstance(
                this.listOfExecuteInstances.get(0).getName() + generatePrefix(),
                newInternalComponent,
                getSuitablePlatform(newVm, newInternalComponent)
        );

        for (RelationshipInstance relationshipInstance : this.listOfRegisteredInternalRelationships){
            RelationshipInstance newRel = new RelationshipInstance(
                    relationshipInstance.getName() + "_" + newVm.getName() + generatePrefix(),
                    newInternalComponent.getRequiredPorts().ofType(relationshipInstance.getType().getRequiredEnd()),
                    relationshipInstance.getProvidedEnd(),
                    relationshipInstance.getType()
            );

            this.listOfSupportedInternalRelationships.add(newRel);

            newDep.getRelationshipInstances().add(newRel);
        }


        DirectCommand addToSever = new DirectCommand(
                "addToLB" + generatePrefix(),
                this.addToLbCommand,
                this.loadBalancer.getVmInstance(),
                newVm
        );

        //this.listOfSupportedDirectComponents.add(addToSever);
        this.listOfExecuteInstances.add(newExec);

        if(!newVm.getType().equals(vmToScale.getType())){
            newDep.getComponents().add(newVm.getType());
        }
        newDep.getComponentInstances().add(newInternalComponent);
        newDep.getComponentInstances().add(newVm);
        newDep.getExecuteInstances().add(newExec);
        newDep.getDirectCommands().add(addToSever);


        return newDep;
    }

    public Deployment addInstance(String vmInstanceType, Deployment currentDeployment){

        Deployment newDep = new Deployment();

        DirectCommand changeDNSConfig = null;

        VMInstance vmToScale = (VMInstance) this.listOfExecuteInstances.get(0).getProvidedEnd().getOwner().getValue();
        InternalComponentInstance componentToScale =
                (InternalComponentInstance) this.listOfExecuteInstances.get(0).getRequiredEnd().getOwner().getValue();


        System.out.println("==================== vmToScale" + vmToScale.getPublicAddress());

        if (this.loadBalancer == null) {

            LoadBalancerEntity currentLoadBalancer = createLoadBalancer();

            newDep.getDirectCommands().add(currentLoadBalancer.getLoadBalancerDirectCommand());
            newDep.getComponentInstances().add(currentLoadBalancer.getVmInstance());
            newDep.getComponentInstances().add(currentLoadBalancer.getComponentInstance());
            newDep.getExecuteInstances().add(currentLoadBalancer.getExecuteInstance());
            newDep.getComponents().add(currentLoadBalancer.getVM());
            newDep.getComponents().add(currentLoadBalancer.getComponent());



            System.out.println("====================  Rel desc ===================");

            for (RelationshipInstance relationshipInstance : currentDeployment.getRelationshipInstances()) {
                //if(!this.listOfSupportedInternalRelationships.contains(relationshipInstance)){
                final ComponentInstance<? extends Component> serverComponent = relationshipInstance.getServerComponent();
                VMInstance instance = (VMInstance) getDestination(serverComponent);
                VMInstance instance2 = (VMInstance) getDestination(relationshipInstance.getClientComponent());

                System.out.println("Rel name is: " + relationshipInstance.getName());
                System.out.println("VM: " + instance.getPublicAddress() + "to VM: " + instance2.getPublicAddress());
                System.out.println("---------------------------------------------------");
            }




            int rr = this.listOfExecuteInstances.size();
                // TODO: ONLY FOR EXPERIMENT 2
            //if(this.listOfExecuteInstances.size() == 1){
                for (RelationshipInstance relationshipInstance : currentDeployment.getRelationshipInstances()){
                    //if(!this.listOfSupportedInternalRelationships.contains(relationshipInstance)){
                    final ComponentInstance<? extends Component> serverComponent = relationshipInstance.getServerComponent();
                        VMInstance instance = (VMInstance) getDestination(serverComponent);
                        VMInstance instance2 = (VMInstance) getDestination(relationshipInstance.getClientComponent());
                        if(instance.getPublicAddress().equals(vmToScale.getPublicAddress())){
                            changeDNSConfig = new DirectCommand(
                                    "changeDNSConfig" + generatePrefix(),
                                    this.changeDNSAddress,
                                    instance2,
                                    instance,
                                    this.loadBalancer.getVmInstance()

                            );
                        }
                    //}
                }
            //}
        }


        VMInstance newVm = scaleVmInstance(vmToScale, vmInstanceType);

        InternalComponentInstance newInternalComponent = scaleInternalComponents(componentToScale);

        ExecuteInstance newExec = scaleExecuteInstance(
                this.listOfExecuteInstances.get(0).getName() + generatePrefix(),
                newInternalComponent,
                getSuitablePlatform(newVm, newInternalComponent)
        );

        for (RelationshipInstance relationshipInstance : this.listOfRegisteredInternalRelationships){
            RelationshipInstance newRel = new RelationshipInstance(
               relationshipInstance.getName() + "_" + newVm.getName() + generatePrefix(),
               newInternalComponent.getRequiredPorts().ofType(relationshipInstance.getType().getRequiredEnd()),
               relationshipInstance.getProvidedEnd(),
               relationshipInstance.getType()
            );

            this.listOfSupportedInternalRelationships.add(newRel);

            newDep.getRelationshipInstances().add(newRel);
        }

        DirectCommand addToSever = new DirectCommand(
                "addToLB" + generatePrefix(),
                this.addToLbCommand,
                this.loadBalancer.getVmInstance(),
                newVm
        );

        //this.listOfSupportedDirectComponents.add(addToSever);
        this.listOfExecuteInstances.add(newExec);

        if(!newVm.getType().equals(vmToScale.getType())){
            newDep.getComponents().add(newVm.getType());
        }
        newDep.getComponentInstances().add(newInternalComponent);
        newDep.getComponentInstances().add(newVm);
        newDep.getExecuteInstances().add(newExec);
        newDep.getDirectCommands().add(addToSever);
        System.out.println("=========================== DNS ================");
       // System.out.println(changeDNSConfig.toString());
        if(changeDNSConfig != null){
            newDep.getDirectCommands().add(changeDNSConfig);
        }


        return newDep;

    }

    public Deployment addNewInstanceOfType(Deployment currentDeployment, String type, int amount){

        Deployment newDeployment =  currentDeployment.clone();

        for(int i = 0; i < amount; i++){

            List<VMInstance> instancesToKill = getInstancesInComponentOfTypeSetToKill(type);

            if(instancesToKill.size() != 0){
                // TODO: Restore one with min rbc
                newDeployment.getComponentInstances().onlyVMs().firstNamed(instancesToKill.get(0).getName()).setToKeep();
            }else{
                newDeployment.merge(addInstance(type, currentDeployment));
            }
        }
        return newDeployment;
    }

    public Deployment removeInstance(Deployment currentDeployment, String typeOfInstance){

        Deployment newDeployment = currentDeployment.clone();

        long timeNow = System.currentTimeMillis();
        VMInstance vmToRemove = null;
        String vmToRemoveIP = "";
        ExecuteInstance exToRemove = null;
        InternalComponentInstance iciToRemove = null;

        for (ExecuteInstance exec : this.listOfExecuteInstances){

            VMInstance currentVm = (VMInstance)exec.getProvidedEnd().getOwner().get();
            InternalComponentInstance currentComponent = (InternalComponentInstance)exec.getRequiredEnd().getOwner().get();

            if (currentVm.getStartTime() < timeNow &&
                    currentVm.getType().getProviderSpecificTypeName().equals(typeOfInstance)){
                vmToRemove = currentVm;
                vmToRemoveIP = currentVm.getPublicAddress();
                exToRemove = exec;
                iciToRemove = currentComponent;
                timeNow = currentVm.getStartTime();
            }
        }

        this.listOfExecuteInstances.remove(exToRemove);

        List<DirectCommand> dcToRemove = new ArrayList<DirectCommand>();

        for (DirectCommand dc : newDeployment.getDirectCommands()){

            // Because in moment of execution VM will be already killed
            if (dc.getClientIP().equals(vmToRemoveIP)){
                dcToRemove.add(dc);
            }
        }

        DirectCommand deleteDcComand = new DirectCommand(
                "remove_" + vmToRemove.getName()+ "_" + generatePrefix(),
                this.removeFromLbCommand,
                this.loadBalancer.getVmInstance(),
                vmToRemove
                );

        List<RelationshipInstance> relToRemove = new ArrayList<RelationshipInstance>();

        for (RelationshipInstance rel : this.listOfSupportedInternalRelationships){

            InternalComponentInstance tmp = (InternalComponentInstance) rel.getProvidedEnd().getOwner().get();
            if (rel.getRequiredEnd().getOwner().get().equals(iciToRemove)){
                relToRemove.add(rel);
            }
        }

        this.listOfSupportedInternalRelationships.removeAll(relToRemove);

        newDeployment.getExecuteInstances().remove(exToRemove);
        newDeployment.getComponentInstances().remove(vmToRemove);
        newDeployment.getComponentInstances().remove(iciToRemove);
        newDeployment.getDirectCommands().removeAll(dcToRemove);
        newDeployment.getDirectCommands().add(deleteDcComand);
        //newDeployment.getRelationshipInstances().removeAll(relToRemove);

        return newDeployment;
    }

    private void unlessNotNull(String message, Object... obj) {
        if (obj != null) {
            for (Object o : obj) {
                if (o == null) {
                    throw new IllegalArgumentException(message);
                }
            }
        } else {
            throw new IllegalArgumentException(message);
        }
    }

    public ExternalComponentInstance getDestination(ComponentInstance component) {
        unlessNotNull("Cannot find destination of null!", component);
        if (component instanceof InternalComponentInstance) {
            InternalComponentInstance internalComponent = (InternalComponentInstance) component;
            return internalComponent.externalHost();
        } else {
            return (ExternalComponentInstance) component;
        }
    }

    private VMInstance scaleVmInstance(VMInstance vmi, String vmInstanceType){
        return new VMInstance(generateNameForComponent(vmi.getName()), getVmType(vmi, vmInstanceType));
    }

    private VM getVmType(VMInstance vmi, String vmInstanceType){

        if(vmi.getType().getProviderSpecificTypeName().contains(vmInstanceType)){
            return vmi.getType();
        }else {
            VM newVm = vmi.getType().clone();
            newVm.setName(vmi.getType().getName() + generatePrefix());
            newVm.setProviderSpecificTypeName(InstanceType.fromValue(vmInstanceType).toString());

            return  newVm;
        }
    }

    private InternalComponentInstance scaleInternalComponents(InternalComponentInstance internalComponentInstance){
        return new InternalComponentInstance(
                internalComponentInstance.getName() + generatePrefix(),
                internalComponentInstance.getType()
        );
    }

    private ExecuteInstance scaleExecuteInstance(String name,
                                                 InternalComponentInstance internalComponentInstance,
                                                 ProvidedExecutionPlatformInstance platform){
         return new ExecuteInstance(generateNameForComponent(name), internalComponentInstance, platform);
    }

    private ProvidedExecutionPlatformInstance getSuitablePlatform(VMInstance vmi,
                                                                  InternalComponentInstance internalComponentInstance){
        return vmi.getProvidedExecutionPlatforms().firstMatchFor(internalComponentInstance);
    }

    private String generateNameForComponent(String initialName){
        if(initialName.split(COMPONENT_NAME_DIVIDER).length == 1){
            return initialName + generatePrefix();
        }else {
            return initialName.split(COMPONENT_NAME_DIVIDER)[0] + generatePrefix();
        }
    }

    private void removeLoadBalancer(Deployment deployment){

        deployment.getComponents().remove(loadBalancer.getComponent());
        deployment.getComponentInstances().remove(loadBalancer.getVmInstance());
        deployment.getComponentInstances().remove(loadBalancer.getComponentInstance());
        deployment.getExecuteInstances().remove(loadBalancer.getExecuteInstance());

    }

    public Deployment initializeLoadBalancer(){

        Deployment newDep = new Deployment();

        LoadBalancerEntity currentLoadBalancer = createLoadBalancer();

        newDep.getDirectCommands().add(currentLoadBalancer.getLoadBalancerDirectCommand());
        newDep.getComponentInstances().add(currentLoadBalancer.getVmInstance());
        newDep.getComponentInstances().add(currentLoadBalancer.getComponentInstance());
        newDep.getExecuteInstances().add(currentLoadBalancer.getExecuteInstance());
        newDep.getComponents().add(currentLoadBalancer.getVM());
        newDep.getComponents().add(currentLoadBalancer.getComponent());

        return newDep;
    }

    private LoadBalancerEntity createLoadBalancer(){

        LoadBalancerEntity currentLoadBalancer = null;

        VMInstance vmFromThisComponent = (VMInstance) this.listOfExecuteInstances.get(0).getProvidedEnd().getOwner().getValue();
        InternalComponentInstance componentToScale = (InternalComponentInstance) this.listOfExecuteInstances.get(0).getRequiredEnd().getOwner().getValue();

        System.out.println("Create Load Balancer");
        Resource lbSoft = new Resource("lbSoft");
        lbSoft.setRetrieveCommand(LB_INSTALL_COMMAND);

        currentLoadBalancer = DefaultLoadBalancer.initializeLoadBalancer(
                vmFromThisComponent.getType(),
                InstanceType.T1Micro,
                lbSoft,
                componentToScale.getProvidedPorts().toList().get(0).getType(),
                this.getName()
        );

        this.loadBalancer = currentLoadBalancer;

        DirectCommand addToSever1 = new DirectCommand(
                "addToLb" + vmFromThisComponent.getName() + "_" + generatePrefix(),
                this.addToLbCommand,
                this.loadBalancer.getVmInstance(),
                vmFromThisComponent
        );

        currentLoadBalancer.setLoadBalancerDirectCommand(addToSever1);
        this.loadBalancer.setLoadBalancerDirectCommand(addToSever1);

        System.out.println(currentLoadBalancer.toString());

        return currentLoadBalancer;
    }

    private String generatePrefix(){
        return "_" + String.valueOf(System.currentTimeMillis());
    }

    private List<VMInstance> getListOfRegisteredVmInstances(){
        List<VMInstance> listOfVmInstances = new ArrayList<VMInstance>();

        for (ExecuteInstance ex : this.listOfExecuteInstances){
            listOfVmInstances.add((VMInstance) ex.getProvidedEnd().getOwner().getValue());
        }

        return listOfVmInstances;
    }

    public String getAddToLbCommand() {
        return addToLbCommand;
    }

    public void setAddToLbCommand(String addToLbCommand) {
        this.addToLbCommand = addToLbCommand;
    }

    public String getChangeDNSAddressCommand() {
        return changeDNSAddress;
    }

    public void setChangeDNSAddressCommand(String changeDNSAddress) {
        this.changeDNSAddress = changeDNSAddress;
    }

    public String getRemoveFromLbCommand() {
        return removeFromLbCommand;
    }

    public void setRemoveFromLbCommand(String removeFromLbCommand) {
        this.removeFromLbCommand = removeFromLbCommand;
    }

    public void addInternalRelationships(RelationshipInstance internalRelationships) {
        this.listOfSupportedInternalRelationships.add(internalRelationships);
        this.listOfRegisteredInternalRelationships.add(internalRelationships);
    }

    public LoadBalancerEntity getLoadBalancer() {
        return loadBalancer;
    }

    public List<ExecuteInstance> getListOfExecuteInstances() {
        return listOfExecuteInstances;
    }

    public List<VMInstance> getListOfVMInstancesInComponent(){
        List<VMInstance> vmInstances = new ArrayList<VMInstance>();

        for(ExecuteInstance executeInstance: this.listOfExecuteInstances){
            vmInstances.add((VMInstance) executeInstance.getProvidedEnd().getOwner().getValue());
        }

        return vmInstances;
    }

    public List<VMInstance> getInstancesInComponentOfType(String type){
        List<VMInstance> vmInstances = new ArrayList<VMInstance>();

        for(ExecuteInstance executeInstance: this.listOfExecuteInstances){
            VMInstance instance =  (VMInstance) executeInstance.getProvidedEnd().getOwner().getValue();
            if (instance.getType().getProviderSpecificTypeName().equals(type)){
                vmInstances.add(instance);
            }
        }
        return vmInstances;
    }

    public List<VMInstance> getInstancesInComponentOfTypeNotSetToKill(String type){
        List<VMInstance> vmInstances = new ArrayList<VMInstance>();

        for(ExecuteInstance executeInstance: this.listOfExecuteInstances){
            VMInstance instance =  (VMInstance) executeInstance.getProvidedEnd().getOwner().getValue();
            if (instance.getType().getProviderSpecificTypeName().equals(type) && instance.isSetToKill() == false){
                vmInstances.add(instance);
            }
        }
        return vmInstances;
    }

    private List<VMInstance> getInstancesInComponentOfTypeSetToKill(String type){
        List<VMInstance> vmInstances = new ArrayList<VMInstance>();

        for(ExecuteInstance executeInstance: this.listOfExecuteInstances){
            VMInstance instance =  (VMInstance) executeInstance.getProvidedEnd().getOwner().getValue();
            if (instance.getType().getProviderSpecificTypeName().equals(type) && instance.isSetToKill() == true){
                vmInstances.add(instance);
            }
        }
        return vmInstances;
    }

    private VMInstance getLastInstanceToKill(List<VMInstance> vmInstances){

        VMInstance lastInstance = null;
        long lastTimeBag = 0;

        for(VMInstance vmInstance : vmInstances){
            long currentTimeBag = ((((System.currentTimeMillis() - vmInstance.getStartTime()) / (60 * 1000)) % 60));
            if ( currentTimeBag >= lastTimeBag){
                lastInstance = vmInstance;
                lastTimeBag = vmInstance.getStartTime();
            }
        }

        return  lastInstance;
    }

    private String getConnectorIP(){

        if(this.loadBalancer != null){
            return ((VMInstance) this.loadBalancer.getExecuteInstance().getProvidedEnd().getOwner().get()).getPublicAddress();
        }else{

            ExecuteInstance executeInstance = listOfExecuteInstances.get(0);
            return ((VMInstance) executeInstance.getProvidedEnd().getOwner().get()).getPublicAddress();
        }
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int[] getKillingTypeOfInstancesTypes() {
        return killingTypeOfInstancesTypes;
    }

    public void setKillingTypeOfInstancesTypes(int[] killingTypeOfInstancesTypes) {
        this.killingTypeOfInstancesTypes = killingTypeOfInstancesTypes;
    }

    public int[] getStartTypeOfInstancesTypes() {
        return startTypeOfInstancesTypes;
    }

    public void setStartTypeOfInstancesTypes(int[] startTypeOfInstancesTypes) {
        this.startTypeOfInstancesTypes = startTypeOfInstancesTypes;
    }

    public String[] getSupportedTypesOfInstances() {
        return supportedTypesOfInstances;
    }

    public void setSupportedTypesOfInstances(String[] supportedTypesOfInstances) {
        this.supportedTypesOfInstances = supportedTypesOfInstances;
    }
}
