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.Locale;
import java.util.Map;

import org.apache.commons.contract.Context;
import org.apache.commons.contract.Processor;
import org.apache.commons.contract.Result;
import org.apache.commons.contract.constraints.ArrayConstraints;
import org.apache.commons.contract.constraints.BooleanConstraints;
import org.apache.commons.contract.constraints.Constraints;
import org.apache.commons.contract.constraints.LocaleConstraints;
import org.apache.commons.contract.constraints.NumberConstraints;
import org.apache.commons.contract.constraints.StringConstraints;
import org.apache.commons.contract.descriptor.ParameterDescriptor;
import org.apache.commons.contract.i18n.ParameterMessage;
import org.apache.commons.i18n.LocalizedError;
import org.apache.commons.i18n.LocalizedMessage;
import org.apache.commons.i18n.MessageManager;
import org.apache.slide.projector.Projector;
import org.apache.slide.projector.constraints.URIConstraints;
import org.apache.slide.projector.context.HttpContext;
import org.apache.slide.projector.descriptor.ProcessorDescriptor;
import org.apache.slide.projector.engine.ProcessorManager;
import org.apache.slide.projector.processor.ConfigurationException;
import org.apache.slide.projector.processor.ProcessException;
import org.apache.slide.projector.processor.process.Process;
import org.apache.slide.projector.value.Streamable;
import org.apache.slide.projector.value.Text;
import org.apache.slide.projector.value.URI;
import org.apache.slide.projector.value.URIValue;

/**
 * @version $Revision: 1.6 $
 */
public class FormGenerator extends ControlComposer {
    public final static String CONTROLS = "controls";
    public final static String TRIGGERS = "triggers";
    public final static String TARGET_STEP = "targetStep";

    protected final static String TRIGGER_IMAGE = "image";
    protected final static URI TEXTFIELD = new URIValue("textfield");
    protected final static URI TEXTAREA = new URIValue("textarea");
    protected final static URI LISTBOX = new URIValue("listbox");
    protected final static URI COMBOBOX = new URIValue("combobox");
    protected final static URI CHECKBOX = new URIValue("checkbox");
    protected final static URI ERRORS_TABLE = new URIValue("errors");
    protected final static URI TRIGGER_URI = new URIValue("imageTrigger");
    protected final static URI DEFAULT_TRIGGER_IMAGE = new URIValue("/projector/applications/core/images/button_ok.png");
    protected final static URI DEFAULT_CONTROL_CONTAINER = new URIValue("bigControl");
    protected final static URI DEFAULT_TRIGGER_CONTAINER = new URIValue("triggerContainer");
    protected final static URI DEFAULT_ERROR_RENDERER = new URIValue("errors");

    protected final static String PROCESSOR_NAME = "processor-name";
    protected final static String PROCESSOR_TITLE = "processor-title";
    protected final static String PROCESSOR_TEXT = "processor-text";
    protected final static String PROCESSOR_SMALL_ICON = "processor-small-icon";
    protected final static String PROCESSOR_LARGE_ICON = "processor-large-icon";

    protected final static String STYLE = "style";
    protected final static String ORANGE_STYLE = "/style.css";
    protected final static String LAUNCH_PROCESSOR = "trigger:launch";
    
    private ParameterDescriptor[] parameterDescriptors;

    public FormGenerator() {
    	super();
    }

