/*
 *============================================================================
 * 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 long-running asynchronous task
 *
 * @author Tom Wilkie <tom.wilkie@gmail.com>
 */
public class Task {

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

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

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

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

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

    /**
     * Represents all the fields in a Task
     */
    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", "status", this.status);
            print.printf("%1$20s: %2$s\n", "session", this.session);
            print.printf("%1$20s: %2$s\n", "residentOn", this.residentOn);
            print.printf("%1$20s: %2$s\n", "progress", this.progress);
            print.printf("%1$20s: %2$s\n", "type", this.type);
            print.printf("%1$20s: %2$s\n", "result", this.result);
            print.printf("%1$20s: %2$s\n", "errorInfo", this.errorInfo);
            return writer.toString();
        }

        /**
         * Convert a task.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("status", this.status);
            map.put("session", this.session);
            map.put("resident_on", this.residentOn);
            map.put("progress", this.progress);
            map.put("type", this.type);
            map.put("result", this.result);
            map.put("error_info", this.errorInfo);
            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;
        /**
         * current status of the task
         */
        public Types.TaskStatusType status;
        /**
         * the session that created the task
         */
        public Session session;
        /**
         * the host on which the task is running
         */
        public Host residentOn;
        /**
         * if the task is still pending, this field contains the estimated percentage complete (0.-1.). If task has completed (successfully or unsuccessfully) this should be 1.
         */
        public Double progress;
        /**
         * if the task has completed successfully, this field contains the type of the encoded result (i.e. name of the class whose reference is in the result field). Undefined otherwise.
         */
        public String type;
        /**
         * if the task has completed successfully, this field contains the result value (either Void or an object reference). Undefined otherwise.
         */
        public String result;
        /**
         * if the task has failed, this field contains the set of associated error strings. Undefined otherwise.
         */
        public Set<String> errorInfo;
    }

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

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

    /**
     * Get all the task instances with the given label.
     *
     * @param label label of object to return
     * @return references to objects with matching names
     */
    public static Set<Task> getByNameLabel(String label) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.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.toSetOfTask(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 task.
     *
     * @return value of the field
     */
    public String getUuid() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.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 task.
     *
     * @return value of the field
     */
    public String getNameLabel() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.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 task.
     *
     * @return value of the field
     */
    public String getNameDescription() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.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 status field of the given task.
     *
     * @return value of the field
     */
    public Types.TaskStatusType getStatus() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.get_status";
        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.toTaskStatusType(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

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

    /**
     * Get the resident_on field of the given task.
     *
     * @return value of the field
     */
    public Host getResidentOn() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.get_resident_on";
        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 progress field of the given task.
     *
     * @return value of the field
     */
    public Double getProgress() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.get_progress";
        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.toDouble(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 task.
     *
     * @return value of the field
     */
    public String getType() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.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 result field of the given task.
     *
     * @return value of the field
     */
    public String getResult() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.get_result";
        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 error_info field of the given task.
     *
     * @return value of the field
     */
    public Set<String> getErrorInfo() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "task.get_error_info";
        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.toSetOfString(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
        }
        throw new Types.BadServerResponse(response);
    }

    /**
     * Cancel this task.  If task.allowed_operations does not contain Cancel, then this will fail with OPERATION_NOT_ALLOWED.  The task will show the status 'cancelling', and you should continue to check its status until it shows 'cancelled'.  There is no guarantee as to the time within which this task will be cancelled.
     *
     */
    public void cancel() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException,
       Types.OperationNotAllowed {
        String method_call = "task.cancel";
        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("OPERATION_NOT_ALLOWED")) {
                throw new Types.OperationNotAllowed();
            }
        }
        throw new Types.BadServerResponse(response);
    }

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

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

}