/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.richfaces.renderkit;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

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

import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.ComponentVariables;
import org.ajax4jsf.renderkit.ComponentsVariableResolver;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.util.HtmlDimensions;
import org.richfaces.component.UISwitchablePanel;
import org.richfaces.component.UITab;
import org.richfaces.component.UITabPanel;
import org.richfaces.component.util.HtmlUtil;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.event.SwitchablePanelSwitchEvent;


/**
 * @author Nick Belaevski - nbelaevski@exadel.com
 *         created 12.01.2007
 */
public class TabPanelRendererBase extends org.ajax4jsf.renderkit.HeaderResourcesRendererBase {

    public final static String ACTIVE_CELL_CLASSES = "rich-tabhdr-cell-active";
    public final static String INACTIVE_CELL_CLASSES = "rich-tabhdr-cell-inactive";
    public final static String DISABLED_CELL_CLASSES = "rich-tabhdr-cell-disabled";
    private final String TABS_WITH_SAME_NAMES_ERROR = "tabs with the same name not allowed";  

    protected Class getComponentClass() {
        return UITabPanel.class;
    }

    protected void doDecode(FacesContext context, UIComponent component) {
        super.doDecode(context, component);

        UITabPanel panel = (UITabPanel) component;

        String clientId = component.getClientId(context);
        Map requestParameterMap = context.getExternalContext().getRequestParameterMap();

        UITab eventTab = null;

        for (Iterator tabsIterator = panel.getRenderedTabs();
             tabsIterator.hasNext() && eventTab == null;) {

            UITab tab = (UITab) tabsIterator.next();
            if (tab.isDisabled()) {
                continue;
            }

            String tabClientId = tab.getClientId(context);
            if (null != requestParameterMap.get(tabClientId) ||
                    null != requestParameterMap.get(tabClientId + "_server_submit"))
            {

                eventTab = tab;
            }
        }

        if (eventTab != null) {
            new SwitchablePanelSwitchEvent(panel, null, eventTab).queue();
            new ActionEvent(eventTab).queue();

            if (UISwitchablePanel.AJAX_METHOD.equals(eventTab.getSwitchTypeOrDefault()))
            {
                new AjaxEvent(eventTab).queue();
            }
        } else {
            String newValue = (String) requestParameterMap.get(clientId);
            
            if (null != newValue) {
            		new SwitchablePanelSwitchEvent(panel, newValue, null).queue();
            }
        }
    }

  
    private static final TabInfoCollector collector = new TabInfoCollector() {
        private final JSReference JSR_ACTIVE_CLASS = new JSReference("activeClass");
        private final JSReference JSR_ID = new JSReference("id");
        private final JSReference JSR_INACTIVE_CLASS = new JSReference("inactiveClass");

        private final JSReference JSR_CELL_ACTIVE_CLASS = new JSReference("cellActiveClass");
        private final JSReference JSR_CELL_INACTIVE_CLASS = new JSReference("cellInactiveClass");

        private final JSReference JSR_NAME = new JSReference("name");
        private final JSReference JSR_ONTABLEAVE = new JSReference("ontableave");
        private final JSReference JSR_ONTABENTER = new JSReference("ontabenter");
        
        public Object collectTabInfo(FacesContext context, UITab tab) {
            Map info = new HashMap();
            info.put(JSR_ID, tab.getClientId(context));
            info.put(JSR_ACTIVE_CLASS, TabPanelRendererBase.getActiveTabClass(tab));
            info.put(JSR_INACTIVE_CLASS, TabPanelRendererBase.getInactiveTabClass(tab));

            info.put(JSR_CELL_ACTIVE_CLASS, ACTIVE_CELL_CLASSES);
            info.put(JSR_CELL_INACTIVE_CLASS, INACTIVE_CELL_CLASSES);

            info.put(JSR_NAME, tab.getName());
            info.put(JSR_ONTABLEAVE, tab.getAttributes().get("ontableave"));
            info.put(JSR_ONTABENTER, tab.getAttributes().get("ontabenter"));

            return info;
        }
    };

    private static final TabPanelInfoCollector panelInfoCollector = new TabPanelInfoCollector(){
    	
        private final JSReference JSR_ONTABCHANGE = new JSReference("ontabchange");
        private final JSReference JSR_ID = new JSReference("id");
           	
		public Object collectTabPanelInfo(FacesContext context, UITabPanel tabPanel) {
        	Map info = new HashMap();
            info.put(JSR_ONTABCHANGE, tabPanel.getAttributes().get("ontabchange"));
            info.put(JSR_ID, tabPanel.getClientId(context));
            
			return info;
		}
    	
    };
    
