/*
 * Copyright (c) 2003-2004, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
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.osgi.service.log.LogReaderService;

import org.knopflerfish.service.console.ConsoleService;

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;
  LogReaderImpl logReader;
  ConsoleServiceImpl console;

  BundleContextImpl(RemoteFWClient fw) {
    this.fw  = fw;
    startLevel = new StartLevelImpl(fw);
    pkgAdmin   = new PackageAdminImpl(fw);
    logReader  = new LogReaderImpl(fw);
    console    = new ConsoleServiceImpl(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) {
                  if(bDebug) {
                    System.out.println("doEvents");
                  }
                  doEvents();
                }
                if(bDebug) {
                  System.out.println("sleep " + delay);
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
              try {
                Thread.sleep(delay);
              } catch (InterruptedException ignore) {}
            }
          }
        };
      bRun = true;
      runner.start();
    }
  }

  void stop() {
    if (logReader != null) {
      logReader.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 == null ? "null" : String.valueOf(evs.length)));
      }
      if (evs == null) return;
      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 == null ? "null" : String.valueOf(evs.length/2)));
      }
      if (evs == null) return;
      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 == null ? "null" : String.valueOf(evs.length)));
      }
      if (evs == null) return;
      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 == null || 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();
      if (bids == null) bids = new long[0];
      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(fw, (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;
      }
      if(inArray(clazzes, LogReaderService.class.getName())) {
        return logReader;
      }
      if(inArray(clazzes, ConsoleService.class.getName())) {
        return console;
      }
      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;
  }

public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
	// TODO Auto-generated method stub
	return null;
}
}

