// FormCardResource.java
// $Id: FormCardResource.java,v 1.13 1997/03/07 12:42:34 abaird Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.forms;

import java.util.*;
import java.net.*;

import w3c.www.http.*;
import w3c.jigsaw.http.* ;
import w3c.jigsaw.resources.*;
import w3c.jigsaw.html.HtmlGenerator ;

public class FormCardResource extends PostableResource {
    FormResource    form       = null ;
    FormCardHandler handler    = null ;
    Vector          vfields    = null ;
    String          name       = null ;
    String          title      = null ;
    String          url        = null ;
    String          okLabel    = "Ok";
    String          resetLabel = "Reset";

    Vector vbuttons  = null ;
    boolean deleted = false ;

    /**
     * Mark this card as removed.
     */

    public synchronized void delete() {
	deleted = true ;
    }

    /**
     * Is this card deleted ?
     */

    public synchronized boolean isDeleted() {
	return deleted ;
    }

    /**
     * Set the Ok button label.
     * @param label The label to use for the commit button.
     */

    public void setOkLabel(String label) {
	okLabel = label;
    }

    /**
     * Set the reset button label.
     * @param label The label to use for the reset button.
     */

    public void setResetLabel(String label) {
	resetLabel = label;
    }

    /**
     * Our own lookup, to implement multiple buttons:
     */

    public boolean lookup(LookupState ls, LookupResult lr) 
	throws HTTPException
    {
	if ( super.lookup(ls, lr) )
	    return true;
	String label = ls.getNextComponent() ;
	// Is this a valid button ?
	for (int i = 0 ; i < vbuttons.size() ; i++) {
	    String button = (String) vbuttons.elementAt(i) ;
	    if ( button.equals(label) ) {
		// Notify the button click:
		try {
		    handler.notifyButtonClick(label) ;
		} catch (FormProcessingException ex) {
		    lr.setTarget(null);
		    return false;
		}
		lr.setTarget(this);
		return true;
	    }
	}
	// Unknown button:
	lr.setTarget(null);
	return true;
    }

    /**
     * Get this card name.
     */

    public String getName() {
	return name ;
    }

    public void rename(String name) {
	this.name = name;
	this.url  = form.getURLPath()+"/"+URLEncoder.encode(name);
    }

    /**
     * Get this card title.
     */

    public String getTitle() {
	return title ;
    }
     
    /**
     * Dump this card into the given HtmlGenerator.
     * @param into The HtmlGenerator to dump ourself into.
     */

    protected void dump (HtmlGenerator g) {
	// Dump the fields:
	g.append ("<form action=\"" + url + "\" method=\"POST\">") ;
	g.append("<table width=\"100%\">");
	if ( vfields.size() > 0 ) {
	    for (int i = 0 ; i < vfields.size() ; i++) 
		((FormField) vfields.elementAt(i)).dump(g) ;
	}
	g.append("</table>");
	g.append ("<p><input type=\"submit\" name=\"submit\" value=\""
		  + okLabel 
		  + "\">");
	g.append ("<input type=\"reset\" name=\"submit\" value=\""
		  + resetLabel
		  + "\">");
	g.append ("</form>") ;
	// Dump the buttons:
	if ( vbuttons.size() > 0 ) {
	    g.append("<hr>");
	    for (int i = 0 ; i < vbuttons.size() ; i++) {
		String label = (String) vbuttons.elementAt(i) ;
		g.append("<form method=\"GET\" action=\""
			 + url+"/"+label
			 +"\">");
		g.append("<input type=\"submit\""
			 + " name=\"BUTTONRESERVEDBUTTON\""
			 + " value=\""+label+"\"></form>");
	    }
	}
    }

    /**
     * Add a new field to this form.
     * @param field The field to add.
     */

    public void addField (FormFieldInterface field) {
	vfields.addElement (field) ;
    }

    /**
     * Add a new button.
     * Button click are simulated, the form handler will get notified of
     * them through the notifyButtonClick callback.
     * @param label The button name.
     */
    
    public void addButton(String label) {
	vbuttons.addElement(label) ;
    }

    /**
     * remove the field whose name is given.
     * @param name The name of the field to remove.
     */

    public void removeField(String name) {
	for (int i = 0 ; i < vfields.size() ; i++) {
	    FormField field = (FormField) vfields.elementAt(i) ;
	    if ( field.getName().equals(name) ) {
		vfields.removeElementAt(i) ;
		return ;
	    }
	}
    }

    /**
     * Reset this form card.
     * Remove all the fields and all the buttons defined for this card.
     */

    public void reset() {
	vfields.setSize(0) ;
	vbuttons.setSize(0) ;
    }

    /**
     * Lookup a field in this card.
     * @param name The name of the field to look for.
     * @return An instance of FormFieldInterface or <strong>null</strong>
     *    if none was found.
     */

    public FormFieldInterface lookupField (String name) {
	for (int i = 0 ; i < vfields.size() ; i++) {
	    FormField field = (FormField) vfields.elementAt(i) ;
	    if ( field.getName().equals(name) )
		return field ;
	}
	return null ;
    }
    
		 
    /**
     * Update the field whose name is given, with the provided value.
     * @param name The field name.
     * @param value The new value for this field.
     * @exception HTTPException If the field couldn't be set.
     */

