/*
 *============================================================================
 * 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 storage repository
 *
 * @author Tom Wilkie <tom.wilkie@gmail.com>
 */
public class SR {

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

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

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

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

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

    /**
     * Represents all the fields in a SR
     */
    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", "nameLabel", this.nameLabel);
            print.printf("%1$20s: %2$s\n", "nameDescription", this.nameDescription);
            print.printf("%1$20s: %2$s\n", "VDIs", this.VDIs);
            print.printf("%1$20s: %2$s\n", "PBDs", this.PBDs);
            print.printf("%1$20s: %2$s\n", "virtualAllocation", this.virtualAllocation);
            print.printf("%1$20s: %2$s\n", "physicalUtilisation", this.physicalUtilisation);
            print.printf("%1$20s: %2$s\n", "physicalSize", this.physicalSize);
            print.printf("%1$20s: %2$s\n", "type", this.type);
            print.printf("%1$20s: %2$s\n", "contentType", this.contentType);
            print.printf("%1$20s: %2$s\n", "shared", this.shared);
            print.printf("%1$20s: %2$s\n", "otherConfig", this.otherConfig);
            return writer.toString();
        }

        /**
         * Convert a SR.Record to a Map
         */
        public Map<String,Object> toMap() {
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("uuid", this.uuid);
            map.put("name_label", this.nameLabel);
            map.put("name_description", this.nameDescription);
            map.put("VDIs", this.VDIs);
            map.put("PBDs", this.PBDs);
            map.put("virtual_allocation", this.virtualAllocation);
            map.put("physical_utilisation", this.physicalUtilisation);
            map.put("physical_size", this.physicalSize);
            map.put("type", this.type);
            map.put("content_type", this.contentType);
            map.put("shared", this.shared);
            map.put("other_config", this.otherConfig);
            return map;
        }

        /**
         * unique identifier/object reference
         */
        public String uuid;
        /**
         * a human-readable name
         */
        public String nameLabel;
        /**
         * a notes field containg human-readable description
         */
        public String nameDescription;
        /**
         * managed virtual disks
         */
        public Set<VDI> VDIs;
        /**
         * physical blockdevices
         */
        public Set<PBD> PBDs;
        /**
         * sum of virtual_sizes of all VDIs in this storage repository (in bytes)
         */
        public Long virtualAllocation;
        /**
         * physical space currently utilised on this storage repository (in bytes). Note that for sparse disk formats, physical_utilisation may be less than virtual_allocation
         */
        public Long physicalUtilisation;
        /**
         * total physical size of the repository (in bytes)
         */
        public Long physicalSize;
        /**
         * type of the storage repository
         */
        public String type;
        /**
         * the type of the SR's content, if required (e.g. ISOs)
         */
        public String contentType;
        /**
         * true if this SR is (capable of being) shared between multiple hosts
         */
        public Boolean shared;
        /**
         * additional configuration
         */
        public Map<String, String> otherConfig;
    }

    /**
     * Get a record containing the current state of the given SR.
     *
     * @return all fields from the object
     */
    public SR.Record getRecord() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.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.toSRRecord(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get a reference to the SR instance with the specified UUID.
     *
     * @param uuid UUID of object to return
     * @return reference to the object
     */
    public static SR getByUuid(String uuid) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.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.toSR(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get all the SR instances with the given label.
     *
     * @param label label of object to return
     * @return references to objects with matching names
     */
    public static Set<SR> getByNameLabel(String label) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_by_name_label";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(label)};
        Map response = ConnectionHelper.instance().dispatch(
            method_call, method_params);
        if(response.get("Status").equals("Success")) {
            Object result = response.get("Value");
            return Types.toSetOfSR(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 SR.
     *
     * @return value of the field
     */
    public String getUuid() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.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 name/label field of the given SR.
     *
     * @return value of the field
     */
    public String getNameLabel() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_name_label";
        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 name/description field of the given SR.
     *
     * @return value of the field
     */
    public String getNameDescription() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_name_description";
        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 VDIs field of the given SR.
     *
     * @return value of the field
     */
    public Set<VDI> getVDIs() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_VDIs";
        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.toSetOfVDI(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the PBDs field of the given SR.
     *
     * @return value of the field
     */
    public Set<PBD> getPBDs() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_PBDs";
        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.toSetOfPBD(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the virtual_allocation field of the given SR.
     *
     * @return value of the field
     */
    public Long getVirtualAllocation() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_virtual_allocation";
        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 physical_utilisation field of the given SR.
     *
     * @return value of the field
     */
    public Long getPhysicalUtilisation() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_physical_utilisation";
        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 physical_size field of the given SR.
     *
     * @return value of the field
     */
    public Long getPhysicalSize() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_physical_size";
        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 type field of the given SR.
     *
     * @return value of the field
     */
    public String getType() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_type";
        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 content_type field of the given SR.
     *
     * @return value of the field
     */
    public String getContentType() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_content_type";
        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 shared field of the given SR.
     *
     * @return value of the field
     */
    public Boolean getShared() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_shared";
        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.toBoolean(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get the other_config field of the given SR.
     *
     * @return value of the field
     */
    public Map<String, String> getOtherConfig() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_other_config";
        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.toMapOfStringString(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Set the name/label field of the given SR.
     *
     * @param label New value to set
     */
    public void setNameLabel(String label) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.set_name_label";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(label)};
        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 name/description field of the given SR.
     *
     * @param description New value to set
     */
    public void setNameDescription(String description) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.set_name_description";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(description)};
        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 shared field of the given SR.
     *
     * @param shared New value to set
     */
    public void setShared(Boolean shared) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.set_shared";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(shared)};
        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 other_config field of the given SR.
     *
     * @param otherConfig New value to set
     */
    public void setOtherConfig(Map<String, String> otherConfig) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.set_other_config";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(otherConfig)};
        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);
    }

    /**
     * Add the given key-value pair to the other_config field of the given SR.
     *
     * @param key Key to add
     * @param value Value to add
     */
    public void addToOtherConfig(String key, String value) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.add_to_other_config";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(key), Marshalling.toXMLRPC(value)};
        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);
    }

    /**
     * Remove the given key and its corresponding value from the other_config field of the given SR.  If the key is not in that Map, then do nothing.
     *
     * @param key Key to remove
     */
    public void removeFromOtherConfig(String key) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.remove_from_other_config";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(this.ref), Marshalling.toXMLRPC(key)};
        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);
    }

    /**
     * Return a set of all the SR types supported by the system
     *
     * @return the supported SR types
     */
    public static Set<String> getSupportedTypes() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.get_supported_types";
        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.toSetOfString(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Return a list of all the SRs known to the system.
     *
     * @return references to all objects
     */
    public static Set<SR> getAll() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.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.toSetOfSR(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Get all the SR Records at once, in a single XML RPC call
     *
     * @return A map from SR to SR.Record
     */
    public static Map<SR, SR.Record> getAllRecords() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "SR.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.toMapOfSRSRRecord(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

}