     /**
     * @param context
     * @param component
     * @return
     * @throws IOException
     */
    public static String encodeStyles(FacesContext context, UIComponent component) throws IOException {
        String widthAttrValue = (String) component.getAttributes().get(HTML.width_ATTRIBUTE);
        String heightAttrValue = (String) component.getAttributes().get(HTML.height_ATTRIBUTE);
        String styleAttrValue = (String) component.getAttributes().get(HTML.style_ATTRIBUTE);
        String style = styleAttrValue != null ? styleAttrValue : "";

        if (!parameterPresent(styleAttrValue, HTML.width_ATTRIBUTE)) {
            String width = (widthAttrValue != null && widthAttrValue.length() > 0) ? HtmlUtil.qualifySize(widthAttrValue) : "";
            style = addParameter(style, HTML.width_ATTRIBUTE + ":" + width);
        }

        if (!parameterPresent(styleAttrValue, HTML.height_ATTRIBUTE)) {
            String height = (heightAttrValue != null && heightAttrValue.length() > 0) ? HtmlUtil.qualifySize(heightAttrValue) : "";
            if (height.length() > 0) {
                style = addParameter(style, HTML.height_ATTRIBUTE + ":" + height);
            }
        }

        return style;
    }

    /**
     * @param style
     * @param name
     * @return
     */
    protected static boolean parameterPresent(String style, String name) {
        if (style != null && style.length() > 0) {
            String[] styles = style.split(";");
            for (int i = 0; i < styles.length; i++) {
                String[] pair = styles[i].split(":");
                if (pair[0].trim().equals(name)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static String addParameter(String style, String element) {
        String s = style.trim();
        return style + (s.length() == 0 || s.endsWith(";") ? "" : ";") + element;
    }

    protected String getValueAsString(FacesContext context, UITabPanel tabPanel) {
    	return getUtils().getValueAsString(context, tabPanel);
    }

    private Object checkValue(Object value) {
    	if (value instanceof String) {
			String s = (String) value;
			
			if (s.length() == 0) {
				return null;
			} else {
				return s;
			}
		}

    	return value;
    }
    
    protected static class TabsIteratorHelper {
    	private Iterator tabs;
    	
    	private UITab namedTab = null;
    	private UITab firstApplicableTab = null;
    	private boolean fallback;
    	
		public TabsIteratorHelper(Iterator tabs, Object name) {
			super();
			this.tabs = tabs;
			
			if (name != null) {
				//findTabByName
				while (tabs.hasNext()) {
					UITab tab = nextTab();
					if (name.equals(tab.getName())) {
						this.namedTab = tab;
						break;
					}
				}
			}
			
			if (namedTab == null) {
				if (name != null) {
	            	//tried but failed
					fallback = true;
				}
				
				this.namedTab = findAnyTab();
			} else if (namedTab.isDisabled()) {
	        	UITab tab = findAnyTab();
	        	if (this.namedTab != tab && tab != null && !tab.isDisabled()) {
	            	fallback = true;
	            	this.namedTab = tab;
	        	} else {
	        		//use disabled tab found by name
	        	}
			}
		}
		
		private UITab nextTab() {
			UITab tab = (UITab) tabs.next();
			
			if (firstApplicableTab == null) {
				firstApplicableTab = tab;
			} else if (firstApplicableTab.isDisabled() && !tab.isDisabled()) {
				//more appropriate
				firstApplicableTab = tab;
			}
			
			return tab;
		}
		
		public UITab getTab() {
			return this.namedTab;
		}
		
		public boolean isFallback() {
			return fallback;
		}
		
		private UITab findAnyTab() {
			while ((firstApplicableTab == null || firstApplicableTab.isDisabled()) && tabs.hasNext()) {
				nextTab();
			}

			return firstApplicableTab;
		}
    }
    
    
    protected void preEncodeBegin(FacesContext context, UIComponent component) throws IOException {
    	super.preEncodeBegin(context, component);
        if(component instanceof UITabPanel){
        	UITabPanel tabPanel = (UITabPanel)component;
            ComponentVariables componentVariables = ComponentsVariableResolver.getVariables(this, tabPanel);
        	componentVariables.setVariable("tabPanel", getTabPanelInfoCollector().collectTabPanelInfo(context, (UITabPanel)component));
    	}
    }
    
    public void encodeTabs(FacesContext context, UITabPanel tabPanel) throws IOException {
        ComponentVariables componentVariables = ComponentsVariableResolver.getVariables(this, tabPanel);

        tabPanel.setRenderedValue(null);
        
        Object checkedValue = checkValue(tabPanel.getValue());

        UITabPanel pane = tabPanel;
        UITab activeTab = null;

        TabsIteratorHelper helper = new TabsIteratorHelper(pane.getRenderedTabs(), checkedValue);
        activeTab = helper.getTab();

        if (activeTab == null) {
        	Object label = MessageUtil.getLabel(context, tabPanel);
        	String message = label + ": tab panel has no enabled or rendered tabs!";
        	context.getExternalContext().log(message);
        	return ;
        }

        if (helper.isFallback()) {
        	Object label = MessageUtil.getLabel(context, tabPanel);
        	String message = label + ": tab panel [@selectedTab=" + checkedValue + 
        			"] has no enabled or rendered tab with such name. Tab: " + activeTab.getName() + 
        			" will be used instead!";
        	context.getExternalContext().log(message);
        	tabPanel.setRenderedValue(activeTab.getName());
    	} else if (checkedValue == null) {
        	tabPanel.setRenderedValue(activeTab.getName());
    	} else {
    		tabPanel.setRenderedValue(null);
    	}
        
        helper = null;

        ArrayList tabs = new ArrayList();
        boolean clientSide = UISwitchablePanel.CLIENT_METHOD.equals(pane.getSwitchType());

        TabInfoCollector tabInfoCollector = getTabInfoCollector();
        
        Set<Object> tabNamesSet = new HashSet<Object>();
        
        for (Iterator iter = pane.getRenderedTabs(); iter.hasNext();) {
            UITab tab = (UITab) iter.next();
            boolean active = activeTab == tab;
            tab.setActive(active);

            if (!clientSide) {
                clientSide = UISwitchablePanel.CLIENT_METHOD.equals(tab.getSwitchTypeOrDefault());
            }
            
            if (!tabNamesSet.add(tab.getName())) {
        		throw new FacesException(TABS_WITH_SAME_NAMES_ERROR);
            }
                        
            tab.encodeTab(context, active);

            if (!tab.isDisabled()) {
                tabs.add(tabInfoCollector.collectTabInfo(context, tab));
            }
        }
        
        // Store flag for exist client-side tabs.
        componentVariables.setVariable("clientSide", new Boolean(clientSide));
        componentVariables.setVariable("tabs", tabs);
    }

    public static String getActiveTabCellClass(UITab tab) {
        return TabClassBuilder.activeTabClassBuilder.buildTabClass(tab);
    }

    public static String getDisabledTabCellClass(UITab tab) {
        return TabClassBuilder.disabledTabClassBuilder.buildTabClass(tab);
    }

    public static String getInactiveTabCellClass(UITab tab) {
        return TabClassBuilder.inactiveTabClassBuilder.buildTabClass(tab);
    }


    public static String getActiveTabClass(UITab tab) {
        return TabClassBuilder.activeTabClassBuilder.buildTabClass(tab);
    }

    public static String getDisabledTabClass(UITab tab) {
        return TabClassBuilder.disabledTabClassBuilder.buildTabClass(tab);
    }

    public static String getInactiveTabClass(UITab tab) {
        return TabClassBuilder.inactiveTabClassBuilder.buildTabClass(tab);
    }

    /**
     * Encode JavaScript function for switch tabs.
     *
     * @param context
     * @throws IOException
     */
    public void encodeTabsScript(FacesContext context, UITabPanel pane) throws IOException {
        ComponentVariables variables = ComponentsVariableResolver.getVariables(this, pane);
        ArrayList tabs = (ArrayList) variables.getVariable("tabs");
        // TODO - create tab control function.
        getUtils().writeScript(context, pane, "RichFaces.panelTabs['" + pane.getClientId(context) + "']=" + ScriptUtils.toScript(tabs) + ";");
    }
    
    public void encodeTabPanelScript(FacesContext context, UITabPanel pane) throws IOException {
        ComponentVariables variables = ComponentsVariableResolver.getVariables(this, pane);
        Object tabPanel = variables.getVariable("tabPanel");
        getUtils().writeScript(context, pane, "RichFaces.tabPanel['" + pane.getClientId(context) + "']=" + ScriptUtils.toScript(tabPanel) + ";");
    }

    protected TabInfoCollector getTabInfoCollector() {
        return collector;
    }
    
    protected TabPanelInfoCollector getTabPanelInfoCollector(){
    	return panelInfoCollector;
    }
    
    public String encodeHeaderSpacing(FacesContext context, UITabPanel pane) throws IOException {
        String headerSpacing = pane.getHeaderSpacing();
        Double decoded = HtmlDimensions.decode(headerSpacing);
        return "width: " + HtmlUtil.qualifySize(headerSpacing) + "; ";
    }
    
}
