/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.custom.inputHtml;

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

import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.ConverterException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderer;
import org.apache.myfaces.component.UserRoleUtils;
import org.apache.myfaces.custom.tabbedpane.HtmlPanelTab;
import org.apache.myfaces.custom.tabbedpane.HtmlPanelTabbedPane;
import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRenderer;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
import org.apache.myfaces.shared_tomahawk.renderkit.html.util.HTMLEncoder;

/**
 * 
 * @author Sylvain Vieujot (latest modification by $Author: skitching $)
 * @version $Revision: 673833 $ $Date: 2008-07-03 16:58:05 -0500 (jue, 03 jul 2008) $
 */
@JSFRenderer(
   renderKitId = "HTML_BASIC", 
   family = "javax.faces.Input",
   type = "org.apache.myfaces.InputHtml")
public class InputHtmlRenderer extends HtmlRenderer{
    // TODO : Enable multiple editors on one page
    // TODO : Fix i18n (check the kupustart_form.js)
    // TODO : Finish Disabled mode.
    // TODO : Automatic Fallback for non kupu capable browsers (Safari, smartphones, non javascript, ...).
    // TODO : Make Image & Link Library work.
    // TODO : Allow disabeling of tag filtering
    // TODO : Fix height while zoomed

    private static final Log log = LogFactory.getLog(HtmlRendererUtils.class);
    
    protected boolean isDisabled(FacesContext facesContext, UIComponent uiComponent) {
        if( !UserRoleUtils.isEnabledOnUserRole(uiComponent) )
            return false;

        return ((InputHtml)uiComponent).isDisabled();
    }

    private static boolean useFallback(InputHtml editor){
        // TODO : Handle fallback="auto"
        return editor.getFallback().equals("true");
    }

    public boolean getRendersChildren()
    {
        return true;
    }

    public void encodeBegin(FacesContext context, UIComponent component)
            throws IOException
    {
    }

    public void encodeChildren(FacesContext context, UIComponent component)
            throws IOException
    {
    }

    public void encodeEnd(FacesContext context, UIComponent uiComponent) throws IOException {
        RendererUtils.checkParamValidity(context, uiComponent, InputHtml.class);
        
        UIComponent compositeFacet = (UIComponent) uiComponent
                .getFacet(UIComponent.COMPOSITE_FACET_NAME);

        if (compositeFacet == null)
        {
            if (log.isErrorEnabled())
            {
                log
                        .error("facet UIComponent.COMPOSITE_FACET_NAME not found when rendering composite component "
                                + uiComponent.getClientId(context));
            }
            return;
        }
        InputHtml editor = (InputHtml) uiComponent;
        if( HtmlRendererUtils.isDisplayValueOnly(editor) )
            encodeDisplayValueOnly(context, editor);
        else if( useFallback(editor) )
            encodeEndFallBackMode(context, editor);
        else{
            // HACK
            // As only one inputHtml is supported by page in this version,
            // we need to make sure we don't encode hidden inputHtml
            // TODO : Fix this by supporting multiple inputHtml per page.

            if( ! isVisible(editor) ){
                encodeHidden(context, editor);
            }else if( ! hasThisPageAlreadyRenderedAnInputHtml( context ) ){
                compositeFacet.encodeAll(context);
                setThisPageAlreadyRenderedAnInputHtml( context );
            }else{
                log.warn("Only one inputHtml can be displayed at the same time. The component will be rendered isung a textarea." +
                         "\nConpoment : "+RendererUtils.getPathToComponent( editor ));
                encodeEndFallBackMode(context, editor);
            }
        }
    }

    static private boolean hasThisPageAlreadyRenderedAnInputHtml(FacesContext context){
            return context.getExternalContext().getRequestMap().containsKey( InputHtmlRenderer.class.getName() );
    }

    static private void setThisPageAlreadyRenderedAnInputHtml(FacesContext context){
        context.getExternalContext().getRequestMap().put(InputHtmlRenderer.class.getName(), Boolean.TRUE);
    }

