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.Result;
import org.apache.commons.contract.Store;
import org.apache.commons.contract.constraints.BooleanConstraints;
import org.apache.commons.contract.constraints.MapConstraints;
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.descriptor.StateDescriptor;
import org.apache.commons.contract.i18n.ParameterMessage;
import org.apache.slide.projector.Projector;
import org.apache.slide.projector.engine.ProcessorManager;
import org.apache.slide.projector.processor.ConfigurationException;
import org.apache.slide.projector.processor.TemplateRenderer;
import org.apache.slide.projector.value.Streamable;

/**
 * @version $Revision: 1.8 $
 */
public class TablePager extends TemplateRenderer {
	public final static String SHOW_RANGE = "showRange";
    public final static String RANGE = "range";
    public final static String PARAMETER = "parameter";

    final private static String HANDLER = "handler";
    public final static String TOTAL_PAGES = "totalPages";
    public final static String CURRENT_PAGE = "currentPage";
    public final static String FIRST_ITEM = "firstItem";
    public final static String LAST_ITEM = "lastItem";
    public final static String CURRENT_ITEM_NUMBER = "currentItem";

    public final static String FAILED = "failed";

    protected final static String PRE_FRAGMENT = "pre";
    protected final static String POST_FRAGMENT = "post";
    protected final static String FIRST_FRAGMENT = "first";
    protected final static String FIRST_SELECTED_FRAGMENT = "first selected";
    protected final static String LAST_FRAGMENT = "last";
    protected final static String LAST_SELECTED_FRAGMENT = "last selected";
    protected final static String PREVIOUS_FRAGMENT = "previous";
    protected final static String PREVIOUS_DISABLED_FRAGMENT = "previous disabled";
    protected final static String NEXT_FRAGMENT = "next";
    protected final static String NEXT_DISABLED_FRAGMENT = "next disabled";
    protected final static String PAGE_FRAGMENT = "page";
    protected final static String SELECTED_PAGE_FRAGMENT = "page selected";
    protected final static String PAGE_SEPARATOR_FRAGMENT = "page separator";
    protected final static String ETC_FRAGMENT = "etc...";

    final private static int IN = 0;
    final private static int OUT = 1;
    final private static int BORDER = 2;

    private Template pre, post, first, firstSelected, last, lastSelected;
    private Template previous, previousDisabled, next, nextDisabled, page, selectedPage, pageSeparator, etc;

    private ParameterDescriptor []parameterDescriptors;

    public TablePager() {
        setOptionalFragments(new String[] { PRE_FRAGMENT, POST_FRAGMENT, FIRST_FRAGMENT, FIRST_SELECTED_FRAGMENT, LAST_FRAGMENT, LAST_SELECTED_FRAGMENT, PREVIOUS_FRAGMENT, PREVIOUS_DISABLED_FRAGMENT, NEXT_FRAGMENT, NEXT_DISABLED_FRAGMENT,
                                            PAGE_FRAGMENT, PAGE_SEPARATOR_FRAGMENT, SELECTED_PAGE_FRAGMENT, ETC_FRAGMENT });
    }

