/*
  Copyright (C) 2008 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.ofs.htaccess;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.foundation.NSKeyValueCoding;
import org.opengroupware.jope.foundation.NSObject;
import org.opengroupware.jope.foundation.UString;
import org.opengroupware.jope.ofs.htaccess.eval.AccessFileName;
import org.opengroupware.jope.ofs.htaccess.eval.AllowOverride;
import org.opengroupware.jope.ofs.htaccess.eval.FilesMatch;
import org.opengroupware.jope.ofs.htaccess.eval.Limit;
import org.opengroupware.jope.ofs.htaccess.eval.LimitExcept;
import org.opengroupware.jope.ofs.htaccess.eval.LocationMatch;
import org.opengroupware.jope.ofs.htaccess.eval.Options;
import org.opengroupware.jope.ofs.htaccess.eval.Require;
import org.opengroupware.jope.ofs.htaccess.eval.SimpleKeyValueDirective;

/**
 * HtConfigBuilder
 * <p>
 * This object 'executes' the directives contained in a HtConfigFile against
 * a given lookup context.
 */
public class HtConfigBuilder extends NSObject {
  protected static final Log log = LogFactory.getLog("JoConfig");
  
  protected static final Map<String, IHtConfigEvaluation> defDirectiveToEval;
  static {
    /* For now we hardcode the directives ..., later we want to have a
     * registry mapping the directive name to some handler. Possibly doing
     * conversions before triggering the handler, eg string=>int args.
     */
    defDirectiveToEval = new HashMap<String, IHtConfigEvaluation>(64);
    defDirectiveToEval.put("<filesmatch",    new FilesMatch());
    defDirectiveToEval.put("<locationmatch", new LocationMatch());
    defDirectiveToEval.put("<limit",         new Limit());
    defDirectiveToEval.put("<limitexcept",   new LimitExcept());
    defDirectiveToEval.put("allowoverride",  new AllowOverride());
    defDirectiveToEval.put("accessfilename", new AccessFileName());
    defDirectiveToEval.put("options",        new Options());
    defDirectiveToEval.put("require",        new Require());
    defDirectiveToEval.put("authtype",       new SimpleKeyValueDirective());
    defDirectiveToEval.put("authname",       new SimpleKeyValueDirective());
    defDirectiveToEval.put("authuserfile",   new SimpleKeyValueDirective());
    defDirectiveToEval.put("authgroupfile",  new SimpleKeyValueDirective());
    defDirectiveToEval.put("authloginurl",   new SimpleKeyValueDirective());
  }
  public static final HtConfigBuilder sharedBuilder = new HtConfigBuilder();

  protected final Map<String, IHtConfigEvaluation> directiveToEval;
  
  public HtConfigBuilder(Map<String, IHtConfigEvaluation> _directives) {
    this.directiveToEval =
      _directives != null ? _directives : defDirectiveToEval;
  }
  public HtConfigBuilder() {
    this(null /* default */);
  }

  /**
   * Walks over the configuration file and creates a Map containing the
   * configured values by evaluating the directives.
   * 
   * @param _cursor    - the object the configuration was looked up in
   * @param _lookupCtx - the context the lookup is relative to
   * @param _ctx       - the IJoContext all this is happening in
   * @return a configuration, or null if no values were added
   */
  public Map<String, ?> buildConfiguration
    (final Object _lookupCtx, final HtConfigFile _cfgfile)
  {
    if (_cfgfile == null) {
      log.debug("got no parsed representation of config: " + this);
      return null;
    }
    
    Map<String, Object> cfg = new HashMap<String, Object>(16);
    this.processChildren(cfg, _cfgfile, _lookupCtx);
    return cfg.size() > 0 ? cfg : null;
  }

  
  /* HtAccess */

  public void processChildren
    (final Map<String, Object> _cfg, final IHtConfigContainer _container,
     final Object _lookupCtx)
  {
    for (IHtConfigNode node: _container.nodes()) {
      if (!(node instanceof HtConfigDirective))
        continue;
      
      this.processDirective(_cfg, (HtConfigDirective)node, _lookupCtx);
    }
  }
  
  /**
   * Retrieves the _name from the _lookupCtx via KVC. If the name is an array
   * or a Collection, the members are joined using a '/'.
   * 
   * @param _name      - the key to be looked up, eg 'path'
   * @param _lookupCtx - the lookup context
   * @return a String for the given key
   */
  public String arrayValueFromLookupCtx(String _name, final Object _lookupCtx) {
    if (_lookupCtx == null)
      return null;
    
    Object p = NSKeyValueCoding.Utility.valueForKey(_lookupCtx, _name);
    if (p == null)
      return null;
    
    if (p instanceof String)
      return (String)p;
    
    if (p instanceof String[])
      return UString.componentsJoinedByString((String[])p, "/");
    
    if (p instanceof Collection)
      return UString.componentsJoinedByString((Collection)p, "/");
    
    log.error("unexpected '" +_name+"' key in lookupctx: " + _lookupCtx);
    return null;
  }
  
  public void processDirective
    (final Map<String, Object> _cfg, final HtConfigDirective _directive,
     final Object _lookupCtx)
  {
    /* Note: do not mix up config keys and directive names. Similiar but
     *       different ;-) Config keys are CASE SENSITIVE! You usually want
     *       to declare them.
     */
    String              dirname   = _directive.name().toLowerCase();
    IHtConfigEvaluation evaluator = this.directiveToEval.get(dirname);
    
    /* eval object */
    
    if (evaluator != null) {
      evaluator.evaluateDirective(this, _directive, _cfg, _lookupCtx);
      return;
    }
    
    /* fallback */
    log.warn("ignoring directive: " + dirname);
  }
}
