package org.knopflerfish.bundle.soap.remotefw.client;

import org.osgi.framework.*;
import org.osgi.util.tracker.*;

import java.util.*;
import org.knopflerfish.service.log.LogRef;
import org.osgi.service.startlevel.*;
import org.osgi.service.packageadmin.*;

import org.knopflerfish.service.soap.remotefw.*;

import java.io.*;
import java.net.*;

import org.knopflerfish.bundle.soap.remotefw.*;

public class BundleContextImpl implements BundleContext {
  RemoteFWClient fw;

  boolean  bDebug   = "true".equals(System.getProperty("org.knopflerfish.soap.remotefw.client.debug", "false"));

  Thread  runner = null;
  boolean bRun   = false;
  long    delay  = 3 * 1000;

  StartLevelImpl startLevel;
  PackageAdminImpl pkgAdmin;

  BundleContextImpl(RemoteFWClient fw) {
    this.fw  = fw;
    startLevel = new StartLevelImpl(fw);
    pkgAdmin   = new PackageAdminImpl(fw);

    try {
      delay = Long.parseLong(System.getProperty("org.knopflerfish.soap.remotefw.client.eventinterval", Long.toString(delay)));
    } catch (Exception e) {
    }
  }

  void start() {
    if(runner == null) {
      runner = new Thread() {
	  public void run() {
	    while(bRun) {
	      try {
		if(!bInEvent) {
		  doEvents();
		}
		if(bDebug) {
		  System.out.println("sleep " + delay);
		}

		Thread.sleep(delay);
	      } catch (Exception e) {
		//
	      }
	    }
	  }
	};
      bRun = true;
      runner.start();
    }
  }

  void stop() {
    if(runner != null) {
      bRun = false;
      try {
	runner.wait(delay * 2);
      } catch (Exception ignored) {
      }
      runner = null;
    }
  }

  Object eventLock = new Object();

  int eventCount = 0;

  boolean bInEvent = false;

  void doEvents() {
    synchronized(eventLock) {
      try {
	if(bInEvent) {
	  Exception e = new RuntimeException("already in doEvents");
	  e.printStackTrace();
	  return;
	}
	bInEvent = true;
	eventCount++;
	if(bDebug) {
	  System.out.println("doAllEvents " + eventCount);
	}
	doBundleEvents();
	doServiceEvents();
	doFrameworkEvents();
	
	if(bDebug) {
	  System.out.println("done doAllEvents " + eventCount);
	}
      } finally {
	bInEvent = false;
      }
    }
  }

  void doBundleEvents() {
    synchronized(eventLock) {
      long[] evs = fw.getBundleEvents();
      if(bDebug) {
	System.out.println("doBundleEvents " + evs.length);
      }
      for(int i = 0; i < evs.length / 2; i++) {
	long bid = evs[i * 2];
	Bundle      b  = getBundle(bid);
	if(b == null) {
	  System.out.println("*** No bundle bid=" + bid);
	} else {
	  BundleEvent ev = new BundleEvent((int)evs[i * 2 +1], b);
	  
	  sendBundleEvent(ev);
	}
      }
    }
  }

  void doFrameworkEvents() {
    synchronized(eventLock) {
      
      long[] evs = fw.getFrameworkEvents();
      if(bDebug) {
	System.out.println("doFrameworkEvents " + evs.length / 2);
      }
      for(int i = 0; i < evs.length / 2; i++) {
	FrameworkEvent ev = new FrameworkEvent((int)evs[i * 2 + 1],
					       getBundle(evs[i*2]),
					       null);
	
	sendFrameworkEvent(ev);
      }
      
      if(bDebug) {
	System.out.println("*** done doFrameworkEvents " + evs.length);
      }
    }
  }

  void doServiceEvents() {
    synchronized(eventLock) {
      long[] evs = fw.getServiceEvents();
      if(bDebug) {
	System.out.println("doServiceEvents " + evs.length);
      }
      for(int i = 0; i < evs.length / 2; i++) {
	long             sid  = evs[i * 2];
	int              type = (int)evs[i * 2 + 1];
	ServiceReference sr   = getServiceReference(sid);
	
	if(sr == null) {
	  System.err.println("*** no sid=" + sid);
	} else {
	  ServiceEvent     ev   = new ServiceEvent(type, sr);
	  
	  sendServiceEvent(ev);
	}
      }
    }
  }

  void sendBundleEvent(BundleEvent ev) {
    synchronized(bundleListeners) {
      for(Iterator it = bundleListeners.iterator(); it.hasNext();) {
	BundleListener l = (BundleListener)it.next();
	try {
	  l.bundleChanged(ev);
	} catch (Exception e) {
	  e.printStackTrace();
	}
      }
    }
  }

  void sendFrameworkEvent(FrameworkEvent ev) {
    synchronized(frameworkListeners) {
      for(Iterator it = frameworkListeners.iterator(); it.hasNext();) {
	FrameworkListener l = (FrameworkListener)it.next();
	try {
	  l.frameworkEvent(ev);
	} catch (Exception e) {
	  e.printStackTrace();
	}
      }
    }
  }

  void sendServiceEvent(ServiceEvent ev) {
    synchronized(serviceListeners) {
      //      Hashtable props = new Hashtable();
      ServiceReferenceImpl sr = (ServiceReferenceImpl)ev.getServiceReference();
      Hashtable props = sr.props;

      for(Iterator it = serviceListeners.keySet().iterator(); it.hasNext();) {
	ServiceListener l = (ServiceListener)it.next();
	Filter filter     = (Filter)serviceListeners.get(l);

	if(filter.match(props)) {
	  try {
	    l.serviceChanged(ev);
	  } catch (Exception e) {
	    e.printStackTrace();
	  }
	}
      }
    }
  }

