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

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
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.Store;
import org.apache.commons.contract.constraints.ArrayConstraints;
import org.apache.commons.contract.constraints.BooleanConstraints;
import org.apache.commons.contract.constraints.MapConstraints;
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.slide.projector.constraints.AnyConstraints;
import org.apache.slide.projector.constraints.URIConstraints;
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.SimpleProcessor;
import org.apache.slide.projector.value.Streamable;
import org.apache.slide.projector.value.Text;
import org.apache.slide.projector.value.URI;

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

public class TableGenerator extends TableMapRenderer {
    final static String PARAMETER = "parameter";

    final private static String COLUMN_FRAGMENT = "column";
    final private static String HEADER_COLUMN_FRAGMENT = "header column";
    final private static String COLLAPSED_COLUMN_FRAGMENT = "collapsed column";
    final private static String COLLAPSED_HEADER_COLUMN_FRAGMENT = "collapsed header column";
    final private static String ASCENDING_HEADER_COLUMN_FRAGMENT = "ascending header column";
    final private static String DESCENDING_HEADER_COLUMN_FRAGMENT = "descending header column";

    final private static String COLUMNS_PARAMETER = "columns";
    final private static String RESIZABLE = "resizable";
    final private static String SORTABLE = "sortable";
    final private static String HEADER = "header";
    final private static String KEY = "key";
    final private static String VALUE = "value";
    final private static String NAME = "name";
    final private static String PROCESSOR = "processor";
    final private static String PARAMETERS = "parameters";
    final private static String RESULT = "result";
    final private static String REPLACE_COLUMN = "replaceColumn";
    
    final private static String HEADERS = "headers";
    final private static String COLUMNS = "columns";

    final private static String SORTABLE_HANDLER = "sortableHandler";
    final private static String RESIZABLE_HANDLER = "resizableHandler";

    private Template columnTemplate, collapsedColumnTemplate, headerColumnTemplate, collapsedHeaderColumnTemplate;
	private Template ascendingHeaderColumnTemplate, descendingHeaderColumnTemplate;
	
    private ParameterDescriptor []parameterDescriptors;

    public TableGenerator() {
        setRequiredFragments(new String[] { DEFAULT_FRAGMENT, COLUMN_FRAGMENT, HEADER_COLUMN_FRAGMENT });
        setOptionalFragments(new String[] { COLLAPSED_HEADER_COLUMN_FRAGMENT, COLLAPSED_COLUMN_FRAGMENT, ASCENDING_HEADER_COLUMN_FRAGMENT, DESCENDING_HEADER_COLUMN_FRAGMENT, EMPTY_FRAGMENT, HEADER_FRAGMENT, FOOTER_FRAGMENT, FIRST_FRAGMENT, LAST_FRAGMENT, EVEN_FRAGMENT });
        setRepeatedFragments(new String[] { FIRST_FRAGMENT, LAST_FRAGMENT, DEFAULT_FRAGMENT, EVEN_FRAGMENT });
        ignoreUndefinedFragments(false);
    }