    /**
     * Try to figure out if this component is visible to avoid rendering the code if not necessary.
     */
    private boolean isVisible(InputHtml editor){
            for(UIComponent parent = editor.getParent(); parent != null ; parent = parent.getParent()){
                if( parent instanceof HtmlPanelTab ){
                    HtmlPanelTab panelTab = (HtmlPanelTab) parent;
                    HtmlPanelTabbedPane panelTabbedPane = null;
                    for(UIComponent panelAncestor=panelTab.getParent(); panelAncestor!=null ; panelAncestor=panelAncestor.getParent()){
                        if( panelAncestor instanceof HtmlPanelTabbedPane ){
                            panelTabbedPane = (HtmlPanelTabbedPane)panelAncestor;
                            break;
                        }
                    }

                    if( panelTabbedPane != null ){
                    if( panelTabbedPane.isClientSide() ){
                        parent = panelTabbedPane;
                        continue;
                    }

                    // Not client side tabbed pane.
                    // We need to check if the current panel tab is bisible;
                    int selectedIndex = panelTabbedPane.getSelectedIndex();
                    List children = panelTabbedPane.getChildren();
                    int tabIdx = 0;
                    for (int i = 0, len = children.size(); i < len && tabIdx <= selectedIndex ; i++){
                        UIComponent child = htmlTabbedPaneRenderer_getUIComponent((UIComponent)children.get(i));
                        if (child instanceof HtmlPanelTab){
                                if( child == panelTab ){
                                    if( ! child.isRendered() || tabIdx != selectedIndex){
                                    return false;
                                }else{
                                        // It's visible. Check at upper level.
                                        parent = panelTabbedPane;
                                    continue;
                                }
                                }
                            tabIdx++;
                        }
                        }
                    }else{
                        log.debug("pannelTabbedPane == null for component "+RendererUtils.getPathToComponent(panelTab));
                    }
                }
            }

            return true;
    }

    /**
     * This is a copy of HtmlTabbedPaneRenderer.getUIComponent
     */
    private UIComponent htmlTabbedPaneRenderer_getUIComponent(UIComponent uiComponent)
    {
        /* todo: forms other than UIForm, e.g. Trinidad's UIForm */
        if (uiComponent instanceof UIForm || uiComponent instanceof UINamingContainer)
        {
            List children = uiComponent.getChildren();
            for (int i = 0, len = children.size(); i < len; i++)
            {
                uiComponent = htmlTabbedPaneRenderer_getUIComponent((UIComponent)children.get(i));
            }
        }
        return uiComponent;
    }

    private void encodeHidden(FacesContext context, InputHtml editor) throws IOException {
        String clientId = editor.getClientId(context);
        // Use a hidden textarea
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(HTML.TEXTAREA_ELEM, editor);

        writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
        writer.writeAttribute(HTML.STYLE_ATTR, "display:none", null);

        String text = RendererUtils.getStringValue(context, editor);
        writer.writeText(text, JSFAttr.VALUE_ATTR);

        writer.endElement(HTML.TEXTAREA_ELEM);
    }

    private void encodeDisplayValueOnly(FacesContext context, InputHtml editor) throws IOException {
        // Use only a textarea
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(HTML.SPAN_ELEM, editor);
        HtmlRendererUtils.writeIdIfNecessary(writer, editor, context);

        HtmlRendererUtils.renderDisplayValueOnlyAttributes(editor, writer);

        String text = RendererUtils.getStringValue(context, editor);
        writer.write( editor.getHtmlBody( text ) );

        writer.endElement(HTML.SPAN_ELEM);
    }

    private void encodeEndFallBackMode(FacesContext context, InputHtml editor) throws IOException {
        String clientId = editor.getClientId(context);
        // Use only a textarea
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(HTML.TEXTAREA_ELEM, editor);

        writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
        HtmlRendererUtils.writeIdIfNecessary(writer, editor, context);

        if( editor.getStyle()!=null )
            writer.writeAttribute(HTML.STYLE_ATTR, editor.getStyle(), null);
        if( editor.getStyleClass()!=null )
            writer.writeAttribute(HTML.CLASS_ATTR, editor.getStyleClass(), null);

        if (isDisabled(context, editor))
            writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, null);

        String text = RendererUtils.getStringValue(context, editor);
        writer.write( htmlToPlainText(text, editor) );

        writer.endElement(HTML.TEXTAREA_ELEM);
    }

    private static String htmlToPlainText(String html, InputHtml editor){
        return editor.getHtmlBody( html )
                .replaceAll("<br.*>","\n")
                .replaceAll("<.+?>", "");
    }

    public void decode(FacesContext facesContext, UIComponent uiComponent) {
        RendererUtils.checkParamValidity(facesContext, uiComponent, InputHtml.class);
        InputHtml editor = (InputHtml) uiComponent;

        Map paramMap = facesContext.getExternalContext().getRequestParameterMap();
        String clientId = uiComponent.getClientId(facesContext);

        if (paramMap.containsKey(clientId)) {
            //request parameter found, set submittedValue
            String submitedText = (String)paramMap.get(clientId);
            String htmlText = useFallback(editor) ? HTMLEncoder.encode(submitedText, true, true) : submitedText;

            editor.setSubmittedValue( htmlText );
        } else {
            log.warn(HtmlRendererUtils.NON_SUBMITTED_VALUE_WARNING + " Component : "+
                     RendererUtils.getPathToComponent( editor ));
        }
    }

    public Object getConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object submittedValue) throws ConverterException {
        RendererUtils.checkParamValidity(facesContext, uiComponent, InputHtml.class);
        InputHtml editor = (InputHtml) uiComponent;
        String submittedDocument = editor.getValueFromDocument((String)submittedValue);
        return RendererUtils.getConvertedUIOutputValue(facesContext, editor, submittedDocument);
    }
}
