/*
 * JBoss, Home of Professional Open Source
 * Copyright ${year}, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.renderkit;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.richfaces.component.AbstractColumn;
import org.richfaces.component.SortOrder;
import org.richfaces.component.UIDataTableBase;
import org.richfaces.model.SortMode;
import org.richfaces.validator.MessageFactory;

/**
 * @author Anton Belevich
 *
 */
public abstract class SortingFilteringRowsRenderer extends AbstractRowsRenderer {
    private static final String FILTERING_STRING = "rich:filtering";
    private static final String SORTING_STRING = "rich:sorting";
    private static final String FILTER_VALUE_STRING = "filterValue";
    private static final String SORT_ORDER_STRING = "sortOrder";
    private static final String SORT_PRIORITY_STRING = "sortPriority";
    private static final String SEPARATOR = ":";

    protected void decodeSortingFiltering(FacesContext context, UIComponent component) {
        if (component instanceof UIDataTableBase) {

            UIDataTableBase dataTableBase = (UIDataTableBase) component;
            Map<String, String> requestMap = context.getExternalContext().getRequestParameterMap();
            String clientId = dataTableBase.getClientId(context);

            String filtering = requestMap.get(clientId + FILTERING_STRING);
            if (filtering != null && filtering.trim().length() > 0) {
                decodeFiltering(context, dataTableBase, filtering);
            }

            String sorting = requestMap.get(clientId + SORTING_STRING);
            if (sorting != null && sorting.trim().length() > 0) {
                decodeSorting(context, dataTableBase, sorting);
            }
        }
    }

    protected void decodeFiltering(FacesContext context, UIDataTableBase dataTableBase, String value) {
        String[] values = value.split(SEPARATOR);
        if (Boolean.parseBoolean(values[2])) {
            for (Iterator<UIComponent> iterator = dataTableBase.columns(); iterator.hasNext();) {
                UIComponent column = iterator.next();
                if (values[0].equals(column.getId())) {
                    updateAttribute(context, column, FILTER_VALUE_STRING, values[1]);
                } else {
                    updateAttribute(context, column, FILTER_VALUE_STRING, null);
                }
            }
        } else {
            UIComponent child = dataTableBase.findComponent(values[0]);
            try {

                updateAttribute(context, child, FILTER_VALUE_STRING, values[1]);
            } catch (FacesException e) {
                if (child instanceof AbstractColumn && ((AbstractColumn)child).isBuiltInFilterControlsEnabled() && e.getCause() instanceof ELException) {
                    addFilterConverterErrorMessage(context, (AbstractColumn) child, values[1], e);
                } else {
                    throw e;
                }
            }
        }
        context.getPartialViewContext().getRenderIds().add(dataTableBase.getSortingAndFilteringRenderTargetId(context)); // TODO Use partial re-rendering here
    }

    private void addFilterConverterErrorMessage(FacesContext context, AbstractColumn column, String submittedValue, Exception exception) {
        column.getAttributes().put("submittedFilterValue", submittedValue);
        FacesMessage message;
        String converterMessageString = column.getFilterConverterMessage();
        if (null != converterMessageString) {
            message = new FacesMessage(FacesMessage.SEVERITY_ERROR, converterMessageString, converterMessageString);
        } else {
            message = MessageFactory.createMessage(context, "org.richfaces.BUILT_IN_FILTER_VALUE_CONVERSION_ERROR");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            message.setDetail(exception.getCause().getLocalizedMessage());
        }
        context.addMessage(column.getClientId(), message);
    }

    protected void decodeSorting(FacesContext context, UIDataTableBase dataTableBase, String value) {
        Set<Object> sortPriority = new LinkedHashSet<Object>();

        String[] values = value.split(SEPARATOR);
        String columnId = values[0];
        String sortOrder = values[1];
        boolean isClear = Boolean.parseBoolean(values[2]);

        if (isClear || SortMode.single.equals(dataTableBase.getSortMode())) {
            for (Iterator<UIComponent> iterator = dataTableBase.columns(); iterator.hasNext();) {
                UIComponent column = iterator.next();
                if (columnId.equals(column.getId())) {
                    updateSortOrder(context, column, sortOrder);
                    sortPriority.add(columnId);
                } else {
                    updateAttribute(context, column, SORT_ORDER_STRING, SortOrder.unsorted);
                }
            }
        } else {
            updateSortOrder(context, dataTableBase.findComponent(columnId), sortOrder);
            Collection<?> priority = dataTableBase.getSortPriority();
            if (priority != null) {
                priority.remove(columnId);
                sortPriority.addAll(priority);
            }
            sortPriority.add(columnId);
        }
        updateAttribute(context, dataTableBase, SORT_PRIORITY_STRING, sortPriority);
        context.getPartialViewContext().getRenderIds().add(dataTableBase.getSortingAndFilteringRenderTargetId(context));
    }

