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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.contract.Context;
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.NumberConstraints;
import org.apache.commons.contract.constraints.StringConstraints;
import org.apache.commons.contract.descriptor.ParameterDescriptor;
import org.apache.commons.i18n.LocalizedError;
import org.apache.slide.projector.processor.ConfigurationException;
import org.apache.slide.projector.processor.ProcessException;
import org.apache.slide.projector.value.Streamable;
import org.apache.slide.projector.value.Text;

/**
 * @version $Revision: 1.5 $
 */

public class EnclosingControl extends Control {
    protected final static int MAX_OPTIONS = 100;

    private final static String OPTIONS = "options";
    private final static String ENTRY_POSTFIX = " entry";
    private final static String SELECTED_ENTRY_POSTFIX = " selected entry";

    protected Template optionalEntryFragment, optionalSelectedEntryFragment, optionalValidEntryFragment, optionalValidSelectedEntryFragment, optionalInvalidEntryFragment, optionalInvalidSelectedEntryFragment;
    protected Template requiredEntryFragment, requiredSelectedEntryFragment, requiredValidEntryFragment, requiredValidSelectedEntryFragment, requiredInvalidEntryFragment, requiredInvalidSelectedEntryFragment;

    public EnclosingControl() {
        setRequiredFragments(new String[] { OPTIONAL+getName(), OPTIONAL+getName()+ENTRY_POSTFIX, OPTIONAL+getName()+SELECTED_ENTRY_POSTFIX,
                                            REQUIRED+getName(), REQUIRED+getName()+ENTRY_POSTFIX, REQUIRED+getName()+SELECTED_ENTRY_POSTFIX });
        setOptionalFragments(new String[] { OPTIONAL_VALID_CONTROL+getName(), OPTIONAL_INVALID_CONTROL+getName(), OPTIONAL_VALID_CONTROL+getName()+ENTRY_POSTFIX, OPTIONAL_INVALID_CONTROL+getName()+ENTRY_POSTFIX,
                                            OPTIONAL_VALID_CONTROL+getName()+SELECTED_ENTRY_POSTFIX, OPTIONAL_INVALID_CONTROL+getName()+SELECTED_ENTRY_POSTFIX,
                                            REQUIRED_VALID_CONTROL+getName(), REQUIRED_INVALID_CONTROL+getName(), REQUIRED_VALID_CONTROL+getName()+ENTRY_POSTFIX, REQUIRED_INVALID_CONTROL+getName()+ENTRY_POSTFIX,
                                            REQUIRED_VALID_CONTROL+getName()+SELECTED_ENTRY_POSTFIX, REQUIRED_INVALID_CONTROL+getName()+SELECTED_ENTRY_POSTFIX});
    }

    public Result process(Map parameter, Context context) throws Exception {
        ParameterDescriptor parameterDescriptor = getParameterDescriptor(parameter, context);
        String state = parameter.get(STATE).toString();
        Object value = parameter.get(VALUE);
        String[] options = getOptions(parameterDescriptor.getConstraints());
        StringBuffer renderedEntries = new StringBuffer();
        Template template = optionalEntryFragment;
        for (int j = 0; j < options.length; j++) {
            if (valueMatches(options[j], value)) {
                if ( state.equals(OPTIONAL_CONTROL) ) {
                  template = optionalSelectedEntryFragment;
                } else if ( state.equals(OPTIONAL_VALID_CONTROL) ) {
                    template = optionalValidSelectedEntryFragment;
                } else if ( state.equals(OPTIONAL_INVALID_CONTROL) ) {
                    template = optionalInvalidSelectedEntryFragment;
                } else if ( state.equals(REQUIRED_CONTROL) ) {
                  template = requiredSelectedEntryFragment;
                } else if ( state.equals(REQUIRED_VALID_CONTROL) ) {
                    template = requiredValidSelectedEntryFragment;
                } else if ( state.equals(REQUIRED_INVALID_CONTROL) ) {
                    template = requiredInvalidSelectedEntryFragment;
                }
            } else {
                if ( state.equals(OPTIONAL_CONTROL) ) {
                  template = optionalEntryFragment;
                } else if ( state.equals(OPTIONAL_VALID_CONTROL) ) {
                    template = optionalValidEntryFragment;
                } else if ( state.equals(OPTIONAL_INVALID_CONTROL) ) {
                    template = optionalInvalidEntryFragment;
                } else if ( state.equals(REQUIRED_CONTROL) ) {
                  template = requiredEntryFragment;
                } else if ( state.equals(REQUIRED_VALID_CONTROL) ) {
                    template = requiredValidEntryFragment;
                } else if ( state.equals(REQUIRED_INVALID_CONTROL) ) {
                    template = requiredInvalidEntryFragment;
                }
            }
            parameter.put(VALUE, new String(options[j]));
            template.evaluate(renderedEntries, parameter);
        }
        parameter.put(OPTIONS, new String(renderedEntries.toString()));
        return super.process(parameter, context);
    }

