/*
 * 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.ID_ATTRIBUTE;
import static org.richfaces.renderkit.RenderKitUtils.renderPassThroughAttributes;
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.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialResponseWriter;
import javax.faces.context.ResponseWriter;

import org.ajax4jsf.javascript.JSObject;
import org.richfaces.TooltipMode;
import org.richfaces.cdk.annotations.JsfRenderer;
import org.richfaces.component.AbstractTooltip;
import org.richfaces.component.Positioning;
import org.richfaces.javascript.JavaScriptService;
import org.richfaces.renderkit.HtmlConstants;
import org.richfaces.renderkit.MetaComponentRenderer;
import org.richfaces.renderkit.RenderKitUtils;
import org.richfaces.application.ServiceTracker;
import org.richfaces.renderkit.util.RendererUtils;

/**
 * @author amarkhel
 * @since 2010-10-24
 */
@ResourceDependencies({ @ResourceDependency(library = "javax.faces", name = "jsf.js"),
        @ResourceDependency(library = "org.richfaces", name = "jquery.js"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces.js"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces-queue.reslib"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces-base-component.js"),
        @ResourceDependency(library = "org.richfaces", name = "jquery.position.js"), @ResourceDependency(library = "org.richfaces", name = "richfaces-event.js"),
        @ResourceDependency(library = "org.richfaces", name = "popup.js"),
        @ResourceDependency(library = "org.richfaces", name = "tooltip.js"),
        @ResourceDependency(library = "org.richfaces", name = "tooltip.ecss") })
@JsfRenderer(type = "org.richfaces.TooltipRenderer", family = AbstractTooltip.COMPONENT_FAMILY)
public class TooltipRenderer extends DivPanelRenderer implements MetaComponentRenderer {
    public static final String HIDE = "hide";
    public static final String SHOW = "show";
    public static final String BEFORE_HIDE = "beforehide";
    public static final String BEFORE_SHOW = "beforeshow";
    private static final int DEFAULT_ZINDEX_VALUE = 1000;
    private static final RendererUtils RENDERER_UTILS = RendererUtils.getInstance();

    @Override
    protected void doDecode(FacesContext context, UIComponent component) {
        AbstractTooltip tooltip = (AbstractTooltip) component;

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

        String compClientId = component.getClientId(context);
        String clientId = requestMap.get(compClientId);
        if (clientId != null && clientId.equals(compClientId)) {
            context.getPartialViewContext().getRenderIds().add(tooltip.getContentClientId(context));

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

    @Override
    protected void doEncodeBegin(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException {
        AbstractTooltip tooltip = (AbstractTooltip) component;

        writer.startElement(getMarkupElement(tooltip), component);
        writer.writeAttribute(ID_ATTRIBUTE, component.getClientId(context), "clientId");

        writer.writeAttribute(HtmlConstants.STYLE_ATTRIBUTE, "display: none;", null);
        writer.startElement(getMarkupElement(tooltip), component);
        writer.writeAttribute(ID_ATTRIBUTE, component.getClientId(context) + ":wrp", null);
        writer.writeAttribute(CLASS_ATTRIBUTE, getStyleClass(component), null);

        int zindex = tooltip.getZindex();
        if (zindex == Integer.MIN_VALUE) {
            zindex = DEFAULT_ZINDEX_VALUE;
        }

        String style = concatStyles("z-index:" + zindex, getStyle(component));
        if (style != null && style.trim().length() > 0) {
            writer.writeAttribute(HtmlConstants.STYLE_ATTRIBUTE, style, null);
        }

        renderPassThroughAttributes(context, component, getPassThroughAttributes());

        writer.startElement(getMarkupElement(tooltip), component);
        writer.writeAttribute(ID_ATTRIBUTE, component.getClientId(context) + ":cntr", null);
        writer.writeAttribute(CLASS_ATTRIBUTE, "rf-tt-cntr", null);

        if (tooltip.getMode() == TooltipMode.ajax) {
            encodeLoading(writer, context, tooltip);
        }

        encodeContentBegin(writer, context, tooltip);
    }

    private void encodeContentBegin(ResponseWriter writer, FacesContext context, AbstractTooltip tooltip) throws IOException {
        writer.startElement(getMarkupElement(tooltip), tooltip);
        writer.writeAttribute(ID_ATTRIBUTE, tooltip.getClientId(context) + ":content", null);
        writer.writeAttribute(CLASS_ATTRIBUTE, "rf-tt-cnt", null);
        if (tooltip.getChildCount() == 0) {
            writer.write(tooltip.getValue().toString());
        }
    }

    private void encodeLoading(ResponseWriter writer, FacesContext context, AbstractTooltip tooltip) throws IOException {
        writer.startElement(getMarkupElement(tooltip), tooltip);
        writer.writeAttribute(ID_ATTRIBUTE, tooltip.getClientId(context) + ":loading", null);
        writer.writeAttribute(CLASS_ATTRIBUTE, "rf-tt-loading", null);
        UIComponent loading = tooltip.getFacet("loading");
        if (loading != null && loading.isRendered()) {
            loading.encodeAll(context);
        } else {
            writer.writeText("Loading...", null);
        }
        writer.endElement(getMarkupElement(tooltip));
    }

    private String getMarkupElement(AbstractTooltip component) {
        switch (component.getLayout()) {
            case block:
                return HtmlConstants.DIV_ELEM;

            case inline:
                return HtmlConstants.SPAN_ELEM;

            default:
                throw new IllegalStateException("Unknown tooltip layout (=" + component.getLayout() + ")");
        }
    }

    @Override
    protected String getStyleClass(UIComponent component) {
        return concatClasses("rf-tt", attributeAsString(component, "styleClass"));
    }

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

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

        Map<String, Object> options = new HashMap<String, Object>();

        RenderKitUtils.addToScriptHash(options, "ajax", getAjaxOptions(context, tooltip), TooltipMode.DEFAULT);

        Positioning jointPoint = tooltip.getJointPoint();
        if (jointPoint == null) {
            jointPoint = org.richfaces.component.Positioning.DEFAULT;
        }
        Positioning direction = tooltip.getDirection();
        if (direction == null) {
            direction = org.richfaces.component.Positioning.DEFAULT;
        }

        RenderKitUtils.addToScriptHash(options, "jointPoint", jointPoint.getValue(), Positioning.DEFAULT.getValue());
        RenderKitUtils.addToScriptHash(options, "direction", direction.getValue(), Positioning.DEFAULT.getValue());
        RenderKitUtils.addToScriptHash(options, "attached", tooltip.isAttached(), true);
        RenderKitUtils.addToScriptHash(options, "offset", getOffset(tooltip));
        RenderKitUtils.addToScriptHash(options, "mode", tooltip.getMode(), TooltipMode.DEFAULT);
        RenderKitUtils.addToScriptHash(options, "hideDelay", tooltip.getHideDelay(), 0);
        RenderKitUtils.addToScriptHash(options, "hideEvent", tooltip.getHideEvent(), "mouseleave");
        RenderKitUtils.addToScriptHash(options, "showDelay", tooltip.getShowDelay(), 0);
        RenderKitUtils.addToScriptHash(options, "showEvent", tooltip.getShowEvent(), "mouseenter");
        RenderKitUtils.addToScriptHash(options, "followMouse", tooltip.isFollowMouse(), true);
        String target = tooltip.getTarget();
        UIComponent targetComponent = RENDERER_UTILS.findComponentFor(component, target);
        if (targetComponent != null) {
            target = targetComponent.getClientId();
        }
        RenderKitUtils.addToScriptHash(options, "target", target);

        addEventOption(context, tooltip, options, HIDE);
        addEventOption(context, tooltip, options, SHOW);
        addEventOption(context, tooltip, options, BEFORE_HIDE);
        addEventOption(context, tooltip, options, BEFORE_SHOW);

        return options;
    }

    public Integer[] getOffset(AbstractTooltip tooltip) {
        int horizontalOffset = tooltip.getHorizontalOffset();
        int verticalOffset = tooltip.getVerticalOffset();
        if (horizontalOffset == Integer.MIN_VALUE) {
            horizontalOffset = 10;
        }
        if (verticalOffset == Integer.MIN_VALUE) {
            verticalOffset = 10;
        }
        return new Integer[] { horizontalOffset, verticalOffset };
    }

    private void encodeContentEnd(ResponseWriter writer, FacesContext context, AbstractTooltip tooltip) throws IOException {
        writer.endElement(getMarkupElement(tooltip));
    }

    @Override
    protected void doEncodeEnd(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException {
        encodeContentEnd(writer, context, (AbstractTooltip) component);

        writer.endElement(getMarkupElement((AbstractTooltip) component));

        writer.endElement(getMarkupElement((AbstractTooltip) component));

        writeJavaScript(writer, context, component);

        writer.endElement(getMarkupElement((AbstractTooltip) component));
    }

    protected void writeJavaScript(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException {
        Object script = getScriptObject(context, component);
        if (script == null) {
            return;
        }

        JavaScriptService javaScriptService = ServiceTracker.getService(JavaScriptService.class);
        javaScriptService.addScript(context, script);
    }

    public void encodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) throws IOException {
        if (AbstractTooltip.CONTENT_META_COMPONENT_ID.equals(metaComponentId)) {
            AbstractTooltip tooltip = (AbstractTooltip) component;
            PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter();
            writer.startUpdate(tooltip.getClientId(context) + ":" + AbstractTooltip.CONTENT_META_COMPONENT_ID);

            encodeContentBegin(writer, context, tooltip);
            for (UIComponent child : tooltip.getChildren()) {
                child.encodeAll(context);
            }
            encodeContentEnd(writer, context, tooltip);

            writer.endUpdate();
        }
    }

    public void decodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) {
        throw new UnsupportedOperationException();
    }

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

    @Override
    protected void doEncodeChildren(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException {

        AbstractTooltip tooltip = (AbstractTooltip) component;

        if (tooltip.getMode() == TooltipMode.client) {
            if (tooltip.getChildCount() > 0) {
                for (UIComponent kid : tooltip.getChildren()) {
                    kid.encodeAll(context);
                }
            }
        }
    }

    @Override
    public boolean getRendersChildren() {
        return true;
    }
}
