package org.apache.slide.projector.processor.form;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.commons.contract.Context;
import org.apache.commons.contract.Processor;
import org.apache.commons.contract.Result;
import org.apache.commons.contract.Store;
import org.apache.commons.contract.constraints.StringConstraints;
import org.apache.commons.contract.constraints.ValidationException;
import org.apache.commons.contract.descriptor.ParameterDescriptor;
import org.apache.commons.contract.descriptor.ResultDescriptor;
import org.apache.commons.contract.descriptor.ResultEntryDescriptor;
import org.apache.commons.contract.descriptor.StateDescriptor;
import org.apache.commons.contract.i18n.ParameterMessage;
import org.apache.commons.i18n.LocalizedError;
import org.apache.slide.projector.Projector;
import org.apache.slide.projector.constraints.AnyConstraints;
import org.apache.slide.projector.context.ProjectorContext;
import org.apache.slide.projector.engine.ProcessorManager;
import org.apache.slide.projector.processor.SimpleProcessor;
import org.apache.slide.projector.processor.process.Process;
import org.apache.slide.projector.value.URI;
import org.apache.slide.projector.value.URIValue;

/**
 * @version $Revision: 1.7 $
 */

public class FormHandler implements Processor {
	public final static String RESULT = "result";

	private final static String INSTRUCTION_IDENTIFIER = "instruction:";
	
	private final static ParameterDescriptor[] parameterDescriptors = new ParameterDescriptor[0];
	
	private final static ResultDescriptor []resultDescriptors = new ResultDescriptor[] {
	        new ResultDescriptor(StateDescriptor.OK_DESCRIPTOR,
	                new ResultEntryDescriptor[] {
	                new ResultEntryDescriptor(SimpleProcessor.OUTPUT, new ParameterMessage("trigger/output"), AnyConstraints.UNCONSTRAINED)
	        })
	};
	
    public Result process(Map parameter, Context context) throws Exception {
    	// Lookup valid instruction
    	String trigger = null;
    	for ( Iterator i = parameter.entrySet().iterator(); i.hasNext(); ) {
    		Map.Entry entry = (Map.Entry)i.next();
    		String key = (String)entry.getKey();
    		if ( key.startsWith(Form.TRIGGER_IDENTIFIER) ) {
    			if ( key.indexOf('.') > 0 ) {
    				trigger = key.substring(0, key.indexOf('.'));
    				break;
    			}
    		}
    	}
    	if ( trigger == null ) {
    		throw new ValidationException(new LocalizedError("trigger/triggerParameterMissing"));
    	}
    	// Decode instruction
		StringTokenizer tokenizer = new StringTokenizer(StringConstraints.UNCONSTRAINED.cast(parameter.get(INSTRUCTION_IDENTIFIER+trigger), context).toString(), ";");
		URI actionURI = new URIValue(tokenizer.nextToken());
		boolean validate = Boolean.valueOf(tokenizer.nextToken()).booleanValue();
		boolean wizard = Boolean.valueOf(tokenizer.nextToken()).booleanValue();
		String lastStep = tokenizer.nextToken();
		String targetStep = tokenizer.nextToken();
		String domain = tokenizer.nextToken();
		List involvedParamters = new ArrayList();
		while ( tokenizer.hasMoreTokens() ) {
			involvedParamters.add(tokenizer.nextToken());
		}
		Store store = context.getStore(Projector.SESSION_STORE);
		// 1. Store all (request) parameters into requested domain
		Map map = (Map)store.get(domain, context);
		if ( map == null ) {
			map = new HashMap();
			store.put(domain, map, context);
		}
        map.put(ControlComposer.VALIDATE, new Boolean(validate));
    	map.put(Process.STEP, targetStep);
    	map.putAll(parameter);
    	if ( validate ) {
    		// 2. Validate parameters
    		Processor processor = ProcessorManager.getInstance().getProcessor(actionURI);
    		try {
    			// Validate only given parameters to enable wizard like forms
    			ParameterDescriptor[] parameterDescriptors = processor.getParameterDescriptors();
    	        for ( int i = 0; i < parameterDescriptors.length; i++ ) {
    	        	String parameterName = parameterDescriptors[i].getName();
    	            if ( involvedParamters.contains(parameterName) ) {
    	            	map.put(parameterName, ProcessorManager.prepareValue(parameterDescriptors[i], parameter.get(parameterName), context));
    	            }
    	        }
    	        if ( wizard ) { 
        	        // Don't validate target step form
    	        	map.put(ControlComposer.VALIDATE, Boolean.FALSE);
    	        }
    	    } catch ( ValidationException exception ) {
    	    	// 3. Go back to form step if validation failes
    			map.put(Process.STEP, lastStep);
    		}
    	}
    	// 4. Launch target step if validation is successfull
		Processor formProcess = ProcessorManager.getInstance().getProcessor(new URIValue(domain));
        map.remove(RESULT);
        Result formResult = formProcess.process(parameter,  context);
        map.put(RESULT, formResult);
        map.remove(Process.STEP);
		Processor bookmark = ProcessorManager.getInstance().getProcessor(((ProjectorContext)context).getBookmark());
        return bookmark.process(parameter,  context);
    }

    public ParameterDescriptor[] getParameterDescriptors() {
        return parameterDescriptors;
    }

    public ResultDescriptor[] getResultDescriptors() {
        return resultDescriptors;
    }
}