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

import java.util.Iterator;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UINamingContainer;
import javax.faces.component.html.HtmlPanelGroup;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.MethodBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;

import org.apache.myfaces.component.AlignProperty;
import org.apache.myfaces.component.DataProperties;
import org.apache.myfaces.component.EventAware;
import org.apache.myfaces.component.PanelProperties;
import org.apache.myfaces.component.UniversalProperties;
import org.apache.myfaces.component.UserRoleAware;

/**
 * TODO: Document this component.
 * 
 * Unless otherwise specified, all attributes accept static values or EL expressions.
 * 
 * @JSFComponent
 *   name = "t:panelTabbedPane"
 *   class = "org.apache.myfaces.custom.tabbedpane.HtmlPanelTabbedPane"
 *   tagClass = "org.apache.myfaces.custom.tabbedpane.HtmlPanelTabbedPaneTag"
 *   tagHandler = "org.apache.myfaces.custom.tabbedpane.HtmlPanelTabbedPaneTagHandler"
 * 
 * @author Manfred Geiler (latest modification by $Author: lu4242 $)
 * @version $Revision: 745263 $ $Date: 2009-02-17 16:51:58 -0500 (Tue, 17 Feb 2009) $
 */
public abstract class AbstractHtmlPanelTabbedPane
        extends HtmlPanelGroup
        implements UniversalProperties, EventAware, PanelProperties,
        AlignProperty, DataProperties, UserRoleAware
        