  Set bundleListeners    = new HashSet();
  Set frameworkListeners = new HashSet();
  Map serviceListeners   = new HashMap();

  public void addBundleListener(BundleListener listener) { 
    synchronized(bundleListeners) {
      bundleListeners.add(listener);
    }
  }

  public void addFrameworkListener(FrameworkListener listener) { 
    synchronized(frameworkListeners) {
      frameworkListeners.add(listener);
    }
  }

  public void addServiceListener(ServiceListener listener) { 
    try {
      addServiceListener(listener, "(objectclass=*)");
    } catch (Exception e) {
    }
  }

  public void addServiceListener(ServiceListener listener, String filter) 
  throws InvalidSyntaxException { 
    synchronized(serviceListeners) {
      if(filter == null || "".equals(filter)) {
	filter = "(objectclass=*)";
      }
      Filter f = createFilter(filter);
      serviceListeners.put(listener, f);
    }
  }

  public Filter createFilter(String filter) throws InvalidSyntaxException { 
    return Activator.bc.createFilter(filter);
  }

  // Long (bid) -> BundleImpl
  Map bundleMap = new HashMap();

  public Bundle getBundle() { 
    return getBundle(fw.getBundle());
  }

  public Bundle getBundle(long id) { 
    synchronized(bundleMap) {
      Long bid = new Long(id);
      BundleImpl b = (BundleImpl)bundleMap.get(bid);
      if(b == null) {
	b = new BundleImpl(fw, id);
	bundleMap.put(bid, b);
      }
      return b;
    }
  }


  public Bundle[] getBundles() { 
    synchronized(bundleMap) {
      long[] bids = fw.getBundles();

      Bundle[] bl = new Bundle[bids.length];
      for(int i = 0; i < bids.length; i++) {
	bl[i] = getBundle(bids[i]);
      }

      return bl;
    }
  }

  public File getDataFile(String filename) { 
    throw new RuntimeException("Not implemented");
  }

  public String getProperty(String key) { 
    return fw.getBundleContextProperty(key);
  }

  // Long -> ServerReferenceImpl
  Map serviceMap = new HashMap();

  ServiceReference getServiceReference(long sid) {
    Long SID = new Long(sid);
    ServiceReferenceImpl sr = (ServiceReferenceImpl)serviceMap.get(SID);
    if(sr != null) {
      return sr;
    }

    long[] srl = fw.getServiceReferences("(service.id=" + sid + ")");
    if(srl.length == 2) {
      sr = new ServiceReferenceImpl((BundleImpl)getBundle(srl[1]), srl[0]);
      serviceMap.put(SID, sr);
      return sr;
    }
    throw new IllegalArgumentException("No service id=" + sid + ", length=" + srl.length);
  }

  public Object getService(ServiceReference sr) { 
    String[] clazzes = (String[])sr.getProperty("objectclass");

    if(sr instanceof ServiceReferenceImpl) {
      if(inArray(clazzes, StartLevel.class.getName())) {
	return startLevel;
      }
      if(inArray(clazzes, PackageAdmin.class.getName())) {
	return pkgAdmin;
      }
      return null;
    } else {
      throw new IllegalArgumentException("Bad ServiceReference passed: " + sr);
    }
  }

  public ServiceReference getServiceReference(String clazz) { 
    ServiceReference[] srl = getServiceReferences(clazz, null);
    if(srl != null && srl.length > 0) {
      return srl[0];
    }
    return null;
  }

  public ServiceReference[] getServiceReferences(String clazz, String filter)
  { 
    long[] sids = fw.getServiceReferences2(clazz, filter);

    if(sids == null || sids.length == 0) {
      return null;
    }

    ServiceReference[] srl = new ServiceReference[sids.length / 2];


    for(int i = 0; i < srl.length; i++) {
      srl[i] = getServiceReference(sids[i * 2]);
    }
    return srl;
  }

  public Bundle installBundle(String location) { 
    return new BundleImpl(fw, fw.installBundle(location));
  }


  public Bundle installBundle(String location, InputStream in) { 
    throw new RuntimeException("Not implemented");
  }

  public ServiceRegistration registerService(String[] clazzes, 
					     Object service, 
					     Dictionary properties) { 
    throw new RuntimeException("registerService not implemented: service=" + service + ", classes=" + RemoteFWClient.toDisplay(clazzes));
   }

  
  public ServiceRegistration registerService(String clazz, 
					     Object service, 
					     Dictionary properties) { 
    return registerService(new String[] { clazz }, service, properties);
  }


  public void removeBundleListener(BundleListener listener) { 
    synchronized(bundleListeners) {
      bundleListeners.remove(listener);
    }
  }

  public void removeFrameworkListener(FrameworkListener listener) { 
    synchronized(frameworkListeners) {
      frameworkListeners.remove(listener);
    }
  }
     

  public void removeServiceListener(ServiceListener listener) { 
    synchronized(serviceListeners) {
      serviceListeners.remove(listener);
    }
  }

  public boolean ungetService(ServiceReference sr) { 
    if(bDebug) {
      System.out.println("ungetService: noop: " + sr);
    }
    return true;
  }

  static boolean inArray(Object[] sa, Object s) {
    for(int i = 0; sa != null && i < sa.length; i++) {
      if(sa[i] != null && sa[i].equals(s)) {
	return true;
      }
    }
    return false;
  }
}

