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

import java.util.Iterator;

import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.ActionSource;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;

import org.ajax4jsf.component.AjaxActionComponent;
import org.ajax4jsf.component.AjaxComponent;
import org.ajax4jsf.component.IterationStateHolder;
import org.ajax4jsf.event.AjaxSource;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.event.SimpleTogglePanelSwitchEvent;


/**
 * JSF component class
 */
public abstract class UISimpleTogglePanel extends AjaxActionComponent implements AjaxComponent, AjaxSource, ActionSource, IterationStateHolder
//public abstract class UISimpleTogglePanel extends  UIInput implements AjaxComponent, AjaxSource, ActionSource
{

	public static final String COMPONENT_FAMILY = "javax.faces.Command";
    public static final String SERVER_SWITCH_TYPE = "server";
    public static final String CLIENT_SWITCH_TYPE = "client";
    public static final String AJAX_SWITCH_TYPE = "ajax";
    
    @Deprecated
    public static final boolean COLLAPSED = false;
    @Deprecated
    public static final boolean EXPANDED = true;
    
//    private transient Boolean openedSet = null;

    private transient Boolean wasOpened = Boolean.FALSE; 

    protected void setWasOpened(Boolean wasOpened) {
    	this.wasOpened = wasOpened;
    }
    
    protected boolean isWasOpened() {
    	return wasOpened;
    }
    
    public abstract void setSwitchType(String switchType);

    public abstract String getSwitchType();

    public void setOpened(boolean opened) {
    	setValue(new Boolean(opened).toString());
    }

    public boolean isOpened() {
    	Object value = getValue();
    	if (value instanceof Boolean) {
    		return ((Boolean)value).booleanValue();
    	} else if (value instanceof String) {
			String s = (String) value;
			return Boolean.parseBoolean(s);
		}
    	return true;
    }

    public boolean getRendersChildren() {
        return true;
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     */
    public void processDecodes(FacesContext context) {

        if (context == null) {
            throw new NullPointerException();
        }

        // Skip processing if our rendered flag is false
        if (!isRendered()) {
            return;
        }

        setWasOpened(Boolean.valueOf(CLIENT_SWITCH_TYPE.equals(getSwitchType()) || isOpened()));
        
        if (isWasOpened()) {
            // Process all facets and children of this component
            Iterator<UIComponent> kids = getFacetsAndChildren();
            while (kids.hasNext()) {
                UIComponent kid = kids.next();
                kid.processDecodes(context);
            }
        }
        
        // Process this component itself
        try {
            decode(context);
        } catch (RuntimeException e) {
            context.renderResponse();
            throw e;
        }

    }

    /**
     * @throws NullPointerException {@inheritDoc}
     */
    public void processValidators(FacesContext context) {

        if (context == null) {
            throw new NullPointerException();
        }

        // Skip processing if our rendered flag is false
        if (!isRendered()) {
            return;
        }

        if (isWasOpened()) {
	        // Process all the facets and children of this component
        	Iterator<UIComponent> kids = getFacetsAndChildren();
        	while (kids.hasNext()) {
        		UIComponent kid = kids.next();
        		kid.processValidators(context);
        	}
        }
    }


    /**
     * @throws NullPointerException {@inheritDoc}
     */
    public void processUpdates(FacesContext context) {

        if (context == null) {
            throw new NullPointerException();
        }

        // Skip processing if our rendered flag is false
        if (!isRendered()) {
            return;
        }
        
        if (isWasOpened()) {
	        // Process all facets and children of this component
        	Iterator<UIComponent> kids = getFacetsAndChildren();
	        while (kids.hasNext()) {
	            UIComponent kid = kids.next();
	            kid.processUpdates(context);
	        }
        }
    }
    
    @Override
    public void queueEvent(FacesEvent event) {
    	if (event instanceof SimpleTogglePanelSwitchEvent && this.equals(event.getComponent())) {
			SimpleTogglePanelSwitchEvent switchEvent = (SimpleTogglePanelSwitchEvent) event;

			if (isImmediate()) {
				setOpened(switchEvent.isOpened());
			}
			
			switchEvent.setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
		}

    	super.queueEvent(event);
    }
    
    @Override
    public void broadcast(FacesEvent event) throws AbortProcessingException {
    	super.broadcast(event);

    	if (event instanceof SimpleTogglePanelSwitchEvent) {
			SimpleTogglePanelSwitchEvent switchEvent = (SimpleTogglePanelSwitchEvent) event;

			setOpened(switchEvent.isOpened());
	        
			try {
	            updateModel();
	        } catch (RuntimeException e) {
	        	getFacesContext().renderResponse();
	        	throw e;
			}
		}
    }
    
    private Object value;
    
    protected Object getLocalValue() {
    	return value;
    }
    
    @Override
    public Object getValue() {
    	if (this.value != null) {
    		return this.value;
    	}
    	
    	ValueExpression ve = getValueExpression("value");
    	if (ve != null) {
        	FacesContext context = getFacesContext();
    		
    		try {
            	return ve.getValue(context.getELContext());
        	} catch (ELException e) {
        		throw new FacesException(e);
    		}
    	}
    	
    	return null;
    }
    
    @Override
    public void setValue(Object value) {
    	this.value = value;
    }
    
    @Override
    public Object saveState(FacesContext context) {
    	Object[] state = new Object[2];
    	
    	state[0] = super.saveState(context);
    	state[1] = this.value;
    	
    	return state;
    }
    
    @Override
    public void restoreState(FacesContext context, Object stateObject) {
    	Object[] state = (Object[]) stateObject;

    	super.restoreState(context, state[0]);
    	this.value = state[1];
    }
    
    public Object getIterationState() {
    	Object[] state = new Object[2];
    	
    	state[0] = this.value;
    	state[1] = this.wasOpened;
    	
    	return state;
    }
    
    public void setIterationState(Object stateObject) {
    	if (stateObject != null) {
    		Object[] state = (Object[]) stateObject;
    		
        	this.value = state[0];
        	this.wasOpened = (Boolean) state[1];
    	} else {
    		this.value = null;
    		this.wasOpened = null;
    	}
    }
    
    protected void updateModel() {
    	Object value = getLocalValue();
    	if (value != null) {
        	ValueExpression ve = getValueExpression("value");
        	if (ve != null) {
        		FacesContext context = getFacesContext();
				try {
            		ve.setValue(context.getELContext(), value);
            		setValue(null);
	            } catch (ELException e) {
	                String messageStr = e.getMessage();
	                Throwable result = e.getCause();
	                while (null != result &&
	                     result.getClass().isAssignableFrom(ELException.class)) {
	                    messageStr = result.getMessage();
	                    result = result.getCause();
	                }
	                FacesMessage message;
	                if (null == messageStr) {
	                    message =
	                    	//not an UIInput, but constant is fine
	                    	MessageUtil.getMessage(context, UIInput.UPDATE_MESSAGE_ID, new Object[] {
	                    		MessageUtil.getLabel(context, this) 
	                    	});
	                } else {
	                    message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
	                                               messageStr,
	                                               messageStr);
	                }
	                
	                context.getExternalContext().log(message.getSummary(), result);
	                context.addMessage(getClientId(context), message);
	                context.renderResponse();
	            } catch (IllegalArgumentException e) {
	                FacesMessage message =
	                	MessageUtil.getMessage(context, UIInput.UPDATE_MESSAGE_ID, new Object[] {
	                    		MessageUtil.getLabel(context, this) 
	                    	}); 
	                
	                context.getExternalContext().log(message.getSummary(), e);
	                context.addMessage(getClientId(context), message);
	                context.renderResponse();
	            } catch (Exception e) {
	                FacesMessage message =
	                	MessageUtil.getMessage(context, UIInput.UPDATE_MESSAGE_ID, new Object[] {
	                    		MessageUtil.getLabel(context, this) 
	                    	}); 

	                context.getExternalContext().log(message.getSummary(), e);
	                context.addMessage(getClientId(context), message);
	                context.renderResponse();
	            }
        	}
    	}
    }

}