    public void configure(Streamable config) throws ConfigurationException {
        super.configure(config); 
        ParameterDescriptor []parentParameterDescriptors = super.getParameterDescriptors();
        // remove fragment parameter
        List parameterDescriptorList = new ArrayList();
        for ( int i = 0; i < parentParameterDescriptors.length; i++ ) {
            parameterDescriptorList.add(parentParameterDescriptors[i]);
        }
        List headerParameterDescriptors = getTemplateParameterDescriptor(new String[] { HEADER_COLUMN_FRAGMENT, COLLAPSED_HEADER_COLUMN_FRAGMENT });
        headerParameterDescriptors.add(new ParameterDescriptor(HEADER, new ParameterMessage("tableGenerator/parameter/columns/header"), new StringConstraints(), Text.EMPTY));
        headerParameterDescriptors.add(new ParameterDescriptor(VALUE, new ParameterMessage("tableGenerator/parameter/columns/value"), new AnyConstraints(), null));
        headerParameterDescriptors.add(new ParameterDescriptor(KEY, new ParameterMessage("tableGenerator/parameter/columns/key"), new StringConstraints()));
        headerParameterDescriptors.add(new ParameterDescriptor(NAME, new ParameterMessage("tableGenerator/parameter/columns/name"), new StringConstraints(), new String(VALUE)));
        headerParameterDescriptors.add(new ParameterDescriptor(REPLACE_COLUMN, new ParameterMessage("tableGenerator/parameter/columns/replaceColumn"), new BooleanConstraints(), Boolean.FALSE));
        headerParameterDescriptors.add(new ParameterDescriptor(RESIZABLE, new ParameterMessage("tableGenerator/parameter/columns/resizable"), new BooleanConstraints(), Boolean.FALSE));
        headerParameterDescriptors.add(new ParameterDescriptor(SORTABLE, new ParameterMessage("tableGenerator/parameter/columns/sortable"), new BooleanConstraints(), Boolean.FALSE));
        headerParameterDescriptors.add(new ParameterDescriptor(PROCESSOR, new ParameterMessage("tableGenerator/parameter/columns/processor"), new URIConstraints(), null));
        headerParameterDescriptors.add(new ParameterDescriptor(PARAMETERS, new ParameterMessage("tableGenerator/parameter/columns/parameters"), MapConstraints.UNCONSTRAINED, null));
        headerParameterDescriptors.add(new ParameterDescriptor(RESULT, new ParameterMessage("tableGenerator/parameter/columns/result"), new StringConstraints(), null));
        parameterDescriptorList.add(new ParameterDescriptor(COLUMNS_PARAMETER, new ParameterMessage("tableGenerator/parameter/columns"),
        		new ArrayConstraints(new MapConstraints((ParameterDescriptor [])headerParameterDescriptors.toArray(new ParameterDescriptor[headerParameterDescriptors.size()])))));
        parameterDescriptorList.add(new ParameterDescriptor(PARAMETER, new ParameterMessage("tableGenerator/parameter/parameter"), MapConstraints.UNCONSTRAINED, new HashMap()));
        parameterDescriptors = (ParameterDescriptor[] )parameterDescriptorList.toArray(new ParameterDescriptor[parameterDescriptorList.size()]);
        collapsedColumnTemplate = getOptionalFragment(COLLAPSED_COLUMN_FRAGMENT);
        collapsedHeaderColumnTemplate = getOptionalFragment(COLLAPSED_HEADER_COLUMN_FRAGMENT);
        ascendingHeaderColumnTemplate = getOptionalFragment(ASCENDING_HEADER_COLUMN_FRAGMENT);
        descendingHeaderColumnTemplate = getOptionalFragment(DESCENDING_HEADER_COLUMN_FRAGMENT);
        try {
            columnTemplate = getRequiredFragment(COLUMN_FRAGMENT);
            headerColumnTemplate = getRequiredFragment(HEADER_COLUMN_FRAGMENT);
        } catch ( ProcessException exception ) {
            throw new ConfigurationException(new LocalizedError("tableRenderer/fragmentsMissing"), exception);
        }
    }

