/*
 * 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.html;

import static org.richfaces.renderkit.HtmlConstants.CLASS_ATTRIBUTE;
import static org.richfaces.renderkit.HtmlConstants.TBODY_ELEMENT;
import static org.richfaces.renderkit.HtmlConstants.TD_ELEM;
import static org.richfaces.renderkit.HtmlConstants.TR_ELEMENT;
import static org.richfaces.renderkit.html.TogglePanelRenderer.addEventOption;
import static org.richfaces.renderkit.html.TogglePanelRenderer.getAjaxOptions;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.ActionEvent;

import org.ajax4jsf.javascript.JSObject;
import org.richfaces.cdk.annotations.JsfRenderer;
import org.richfaces.component.AbstractPanelMenu;
import org.richfaces.component.AbstractPanelMenuItem;
import org.richfaces.component.ComponentIterators;
import org.richfaces.context.ExtendedPartialViewContext;
import org.richfaces.renderkit.HtmlConstants;
import org.richfaces.renderkit.RenderKitUtils;
import org.richfaces.renderkit.util.PanelIcons;
import org.richfaces.renderkit.util.PanelIcons.State;

import com.google.common.base.Predicate;

/**
 * @author akolonitsky
 * @since 2010-10-25
 */
@JsfRenderer(type = "org.richfaces.PanelMenuItemRenderer", family = AbstractPanelMenuItem.COMPONENT_FAMILY)
public class PanelMenuItemRenderer extends DivPanelRenderer {
    public static final String UNSELECT = "unselect";
    public static final String SELECT = "select";
    public static final String BEFORE_SELECT = "beforeselect";
    private static final String CSS_CLASS_PREFIX = "rf-pm-itm";
    private static final String TOP_CSS_CLASS_PREFIX = "rf-pm-top-itm";
    private static final ParentPanelMenuPredicate PARENT_PANEL_MENU_PREDICATE = new ParentPanelMenuPredicate();

    @Override
    protected void doDecode(FacesContext context, UIComponent component) {

        Map<String, String> requestMap = context.getExternalContext().getRequestParameterMap();

        AbstractPanelMenuItem menuItem = (AbstractPanelMenuItem) component;
        String compClientId = component.getClientId(context);
        if (requestMap.get(compClientId) != null) {
            AbstractPanelMenu pm = getParentPanelMenu(menuItem);

            if (pm.isImmediate()) {
                menuItem.setImmediate(true);
            }
            new ActionEvent(menuItem).queue();

            if (context.getPartialViewContext().isPartialRequest()) {

                // TODO nick - why render item by default?
                context.getPartialViewContext().getRenderIds().add(component.getClientId(context));

                // TODO nick - this should be done on encode, not on decode
                addOnCompleteParam(context, component.getClientId(context));
            }
        }
    }

    protected static void addOnCompleteParam(FacesContext context, String itemId) {
        ExtendedPartialViewContext.getInstance(context).appendOncomplete(
            new StringBuilder().append("RichFaces.$('").append(itemId).append("').onCompleteHandler();").toString());
    }

