/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@ow2.org
 *
 * This library 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 2.1 of the License, or any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: JasmineVMMAction.java 3643 2009-05-07 11:28:30Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.action;

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

import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.remote.JMXServiceURL;

import org.ow2.jasmine.jadort.api.entities.deployment.VMImageBean;
import org.ow2.jasmine.jadort.api.entities.topology.VMBean;
import org.ow2.jasmine.vmm.api.VirtualMachineMXBean.PowerState;

/**
 * Action for the JASMINe VMM Agent.
 * 
 * @author Malek Chahine
 * @author S. Ali Tokmen
 */
public class JasmineVMMAction extends VMMAction {

    private String url;

    public JasmineVMMAction(final VMBean vm) {
        this.url = vm.getConnector().getConnectorUrl();
        this.appendToLog("Created JasmineVMMAction for JASMINe VMM Agent on URL \"" + this.url + "\"");
    }

    @Override
    protected void connectViaJMX() throws Exception {
        this.establishJMXConnection(new JMXServiceURL(this.url), null);
    }

    /**
     * Gets the ObjectName for a given VM host name.
     */
    protected ObjectName getObjectNameForVMHost(final String vmName) throws Exception {
        this.checkJMXConnection();

        Set<?> vms = this.mbscnx.queryNames(new ObjectName("*:type=VirtualMachine,name=" + vmName + ",*"), null);
        if (vms.size() < 1) {
            throw new Exception("There are no VirtualMachines named '" + vmName + "' on this VM Manager!");
        } else if (vms.size() > 1) {
            throw new Exception("There are more than one VirtualMachines named '" + vmName + "' on this VM Manager!");
        } else {
            return (ObjectName) vms.iterator().next();
        }
    }

    @Override
    public String getFullVMName(final String vmName) throws Exception {
        this.checkJMXConnection();

        Map<String, PowerState> matchingVMs = new HashMap<String, PowerState>();
        List<String> runningVMs = new ArrayList<String>();
        Set<?> vms = this.mbscnx.queryNames(new ObjectName("*:type=VirtualMachine,*"), null);
        for (Object vmsItem : vms) {
            ObjectName vm = (ObjectName) vmsItem;

            String name = vm.getKeyProperty("name");
            if (name != null && name.equals(vmName) || name.startsWith(vmName + '_')) {
                PowerState powerState = this.getVMState(name);
                matchingVMs.put(name, powerState);

                if (powerState.equals(PowerState.RUNNING)) {
                    runningVMs.add(name);
                }
            }
        }

        if (runningVMs.size() == 0) {
            if (matchingVMs.size() == 0) {
                throw new IllegalStateException("There are no VM hosts with a name matching \"" + vmName + "\"" + "\"");
            } else {
                throw new IllegalStateException("There are no running VM hosts with a name matching \"" + vmName + "\""
                    + ". Non-running hosts are as follows: " + matchingVMs.toString());
            }
        } else if (runningVMs.size() == 1) {
            return runningVMs.get(0);
        } else {
            throw new IllegalStateException("There are " + runningVMs.size() + " running VM hosts with a name matching \""
                + vmName + "\", whereas JaDOrT is expecting only one.");
        }
    }

    @Override
    public void startVM(final String vmName) throws Exception {
        this.appendToLog("Starting VM " + vmName);
        ObjectName vm = this.getObjectNameForVMHost(vmName);

        this.mbscnx.invoke(vm, "start", null, null);

        this.appendToLog("VM " + vmName + " signaled for start");
    }

    @Override
    public void stopVM(final String vmName) throws Exception {
        this.appendToLog("Stopping VM " + vmName);
        ObjectName vm = this.getObjectNameForVMHost(vmName);

        this.mbscnx.invoke(vm, "shutdown", null, null);

        this.appendToLog("VM " + vmName + " signaled for stop");
    }

    @Override
    public PowerState getVMState(final String vmName) throws Exception {
        ObjectName vm = this.getObjectNameForVMHost(vmName);

        String state = this.mbscnx.getAttribute(vm, "State").toString();
        return PowerState.valueOf(state);
    }