    public Result process(Map parameter, Context context) throws Exception {
        Map parameters = (Map)parameter.get(PARAMETER);
        String handlerUrl = ProcessorManager.getInstance().process(ProcessorManager.URL, TableHandler.URL, context).toString();
        String id = parameter.get(TableHandler.ID).toString();
        String storeId = parameter.get(TableHandler.STORE).toString();
        Store store = context.getStore(storeId);
        Map tableMap = (Map)store.get(id, context);
        String sortedBy = null, order = null;
        Map size = null;
        if ( tableMap == null) {
            tableMap = new HashMap();
            store.put(id, tableMap, context);
        } else {
            Object sortedByValue = tableMap.get(TableHandler.SORTED_BY);
            if ( sortedByValue != null && sortedByValue != null ) sortedBy = sortedByValue.toString();
            Object orderValue = tableMap.get(TableHandler.ORDER);
            if ( orderValue != null  && orderValue != null ) order = orderValue.toString();
            Object sizeValue = tableMap.get(TableHandler.SIZE);
            if ( sizeValue != null  && sizeValue != null ) size = (Map)sizeValue;
        }
        // create headers and columns and call super...
    	StringBuffer headersBuffer = new StringBuffer(1024);
    	Object []columns = (Object [])parameter.get(COLUMNS_PARAMETER); 
    	for ( int i = 0; i < columns.length; i++ ) {
    		Map columnMap = (Map)columns[i];
    		boolean sortable = ((Boolean)columnMap.get(SORTABLE)).booleanValue();
    		if ( ascendingHeaderColumnTemplate == null || descendingHeaderColumnTemplate == null ) sortable = false;
    		boolean resizable = ((Boolean)columnMap.get(RESIZABLE)).booleanValue();
    		Object header = columnMap.get(HEADER);
    		Map columnHeaderParameters = new HashMap();
    		columnHeaderParameters.putAll(parameter);
    		columnHeaderParameters.putAll(columnMap);
    		Template template = headerColumnTemplate;
    		String key = columnMap.get(KEY).toString();
			template = headerColumnTemplate;
    		if ( resizable && collapsedColumnTemplate != null && collapsedHeaderColumnTemplate != null ) {
        		if ( size != null && TableHandler.COLLAPSED.equals(size.get(key))) {
        			createResizableHandler(key, TableHandler.EXPAND, handlerUrl, storeId, id, columnHeaderParameters, parameters);
        			template = collapsedHeaderColumnTemplate;
        		} else {
        			createResizableHandler(key, TableHandler.COLLAPSE, handlerUrl, storeId, id, columnHeaderParameters, parameters);
        		}
    		} 
    		if ( sortable && template != collapsedHeaderColumnTemplate ) {
        		String requestedOrder = TableHandler.ASCENDING;
        		if ( sortedBy != null && sortedBy.equals(key) ) {
        			if ( order.equals(TableHandler.ASCENDING) ) {
        				template = ascendingHeaderColumnTemplate;
                		requestedOrder = TableHandler.DESCENDING;
        			} else {
        				template = descendingHeaderColumnTemplate;
                		requestedOrder = TableHandler.ASCENDING;
        			}
        		}
        		createSortableHandler(key, requestedOrder, handlerUrl, storeId, id, columnHeaderParameters, parameters);
    		}
    		template.evaluate(headersBuffer, columnHeaderParameters);
    	}
    	parameter.put(HEADERS, new String(headersBuffer.toString()));
    	Object []input = ((Object [])parameter.get(SimpleProcessor.INPUT));
    	for ( int i = 0; i < input.length; i++ ) {
        	StringBuffer columnsBuffer = new StringBuffer(1024);
            Map inputParameter = (Map)input[i];
    		for ( int j = 0; j < columns.length; j++ ) {
        		Map columnMap = (Map)columns[j];
        		boolean replaceColumn = ((Boolean)columnMap.get(REPLACE_COLUMN)).booleanValue();
        		Object processorUri = columnMap.get(PROCESSOR);
        		Map columnParameters = new HashMap();
        		columnParameters.putAll(parameter);
        		String key = columnMap.get(KEY).toString();
        		String name = columnMap.get(NAME).toString();
        		Object value = columnMap.get(VALUE);
        		if ( value == null || value == null ) {
        			value = inputParameter.get(key);
        		}
        		columnParameters.put(name, value);
        		if ( processorUri != null ) {
                	Processor processor = ProcessorManager.getInstance().getProcessor((URI)processorUri);
                	Object processorParameters = columnMap.get(PARAMETERS);
                	if ( processorParameters instanceof Map ) {
                		columnParameters.putAll((Map)processorParameters);
                	}
            		Result processorResult = ProcessorManager.process(processor, columnParameters, context);
            		String resultKey = columnMap.get(RESULT).toString();
            		Object resultEntry = processorResult.getResultEntries().get(resultKey);
            		if ( replaceColumn ) {
            		    columnsBuffer.append(resultEntry);
            		} else {
            		    StringBuffer resultBuffer = new StringBuffer();
                        resultBuffer.append(resultEntry);
            		    columnParameters.put(VALUE, new String(resultBuffer.toString()));
            		}
        		}
        		if ( !replaceColumn ) {
        			Template template = columnTemplate;
        			if ( size != null && TableHandler.COLLAPSED.equals(size.get(key))) {
        				template = collapsedColumnTemplate;
        			}
        			template.evaluate(columnsBuffer, columnParameters);
        		}
    		}	
    		inputParameter.put(COLUMNS, new String(columnsBuffer.toString()));
    	}
        return super.process(parameter, context);
    }

    public ParameterDescriptor[] getParameterDescriptors() {
        return parameterDescriptors;
    }
    
    protected void createSortableHandler(String column, String requestedOrder, String handlerUrl, String storeName, String id, Map parameters, Map parameter) throws IOException {
        StringBuffer handlerBuffer = new StringBuffer(128);
        handlerBuffer.append(handlerUrl).append('?').append(TableHandler.STORE).append('=').append(storeName).append('&').append(TableHandler.ID).append('=').append(id).
		append('&').append(TableHandler.SORTED_BY).append('=').append(column).append('&').append(TableHandler.ORDER).append('=').append(requestedOrder);
        for ( Iterator j = parameter.entrySet().iterator(); j.hasNext(); ) {
        	Map.Entry entry = (Map.Entry)j.next();
        		handlerBuffer.append('&').append(entry.getKey()).append('=').append(entry.getValue());
        }
        parameters.put(SORTABLE_HANDLER, new String(handlerBuffer.toString()));
    }
    
    protected void createResizableHandler(String column, String requestedSize, String handlerUrl, String storeName, String id, Map parameters, Map parameter) throws IOException {
        StringBuffer handlerBuffer = new StringBuffer(128);
        handlerBuffer.append(handlerUrl).append('?').append(TableHandler.STORE).append('=').append(storeName).append('&').append(TableHandler.ID).append('=').append(id).
		append('&').append(requestedSize).append('=').append(column);
        for ( Iterator j = parameter.entrySet().iterator(); j.hasNext(); ) {
        	Map.Entry entry = (Map.Entry)j.next();
       		handlerBuffer.append('&').append(entry.getKey()).append('=').append(entry.getValue());
        }
        parameters.put(RESIZABLE_HANDLER, new String(handlerBuffer.toString()));
    }
}