/*
 *
 * Filename: Admin.java
 *
 * Author: Joe Wang
 *
 * Copyright (c) 2003-2005 
 * The Trustees of Columbia University in the City of New York
 * Columbia University Information Technology
 *
 * This file is part of NINJa
 *
 * NINJa is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * NINJa 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NINJa; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Created on September 17, 2002, 11:37 AM
 *
 */

package edu.columbia.print.ninja.core;

import javax.servlet.http.*;
import edu.columbia.print.ninja.core.service.*;
import edu.columbia.print.ninja.core.event.*;
import net.jini.event.*;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceMatches;
import net.jini.lookup.entry.ServiceType;
import net.jini.print.job.*;
import net.jini.print.lookup.*;
import net.jini.event.EventMailbox;
import com.sun.rio.resources.client.*;
import com.millipad.util.*;
import java.util.*;
import java.net.*;
import java.rmi.server.*;
import java.util.prefs.*;
import java.lang.reflect.*;
import java.beans.*;

public class AdminBean extends HttpServlet implements java.io.Serializable {

    public static byte ADDED = 1;
    public static byte REMOVED = 2;
    public static byte CHANGED = 3;
    static Map filters;
    static long size;
    static String listPrefs, listName;
    static Class serviceClass;
    static Vector history;
    static JiniClient client;
    static ServiceCache cache;
    static BlockingStack stack;

    /** Holds the remote reference to the Hello World **/
    public String methodName, methodParamClasses, org;
    public Object[] methodParams;
    protected Object service;
    protected ServiceID sid;
    public ServiceItem serviceItem;
    private PropertyChangeSupport propertySupport;
    private Queuer queuer;
    
