/*
  Copyright (C) 2006 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.elements;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.appserver.core.WOAssociation;
import org.opengroupware.jope.appserver.core.WOContext;
import org.opengroupware.jope.appserver.core.WODynamicElement;
import org.opengroupware.jope.foundation.NSObject;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/*
 * WOListWalker
 * 
 * This is a helper object used by WORepetition to walk over a list.
 * 
 * Bindings:
 *   list       [in]  - java.util.List | Collection | Java array | DOM Node
 *   count      [in]  - int
 *   item       [out] - object
 *   index      [out] - int
 *   startIndex [in]  - int
 *   identifier [in]  - string (TODO: currently unescaped)
 *   sublist    [in]  - java.util.List | Collection | Java array | DOM Node
 *   
 * TODO: document
 */
public class WOListWalker extends NSObject {
  protected Log log = LogFactory.getLog("WORepetition");
  
  protected WOAssociation list;
  protected WOAssociation sublist;
  protected WOAssociation count;
  protected WOAssociation item;
  protected WOAssociation index;
  protected WOAssociation startIndex;
  protected WOAssociation identifier;
  
  public WOListWalker(Map<String, WOAssociation> _assocs) {
    if ((this.list = WODynamicElement.grabAssociation(_assocs, "list"))==null)
      this.log.warn("missing 'list' binding!");
    
    this.count      = WODynamicElement.grabAssociation(_assocs, "count");
    this.item       = WODynamicElement.grabAssociation(_assocs, "item");
    this.index      = WODynamicElement.grabAssociation(_assocs, "index");
    this.startIndex = WODynamicElement.grabAssociation(_assocs, "startIndex");
    this.identifier = WODynamicElement.grabAssociation(_assocs, "identifier");
    this.sublist    = WODynamicElement.grabAssociation(_assocs, "sublist");
  }
  
  /* operation */
  
  public void walkList(WOListWalkerOperation _op, WOContext _ctx) {
    /* determine list */
    
    List lList  = null;
    if (this.list != null)
      lList = listForValue(this.list.valueInComponent(_ctx.cursor()));
    else if (this.count != null)
      lList = listForCount(this.count.intValueInComponent(_ctx.cursor()));
    if (lList == null) {
      this.log.error("found no list for WORepetition ...");
      return;
    }
    
    this.walkList(lList, _op, _ctx);
  }

  public void walkList(List _list, WOListWalkerOperation _op, WOContext _ctx) {
    if (_list == null) /* nothing to render */
      return;
    
    int aCount = _list.size();
    
    Object cursor = _ctx.cursor();
    
    /* limits */
    
    int startIdx = 0;
    if (this.startIndex != null)
      startIdx = this.startIndex.intValueInComponent(cursor);
    
    int goCount;
    if (this.count != null)
      goCount = this.count.intValueInComponent(cursor);
    else
      goCount = aCount - startIdx;
    
    if (goCount < 1)
      return;
    
    /* start */
    
    if (this.identifier == null) {
      if (startIdx == 0)
        _ctx.appendZeroElementIDComponent();
      else
        _ctx.appendElementIDComponent(startIdx);
    }
    
    int goUntil;
    if (_list != null) {
      goUntil = (aCount > (startIdx + goCount))
        ? startIdx + goCount
        : aCount;
    }
    else
      goUntil = startIdx + goCount;
    
    /* repeat */
    
    for (int cnt = startIdx; cnt < goUntil; cnt++) {
      Object lItem;
      
      if (this.index != null)
        this.index.setIntValue(cnt, cursor);
      
      lItem = _list != null ? _list.get(cnt) : null;
      
      if (this.item != null) {
        this.item.setValue(lItem, cursor);
      }
      /* TODO: cursor support (neither index nor item are set)
      else {
        if (this.index == null) {
          [_ctx pushCursor:lItem];
        }
      }
      */

      /* get identifier used for action-links */
      
      if (this.identifier != null) {
        /* use a unique id for subelement detection */
        String ident = this.identifier.stringValueInComponent(cursor);
        // TODO: ident = [ident stringByEscapingURL];
        _ctx.appendElementIDComponent(ident);
      }

      /* perform operation for item */
      
      _op.processItem(cnt, lItem, _ctx);
      
      /* append sublists */
      
      if (this.sublist != null) {
        List sub = listForValue(this.sublist.valueInComponent(cursor));
        if (sub != null) this.walkList(sub, _op, _ctx);
      }
      
      /* cleanup */

      if (this.identifier != null)
        _ctx.deleteLastElementIDComponent();
      else
        _ctx.incrementLastElementIDComponent();
    }
    
    /* tear down */
    
    if (this.identifier == null)
      _ctx.deleteLastElementIDComponent(); /* repetition index */
    
    // cursor support:
    // if (this.item == null && this.index == null)
    //  _ctx.popCursor();

    //if (this.index != null) this.index.setUnsignedIntValue(0);
    //if (this.item  != null) this.item.setValue(null);
  }

  /* utility methods for dynamic elements which work on lists */
  
  protected static List listForCount(int _cnt) {
    List<Integer> l = new ArrayList<Integer>(_cnt);
    for (int i = 0; i < _cnt; i++)
      l.add(new Integer(i));
    return l;
  }
  
  @SuppressWarnings("unchecked")
  public static List listForValue(Object _value) {
    // TODO: maybe we want to move this to WODynamicElement for general use?
    if (_value == null)
      return null;
    
    if (_value instanceof List)
      return (List)_value;
    
    /* other types of Collections */
    
    if (_value instanceof Collection)
      return new ArrayList((Collection)_value);
    

    /* Java arrays */
    
    if (_value instanceof String[])
      return Arrays.asList((String[])_value);
    if (_value instanceof Object[])
      return Arrays.asList((Object[])_value);
    if (_value.getClass().isArray())
      return Arrays.asList(_value);
    
    /* XML */
    
    if (_value instanceof NodeList) {
      NodeList nodeList = (NodeList)_value;
      
      // TODO: would be better to use a wrapper which implements List?
      /* copy NodeList to a Collections List*/
      int  len = nodeList.getLength();
      List v   = new ArrayList<Object>(len);
      for (int i = 0; i < len; i++)
        v.add(nodeList.item(i));
      return v;
    }
    if (_value instanceof Node) {
      /* for a regular DOM node we retrieve the children */
      return listForValue(((Node)_value).getChildNodes());
    }
    
    /* Iterators */
    
    if (_value instanceof Iterator) {
      List a     = new ArrayList();
      Iterator i = (Iterator)_value;

      while(i.hasNext())
        a.add(i.next());
      return a;
    }

    if (_value instanceof Enumeration) {
      List a        = new ArrayList();
      Enumeration e = (Enumeration)_value;

      while(e.hasMoreElements())
        a.add(e.nextElement());
      return a;
    }

    /* fallback */
    
    System.err.println
      ("WODynamicElement: treating a list as a single object: " + _value);
    List a = new ArrayList(1);
    a.add(_value);
    return a;
  }
}