    public void configure(Streamable config) throws ConfigurationException {
        super.configure(config);
        try {
            optionalEntryFragment = getRequiredFragment(OPTIONAL_CONTROL + getName() + ENTRY_POSTFIX);
            optionalSelectedEntryFragment = getRequiredFragment(OPTIONAL_CONTROL + getName() + SELECTED_ENTRY_POSTFIX);
            requiredEntryFragment = getRequiredFragment(REQUIRED_CONTROL + getName() + ENTRY_POSTFIX);
            requiredSelectedEntryFragment = getRequiredFragment(REQUIRED_CONTROL + getName() + SELECTED_ENTRY_POSTFIX);
        } catch ( ProcessException exception ) {
            throw new ConfigurationException(new LocalizedError("control/requiredFragmentMissing", new String[] { OPTIONAL+getName()}), exception);
        }
        optionalValidEntryFragment = getOptionalFragment(OPTIONAL_VALID_CONTROL + getName() + ENTRY_POSTFIX, optionalEntryFragment);
        optionalInvalidEntryFragment = getOptionalFragment(OPTIONAL_INVALID_CONTROL + getName() + ENTRY_POSTFIX, optionalEntryFragment);
        optionalValidSelectedEntryFragment = getOptionalFragment(OPTIONAL_VALID_CONTROL + getName() + SELECTED_ENTRY_POSTFIX, optionalSelectedEntryFragment);
        optionalInvalidSelectedEntryFragment = getOptionalFragment(OPTIONAL_INVALID_CONTROL + getName() + SELECTED_ENTRY_POSTFIX, optionalSelectedEntryFragment);
        requiredValidEntryFragment = getOptionalFragment(REQUIRED_VALID_CONTROL + getName() + ENTRY_POSTFIX, requiredEntryFragment);
        requiredInvalidEntryFragment = getOptionalFragment(REQUIRED_INVALID_CONTROL + getName() + ENTRY_POSTFIX, requiredEntryFragment);
        requiredValidSelectedEntryFragment = getOptionalFragment(REQUIRED_VALID_CONTROL + getName() + SELECTED_ENTRY_POSTFIX, requiredSelectedEntryFragment);
        requiredInvalidSelectedEntryFragment = getOptionalFragment(REQUIRED_INVALID_CONTROL + getName() + SELECTED_ENTRY_POSTFIX, requiredSelectedEntryFragment);
    }

    protected String[] getOptions(Constraints valueDescriptor) {
        if (valueDescriptor instanceof ArrayConstraints ) {
            valueDescriptor = ((ArrayConstraints)valueDescriptor).getEntryValueDescriptor();
        }
        if (valueDescriptor instanceof StringConstraints) {
            return ((StringConstraints)valueDescriptor).getAllowedValues();
        } else if (valueDescriptor instanceof BooleanConstraints) {
            return new String[]{BooleanConstraints.TRUE, BooleanConstraints.FALSE};
        } else if (valueDescriptor instanceof NumberConstraints) {
            NumberConstraints numberDescriptor = (NumberConstraints)valueDescriptor;
            if (numberDescriptor.isConstrained()) {
                List allowedValues = new ArrayList();
                if (numberDescriptor.getAllowedValues() != null) {
                    Number[] numbers = numberDescriptor.getAllowedValues();
                    for (int i = 0; i < numbers.length; i++) {
                        allowedValues.add(String.valueOf(numbers[i]));
                    }
                } else {
                    int counter = 0;
                    for (int i = numberDescriptor.getMinimum().intValue(); i <= numberDescriptor.getMaximum().intValue(); i++) {
                        allowedValues.add(String.valueOf(i));
                        counter++;
                        if (counter == MAX_OPTIONS) break;
                    }
                }
                String[] options = new String[allowedValues.size()];
                return (String[])allowedValues.toArray(options);
            }
        }
        return new String[0];
    }

    protected boolean valueMatches(String value, Object resource) {
        if (resource instanceof Text) {
            return value.equals(resource.toString());
        } else if (resource instanceof Number) {
            return value.equals(String.valueOf(resource));
        } else if (resource instanceof Object[]) {
            Object[] array = (Object [])resource;
            for (int i = 0; i < array.length; i++) {
                if (array[i] instanceof Text) {
                    if (array[i].toString().equals(value)) return true;
                }
            }
        }
        return false;
    }
}