/**
 * 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.connectors;


import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

import com.fasterxml.jackson.databind.node.ValueNode;
import org.cloudml.core.ComponentInstance;
import org.cloudml.core.VM;
import org.cloudml.core.VMInstance;
import org.cloudml.core.Property;

import net.flexiant.extility.*;

public class FlexiantConnector implements Connector{

    private static final Logger journal = Logger.getLogger(FlexiantConnector.class.getName());

    private final String endpoint;
    private UserService service;
    private BindingProvider portBP;
    private HashMap<String,String> runtimeInformation=new HashMap<String, String>();

    @SuppressWarnings("restriction")
    public FlexiantConnector(String endPoint, String login, String secretKey) throws MalformedURLException{
        this.endpoint=endPoint;
        System.setProperty ("jsse.enableSNIExtension", "false");

        journal.log(Level.INFO, ">> Connecting to Flexiant ...");

        URL url = ClassLoader.getSystemClassLoader().getResource(
                "UserAdmin.wsdl");

        // Get the UserAPI
        UserAPI api = new UserAPI(url,
                new QName("http://extility.flexiant.net", "UserAPI"));

        // and set the service port on the service
        service = api.getUserServicePort();

        // Get the binding provider
        BindingProvider portBP = (BindingProvider) service;

        // and set the service endpoint
        portBP.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                endPoint);

        journal.log(Level.INFO, ">> Authenticating ...");
        // and the caller's authentication details and password
        portBP.getRequestContext().put(BindingProvider.USERNAME_PROPERTY,
                login);
        portBP.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY,
                secretKey);

    }


    public List<Object> getListServer(){
        try {
            // Create an FQL filter and a filter condition
            SearchFilter sf = new SearchFilter();
            FilterCondition fc = new FilterCondition();

            // set the condition type
            fc.setCondition(Condition.IS_EQUAL_TO);

            // the field to be matched
            fc.setField("status");

            // and a list of values
            fc.getValue().add(ServerStatus.RUNNING.name());
            fc.getValue().add(ServerStatus.STARTING.name());

            // Add the filter condition to the query
            sf.getFilterConditions().add(fc);

            // Set a limit to the number of results
            QueryLimit lim = new QueryLimit();
            lim.setMaxRecords(50);

            // Call the service to execute the query
            ListResult result = service.listResources(sf,lim, ResourceType.SERVER);

            return result.getList();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public List<Object> getListImages(){
        try {
            // Create an FQL filter and a filter condition
            SearchFilter sf = new SearchFilter();
            FilterCondition fc = new FilterCondition();

            // the field to be matched
            fc.setField("ImageType");

            // set the condition type
            fc.setCondition(Condition.IS_EQUAL_TO);

            // Add the filter condition to the query
            sf.getFilterConditions().add(fc);

            // and a list of values
            fc.getValue().add(ImageType.DISK.name());

            // Set a limit to the number of results
            QueryLimit lim = new QueryLimit();
            lim.setMaxRecords(50);

            // Call the service to execute the query
            ListResult result = service.listResources(sf,lim, ResourceType.IMAGE);
            return result.getList();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public HashMap<String,String> createInstance(VMInstance a){
        ComponentInstance.State state = ComponentInstance.State.UNRECOGNIZED;
        try {
            Server template = new Server();
            if(findResourceByName(a.getName(), ResourceType.SERVER).equals("")){
                VM vm = a.getType();
                List<String> sshKeyList = new ArrayList<String> ();

                template.setResourceType(ResourceType.SERVER);

                journal.log(Level.INFO, ">> Provisioning a vm ..."+vm.getGroupName());

                if (vm.getMinCores() > 0 && vm.getMinRam() > 0)
                    template.setProductOfferUUID(findProduct(((double) vm.getMinRam()), vm.getMinCores()));

                if (!vm.getGroupName().equals(""))
                    template.setVdcUUID(findResourceByName(vm.getGroupName(),ResourceType.VDC));

                if(!vm.getImageId().equals(""))
                    template.setImageUUID(findResourceByName(vm.getImageId(),ResourceType.IMAGE)); //TODO: find by OS

                if(!vm.getSshKey().equals(""))
                    sshKeyList.add(findResourceByName(vm.getSshKey(),ResourceType.SSHKEY));

                template.setResourceName(a.getName());
                Nic n=new Nic();
                if(findResourceByName("Default network - Default cluster", ResourceType.NETWORK).equals(""))
                    n.setNetworkUUID(findResourceByName("Default network - KVM", ResourceType.NETWORK));
                else n.setNetworkUUID(findResourceByName("Default network - Default cluster", ResourceType.NETWORK));
                n.setNetworkName(a.getName());
                template.getNics().add(n);

				Disk d = new Disk();
				d.setSize(vm.getMinStorage());
                d.setResourceName(a.getName());
				template.getDisks().add(d);

                //TODO: Add disk

                Job job=service.createServer(template, sshKeyList, null);
                service.waitForJob(job.getResourceUUID(), false);

                a.getProperties().add(new Property("ProviderInstanceType",template.getProductOfferName()));
                a.setId(findResourceByName(a.getName(), ResourceType.SERVER));

				/*Job nicJob=service.createNetworkInterface(n, null);
				service.waitForJob(nicJob.getResourceUUID(), false);

				service.attachNetworkInterface(job.getItemUUID(), nicJob.getItemUUID(), 0, null);*/
                journal.log(Level.INFO, ">> vm type: "+ template.getProductOfferName() +  " named " + template.getResourceName());

                Job startJob=service.changeServerStatus(a.getId(), ServerStatus.RUNNING, true, null, null);
                service.waitForJob(startJob.getResourceUUID(), false);
                Thread.sleep(90000);

                //System.out.println(((Server) findObjectResourceByName(a.getName(), ResourceType.SERVER)).getInitialPassword());
            }
            a.setId(findResourceByName(a.getName(), ResourceType.SERVER));
            Server temp=(Server)findObjectResourceByName(a.getName(), ResourceType.SERVER);
            a.setCore(temp.getCpu());
            //a.setPublicAddress(temp.getNics().get(0).getIpAddresses().get(0).getIpAddress());
            runtimeInformation.put("publicAddress", temp.getNics().get(0).getIpAddresses().get(0).getIpAddress());
            journal.log(Level.INFO, ">> Running VM: " + a.getName() + " id: " + a.getId() + " with public address: " + a.getPublicAddress());
            //a.setStatusAsRunning();
            state = ComponentInstance.State.RUNNING;
        } catch (ExtilityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            //a.setStatusAsError();
            state = ComponentInstance.State.ERROR;
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            //a.setStatusAsError();
            state = ComponentInstance.State.ERROR;
        }
        runtimeInformation.put("status", state.toString());
        return runtimeInformation;
    }

    public void execCommand(VMInstance n, String command, String login, String keyPath){
        SSHConnector sc=new SSHConnector(keyPath, login, n.getPublicAddress());
        sc.execCommandSsh(command);
    }


    public void execCommand(String id, String command, String login, String keyPath){
        Server temp=(Server)findObjectResourceByID(id, ResourceType.SERVER);
        String ip=temp.getNics().get(0).getIpAddresses().get(0).getIpAddress();
        SSHConnector sc=new SSHConnector(keyPath, login, ip);
        sc.execCommandSsh(command);
    }


    public String findResourceByName(String name, ResourceType t){
        try {
            // Create an FQL filter and a filter condition
            SearchFilter sf = new SearchFilter();
            FilterCondition fc = new FilterCondition();

            // the field to be matched
            fc.setField("ResourceName");

            // set the condition type
            fc.setCondition(Condition.IS_EQUAL_TO);

            // Add the filter condition to the query
            sf.getFilterConditions().add(fc);

            // and a list of values
            fc.getValue().add(name);

            // Set a limit to the number of results
            QueryLimit lim = new QueryLimit();
            lim.setMaxRecords(50);
            lim.setLoadChildren(true);

            // Call the service to execute the query
            ListResult result = service.listResources(sf,lim, t);
            if(result.getList().size() >= 0){
                Resource s= (Resource) result.getList().get(0);
                return s.getResourceUUID();
            }else{
                return "";
            }
        } catch (Exception e) {
            return "";
        }
    }

    public Object findObjectResourceByName(String name, ResourceType t){
        try {
            // Create an FQL filter and a filter condition
            SearchFilter sf = new SearchFilter();
            FilterCondition fc = new FilterCondition();

            // the field to be matched
            fc.setField("ResourceName");

            // set the condition type
            fc.setCondition(Condition.IS_EQUAL_TO);

            // Add the filter condition to the query
            sf.getFilterConditions().add(fc);

            // and a list of values
            fc.getValue().add(name);

            // Set a limit to the number of results
            QueryLimit lim = new QueryLimit();
            lim.setMaxRecords(50);
            lim.setLoadChildren(true);

            // Call the service to execute the query
            ListResult result = service.listResources(sf,lim, t);
            return result.getList().get(0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Object findObjectResourceByID(String id, ResourceType t){
        try {
            // Create an FQL filter and a filter condition
            SearchFilter sf = new SearchFilter();
            FilterCondition fc = new FilterCondition();

            // the field to be matched
            fc.setField("ResourceUUID");

            // set the condition type
            fc.setCondition(Condition.IS_EQUAL_TO);

            // Add the filter condition to the query
            sf.getFilterConditions().add(fc);

            // and a list of values
            fc.getValue().add(id);

            // Set a limit to the number of results
            QueryLimit lim = new QueryLimit();
            lim.setMaxRecords(100);
            lim.setLoadChildren(true);

            // Call the service to execute the query
            ListResult result = service.listResources(sf,lim, t);
            return result.getList().get(0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public String findProduct(double ram, int CPU){
        try {
            // Create an FQL filter and a filter condition
            SearchFilter sf = new SearchFilter();
            FilterCondition fc = new FilterCondition();

            // the field to be matched
            fc.setField("ResourceName");

            // set the condition type
            fc.setCondition(Condition.CONTAINS);

            // Add the filter condition to the query
            sf.getFilterConditions().add(fc);

            // and a list of values
            fc.getValue().add(CPU+" CPU");

            // Set a limit to the number of results
            QueryLimit lim = new QueryLimit();
            lim.setMaxRecords(50);

            // Call the service to execute the query
            ListResult result = service.listResources(sf,lim, ResourceType.PRODUCTOFFER);

            if(result.getList().size() > 0){
                ProductOffer p=(ProductOffer)result.getList().get(0);
                Double min=5000.0;
                for(Object o : result.getList()) {
                    ProductOffer s = ((ProductOffer)o);
                    for(ProductComponent t : s.getComponentConfig()){
                        for(Value v: t.getProductConfiguredValues()){
                            if(v.getKey().equals("ram")){
                                double productRam=Double.parseDouble(v.getValue()); //RAM value
                                if(productRam >= ram && productRam <= min){
                                    min=productRam;
                                    p=s;
                                }
                            }
                        }
                    }
                }
                return p.getResourceUUID();
            }
            return "";
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public String getEndpoint(){
        return this.endpoint;
    }

    public void destroyVM(String id) {
        // TODO Auto-generated method stub
        journal.log(Level.INFO, ">> Not yet implemented ");
    }


    /**
     * create the snapshot of a disk (only feature supported now on the MODAClouds platform)
     * @param vmi a VM instance
     * @return id of the snapshot
     */
    public String createSnapshot(VMInstance vmi){
        journal.log(Level.INFO, ">> Creating a snapshot of VM id: "+vmi.getId());
        Snapshot snapshot=new Snapshot();
        Server temp=(Server)findObjectResourceByID(vmi.getId(), ResourceType.SERVER);
        snapshot.setParentUUID(temp.getDisks().get(0).getResourceUUID());
        snapshot.setResourceName(vmi.getName()+"-snapshot");
        snapshot.setType(SnapshotType.DISK);
        snapshot.setResourceType(ResourceType.SNAPSHOT);
        snapshot.setClusterUUID(temp.getClusterUUID());

        try {
            Job job=service.createSnapshot(snapshot,null);
            service.waitForJob(job.getResourceUUID(), false);
        } catch (ExtilityException e) {
            e.printStackTrace();
        }
        journal.log(Level.INFO, ">> Snapshot created with id: "+vmi.getName()+"-snapshot");
        return vmi.getName()+"-snapshot";
    }

    /**
     * Create an image of the specified VM
     * @param vmi a VM instance
     * @return id of the image
     */
    public String createImage(VMInstance vmi){
        journal.log(Level.INFO, ">> Creating an image of VM id: "+vmi.getId());
        Image image=new Image();
        Server temp=(Server)findObjectResourceByID(vmi.getId(), ResourceType.SERVER);
        image.setBaseUUID(temp.getDisks().get(0).getResourceUUID());
        image.setClusterUUID(temp.getClusterUUID());
        image.setResourceName(vmi.getName()+"-image");
        image.setResourceType(ResourceType.IMAGE);
        image.setVdcUUID(temp.getVdcUUID());

        try {
            Job job1=service.changeServerStatus(temp.getResourceUUID(),ServerStatus.STOPPED,true,null,null);
            service.waitForJob(job1.getResourceUUID(), false);

            Job job2=service.createImage(image, null);
            service.waitForJob(job2.getResourceUUID(), false);

            Job job3=service.changeServerStatus(temp.getResourceUUID(),ServerStatus.RUNNING,true,null,null);
            service.waitForJob(job3.getResourceUUID(), false);
        } catch (ExtilityException e) {
            e.printStackTrace();
        }

        journal.log(Level.INFO, ">> Image created with id: "+vmi.getName()+"-image");
        return vmi.getName()+"-image";
    }

    public void closeConnection() {}

    public void updateVMMetadata(VMInstance a) {
        a.setId(findResourceByName(a.getName(), ResourceType.SERVER));
        Server temp=(Server)findObjectResourceByName(a.getName(), ResourceType.SERVER);
        a.setPublicAddress(temp.getNics().get(0).getIpAddresses().get(0).getIpAddress());
    }

    public void uploadFile(String sourcePath, String destinationPath,
                           String VMId, String login, String key) {
        Server temp=(Server)findObjectResourceByID(VMId, ResourceType.SERVER);
        String ip=temp.getNics().get(0).getIpAddresses().get(0).getIpAddress();
        SSHConnector sc=new SSHConnector(key, login, ip);
        sc.upload(sourcePath,destinationPath);
    }

}