    protected void updateField (String name, String value) 
	throws FormFieldException 
    {
	FormFieldInterface field = lookupField (name) ;
	if ( field == null )
	    return ;
	if ( field.setValue (value) ) {
	    if ( handler != null )
		handler.notifyChange (field, field.getValue()) ;
	}
    }

    /**
     * Get this form card entity.
     * This dumps the whole form (including the card button controlers).
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If the request couldn't be handled.
     */

    public Reply get(Request request)
	throws HTTPException
    {
	// If the card has been deleted (for some reason), delegate to form
	if ( isDeleted() )
	    return form.get(request) ;
	// Do we handle 'convert-get' ? If so, leave it to handle:
	if ( getConvertGetFlag() && request.hasState("query") )
	    return super.get(request);
	// Is is time to update our HTML for the card, checkit:
	FormCardResource updated = handler.updateFormCard(this) ;
	if ( updated == null )
	    updated = this ;
	// Emit the HTML:
	Reply reply = request.makeReply(HTTP.OK) ;
	HtmlGenerator g = new HtmlGenerator (title) ;
	form.dump (g, updated) ;
	reply.setHeaderValue(reply.H_CACHE_CONTROL
			     , FormResource.CACHE_CONTROL_NOCACHE);
	reply.setHeaderValue(reply.H_PRAGMA
			     , FormResource.PRAGMA_NOCACHE);
	reply.setStream (g) ;
	return reply ;
    }

    /**
     * Handle a post to this card.
     * This method is synchronized so that only one thread of control executes 
     * the sequence of <code>notifyBeginProcessing</code>, 
     * <code>notifyEndProcessing</code> and <code>notifyChange</code>.
     * @param client The clien tthat issued the request.
     * @param request The request to fullfill.
     * @param data The form data.
     * @exception HTTPException If the form processing erred.
     */

    public synchronized Reply handle (Request request, URLDecoder data) 
	throws HTTPException
    {
	// Run the begin processing handler:
	try {
	    handler.notifyBeginProcessing(this, request) ;
	} catch (FormProcessingException ex) {
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent (ex.getMessage()) ;
	    throw new HTTPException (error) ;
	}
	// Change the fields value apprpriately:
	Enumeration e = data.keys() ;
	while ( e.hasMoreElements() ) {
	    String name  = (String) e.nextElement() ;
	    String value = data.getValue(name) ;
	    try {
		updateField (name, value) ;
	    } catch (FormFieldException ex) {
		Reply         r = request.makeReply(HTTP.OK) ;
		HtmlGenerator g = new HtmlGenerator ("error for " + name) ;
		g.append ("<p>The field " 
                          + name
			  + " has an incorrect value:</p><hr>"
			  + "<p>" + ex.getMessage() + "</p><hr>"
			  + "<p>Click <a href=\""
                          + url
			  + "\">here</a> to continue.");
		r.setHeaderValue(r.H_CACHE_CONTROL
				 , FormResource.CACHE_CONTROL_NOCACHE);
		r.setHeaderValue(r.H_PRAGMA
				 , FormResource.PRAGMA_NOCACHE);
		r.setStream (g) ;
		return r ;
	    }
	}
	// Run the end processing handler:
	String location = null ;
	URL    urlloc   = null;
	try {
	    location = handler.notifyEndProcessing(this, request);
	    if ( location != null ) {
		try {
		    urlloc = new URL(getURL(request), location);
		} catch (MalformedURLException ex) {
		    // This should really not happen
		    ex.printStackTrace();
		}
	    }
	} catch (FormProcessingException ex) {
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent(ex.getMessage()) ;
	    throw new HTTPException (error) ;
	}
	// If a location provided, redirect else get:
	if ( urlloc == null ) {
	    request.delState("query");
	    return get(request) ;
	} else {
	    Reply reloc = request.makeReply(HTTP.MOVED_TEMPORARILY) ;
	    reloc.setContentLength(0) ;
	    reloc.setLocation(urlloc) ;
	    reloc.setHeaderValue(reloc.H_CACHE_CONTROL
				 , FormResource.CACHE_CONTROL_NOCACHE);
	    reloc.setHeaderValue(reloc.H_PRAGMA
				 , FormResource.PRAGMA_NOCACHE);
	    throw new HTTPException(reloc) ;
	}
    }

    /**
     * Create a new form card.
     * Users of the package should create new form cards through the form 
     * object itself.
     * @param form The form we are attached to.
     * @param handler This card form handler.
     * @param name This card's name,
     * @param title The card displayed title.
     */

    protected FormCardResource (FormResource form
				, FormCardHandler handler
				, String name, String title) {
	this.form     = form ;
	this.handler  = handler ;
	this.name     = name ;
	this.title    = title ;
	this.vfields  = new Vector() ;
	this.vbuttons = new Vector() ;
	this.url      = form.getURLPath() + "/" + URLEncoder.encode(name) ;
	// Initialize the object as a Resource:
	Hashtable defs = new Hashtable(5);
	defs.put("url", url);
	defs.put("context", form.getContext());
	initialize(defs);
    }



}