    public AdminBean() {
        propertySupport = new PropertyChangeSupport( this );
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.addPropertyChangeListener(listener);
    }
    
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.removePropertyChangeListener(listener);
    }

    public void init() throws javax.servlet.ServletException {
        try {
            synchronized(getClass()) {
                if(filters==null) filters = new HashMap();
                if(history==null) history = new Vector();
                if(stack==null) stack = new BlockingStack();
            }
            int histSize = Integer.parseInt(getInitParameter("history.size"));
            Listener l = null;
            if(histSize>0) {
                setHistorySize(histSize);
                l = new Listener();
            }
            else setHistorySize(0);
            serviceClass = Class.forName(getInitParameter("monitor.interface"));
            queuer = new Queuer(stack, history, filters);
            synchronized(getClass()) {
                if(client==null) client = new JiniClient();
                if(cache==null) cache = client.createServiceCache(
                    new ServiceTemplate(null, new Class[] {serviceClass}, null),
                    l);
            }
        }
        catch(Exception e) {
            log("can't start jini client", e);
            throw new javax.servlet.ServletException("can't start JiniClient");
        }
        listPrefs = getInitParameter("monitor.list.prefs");
        listName = getInitParameter("monitor.list.name");
        if(listPrefs==null || listName==null) throw new javax.servlet.ServletException("bad monitor.list.prefs,monitor.list.name");
        log("ninja AdminBean.init m="+Runtime.getRuntime().maxMemory()+" t="+Runtime.getRuntime().totalMemory()+" f="+Runtime.getRuntime().freeMemory());
    }

    public void setFilter(Object f) {
        String o = org;
        if(o==null) o="";
        filters.put(o, f);
    }

    public void setOrganization(String value) {org = value;}

    public String getOrganization() {return org;}

    public String getList(String org) {
        String listFull = listName;
        if(org!=null && org.length()>0) listFull = listFull+"."+org;
        return Preferences.userRoot().node(listPrefs).get(listFull, "");
    }

    public String getList() {
        String listFull = listName;
        if(org!=null && org.length()>0) listFull = listFull+"."+org;
        return Preferences.userRoot().node(listPrefs).get(listFull, "");
    }

    public void setList(String list) {
        String listFull = listName;
        if(org!=null && org.length()>0) listFull = listFull+"."+org;
        Preferences node = Preferences.userRoot().node(listPrefs);
        node.put(listFull, list);
        try {node.flush();}
        catch(Exception e) {log("can't save prefs "+node, e);}
    }

    public static ServiceCache getCache() {return cache;}

    public List getHistory(long end, long start) {
        synchronized(history) {
            int tail = Collections.binarySearch(history, new HistoryItem((byte )0, end, null));
            int head = Collections.binarySearch(history, new HistoryItem((byte )0, start, null));
            if(head<0) head=-head-1;
            if(tail<0) tail=-tail-1;
            LinkedList list=new LinkedList(history.subList(head, tail));
            return list;
        }
    }

    public static void setHistorySize(long value) {
        size = value;
    }

    public static long getHistorySize() {return size;}

    public void destroy() {
        log("ninja AdminBean.destroy");
        queuer.stop();
/*        history = null;
        cache.terminate();
        client.terminate();
        cache = null;
        client = null;
*/    }

    public void setServiceContext(ServiceID sid) {
        try {
/*            Object[] context = (Object[])o;
            ServiceRegistrar reggie = (ServiceRegistrar)context[0];
            ServiceID sid = (ServiceID)context[1];
*/
            if(sid.equals(this.sid) && service!=null) return;
            this.sid = sid;
            serviceItem = cache.getServiceItemFromCache(sid);
            if(serviceItem==null) {
                throw new RuntimeException("Admin.setServiceContext : ServiceItem is null");
            }
            service = serviceItem.service;
        } catch(Throwable t) {
            t.printStackTrace();
            throw new RuntimeException("Admin.setServiceContext : Unable to acquire context", t);
        }
    }

    public void setMethodName(String p) {methodName = p;}
    public void setMethodParamClasses(String p) {methodParamClasses = p;}
    public void setMethodParams(Object[] p) {methodParams = p;}

    public String getMethodName() {return methodName;}
    public String getMethodParamClasses() {return methodParamClasses;}

    public Object getMethodResult() {
        try {
            Class[] paramClasses = null;
            if(methodParamClasses!=null && methodParamClasses.length()>0) {
                StringTokenizer t = new StringTokenizer(methodParamClasses, ",");
                paramClasses = new Class[t.countTokens()];
                for(int i=0;t.hasMoreTokens(); i++) paramClasses[i]=Class.forName(t.nextToken());
            }
            Method method = serviceClass.getMethod(methodName, paramClasses);
            return method.invoke(service,  methodParams);
        }
        catch(Exception e) {
            e.printStackTrace();
//            log("getMethodResult", e);
        }
        return null;
    }

    class Listener extends ServiceCacheAdapter {
        void updateHistory(byte type, ServiceCacheEvent event) {
            stack.push(new Object[] {new Byte(type), event});
        }
        public void serviceAdded(ServiceCacheEvent event) {
            updateHistory(ADDED, event);
        }
        public void serviceRemoved(ServiceCacheEvent event) {
/*            if(!cache.cacheItems.remove(new ServiceCacheItem(event.getServiceID(), null, null))) {
                System.out.println("can't remove ServiceCacheItem:"+event.getServiceID());
            }
*/            updateHistory(REMOVED, event);
        }
        public void serviceChanged(ServiceCacheEvent event) {
            updateHistory(CHANGED, event);
        }
    }

    AttributeEntry getEntry(Entry[] entries, String clazz) {
	for(int i=entries.length-1; i>=0; i--) {
            if(!(entries[i] instanceof AttributeEntry)) continue;
            AttributeEntry entry = (AttributeEntry )entries[i];
            if(!clazz.equals(entry.category)) continue;
            return entry;
	}
        return null;
    }

    public class Queuer implements Runnable {
        BlockingStack stack;
        Vector history;
        Map filters;
        boolean running;
        Queuer(BlockingStack stack, Vector history, Map filters) {
            this.history = history;
            this.stack = stack;
            this.running = true;
            this.filters = filters;
            new Thread(this).start();
        }
        public void stop() {
            running = false;
        }
        public void run() {
            for(;running;) {
                Object[] v = (Object[] )stack.pop();
                Byte b = (Byte )v[0];
                ServiceCacheEvent event = (ServiceCacheEvent )v[1];
                boolean keep = false;
                for(Iterator f = filters.values().iterator(); f.hasNext();) {
                    Object filter = f.next();
                    if(filter!=null && !filter.equals(event)) {
                        keep = true;
                        break;
                    }
                }
                if(!keep) continue;
                synchronized(history) {
                    if(history.size()>size) {
                        //half
                        history.subList(0,history.size()/2).clear();
                    }
                }
                ServiceItem sItem = new ServiceItem(
                    event.getServiceItem().serviceID, null,
                    event.getServiceItem().attributeSets
                );
                HistoryItem item = new HistoryItem(b.byteValue(), System.currentTimeMillis(), sItem);
                synchronized(history) {history.add(item);}
            }
        }
        
    }
    
    public static class HistoryItem implements Comparable {
        byte type;
        long time;
        ServiceItem item;
        HistoryItem(byte type, long time, ServiceItem item) {
            this.type = type;
            this.time = time;
            this.item = item;
        }
        public byte getType() {return type;}
        public long getTime() {return time;}
        public ServiceItem getItem() {return item;}
        public int compareTo(Object obj) {
            HistoryItem item = (HistoryItem )obj;
            if(time>item.time) return 1;
            if(time<item.time) return -1;
            return -1;
        }
        
    }
}
