/*
 * The MIT License
 *
 * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.parallels.desktopcloud;

import hudson.model.Node;
import hudson.remoting.Channel;
import hudson.security.Permission;
import hudson.slaves.AbstractCloudComputer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;


public class ParallelsDesktopConnectorSlaveComputer extends AbstractCloudComputer<ParallelsDesktopConnectorSlave>
{
	private static final Logger LOGGER = Logger.getLogger("PDConnectorSlaveComputer");

	public ParallelsDesktopConnectorSlaveComputer(ParallelsDesktopConnectorSlave slave)
	{
		super(slave);
	}

	public boolean checkVmExists(String vmId)
	{
		try
		{
			RunVmCallable command = new RunVmCallable("list", "-i", "--json");
			String callResult = forceGetChannel().call(command);
			JSONArray vms = (JSONArray)JSONSerializer.toJSON(callResult);
			for (int i = 0; i < vms.size(); i++)
			{
				JSONObject vmInfo = vms.getJSONObject(i);
				if (vmId.equals(vmInfo.getString("ID")) || vmId.equals(vmInfo.getString("Name")))
					return true;
			}
			return true;
		}
		catch (Exception ex)
		{
			LOGGER.log(Level.SEVERE, ex.toString());
		}
		return false;
	}

	private String getVmIPAddress(String vmId) throws Exception
	{
		int TIMEOUT = 60;
		for (int i = 0; i < TIMEOUT; ++i)
		{
			RunVmCallable command = new RunVmCallable("list", "-f", "--json", vmId);
			String callResult = forceGetChannel().call(command);
			LOGGER.log(Level.SEVERE, " - (" + i + "/" + TIMEOUT + ") calling for IP");
			LOGGER.log(Level.SEVERE, callResult);
			JSONArray vms = (JSONArray)JSONSerializer.toJSON(callResult);
			JSONObject vmInfo = vms.getJSONObject(0);
			String ip = vmInfo.getString("ip_configured");
			if (!ip.equals("-"))
				return ip;
			Thread.sleep(1000);
		}
		throw new Exception("Failed to get IP for VM '" + vmId + "'");
	}

	public Node createSlaveOnVM(ParallelsDesktopVM vm) throws Exception
	{
		String vmId = vm.getVmid();
		String slaveName = vm.getSlaveName();
		LOGGER.log(Level.SEVERE, "Starting slave '" + slaveName+ "'");
		LOGGER.log(Level.SEVERE, "Starting virtual machine '" + vmId + "'");
		RunVmCallable command = new RunVmCallable("start", vmId);
		try
		{
			forceGetChannel().call(command);
			LOGGER.log(Level.SEVERE, "Waiting for IP...");
			String ip = getVmIPAddress(vmId);
			LOGGER.log(Level.SEVERE, "Got IP address for VM " + vmId + ": " + ip);
			vm.setLauncherIP(ip);
		}
		catch (Exception ex)
		{
			LOGGER.log(Level.SEVERE, ex.toString());
		}
		return new ParallelsDesktopVMSlave(vm, this);
	}

	public void stopVM(String vmId)
	{
		try
		{
			LOGGER.log(Level.SEVERE, "Suspending...");
			RunVmCallable command = new RunVmCallable("suspend", vmId);
			String res = forceGetChannel().call(command);
			LOGGER.log(Level.SEVERE, res);
		}
		catch (Exception ex)
		{
			LOGGER.log(Level.SEVERE, ex.toString());
		}
	}
	
	public Channel forceGetChannel() throws InterruptedException, ExecutionException
	{
		Channel channel = getChannel();
		if (channel == null)
		{
			connect(true).get();
			channel = getChannel();
		}
		return channel;
	}

	private static final class RunVmCallable extends MasterToSlaveCallable<String, IOException>
	{
		private static final String cmd = "/usr/local/bin/prlctl";
		private final String[] params;
		
		public RunVmCallable(String... params)
		{
			this.params = params;
		}

		@Override
		public String call() throws IOException
		{
			List<String> cmds = new ArrayList<String>();
			cmds.add(cmd);
			cmds.addAll(Arrays.asList(this.params));
			
			LOGGER.log(Level.SEVERE, "Running command:");
			for (String s: cmds)
				LOGGER.log(Level.SEVERE, " [" + s + "]");
			ProcessBuilder pb = new ProcessBuilder(cmds);
			pb.redirectErrorStream(true);
			Process pr = pb.start();
			BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
			String line;
			String result = "";
			while ((line = in.readLine()) != null) 
			{
				result += line;
			}
			try
			{
				pr.waitFor();
			}
			catch (InterruptedException ex)
			{
				LOGGER.log(Level.SEVERE, ex.toString());
			}
			return result;
		}
	}
	
	@Override
	public boolean hasPermission(Permission permission)
	{
		if (permission == CONFIGURE)
			return false;
		return super.hasPermission(permission);
	}
}