package org.opengroupware.jope.appserver;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/*
 * Threading: this object is for use in one thread only (not synced)
 */

public class WORequest extends WOMessage {
  
  protected String   method      = null;
  protected String   uri         = null;
  protected Map<String,Object[]> formValues = null;
  
  /* derived information */
  protected List<String> browserLanguages = null;
  
  /* URI components */
  protected String   appName     = null;
  protected String   rhKey       = null;
  protected String   rhPath      = null;
  protected String[] rhPathArray = null;
  
  protected WEClientCapabilities cc = null;
  
  /* construction */
  
  public WORequest() {
    super();
  }
  public WORequest(String _method, String _url, String _httpVersion,
                   Map<String,List<String>> _headers,
                   byte[] _contents, Map _userInfo)
  {
    super();
    this.init(_method, _url, _httpVersion, _headers, _contents, _userInfo);
  }
  
  public void init(String _method, String _url, String _httpVersion,
                   Map<String,List<String>> _headers,
                   byte[] _contents, Map _userInfo)
  {
    super.init(_httpVersion, _headers, _contents, _userInfo);
    
    this.method = _method;
    this.uri    = _url;
    
    this._processURL();
  }
  
  /* URL processing */
  
  protected void _processURL() {
    // TODO: improve path processing
    String[] urlParts;
    int partCount, charsConsumed = 0;
    
    urlParts  = this.uri.split("/");
    partCount = urlParts.length;
    if (partCount > 1) {
      this.appName  = urlParts[1];
      charsConsumed += this.appName.length();
      charsConsumed++;
    }
    if (partCount > 2) {
      this.rhKey    = urlParts[2];
      charsConsumed += this.rhKey.length();
      charsConsumed++;
    }
    
    if (this.uri.length() > charsConsumed) {
      if (this.uri.charAt(charsConsumed) == '/')
        charsConsumed++;
    }
    this.rhPath = this.uri.substring(charsConsumed);
    
    if (partCount > 2) {
      this.rhPathArray = new String[partCount - 3];
      System.arraycopy(urlParts, 3, this.rhPathArray, 0, partCount - 3);
    }
    else
      this.rhPathArray = new String[0];
  }
  
  /* accessors */
  
  public String method() {
    return this.method;
  }
  
  public String uri() {
    return this.uri;
  }

  public List<String> browserLanguages() {
    if (this.browserLanguages != null)
      return this.browserLanguages;
    
    this.browserLanguages = 
      this.parseMultiValueHeader("accept-language", true /* process quality */);
    if (this.browserLanguages == null) {
      /* cache that we have none */
      this.browserLanguages = new ArrayList<String>();
    }
    return this.browserLanguages;
  }
  
  public String applicationName() {
    // TODO: do not deliver .woa extensions
    return this.appName;
  }
  
  /* parsing headers */
  
  protected List<String> parseMultiValueHeader
    (String _name, boolean _processQuality)
  {
    // TODO: implement quality sorting?
    List<String> values = this.headersForKey(_name);
    if (values        == null) return null;
    if (values.size() == 0)    return values;
    
    List<String> svals = new ArrayList<String>(4);
    for (String value: values) {
      String[] parts = value.split(",");
      for (int i = 0; i < parts.length; i++) {
        String s   = parts[i];
        int    idx = s.lastIndexOf(';');
        
        if (idx != -1) s = s.substring(0, idx);
        svals.add(s);
      }
    }
    return svals;
  }
  
  /* streaming support */
  
  public OutputStream outputStream() {
    /* This is overridden by subclasses which provide streaming */
    return null;
  }
  
  /* URL */
  
  public String requestHandlerKey() {
    return this.rhKey;
  }
  public String requestHandlerPath() {
    return this.rhPath;
  }
  public String[] requestHandlerPathArray() {
    return this.rhPathArray;
  }
  
  /* form values */
  
  protected String defaultFormValueEncoding = "latin1";
  
  public void setDefaultFormValueEncoding(String _encoding) {
    this.defaultFormValueEncoding = _encoding;
  }
  public String defaultFormValueEncoding() {
    return this.defaultFormValueEncoding;
  }
  
  public boolean isMultipartFormData() {
    String ct = this.headerForKey("content-type");
    if (ct == null) return false;
    return ct.toLowerCase().startsWith("multipart/form-data");
  }
  
  public Object formValueForKey(String _key) {
    // TODO: this does not work yet for POST!
    Object[] values;
    
    if ((values = this.formValuesForKey(_key)) == null)
      return null;
    
    if (values.length == 0)
      return null;
      
    return values[0];
  }
  public String stringFormValueForKey(String _key) {
    Object ov;
    
    if ((ov = this.formValueForKey(_key)) == null)
      return null;
    
    if (ov instanceof String)
      return (String)ov;
    
    return ov.toString();
  }
  
  public Object[] formValuesForKey(String _key) {
    if (this.formValues == null) /* we never return null */
      return new Object[0];
    
    Object[] vals = this.formValues.get(_key);
    return ((vals == null) ? new Object[0] : vals);
  }
  
  public String[] formValueKeys() {
    return this.formValues.keySet().toArray(new String[0]);
  }
  
  public Map<String,Object[]> formValues() {
    // TODO: implement me
    if (this.formValues == null)
      return new HashMap<String,Object[]>(1);
    return this.formValues;
  }
  
  /* cookie values */
  
  public Collection<String> cookieValuesForKey(String _key) {
    Collection<String> values = new ArrayList<String>(4);
    for (WOCookie c: this.cookies()) {
      if (!_key.equals(c.name()))
        continue;
      values.add(c.value());
    }
    return values;
  }
  
  public String cookieValueForKey(String _key) {
    for (WOCookie c: this.cookies()) {
      if (_key.equals(c.name()))
        return c.value();
    }
    return null;
  }
  
  public Map<String,Collection<String>> cookieValues() {
    Map<String,Collection<String>> values = 
      new HashMap<String,Collection<String>>(4);
    
    for (WOCookie c: this.cookies()) {
      Collection<String> vals;
      String k = c.name();
      
      if (values.containsKey(k))
        vals = values.get(k);
      else {
        vals = new ArrayList<String>(4);
        values.put(k, vals);
      }
      
      vals.add(c.value());
    }
    return values;   
  }
  
  /* session */
  
  public static final String SessionIDKey = "wosid";
  
  public String sessionID() {
    String v;
    
    v = this.stringFormValueForKey(WORequest.SessionIDKey);
    if (v != null) return v;
    
    v = this.cookieValueForKey(WORequest.SessionIDKey);
    if (v != null) return v;
    
    return null;
  }
  public boolean isSessionIDInRequest() {
    return this.sessionID() != null ? true : false;
  }
  
  /* client capabilities */
  
  public WEClientCapabilities clientCapabilities() {
    if (this.cc == null)
      this.cc = new WEClientCapabilities(this);
    return this.cc;
  }
  
  /* description */
  
  public void appendAttributesToDescription(StringBuffer d) {
    super.appendAttributesToDescription(d);
    
    if (this.method != null)
      d.append(" method=" + this.method);
    if (this.uri != null)
      d.append(" uri=" + this.uri);
    if (this.headers != null)
      d.append(" headers=" + this.headers);
  }
}