    public Result process(Map parameters, Context context) throws Exception {
        Map parameter = (Map)parameters.get(PARAMETER);
        boolean showRange = ((Boolean)parameters.get(SHOW_RANGE)).booleanValue();
        int range = ((Number)parameters.get(RANGE)).intValue();
        String handlerUrl = ProcessorManager.getInstance().process(ProcessorManager.URL, TableHandler.URL, context).toString();
        String id = parameters.get(TableHandler.ID).toString();
        String storeId = parameters.get(TableHandler.STORE).toString();
        Store store = context.getStore(storeId);
        Map state = (Map)store.get(id, context);
        if ( state == null ) return new Result(FAILED);
        int itemsPerPage = ((Number)state.get(TableMapRenderer.ITEMS_PER_PAGE)).intValue();
        if (itemsPerPage <= 0) {
            return new Result(FAILED);
        }
        Result result = new Result(StateDescriptor.OK);
        Number firstItem = ((Number)state.get(TableHandler.CURRENT_POSITION));
        int currentPosition = firstItem.intValue();
        int currentPage = currentPosition / itemsPerPage;
        int length = ((Number)state.get(TableMapRenderer.LENGTH)).intValue();
        int totalPages = (int)Math.floor((double)length / ((double)itemsPerPage) + 1);
        int lastItem = Math.min(currentPosition + itemsPerPage, length);
        result.addResultEntry(FIRST_ITEM, firstItem);
        result.addResultEntry(LAST_ITEM, new Integer(lastItem));
        result.addResultEntry(CURRENT_PAGE, new Integer(currentPage));
        result.addResultEntry(TOTAL_PAGES, new Integer(totalPages));
        boolean hasNext = false, hasPrevious = false;
        if (itemsPerPage > 0) {
            hasNext = (currentPosition + itemsPerPage < length);
            hasPrevious = (currentPosition > 0);
        }
        StringBuffer buffer = new StringBuffer();
        if (pre != null) pre.evaluate(buffer, parameters);
        createHandler(0, itemsPerPage, handlerUrl, storeId, id, parameters, parameter);
        if ( firstSelected != null && !hasPrevious ) {
            firstSelected.evaluate(buffer, parameters);
        } else if ( first  != null) {
            first.evaluate(buffer, parameters);
        }
        createHandler(Math.max(0, currentPosition - itemsPerPage), itemsPerPage, handlerUrl, storeId, id, parameters, parameter);
        if (hasPrevious) {
            if ( previous != null) previous.evaluate(buffer, parameters);
        } else {
            if ( previousDisabled != null) previousDisabled.evaluate(buffer, parameters);
        }
        int step = itemsPerPage > 0 ? itemsPerPage : 1;
        boolean first = true;
        lastItem = 0;
        int itemsInRange = range * step;
        int lowerBorder = currentPosition - itemsInRange;
        int upperBorder = currentPosition + itemsInRange;
        int i = 0;
        for (; i < length; i += step) {
            lastItem = i;
            parameters.put(CURRENT_PAGE, new Integer(i / itemsPerPage + 1));
            createHandler(i, Math.min(i+itemsPerPage, length), handlerUrl, storeId, id, parameters, parameter);
            int rangeState = IN;
            if (showRange && (i < lowerBorder || i > upperBorder)) rangeState = OUT;
            if (showRange && (i == lowerBorder || i == upperBorder)) rangeState = BORDER;
            boolean pagingTemplate = false;
            Template templateToUse = null;
            if (i == currentPosition && selectedPage != null) {
                pagingTemplate = true;
                templateToUse = selectedPage;
            } else if (rangeState == IN && page != null) {
                pagingTemplate = true;
                templateToUse = page;
            } else if (rangeState == IN) {
                pagingTemplate = true;
                templateToUse = page;
            } else if (rangeState == BORDER) {
                templateToUse = etc;
            }
            if (templateToUse != null ) {
                if (pagingTemplate) {
                    if (!first) {
                        if (pageSeparator != null) pageSeparator.evaluate(buffer, parameters);
                    }
                    first = false;
                }
                templateToUse.evaluate(buffer, parameters);
            }
        }
        createHandler(currentPosition + itemsPerPage, itemsPerPage, handlerUrl, storeId, id, parameters, parameter);
        if (hasNext) {
            if ( next != null) next.evaluate(buffer, parameters);
        } else {
            if ( nextDisabled != null) nextDisabled.evaluate(buffer, parameters);
        }
        createHandler(i-itemsPerPage, length, handlerUrl, storeId, id, parameters, parameter);
        if ( lastSelected != null && !hasNext ) {
            lastSelected.evaluate(buffer, parameters);
        } else if ( last != null) {
            last.evaluate(buffer, parameters);
        }
        if ( post != null) post.evaluate(buffer, parameters);
        result.addResultEntry(OUTPUT, new String(buffer.toString()));
        return result;
    }
    
    protected void createHandler(int i, int lastItem, String handlerUrl, String storeName, String id, Map parameters, Map parameter) throws IOException {
        parameters.put(TableHandler.TARGET_POSITION, new Integer(i));
        StringBuffer handlerBuffer = new StringBuffer(128);
        handlerBuffer.append(handlerUrl).append('?').append(TableHandler.TARGET_POSITION).append('=').append(i).
                append('&').append(TableHandler.STORE).append('=').append(storeName).append('&').append(TableHandler.ID).append('=').append(id);
        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(HANDLER, new String(handlerBuffer.toString()));
        parameters.put(FIRST_ITEM, new Integer(i+1));
        parameters.put(LAST_ITEM, new Integer(lastItem));
    }
    
    public void configure(Streamable config) throws ConfigurationException {
        super.configure(config);
        // remove fragment parameter
        List parameterList = new ArrayList();
        parameterList.add(new ParameterDescriptor(PARAMETER, new ParameterMessage("tablePager/parameter"), MapConstraints.UNCONSTRAINED, new HashMap()));
        parameterList.add(new ParameterDescriptor(SHOW_RANGE, new ParameterMessage("tablePager/showRange"), new BooleanConstraints(), Boolean.FALSE));
        parameterList.add(new ParameterDescriptor(RANGE, new ParameterMessage("tablePager/range"), new NumberConstraints(), new Integer(0)));
        parameterList.add(new ParameterDescriptor(TableHandler.ID, new ParameterMessage("tableRenderer/id"), new StringConstraints(), new String("table")));
        parameterList.add(new ParameterDescriptor(TableHandler.STORE, new ParameterMessage("tableRenderer/store"), new StringConstraints(Projector.STORES), Projector.SESSION_STORE));
        parameterDescriptors = (ParameterDescriptor[])parameterList.toArray(new ParameterDescriptor[parameterList.size()]);

        pre = getOptionalFragment(PRE_FRAGMENT);
        post = getOptionalFragment(POST_FRAGMENT);
        first = getOptionalFragment(FIRST_FRAGMENT);
        firstSelected = getOptionalFragment(FIRST_SELECTED_FRAGMENT);
        last = getOptionalFragment(LAST_FRAGMENT);
        lastSelected = getOptionalFragment(LAST_SELECTED_FRAGMENT);
        previous = getOptionalFragment(PREVIOUS_FRAGMENT);
        previousDisabled = getOptionalFragment(PREVIOUS_DISABLED_FRAGMENT);
        next = getOptionalFragment(NEXT_FRAGMENT);
        nextDisabled = getOptionalFragment(NEXT_DISABLED_FRAGMENT);
        page = getOptionalFragment(PAGE_FRAGMENT);
        pageSeparator = getOptionalFragment(PAGE_SEPARATOR_FRAGMENT);
        selectedPage = getOptionalFragment(SELECTED_PAGE_FRAGMENT);
        etc = getOptionalFragment(ETC_FRAGMENT);
    }

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