/*
 *============================================================================
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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
 *============================================================================
 * Copyright (C) 2007 Tom Wilkie <tom.wilkie@gmail.com>
 *============================================================================
 */
package com.xensource.xenapi;

import java.util.Map;
import java.util.Set;
import java.util.Date;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.xmlrpc.XmlRpcException;

/**
 * A physical network interface (note separate VLANs are represented as several PIFs)
 *
 * @author Tom Wilkie <tom.wilkie@gmail.com>
 */
public class PIF {

    /**
     * The XenAPI reference to this object.
     */
    protected final String ref;

    private PIF(String ref) {
       this.ref = ref;
    }

    /**
     * This code helps ensure there is only one
     * PIF instance per XenAPI reference.
     */
    private static final Map<String,SoftReference<PIF>> cache = 
        new HashMap<String,SoftReference<PIF>>();

    protected static synchronized PIF getInstFromRef(String ref) {
        if(PIF.cache.containsKey(ref)) {
            PIF instance = 
                PIF.cache.get(ref).get();
            if(instance != null) {
                return instance;
            }
        }

        PIF instance = new PIF(ref);
        PIF.cache.put(ref, new SoftReference<PIF>(instance));
        return instance;
    }

    /**
     * Represents all the fields in a PIF
     */
    public static class Record  implements Types.Record{
        public String toString() {
            StringWriter writer = new StringWriter();
            PrintWriter print = new PrintWriter(writer);
            print.printf("%1$20s: %2$s\n", "uuid", this.uuid);
            print.printf("%1$20s: %2$s\n", "device", this.device);
            print.printf("%1$20s: %2$s\n", "network", this.network);
            print.printf("%1$20s: %2$s\n", "host", this.host);
            print.printf("%1$20s: %2$s\n", "MAC", this.MAC);
            print.printf("%1$20s: %2$s\n", "MTU", this.MTU);
            print.printf("%1$20s: %2$s\n", "VLAN", this.VLAN);
            print.printf("%1$20s: %2$s\n", "metrics", this.metrics);
            return writer.toString();
        }

        /**
         * Convert a PIF.Record to a Map
         */
        public Map<String,Object> toMap() {
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("uuid", this.uuid);
            map.put("device", this.device);
            map.put("network", this.network);
            map.put("host", this.host);
            map.put("MAC", this.MAC);
            map.put("MTU", this.MTU);
            map.put("VLAN", this.VLAN);
            map.put("metrics", this.metrics);
            return map;
        }

        /**
         * unique identifier/object reference
         */
        public String uuid;
        /**
         * machine-readable name of the interface (e.g. eth0)
         */
        public String device;
        /**
         * virtual network to which this pif is connected
         */
        public Network network;
        /**
         * physical machine to which this pif is connected
         */
        public Host host;
        /**
         * ethernet MAC address of physical interface
         */
        public String MAC;
        /**
         * MTU in octets
         */
        public Long MTU;
        /**
         * VLAN tag for all traffic passing through this interface
         */
        public Long VLAN;
        /**
         * metrics associated with this PIF
         */
        public PIFMetrics metrics;
    }

    /**
     * Get a record containing the current state of the given PIF.
     *
     * @return all fields from the object
     */
    public PIF.Record getRecord() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_record";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toPIFRecord(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get a reference to the PIF instance with the specified UUID.
     *
     * @param uuid UUID of object to return
     * @return reference to the object
     */
    public static PIF getByUuid(String uuid) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_by_uuid";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(uuid)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toPIF(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the uuid field of the given PIF.
     *
     * @return value of the field
     */
    public String getUuid() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_uuid";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toString(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the device field of the given PIF.
     *
     * @return value of the field
     */
    public String getDevice() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_device";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toString(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the network field of the given PIF.
     *
     * @return value of the field
     */
    public Network getNetwork() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_network";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toNetwork(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the host field of the given PIF.
     *
     * @return value of the field
     */
    public Host getHost() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_host";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toHost(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the MAC field of the given PIF.
     *
     * @return value of the field
     */
    public String getMAC() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_MAC";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toString(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the MTU field of the given PIF.
     *
     * @return value of the field
     */
    public Long getMTU() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_MTU";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toLong(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the VLAN field of the given PIF.
     *
     * @return value of the field
     */
    public Long getVLAN() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_VLAN";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toLong(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the metrics field of the given PIF.
     *
     * @return value of the field
     */
    public PIFMetrics getMetrics() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_metrics";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toPIFMetrics(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Set the device field of the given PIF.
     *
     * @param device New value to set
     */
    public void setDevice(String device) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.set_device";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(device)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Set the MAC field of the given PIF.
     *
     * @param MAC New value to set
     */
    public void setMAC(String MAC) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.set_MAC";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(MAC)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Set the MTU field of the given PIF.
     *
     * @param MTU New value to set
     */
    public void setMTU(Long MTU) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.set_MTU";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(MTU)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Set the VLAN field of the given PIF.
     *
     * @param VLAN New value to set
     */
    public void setVLAN(Long VLAN) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.set_VLAN";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(VLAN)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Create a VLAN interface from an existing physical interface
     *
     * @param device physical interface on which to crate the VLAN interface
     * @param network network to which this interface should be connected
     * @param host physical machine to which this PIF is connected
     * @param VLAN VLAN tag for the new interface
     * @return The reference of the created PIF object
     */
    public static PIF createVLAN(String device, Network network, Host host, Long VLAN) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException,
       Types.VlanTagInvalid {
        String method_call = "PIF.create_VLAN";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(device), Marshalling.toXMLRPC(network), Marshalling.toXMLRPC(host), Marshalling.toXMLRPC(VLAN)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toPIF(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
            if(error[0].equals("VLAN_TAG_INVALID")) {
                throw new Types.VlanTagInvalid((String) error[1]);
            }
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Destroy the interface (provided it is a synthetic interface like a VLAN; fail if it is a physical interface)
     *
     */
    public void destroy() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException,
       Types.PifIsPhysical {
        String method_call = "PIF.destroy";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
            if(error[0].equals("PIF_IS_PHYSICAL")) {
                throw new Types.PifIsPhysical((String) error[1]);
            }
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Return a list of all the PIFs known to the system.
     *
     * @return references to all objects
     */
    public static Set<PIF> getAll() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_all";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toSetOfPIF(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get all the PIF Records at once, in a single XML RPC call
     *
     * @return A map from PIF to PIF.Record
     */
    public static Map<PIF, PIF.Record> getAllRecords() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "PIF.get_all_records";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toMapOfPIFPIFRecord(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

}