    @Override
    public void waitForVMState(final String vmName, final PowerState state, final long timeout) throws Exception {
        this.appendToLog("Waiting for VM " + vmName + " to reach state " + state);
        ObjectName vm = this.getObjectNameForVMHost(vmName);

        PowerState currentState = null;
        final long stopTime = System.currentTimeMillis() + timeout * 1000;
        while (currentState != state && System.currentTimeMillis() < stopTime) {
            Thread.sleep(1000);
            String currentStateString = this.mbscnx.getAttribute(vm, "State").toString();
            currentState = PowerState.valueOf(currentStateString);
        }

        if (currentState == state) {
            this.appendToLog("VM " + vmName + " has reached state " + state);
            if (PowerState.RUNNING.equals(state)) {
                String guestIpAddress = this.mbscnx.getAttribute(vm, "GuestIpAddress").toString();
                this.appendToLog("VM guest has IP address " + guestIpAddress);
            }
        } else {
            throw new Exception("VM " + vmName + " did not reach state " + state + " after " + timeout
                + " seconds! Try retrying manually using the \"Retry server\" button...");
        }
    }

    @Override
    public void deployImageOnVM(final String copyVM, final String newVMName, final String vmImageUUID) throws Exception {
        this.checkJMXConnection();

        String newVmName = newVMName.substring(newVMName.lastIndexOf('/') + 1);
        this.appendToLog("Creating VM " + newVmName + " based on VM " + copyVM + " and VM image with UUID" + vmImageUUID);

        ObjectName vm = null;
        Set<?> vms = this.mbscnx.queryNames(new ObjectName("*:type=VirtualMachine,name=" + copyVM + ",*"), null);
        if (vms == null || vms.size() < 1) {
            this.mbscnx = null;
            throw new Exception("There are no VirtualMachines named '" + copyVM + "' on this VM Manager!");
        } else if (vms.size() > 1) {
            this.mbscnx = null;
            throw new Exception("There are more than one VirtualMachines named '" + copyVM + "' on this VM Manager!");
        } else {
            vm = (ObjectName) vms.iterator().next();
        }

        ObjectName hostMBean = (ObjectName) this.mbscnx.getAttribute(vm, "HostMBean");
        Long memorySizeMB = (Long) this.mbscnx.getAttribute(vm, "MemorySizeMB");
        Integer numVCPUs = (Integer) this.mbscnx.getAttribute(vm, "NumVCPUs");

        // The diskSizeMB parameter is ignored
        // when creating a VM host from a VM image
        Integer diskSizeMB = Integer.valueOf(1);

        String[] fields = new String[] {"name", "memorySizeMB", "numVCPU", "diskSizeMB", "vmImageUUID"};
        OpenType[] types = new OpenType[] {SimpleType.STRING, SimpleType.LONG, SimpleType.INTEGER, SimpleType.INTEGER,
            SimpleType.STRING};
        CompositeType compositeType = new CompositeType("VMConfigSpec", "VMConfigSpec", fields, fields, types);
        CompositeDataSupport compositeData = new CompositeDataSupport(compositeType, fields, new Object[] {newVmName,
            memorySizeMB, numVCPUs, diskSizeMB, vmImageUUID});

        Object[] opParams = new Object[] {compositeData, true};
        String[] opSignature = new String[] {CompositeData.class.getName(), "boolean"};
        this.mbscnx.invoke(hostMBean, "createVM", opParams, opSignature);

        this.appendToLog("VM " + newVmName + " created based on VM " + copyVM + " and VM image with UUID" + vmImageUUID);
    }

    @Override
    public void destroyVM(final String vmName) throws Exception {
        this.appendToLog("Destroying VM " + vmName);
        ObjectName vm = this.getObjectNameForVMHost(vmName);

        this.mbscnx.invoke(vm, "destroy", null, null);

        this.appendToLog("VM " + vmName + " destroyed");
    }

    @Override
    public List<VMImageBean> getVMImages() throws Exception {
        this.checkJMXConnection();

        List<VMImageBean> vmImages = new ArrayList<VMImageBean>();
        for (Object mBean : this.mbscnx.queryNames(new ObjectName("*:type=VirtualMachineImage,*"), null)) {
            ObjectName vmImage = (ObjectName) mBean;
            String name = this.mbscnx.getAttribute(vmImage, "Name").toString();
            String uuid = this.mbscnx.getAttribute(vmImage, "UUID").toString();
            vmImages.add(new VMImageBean(name, uuid));
        }

        return vmImages;
    }
}