    @Override
    protected void doEncodeBegin(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException {
        super.doEncodeBegin(writer, context, component);

        AbstractPanelMenuItem menuItem = (AbstractPanelMenuItem) component;
        encodeHeaderGroupBegin(writer, context, menuItem, getCssClass(menuItem, ""));
    }

    private void encodeHeaderGroupBegin(ResponseWriter writer, FacesContext context, AbstractPanelMenuItem menuItem,
        String classPrefix) throws IOException {
        writer.startElement("table", null);
        writer.writeAttribute(CLASS_ATTRIBUTE, classPrefix + "-gr", null);
        writer.startElement(TBODY_ELEMENT, null);
        writer.startElement(TR_ELEMENT, null);

        encodeHeaderGroupLeftIcon(writer, context, menuItem, classPrefix);

        writer.startElement(TD_ELEM, null);
        writer.writeAttribute(CLASS_ATTRIBUTE, classPrefix + "-lbl", null);

        String label = menuItem.getLabel();
        if (label != null) {
            writer.writeText(label, null);
        }
    }

    private void encodeHeaderGroupEnd(ResponseWriter writer, FacesContext context, AbstractPanelMenuItem menuItem,
        String classPrefix) throws IOException {
        writer.endElement(TD_ELEM);

        encodeHeaderGroupRightIcon(writer, context, menuItem, classPrefix);

        writer.endElement(TR_ELEMENT);
        writer.endElement(TBODY_ELEMENT);
        writer.endElement("table");
    }

    private PanelIcons.State getState(AbstractPanelMenuItem item) {
        return PanelMenuItemRenderer.isParentPanelMenuDisabled(item) || item.isDisabled() ? State.commonDisabled : State.common;
    }

    private void encodeHeaderGroupRightIcon(ResponseWriter writer, FacesContext context, AbstractPanelMenuItem menuItem,
        String classPrefix) throws IOException {
        String icon = PanelMenuItemRenderer.isParentPanelMenuDisabled(menuItem) || menuItem.isDisabled() ? menuItem
            .getRightDisabledIcon() : menuItem.getRightIcon();
        String cssClasses = concatClasses(classPrefix + "-exp-ico", menuItem.getRightIconClass());

        if (icon == null || icon.trim().length() == 0) {
            icon = PanelIcons.transparent.toString();
        }
        encodeTdIcon(writer, context, cssClasses, icon, getState(menuItem));
    }

    private void encodeHeaderGroupLeftIcon(ResponseWriter writer, FacesContext context, AbstractPanelMenuItem menuItem,
        String classPrefix) throws IOException {
        String icon = PanelMenuItemRenderer.isParentPanelMenuDisabled(menuItem) || menuItem.isDisabled() ? menuItem
            .getLeftDisabledIcon() : menuItem.getLeftIcon();
        String cssClasses = concatClasses(classPrefix + "-ico", menuItem.getLeftIconClass());

        if (icon == null || icon.trim().length() == 0) {
            icon = PanelIcons.transparent.toString();
        }
        encodeTdIcon(writer, context, cssClasses, icon, getState(menuItem));
    }

    private boolean isIconRendered(String attrIconValue) {
        if (attrIconValue != null && attrIconValue.trim().length() > 0 && !PanelIcons.none.toString().equals(attrIconValue)) {
            return true;
        }
        return false;
    }

    // TODO nick - the same as in PanelMenuGroupRenderer
    public void encodeTdIcon(ResponseWriter writer, FacesContext context, String classPrefix, String attrIconValue,
        PanelIcons.State state) throws IOException {
        if (!isIconRendered(attrIconValue)) {
            return;
        }

        writer.startElement(TD_ELEM, null);
        try {
            PanelIcons icon = PanelIcons.valueOf(attrIconValue);
            writer.writeAttribute(CLASS_ATTRIBUTE, concatClasses(classPrefix, state.getCssClass(icon)), null);
        } catch (IllegalArgumentException e) {
            writer.writeAttribute(CLASS_ATTRIBUTE, classPrefix, null);
            if (attrIconValue != null && attrIconValue.trim().length() != 0) {
                writer.startElement(HtmlConstants.IMG_ELEMENT, null);
                writer.writeAttribute(HtmlConstants.ALT_ATTRIBUTE, "", null);
                writer.writeURIAttribute(HtmlConstants.SRC_ATTRIBUTE, RenderKitUtils.getResourceURL(attrIconValue, context),
                    null);
                writer.endElement(HtmlConstants.IMG_ELEMENT);
            }
        }

        writer.endElement(TD_ELEM);
    }

    @Override
    protected String getStyleClass(UIComponent component) {
        AbstractPanelMenuItem menuItem = (AbstractPanelMenuItem) component;
        return concatClasses(
            getCssClass(menuItem, ""),
            attributeAsString(component, "styleClass"),
            PanelMenuItemRenderer.isParentPanelMenuDisabled(menuItem) || menuItem.isDisabled() ? getCssClass(menuItem, "-dis")
                : "",
            (menuItem.isActiveItem() ? getCssClass(menuItem, "-sel") : ""),
            PanelMenuItemRenderer.isParentPanelMenuDisabled(menuItem) || menuItem.isDisabled() ? attributeAsString(component,
                "disabledClass") : "");
    }

    public String getCssClass(AbstractPanelMenuItem item, String postfix) {
        return (item.isTopItem() ? TOP_CSS_CLASS_PREFIX : CSS_CLASS_PREFIX) + postfix;
    }

    @Override
    protected JSObject getScriptObject(FacesContext context, UIComponent component) {
        return new JSObject("RichFaces.ui.PanelMenuItem", component.getClientId(context), getScriptObjectOptions(context,
            component));
    }

    @Override
    protected Map<String, Object> getScriptObjectOptions(FacesContext context, UIComponent component) {
        AbstractPanelMenuItem panelMenuItem = (AbstractPanelMenuItem) component;

        Map<String, Object> options = new HashMap<String, Object>();
        // TODO nick - ajax options should not be rendered in client mode
        options.put("ajax", getAjaxOptions(context, panelMenuItem));
        options.put("disabled", PanelMenuItemRenderer.isParentPanelMenuDisabled(panelMenuItem) || panelMenuItem.isDisabled());
        options.put("mode", panelMenuItem.getMode());
        options.put("name", panelMenuItem.getName());
        options.put("selectable", panelMenuItem.isSelectable());
        options.put("unselectable", panelMenuItem.isUnselectable());
        options.put("stylePrefix", getCssClass(panelMenuItem, ""));

        addEventOption(context, panelMenuItem, options, UNSELECT);
        addEventOption(context, panelMenuItem, options, SELECT);
        addEventOption(context, panelMenuItem, options, BEFORE_SELECT);

        return options;
    }

    @Override
    protected void doEncodeEnd(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException {
        AbstractPanelMenuItem menuItem = (AbstractPanelMenuItem) component;
        encodeHeaderGroupEnd(writer, context, menuItem, getCssClass(menuItem, ""));

        super.doEncodeEnd(writer, context, component);
    }

    @Override
    protected Class<? extends UIComponent> getComponentClass() {
        return AbstractPanelMenuItem.class;
    }

    private static AbstractPanelMenu getParentPanelMenu(AbstractPanelMenuItem menuItem) {
        return (AbstractPanelMenu) ComponentIterators.getParent(menuItem, PARENT_PANEL_MENU_PREDICATE);
    }

    static boolean isParentPanelMenuDisabled(AbstractPanelMenuItem menuItem) {
        AbstractPanelMenu parentPanelMenu = (AbstractPanelMenu) ComponentIterators.getParent(menuItem,
            PARENT_PANEL_MENU_PREDICATE);
        if (parentPanelMenu != null) {
            return parentPanelMenu.isDisabled();
        }
        return false;
    }

    private static class ParentPanelMenuPredicate implements Predicate<UIComponent> {
        public boolean apply(UIComponent comp) {
            return comp instanceof AbstractPanelMenu;
        }
    }
}