    public Result process(Map parameter, Context context) throws Exception {
        URI actionUri = (URIValue)parameter.get(Control.ACTION);
        Processor action = ProcessorManager.getInstance().getProcessor(actionUri);
        Locale locale = (Locale)parameter.get(LOCALE);
        ParameterDescriptor []actionParameterDescriptors = action.getParameterDescriptors();
        StringBuffer buffer = new StringBuffer();
        List controlDescriptors = new ArrayList();
        // Collect controls to compose...
        for (int i = 0; i < actionParameterDescriptors.length; i++) {
        	String parameterName = actionParameterDescriptors[i].getName(); 
        	Map controlDescriptor = new HashMap();
        	controlDescriptor.put(Control.ACTION, actionUri);
        	controlDescriptor.put(Control.PARAMETER, parameterName);
        	controlDescriptor.put(CONTROL, getControlURI(actionParameterDescriptors[i].getConstraints()));
        	controlDescriptor.put(CONTROL_CONTAINER, DEFAULT_CONTROL_CONTAINER);
        	controlDescriptor.put(CONTROL_NAME, parameterName);
        	controlDescriptors.add(controlDescriptor);
        }
        parameter.put(CONTROL_DESCRIPTIONS, controlDescriptors.toArray());

        // Finally add testing trigger...
    	Map triggerDescriptor = new HashMap();
    	triggerDescriptor.put(Trigger.ACTION, actionUri);
    	triggerDescriptor.put(Trigger.VALIDATE, Boolean.TRUE);
    	triggerDescriptor.put(Trigger.INVOLVED_PARAMETERS, null);
    	triggerDescriptor.put(Process.STEP, parameter.get(TARGET_STEP));
    	triggerDescriptor.put(TRIGGER, TRIGGER_URI);
    	triggerDescriptor.put(TRIGGER_NAME, LAUNCH_PROCESSOR);
    	triggerDescriptor.put(TRIGGER_IMAGE, ProcessorManager.getInstance().process(ProcessorManager.BINARY, DEFAULT_TRIGGER_IMAGE, "url", context));
    	triggerDescriptor.put(TRIGGER_CONTAINER, DEFAULT_TRIGGER_CONTAINER);
        parameter.put(TRIGGER_DESCRIPTIONS, new Object[] { triggerDescriptor });
        parameter.put(ERRORS_PROCESSOR, DEFAULT_ERROR_RENDERER);
        
        parameter.put(HANDLER, ProcessorManager.getInstance().process(ProcessorManager.URL, Projector.DEFAULT_FORM_HANDLER, context));
        parameter.put(METHOD, new String(POST));
        Result controlComposerResult = super.process(parameter, context);
        StringBuffer controlBuffer = new StringBuffer();
        Object []generatedControls = (Object [])controlComposerResult.getResultEntries().get(ControlComposer.GENERATED_CONTROLS);
        for ( int i = 0; i < generatedControls.length; i++ ) {
        	Iterator j = ((Map)generatedControls[i]).values().iterator();
        	Text renderedControl = (Text)j.next();
        	buffer.append(renderedControl.toString());
        }
        String composedControls = new String(buffer.toString());
        parameter.put(CONTROLS, composedControls);
        StringBuffer triggerBuffer = new StringBuffer();
        Object []generatedTriggers = ((Object [])controlComposerResult.getResultEntries().get(ControlComposer.GENERATED_TRIGGERS));
        for ( int i = 0; i < generatedTriggers.length; i++ ) {
        	Iterator j = ((Map)generatedTriggers[i]).values().iterator();
        	Text renderedTrigger = (Text)j.next();
        	triggerBuffer.append(renderedTrigger.toString());
        }
        String composedTriggers= new String(triggerBuffer.toString());
        parameter.put(TRIGGERS, composedTriggers);
        ProcessorDescriptor processorDescriptor = ProcessorManager.getInstance().getProcessorDescriptor(actionUri);
        parameter.put(PROCESSOR_NAME, processorDescriptor.getName());
        parameter.put(PROCESSOR_TITLE, ((LocalizedMessage)processorDescriptor.getDescription()).getTitle(locale, processorDescriptor.getName()));
        parameter.put(PROCESSOR_TEXT, ((LocalizedMessage)processorDescriptor.getDescription()).getText(locale, ""));
        parameter.put(PROCESSOR_LARGE_ICON, ProcessorManager.getInstance().process(ProcessorManager.BINARY, processorDescriptor.getLargeIcon(), "url", context));
        parameter.put(PROCESSOR_SMALL_ICON, ProcessorManager.getInstance().process(ProcessorManager.BINARY, processorDescriptor.getSmallIcon(), "url", context));
        parameter.put(TITLE, processorDescriptor.getName());
        parameter.put(STYLE, ((HttpContext)context).getContextPath() + ORANGE_STYLE);
        parameter.put(ERRORS, controlComposerResult.getResultEntries().get(ControlComposer.RENDERED_ERRORS)); 
        parameter.put(ERRORS_TITLE, MessageManager.getText("formGenerator/errors", LocalizedMessage.TEXT, new Object[0], locale));
        Template template = defaultTemplate;
        String state = controlComposerResult.getState();
        if ( state == VALID_STATE && validTemplate != null ) {
            template = validTemplate;
        } else if ( state == INVALID_STATE && invalidTemplate != null ) {
            template = invalidTemplate;
        }
        return new Result(state, OUTPUT, renderFragment(template, parameter));
    }

    public void configure(Streamable config) throws ConfigurationException {
        super.configure(config);
        parameterDescriptors = new ParameterDescriptor[] {
            new ParameterDescriptor(ACTION, new ParameterMessage("formGenerator/action"), new URIConstraints()),
            new ParameterDescriptor(LOCALE, new ParameterMessage("formGenerator/locale"), new LocaleConstraints(), Locale.getDefault()),
            new ParameterDescriptor(TARGET_STEP, new ParameterMessage("formGenerator/targetStep"), new StringConstraints())
        };
        try {
            defaultTemplate = getRequiredFragment(DEFAULT_FORM);
        } catch ( ProcessException exception ) {
            throw new ConfigurationException(new LocalizedError("form/defaultFragmentMissing"));
        }
        validTemplate = getOptionalFragment(VALID_FORM);
        invalidTemplate = getOptionalFragment(INVALID_FORM);
    }

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

    public static URI getControlURI(Constraints descriptor) {
        if ( descriptor instanceof ArrayConstraints ) {
            return LISTBOX;
        }
        if ( descriptor instanceof StringConstraints ) {
            if ( ((StringConstraints)descriptor).isEnumerable() ) {
                return COMBOBOX;
            } else if ( ((StringConstraints)descriptor).getMaximumLength() < 256 ) {
                return TEXTFIELD;
            } else {
                return TEXTAREA;
            }
        }
        if ( descriptor instanceof BooleanConstraints ) {
            return CHECKBOX;
        }
        if ( descriptor instanceof NumberConstraints ) {
            if ( ((NumberConstraints)descriptor).isConstrained() ) {
                return COMBOBOX;
            }
            return TEXTFIELD;
        }
        return TEXTFIELD;
    }
}