    private void updateSortOrder(FacesContext context, UIComponent component, String value) {
        SortOrder sortOrder = SortOrder.ascending;
        try {
            sortOrder = SortOrder.valueOf(value);
        } catch (IllegalArgumentException e) {
            // If value isn't name of enum constant of SortOrder, toggle sortOrder of column.
            if (SortOrder.ascending.equals(component.getAttributes().get(SORT_ORDER_STRING))) {
                sortOrder = SortOrder.descending;
            }
        }
        updateAttribute(context, component, SORT_ORDER_STRING, sortOrder);
    }

    protected void updateAttribute(FacesContext context, UIComponent component, String attribute, Object value) {
        Object oldValue = component.getAttributes().get(attribute);
        if ((oldValue != null && !oldValue.equals(value)) || (oldValue == null && value != null)) {
            ELContext elContext = context.getELContext();
            ValueExpression ve = component.getValueExpression(attribute);
            if (ve != null && !ve.isReadOnly(elContext)) {
                component.getAttributes().put(attribute, null);
                try {
                    ve.setValue(elContext, value);
                } catch (ELException e) {
                    throw new FacesException(e);
                }
            } else {
                component.getAttributes().put(attribute, value);
            }
        }
    }

    protected void renderFilterRow(FacesContext context, UIDataTableBase table, Iterator<UIComponent> columns, String cssPrefix) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        String clientId = table.getClientId(context);
        writer.startElement(HtmlConstants.TR_ELEMENT, table);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, String.format("%s-flt", cssPrefix), null);
        while (columns.hasNext()) {
            UIComponent column = columns.next();
            if (column.isRendered()) {
                writer.startElement(HtmlConstants.TD_ELEM, column);
                writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, String.format("%1$s-flt-c %1$s-c-%2$s", cssPrefix, column.getId()), null);
                writer.startElement(HtmlConstants.DIV_ELEM, column);
                writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, String.format("%s-flt-cnt", cssPrefix), null);
                if (column.getAttributes().get("filterField") != null &&  ! "custom".equals(column.getAttributes().get("filterType"))) {
                    writer.startElement(HtmlConstants.INPUT_ELEM, column);
                    writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":" + column.getId() + ":flt", null);
                    writer.writeAttribute(HtmlConstants.NAME_ATTRIBUTE, clientId + ":" + column.getId() + ":flt", null);
                    String inputClass = String.format("%s-flt-i", cssPrefix);
                    List<FacesMessage> messages = context.getMessageList(column.getClientId());
                    if (! messages.isEmpty()) {
                        inputClass += String.format(" %s-flt-i-err", cssPrefix);
                        writer.writeAttribute("value", column.getAttributes().get("submittedFilterValue"), null);
                    } else {
                        writer.writeAttribute("value", column.getAttributes().get("filterValue"), null);
                    }
                    writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, inputClass, null);
                    writer.writeAttribute("data-columnid", column.getId(), null);
                    writer.endElement(HtmlConstants.INPUT_ELEM);
                }
                writer.endElement(HtmlConstants.DIV_ELEM);
                writer.endElement(HtmlConstants.TD_ELEM);
            }
        }
        writer.endElement(HtmlConstants.TR_ELEMENT);
    }

    protected void renderSortButton(FacesContext context, UIComponent column, String cssPrefix) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(HtmlConstants.SPAN_ELEM, column);
        String classAttr = String.format("%1$s-srt %1$s-srt-btn ", cssPrefix);
        SortOrder sortOrder = (SortOrder) column.getAttributes().get("sortOrder");
        if (sortOrder == null || sortOrder == SortOrder.unsorted) {
            classAttr = classAttr + String.format("%s-srt-uns", cssPrefix);
        } else if (sortOrder == SortOrder.ascending) {
            classAttr = classAttr + String.format("%s-srt-asc", cssPrefix);
        } else if (sortOrder == SortOrder.descending) {
            classAttr = classAttr + String.format("%s-srt-des", cssPrefix);
        }
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, classAttr, null);
        writer.writeAttribute("data-columnid", column.getId(), null);
        writer.endElement(HtmlConstants.SPAN_ELEM);
    }

    protected boolean isFilterRowRequiredForColumn(String facetName, UIComponent column) {
        return "header".equals(facetName) && column instanceof AbstractColumn && ((AbstractColumn) column).useBuiltInFilter();
    }

    protected boolean isBuiltInSortRequiredFocColumn(String facetName, UIComponent column) {
        return "header".equals(facetName) && column instanceof AbstractColumn && ((AbstractColumn) column).useBuiltInSort();
    }
}