{
    //private static final Log log = LogFactory.getLog(HtmlPanelTabbedPane.class);

    public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlPanelTabbedPane";
    public static final String COMPONENT_FAMILY = "javax.faces.Panel";
    private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.TabbedPane";
    private static final int DEFAULT_SELECTEDINDEX = 0;
    private static final boolean DEFAULT_SERVER_SIDE_TAB_SWITCH = false;

    private MethodBinding _tabChangeListener = null;
    private static final int DEFAULT_BORDER = Integer.MIN_VALUE;

    public void decode(FacesContext context)
    {
        super.decode(context);    //To change body of overridden methods use File | Settings | File Templates.
    }

    public void processDecodes(javax.faces.context.FacesContext context)
   {
       if (context == null) throw new NullPointerException("context");
       decode(context);

       int tabIdx = 0;
       int selectedIndex = getSelectedIndex();

       Iterator it = getFacetsAndChildren();

       while (it.hasNext())
       {
           UIComponent childOrFacet = getUIComponent((UIComponent) it.next());
           if (childOrFacet instanceof HtmlPanelTab) {
               if (isClientSide() || selectedIndex == tabIdx) {
                   childOrFacet.processDecodes(context);
               }
               tabIdx++;
           } else {
               childOrFacet.processDecodes(context);
           }
       }
   }

    public void processValidators(FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if (!isRendered()) return;

        int tabIdx = 0;
        int selectedIndex = getSelectedIndex();

        Iterator it = getFacetsAndChildren();

        while (it.hasNext())
        {
            UIComponent childOrFacet = getUIComponent((UIComponent) it.next());
            if (childOrFacet instanceof HtmlPanelTab) {
                if (isClientSide() || selectedIndex == tabIdx) {
                    childOrFacet.processValidators(context);
                }
                tabIdx++;
            } else {
                childOrFacet.processValidators(context);
            }
        }
    }

    public void processUpdates(FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if (!isRendered()) return;

        int tabIdx = 0;
        int selectedIndex = getSelectedIndex();

        Iterator it = getFacetsAndChildren();

        while (it.hasNext())
        {
            UIComponent childOrFacet = getUIComponent((UIComponent) it.next());
            if (childOrFacet instanceof HtmlPanelTab) {
                if (isClientSide() || selectedIndex == tabIdx) {
                    childOrFacet.processUpdates(context);
                }
                tabIdx++;
            } else {
                childOrFacet.processUpdates(context);
            }
        }
    }

    private UIComponent getUIComponent(UIComponent uiComponent)
    {
        /*todo: checking for UIForm is not enough - Trinidad form, etc.*/
        if (uiComponent instanceof UINamingContainer || uiComponent instanceof UIForm)
        {
            List children = uiComponent.getChildren();
            for (int i = 0, len = children.size(); i < len; i++)
            {
                uiComponent = getUIComponent((UIComponent)children.get(i));
            }
        }
        return uiComponent;
    }

    public void addTabChangeListener(TabChangeListener listener)
    {
        addFacesListener(listener);
    }

    public void removeTabChangeListener(TabChangeListener listener)
    {
        removeFacesListener(listener);
    }

    /**
     * TODO: This should be something like this:
     * 
     * JSFProperty
     *   returnSignature = "void"
     *   methodSignature = "org.apache.myfaces.custom.tabbedpane.TabChangeEvent"
     * 
     * And be added on tld. But you can do the same with TabChangeListenerTag. 
     * 
     * @return
     */
    public MethodBinding getTabChangeListener()
    {
        return _tabChangeListener;
    }

    public void setTabChangeListener(MethodBinding tabChangeListener)
    {
        _tabChangeListener = tabChangeListener;
    }
    
    public Object saveState(FacesContext context)
    {
        Object values[] = new Object[2];
        values[0] = super.saveState(context);
        values[1] = saveAttachedState(context, _tabChangeListener);
        return values;
    }

    public void restoreState(FacesContext context, Object state)
    {
        Object values[] = (Object[])state;
        super.restoreState(context, values[0]);
        _tabChangeListener = (MethodBinding)restoreAttachedState(context, values[1]);
    }    

    public void broadcast(FacesEvent event) throws AbortProcessingException
    {
        if (event instanceof TabChangeEvent)
        {
            TabChangeEvent tabChangeEvent = (TabChangeEvent)event;
            if (tabChangeEvent.getComponent() == this)
            {
                setSelectedIndex(tabChangeEvent.getNewTabIndex());
                getFacesContext().renderResponse();
            }
        }
        super.broadcast(event);

        MethodBinding tabChangeListenerBinding = getTabChangeListener();
        if (tabChangeListenerBinding != null)
        {
            try
            {
                tabChangeListenerBinding.invoke(getFacesContext(), new Object[]{event});
            }
            catch (EvaluationException e)
            {
                Throwable cause = e.getCause();
                if (cause != null && cause instanceof AbortProcessingException)
                {
                    throw (AbortProcessingException)cause;
                }
                else
                {
                    throw e;
                }
            }
        }
    }
    
    /**
     * Write out information about the toggling mode - the component might
     * be toggled server side or client side.
     */
    public boolean isClientSide()
    {
        return !isServerSideTabSwitch(); 
    }

    /**
     * @JSFProperty
     *   tagExcluded = "true"
     */
    public abstract String getActiveTabVar();
    
    /**
     * Boolean Variable that is set in request scope when 
     * rendering a panelTab. True means that the currently 
     * rendered panelTab is active.
     * 
     * @JSFProperty
     */
    public abstract Boolean getActivePanelTabVar();

    /**
     * Index of tab that is selected by default.
     * 
     * @JSFProperty
     *   defaultValue = "0"
     */
    public abstract int getSelectedIndex();
    
    public abstract void setSelectedIndex(int selectedIndex);

    /**
     * Style class of the active tab cell.
     * 
     * @JSFProperty
     */
    public abstract String getActiveTabStyleClass();

    /**
     * Style class of the inactive tab cells.
     * 
     * @JSFProperty
     */
    public abstract String getInactiveTabStyleClass();

    /**
     * Style class of the active tab sub cell.
     * 
     * @JSFProperty
     */
    public abstract String getActiveSubStyleClass();

    /**
     * Style class of the inactive tab sub cells.
     * 
     * @JSFProperty
     */
    public abstract String getInactiveSubStyleClass();

    /**
     * Style class of the active tab content cell.
     * 
     * @JSFProperty
     */
    public abstract String getTabContentStyleClass();

    /**
     * Style class of the disabled tab cells.
     * 
     * @JSFProperty
     */
    public abstract String getDisabledTabStyleClass();

    /**
     * Toggle client-side/server-side tab switches.
     * 
     * @JSFProperty
     *   defaultValue = "false"
     */
    public abstract boolean isServerSideTabSwitch();
    
    public boolean getServerSideTabSwitch()
    {
        return isServerSideTabSwitch();
    }

    /**
     * Define if the process validation and update model phases
     * should be executed before change between tabs, when
     * serverSideTabSwitch = true (if is false, the switch
     * is done by other way so this property does not have any
     * effect).
     * 
     * Note that if this property is set as false, only a tab 
     * change is done if all input fields inside the form are valid 
     * (including input components outside this panel). 
     * 
     * By default is true, so both phases are not executed.
     * 
     * @JSFProperty
     *   defaultValue = "true"
     */    
    public abstract boolean isImmediateTabChange();
    
    public void queueEvent(FacesEvent event)
    {
        if (event != null && event instanceof TabChangeEvent)
        {
            if (isImmediateTabChange())
            {
                event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
            }
            else
            {
                event.setPhaseId(PhaseId.INVOKE_APPLICATION);
            }
        }
        super.queueEvent(event);
    }
}
