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

import java.util.ArrayList;
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.Result;
import org.apache.commons.contract.constraints.MapConstraints;
import org.apache.commons.contract.constraints.StringConstraints;
import org.apache.commons.contract.constraints.TreeConstraints;
import org.apache.commons.contract.descriptor.ParameterDescriptor;
import org.apache.commons.contract.i18n.ParameterMessage;
import org.apache.slide.projector.context.ProjectorContext;
import org.apache.slide.projector.processor.ConfigurationException;
import org.apache.slide.projector.processor.SimpleProcessor;
import org.apache.slide.projector.processor.TemplateRenderer;
import org.apache.slide.projector.value.Streamable;
import org.apache.slide.projector.value.Text;
import org.apache.slide.projector.value.URI;

public class TreeRenderer extends TemplateRenderer {
    final private static String HEADER_FRAGMENT = "header ";
    final private static String FOOTER_FRAGMENT = "footer ";
    final private static String FIRST_FRAGMENT = "first ";
    final private static String LAST_FRAGMENT = "last ";
    final private static String EVEN_FRAGMENT = "even ";
    final private static String DEFAULT_FRAGMENT = "default ";
    final private static String EMPTY_FRAGMENT = "empty ";
    final private static String OPEN_FOLDER_FRAGMENT = "open ";
    final private static String CLOSED_FOLDER_FRAGMENT = "closed ";
    final private static String ACTIVE_FRAGMENT = "active ";

    final public static String ID = "id";
    final public static String ACTIVE_ID = "activeId";

    private ParameterDescriptor []parameterDescriptors;

    public TreeRenderer() {
        setRequiredFragments(new String[] { DEFAULT_FRAGMENT });
        setOptionalFragments(new String[] { HEADER_FRAGMENT, FOOTER_FRAGMENT, FIRST_FRAGMENT, LAST_FRAGMENT,
                                            EVEN_FRAGMENT, EMPTY_FRAGMENT, OPEN_FOLDER_FRAGMENT, CLOSED_FOLDER_FRAGMENT, ACTIVE_FRAGMENT });
    }

    public void configure(Streamable config) throws ConfigurationException {
        super.configure(config);
        List validMapEntries = new ArrayList();
        for ( Iterator i = parameterDescriptions.iterator(); i.hasNext(); ) {
            ParameterDescriptor parentParameter = (ParameterDescriptor)i.next();
            if ( !parentParameter.getName().equals(FRAGMENT) ) {
                validMapEntries.add(parentParameter);
            }
        }
        parameterDescriptors = new ParameterDescriptor[] {
            new ParameterDescriptor(ID, new ParameterMessage("treeRenderer/id"), new TreeConstraints(new StringConstraints())),
            new ParameterDescriptor(SimpleProcessor.INPUT, new ParameterMessage("treeRenderer/input"), new TreeConstraints(new MapConstraints(validMapEntries)))
        };
    }

    public Result process(Map parameter, Context context) throws Exception {
        Object[] idArray = (Object [])parameter.get(ID);
        Object[] inputArray = (Object [])parameter.get(SimpleProcessor.INPUT);
        URI activeIdResource = (URI)((ProjectorContext)context).getBookmark();
        String activeId = null;
        int[] active = new int[0];
        if ( activeIdResource != null ) {
            activeId = activeIdResource.toString();
            String path = buildPathToActiveNode(null, activeId, idArray);
            if ( path != null ) {
                StringTokenizer tokenizer = new StringTokenizer(path, ",");
                active = new int[tokenizer.countTokens()];
                int counter = 0;
                while ( tokenizer.hasMoreTokens() ) {
                    active[counter] = Integer.parseInt(tokenizer.nextToken());
                    counter++;
                }
            }
        }
        return new Result(OK, OUTPUT, new Text(process(parameter, idArray, inputArray, active, 0, context), "text/html", false ));
    }

    private String buildPathToActiveNode(String path, String activeId, Object[] idArray) {
        for ( int i = 0; i < idArray.length; i++ ) {
            if ( idArray[i] instanceof String && ((String)idArray[i]).equals(activeId) ) {
                if ( path != null) {
                    return path+","+i;
                } else {
                    return String.valueOf(i);
                }
            } else if ( idArray[i] instanceof Object[] ) {
                String newPath;
                if ( path != null) {
                    newPath = path+","+i;
                } else {
                     newPath = String.valueOf(i);
                }
                String nestedPath = buildPathToActiveNode(newPath, activeId, (Object [])idArray[i]);
                if ( nestedPath != null ) {
                    return nestedPath;
                }
            }
        }
        return null;
    }

    public String process(Map parameter, Object[] idArray, Object[] inputArray, int[] active, int level, Context context) throws Exception {
        StringBuffer buffer = new StringBuffer(1024);
        Template defaultTemplate = getRequiredFragment(DEFAULT_FRAGMENT+level);
        Template headerTemplate = getOptionalFragment(HEADER_FRAGMENT+level);
        Template footerTemplate = getOptionalFragment(FOOTER_FRAGMENT+level);
        Template firstTemplate = getOptionalFragment(FIRST_FRAGMENT+level);
        Template lastTemplate = getOptionalFragment(LAST_FRAGMENT+level);
        Template evenTemplate = getOptionalFragment(EVEN_FRAGMENT+level);
        Template activeTemplate = getOptionalFragment(ACTIVE_FRAGMENT+level);
        Template emptyTemplate = getOptionalFragment(EMPTY_FRAGMENT+level);
        Template openTemplate = getOptionalFragment(OPEN_FOLDER_FRAGMENT+level);
        Template closedTemplate = getOptionalFragment(CLOSED_FOLDER_FRAGMENT+level);
        if ( headerTemplate != null ) headerTemplate.evaluate(buffer, parameter);
        for ( int i = 0; i < idArray.length; i++ ) {
            if ( inputArray[i] instanceof Map ) {
                parameter.putAll((Map)inputArray[i]);
            }
            if ( inputArray[i] instanceof Object[]) {
                // Array resources can only follow StringResources that indicate the folders name
                if ( ( active.length <= level || active[level] == i)) {
                    buffer.append(process(parameter, (Object[])idArray[i], (Object[])inputArray[i], active, level+1, context));
                }
            } else if ( i < idArray.length-1 && idArray[i+1] instanceof Object[] && openTemplate != null && (active.length <= level || active[level] == i)) {
                openTemplate.evaluate(buffer, parameter);
            } else if ( i < idArray.length-1 && idArray[i+1] instanceof Object[] && closedTemplate != null ) {
                closedTemplate.evaluate(buffer, parameter);
            } else if ( level == active.length-1 && i == active[level] && activeTemplate != null ) {
                activeTemplate.evaluate(buffer, parameter);
            } else if ( i == 0 && firstTemplate != null ) {
                firstTemplate.evaluate(buffer, parameter);
            } else if ( i == idArray.length-1 && lastTemplate != null ) {
                lastTemplate.evaluate(buffer, parameter);
            } else if ( evenTemplate != null && i%2 == 0 ) {
                evenTemplate.evaluate(buffer, parameter);
            } else if ( defaultTemplate != null ) {
                defaultTemplate.evaluate(buffer, parameter);
            }
        }
        if ( footerTemplate != null ) footerTemplate.evaluate(buffer, parameter);
        return buffer.toString();
    }

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