/*
 *============================================================================
 * 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;

/**
 * Asynchronous event registration and handling
 *
 * @author Tom Wilkie <tom.wilkie@gmail.com>
 */
public class Event {

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

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

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

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

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

    /**
     * Represents all the fields in a Event
     */
    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", "id", this.id);
            print.printf("%1$20s: %2$s\n", "timestamp", this.timestamp);
            print.printf("%1$20s: %2$s\n", "clazz", this.clazz);
            print.printf("%1$20s: %2$s\n", "operation", this.operation);
            print.printf("%1$20s: %2$s\n", "ref", this.ref);
            print.printf("%1$20s: %2$s\n", "objUuid", this.objUuid);
            return writer.toString();
        }

        /**
         * Convert a event.Record to a Map
         */
        public Map<String,Object> toMap() {
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("id", this.id);
            map.put("timestamp", this.timestamp);
            map.put("class", this.clazz);
            map.put("operation", this.operation);
            map.put("ref", this.ref);
            map.put("obj_uuid", this.objUuid);
            return map;
        }

        /**
         * An ID, monotonically increasing, and local to the current session
         */
        public Long id;
        /**
         * The time at which the event occurred
         */
        public Date timestamp;
        /**
         * The name of the class of the object that changed
         */
        public String clazz;
        /**
         * The operation that was performed
         */
        public Types.EventOperation operation;
        /**
         * A reference to the object that changed
         */
        public String ref;
        /**
         * The uuid of the object that changed
         */
        public String objUuid;
    }

    /**
     * Registers this session with the event system.  Specifying the empty list will register for all classes.
     *
     * @param classes register for events for the indicated classes
     */
    public static void register(Set<String> classes) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "event.register";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(classes)};
        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);
    }

    /**
     * Unregisters this session with the event system
     *
     * @param classes remove this session's registration for the indicated classes
     */
    public static void unregister(Set<String> classes) throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException {
        String method_call = "event.unregister";
        String session = ConnectionHelper.instance().getSession();
        Object[] method_params = {Marshalling.toXMLRPC(session), Marshalling.toXMLRPC(classes)};
        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);
    }

    /**
     * Blocking call which returns a (possibly empty) batch of events
     *
     * @return the batch of events
     */
    public static Set<Event.Record> next() throws
       Types.BadServerResponse,
       ConnectionHelper.NoConnectionOnThisThreadException,
       XmlRpcException,
       Types.SessionNotRegistered {
        String method_call = "event.next";
        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.toSetOfEventRecord(result);
        } else if(response.get("Status").equals("Failure")) {
            Object[] error = (Object[]) response.get("ErrorDescription");
            if(error[0].equals("SESSION_NOT_REGISTERED")) {
                throw new Types.SessionNotRegistered((String) error[1]);
            }
        }
        throw new Types.BadServerResponse(response);
    }

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

}