/*
  Copyright (C) 2007 Helge Hess

  This file is part of JOPE.

  JOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  JOPE 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 JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
package org.opengroupware.jope.appserver.publisher;

import java.util.ArrayList;
import java.util.List;

import org.opengroupware.jope.foundation.NSKeyValueCoding;

/*
 * IJoLocation
 * 
 * Implemented by objects which have a containment context. Such objects know
 * their container and the name under which they are stored in that container.
 * 
 * TODO: document
 */
public interface IJoLocation {
  
  public String nameInContainer();
  public Object container();
  
  /* helper functions */

  public static class Utility {
    
    public static boolean isContained(Object _object) {
      /* Note: more or less the opposite of isRoot */
      return containerForObject(_object) != null;
    }
    
    public static Object containerForObject(Object _object) {
      if (_object == null)
        return null; /* well, not really useful ;-) */
      
      if (_object instanceof IJoLocation)
        return ((IJoLocation)_object).container();
      
      if (_object instanceof NSKeyValueCoding)
        return ((NSKeyValueCoding)_object).valueForKey("container");
      
      return null;
    }
    
    public static String nameOfObjectInContainer
      (Object _object, Object _container)
    {
      if (_object == null || _container == null)
        return null;
      
      if (_object instanceof IJoLocation)
        return ((IJoLocation)_object).nameInContainer();
      
      if (_object instanceof NSKeyValueCoding) {
        return (String)((NSKeyValueCoding)_object)
          .valueForKey("nameInContainer");
      }
      
      // TODO: possibly ask container for name of object?
      
      return null;
    }
    
    public static String[] pathToRoot(Object _object) {
      if (_object == null)
        return null;
      
      List<String> list = new ArrayList<String>(16);
      Object current = _object;
      while (current != null) {
        Object container = containerForObject(current);
        String name      = nameOfObjectInContainer(current, container);
        
        if (name == null) /* found an object w/o a name (eg root) */
          break;
        
        list.add(name);
        
        /* continue at container */
        current = container;
      }
      
      int      len  = list.size();
      String[] path = new String[len];
      for (int i = 0; i < len; i++)
        path[len - i - 1] = list.get(i);
      
      return path;
    }
    
    public static IJoLocation locate
      (Object _container, String _name, Object _object)
    {
      if (_object == null)
        return null;
      
      if (_object instanceof IJoLocation) {
        /* Note: in Zope this pushes the location into the object, we currently
         *       assume that the object is already located.
         */
        return (IJoLocation)_object;
      }
      
      /* wrap object to provide the interface */
      
      if (_object instanceof NSKeyValueCoding)
        return new NSObjectWrapper((NSKeyValueCoding)_object);
      
      return new ObjectWrapper(_container, _name, _object);
    }
  }
  
  /* wrapper objects */
  
  public static class NSObjectWrapper implements IJoLocation {
    // TODO: not sure whether we should use KVC for IJoLocation
    
    protected NSKeyValueCoding wrappedObject;
    
    public NSObjectWrapper(NSKeyValueCoding _object) {
      this.wrappedObject   = _object;
    }
    
    /* accessors */
    
    public Object container() {
      return this.wrappedObject.valueForKey("container");
    }
    public String nameInContainer() {
      return (String)this.wrappedObject.valueForKey("nameInContainer");
    }
    
    public NSKeyValueCoding wrappedObject() {
      return this.wrappedObject;
    }
    
    /* JoClass */
    
    public JoClass joClassInContext(IJoContext _ctx) {
      return _ctx.joClassRegistry().joClassForJavaObject
        (this.wrappedObject, _ctx);
    }
    
    public Object lookupName(String _name, IJoContext _ctx, boolean _acquire) {
      /* lookup using JoClass */
      
      JoClass cls = this.joClassInContext(_ctx);
      if (cls != null) {
        Object o = cls.lookupName(this, _name, _ctx);
        if (o != null) return o;
      }
      
      /* if we shall acquire, continue at parent */
      
      if (_acquire) {
        Object container = this.container();
        if (container != null)
          return ((IJoObject)container).lookupName(_name, _ctx, true /* aq */);
      }
      
      return null;
    }
  }
  
  public static class ObjectWrapper implements IJoObject, IJoLocation {
    
    protected Object container;
    protected String nameInContainer;
    protected Object wrappedObject;
    
    public ObjectWrapper(Object _container, String _name, Object _object) {
      this.container       = _container;
      this.nameInContainer = _name;
      this.wrappedObject   = _object;
    }
    
    /* accessors */
    
    public Object container() {
      return this.container;
    }
    public String nameInContainer() {
      return this.nameInContainer;
    }
    
    public Object wrappedObject() {
      return this.wrappedObject;
    }
    
    /* JoClass */
    
    public JoClass joClassInContext(IJoContext _ctx) {
      return _ctx.joClassRegistry().joClassForJavaObject
        (this.wrappedObject, _ctx);
    }
    
    public Object lookupName(String _name, IJoContext _ctx, boolean _acquire) {
      /* lookup using JoClass */
      
      JoClass cls = this.joClassInContext(_ctx);
      if (cls != null) {
        Object o = cls.lookupName(this, _name, _ctx);
        if (o != null) return o;
      }
      
      /* if we shall acquire, continue at parent */
      
      if (_acquire && this.container != null) {
        return ((IJoObject)this.container)
          .lookupName(_name, _ctx, true /* aq */);
      }
      
      return null;
    }
  }
  
}
