/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package javax.faces.component;

import java.util.*;
import java.util.logging.*;

import javax.el.*;

import javax.faces.*;
import javax.faces.application.*;
import javax.faces.context.*;
import javax.faces.convert.*;
import javax.faces.el.*;
import javax.faces.event.*;
import javax.faces.render.*;
import javax.faces.validator.*;

public class UIInput extends UIOutput
  implements EditableValueHolder
{
  private static final Logger log
    = Logger.getLogger(UIInput.class.getName());
  
  public static final String COMPONENT_FAMILY = "javax.faces.Input";
  public static final String COMPONENT_TYPE = "javax.faces.Input";
  
  public static final String CONVERSION_MESSAGE_ID
    = "javax.faces.component.UIInput.CONVERSION";
  public static final String REQUIRED_MESSAGE_ID
    = "javax.faces.component.UIInput.REQUIRED";
  public static final String UPDATE_MESSAGE_ID
    = "javax.faces.component.UIInput.UPDATE";

  private static final Validator []NULL_VALIDATORS = new Validator[0];
  
  private static final HashMap<String,PropEnum> _propMap
    = new HashMap<String,PropEnum>();

  private ValueExpression _valueExpr;
  
  private Boolean _required;
  private ValueExpression _requiredExpr;
  
  private Boolean _immediate;
  private ValueExpression _immediateExpr;

  private String _requiredMessage;
  private ValueExpression _requiredMessageExpr;

  private String _converterMessage;
  private ValueExpression _converterMessageExpr;

  private String _validatorMessage;
  private ValueExpression _validatorMessageExpr;

  //

  private boolean _isValid = true;
  private boolean _isLocalValueSet;

  private Object _submittedValue;

  private ArrayList<Validator> _validatorList;
  private Validator []_validators = NULL_VALIDATORS;

  public UIInput()
  {
    setRendererType("javax.faces.Text");
  }

  /**
   * Returns the component family, used to select the renderer.
   */
  public String getFamily()
  {
    return COMPONENT_FAMILY;
  }

  //
  // properties
  //

  public String getRequiredMessage()
  {
    if (_requiredMessage != null)
      return _requiredMessage;
    else if (_requiredMessageExpr != null)
      return Util.evalString(_requiredMessageExpr, getFacesContext());
    else
      return null;
  }

  public void setRequiredMessage(String value)
  {
    _requiredMessage = value;
  }

  public String getConverterMessage()
  {
    if (_converterMessage != null)
      return _converterMessage;
    else if (_converterMessageExpr != null)
      return Util.evalString(_converterMessageExpr, getFacesContext());
    else
      return null;
  }

  public void setConverterMessage(String value)
  {
    _converterMessage = value;
  }

  public String getValidatorMessage()
  {
    if (_validatorMessage != null)
      return _validatorMessage;
    else if (_validatorMessageExpr != null)
      return Util.evalString(_validatorMessageExpr, getFacesContext());
    else
      return null;
  }

  public void setValidatorMessage(String value)
  {
    _validatorMessage = value;
  }

  /**
   * Returns the value expression with the given name.
   */
  @Override
  public ValueExpression getValueExpression(String name)
  {
    PropEnum prop = _propMap.get(name);

    if (prop != null) {
      switch (_propMap.get(name)) {
      case VALUE:
        return _valueExpr;
      case IMMEDIATE:
        return _immediateExpr;
      case REQUIRED:
        return _requiredExpr;
      case REQUIRED_MESSAGE:
        return _requiredMessageExpr;
      case CONVERTER_MESSAGE:
        return _converterMessageExpr;
      case VALIDATOR_MESSAGE:
        return _validatorMessageExpr;
      }
    }

    return super.getValueExpression(name);
  }

  /**
   * Sets the value expression with the given name.
   */
  @Override
  public void setValueExpression(String name, ValueExpression expr)
  {
    PropEnum prop = _propMap.get(name);

    if (prop != null) {
      switch (_propMap.get(name)) {
      case VALUE:
        if (expr != null && ! expr.isLiteralText())
          _valueExpr = expr;
        break;

      case IMMEDIATE:
        if (expr != null && expr.isLiteralText()) {
          _immediate = (Boolean) expr.getValue(null);
          return;
        }
        else
          _immediateExpr = expr;
        break;

      case REQUIRED:
        if (expr != null && expr.isLiteralText()) {
          _required = (Boolean) expr.getValue(null);
          return;
        }
        else
          _requiredExpr = expr;
        break;

      case REQUIRED_MESSAGE:
        if (expr != null && expr.isLiteralText()) {
          _requiredMessage = (String) expr.getValue(null);
          return;
        }
        else
          _requiredMessageExpr = expr;
        break;

      case CONVERTER_MESSAGE:
        if (expr != null && expr.isLiteralText()) {
          _converterMessage = (String) expr.getValue(null);
          return;
        }
        else
          _converterMessageExpr = expr;
        break;

      case VALIDATOR_MESSAGE:
        if (expr != null && expr.isLiteralText()) {
          _validatorMessage = (String) expr.getValue(null);
          return;
        }
        else
          _validatorMessageExpr = expr;
        break;
      }
    }

    super.setValueExpression(name, expr);
  }

  //
  // EditableValueHolder properties.
  //

  public boolean isRequired()
  {
    if (_required != null)
      return _required;
    else if (_requiredExpr != null)
      return Util.evalBoolean(_requiredExpr, getFacesContext());
    else
      return false;
  }

  public void setRequired(boolean required)
  {
    _required = required;
  }

  public boolean isImmediate()
  {
    if (_immediate != null)
      return _immediate;
    else if (_immediateExpr != null)
      return Util.evalBoolean(_immediateExpr, getFacesContext());
    else
      return false;
  }

  public void setImmediate(boolean immediate)
  {
    _immediate = immediate;
  }

  public Object getSubmittedValue()
  {
    return _submittedValue;
  }

  public void setSubmittedValue(Object submittedValue)
  {
    _submittedValue = submittedValue;
  }

  public void setValue(Object value)
  {
    super.setValue(value);

    setLocalValueSet(true);
  }

  public boolean isLocalValueSet()
  {
    return _isLocalValueSet;
  }

  public void setLocalValueSet(boolean isSet)
  {
    _isLocalValueSet = isSet;
  }

  public void resetValue()
  {
    setValue(null);
    setSubmittedValue(null);
    setLocalValueSet(false);
    setValid(true);
  }

  public boolean isValid()
  {
    return _isValid;
  }
  
  public void setValid(boolean valid)
  {
    _isValid = valid;
  }

  /**
   * @deprecated
   */
  public MethodBinding getValueChangeListener()
  {
    FacesListener []listeners = getFacesListeners(FacesListener.class);
    for (int i = 0; i < listeners.length; i++) {
      if (listeners[i] instanceof ValueChangeListenerAdapter) {
        ValueChangeListenerAdapter adapter
          = (ValueChangeListenerAdapter) listeners[i];

        return adapter.getBinding();
      }
    }

    return null;
  }

  /**
   * @deprecated
   */
  public void setValueChangeListener(MethodBinding binding)
  {
    ValueChangeListener listener
      = new ValueChangeListenerAdapter(binding);

    FacesListener []listeners = getFacesListeners(FacesListener.class);
    for (int i = 0; i < listeners.length; i++) {
      if (listeners[i] instanceof ValueChangeListenerAdapter) {
        removeFacesListener(listeners[i]);
      }
    }
    
    addValueChangeListener(listener);
  }

  public void addValidator(Validator validator)
  {
    Validator []newValidators = new Validator[_validators.length + 1];
    System.arraycopy(_validators, 0, newValidators, 0, _validators.length);
    newValidators[_validators.length] = validator;

    _validators = newValidators;
  }

  public void removeValidator(Validator validator)
  {
    int length = _validators.length;
    for (int i = 0; i < length; i++) {
      if (_validators[i] == validator) {
        Validator []newValidators = new Validator[length - 1];
        System.arraycopy(_validators, 0, newValidators, 0, i);
        System.arraycopy(_validators, i + 1, newValidators, i, length - i - 1);
        _validators = newValidators;
        return;
      }
    }
  }

  public Validator []getValidators()
  {
    return _validators;
  }

  /**
   * @deprecated
   */
  public MethodBinding getValidator()
  {
    int length = _validators.length;
    for (int i = 0; i < length; i++) {
      Validator validator = _validators[i];

      if (validator instanceof ValidatorAdapter) {
        ValidatorAdapter adapter = (ValidatorAdapter) validator;

        return adapter.getBinding();
      }
    }

    return null;
  }

  /**
   * @deprecated
   */
  public void setValidator(MethodBinding binding)
  {
    ValidatorAdapter adapter = new ValidatorAdapter(binding);
    
    int length = _validators.length;
    for (int i = 0; i < length; i++) {
      Validator validator = _validators[i];

      if (validator instanceof ValidatorAdapter) {
        _validators[i] = adapter;
        return;
      }
    }

    addValidator(adapter);
  }

  public void addValueChangeListener(ValueChangeListener listener)
  {
    addFacesListener(listener);
  }
  
  public void removeValueChangeListener(ValueChangeListener listener)
  {
    removeFacesListener(listener);
  }
  
  public ValueChangeListener []getValueChangeListeners()
  {
    return
      (ValueChangeListener []) getFacesListeners(ValueChangeListener.class);
  }

  //
  // processing
  //

  @Override
  public void decode(FacesContext context)
  {
    setValid(true);
    super.decode(context);
  }

  public void processDecodes(FacesContext context)
  {
    if (isRendered()) {
      super.processDecodes(context);

      if (isImmediate()) {
        try {
          validate(context);
        } catch (RuntimeException e) {
          context.renderResponse();

          throw e;
        }

        if (! isValid())
          context.renderResponse();
      }
    }
  }

  public void processUpdates(FacesContext context)
  {
    if (isRendered()) {
      super.processUpdates(context);

      try {
        updateModel(context);
      } catch (RuntimeException e) {
        context.renderResponse();
      
        throw e;
      }

      if (! isValid())
        context.renderResponse();
    }
  }

  public void updateModel(FacesContext context)
  {
    if (! isValid())
      return;

    if (! isLocalValueSet())
      return;

    if (_valueExpr == null)
      return;

    try {
      _valueExpr.setValue(context.getELContext(), getLocalValue());

      setValue(null);
      setLocalValueSet(false);
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
      
      setValid(false);
      
      String summary = Util.l10n(context, UPDATE_MESSAGE_ID,
                                 "{0}: An error occurred while processing your submitted information.",
                                 Util.getLabel(context, this));

      String detail = summary;

      FacesMessage msg = new FacesMessage(summary, detail);

      context.addMessage(getClientId(context), msg);
    }
  }

  @Override
  public void processValidators(FacesContext context)
  {
    if (isRendered()) {
      super.processValidators(context);

      try {
        if (! isImmediate())
          validate(context);

        if (! isValid())
          context.renderResponse();
      } catch (RuntimeException e) {
        context.renderResponse();
      
        throw e;
      }
    }
  }

  public void validate(FacesContext context)
  {
    Object submittedValue = getSubmittedValue();
    if (submittedValue == null)
      return;

    Object value;

    try {
      value = getConvertedValue(context, submittedValue);

      validateValue(context, value);

      if (! isValid()) {
        context.renderResponse();
        return;
      }
    } catch (ConverterException e) {
      log.log(Level.FINE, e.toString(), e);
      setValid(false);
      context.renderResponse();

      final String converterMessage = getConverterMessage();

      FacesMessage msg = e.getFacesMessage();

      if (msg == null) {
        String summary = null;

        if (converterMessage != null)
          summary = converterMessage;
        else
          summary = Util.l10n(context, CONVERSION_MESSAGE_ID,
                              "{0}: Conversion error occurred.",
                              Util.getLabel(context, this));

        String detail = summary;

        msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, summary, detail);
      }
      else if (converterMessage != null) {
        msg.setSummary(converterMessage);
        msg.setDetail(converterMessage);
      }

      context.addMessage(getClientId(context), msg);

      return;
    }
    
    Object oldValue = getValue();
    setValue(value);
    setSubmittedValue(null);

    if (compareValues(oldValue, value)
        && getFacesListeners(FacesListener.class).length > 0) {
      ValueChangeEvent event = new ValueChangeEvent(this, oldValue, value);

      broadcast(event);
    }
  }

  protected Object getConvertedValue(FacesContext context,
                                     Object submittedValue)
    throws ConverterException
  {
    Renderer renderer = getRenderer(context);

    if (renderer != null)
      return renderer.getConvertedValue(context, this, submittedValue);
    else if (submittedValue instanceof String) {
      Converter converter = getConverter();

      if (converter != null)
        return converter.getAsObject(context, this, (String) submittedValue);

      if (_valueExpr != null) {
        Class type = _valueExpr.getType(context.getELContext());

        if (type != null) {
          converter = context.getApplication().createConverter(type);

          if (converter != null) {
            return converter.getAsObject(context,
                                         this,
                                         (String) submittedValue);
          }
        }
      }
    }

    return submittedValue;
  }
  
  protected boolean compareValues(Object oldValue, Object newValue)
  {
    if (oldValue == newValue)
      return false;
    else if (oldValue == null || newValue == null)
      return true;
    else
      return ! oldValue.equals(newValue);
  }

  protected void validateValue(FacesContext context, Object value)
  {
    if (! isValid()) {
    }
    else if (value != null &&
             ! "".equals(value) &&
             ! (value.getClass().isArray() &&
               java.lang.reflect.Array.getLength(value) == 0)) {
      for (Validator validator : getValidators()) {
        try {
          validator.validate(context, this, value);
        } catch (ValidatorException e) {
          log.log(Level.FINER, e.toString(), e);

          FacesMessage msg = e.getFacesMessage();

          String validatorMessage = getValidatorMessage();

          if (msg == null) {
            final String summary;
            final String detail;

            if (validatorMessage != null) {
              summary = validatorMessage;
              detail = validatorMessage;
            }
            else {
              summary = e.getMessage();
              detail = e.toString();
            }

            msg = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                                   summary,
                                   detail);
          }
          else {
            if (validatorMessage != null) {
              msg.setSummary(validatorMessage);
              msg.setDetail(validatorMessage);
            }
          }

          context.addMessage(getClientId(context), msg);
          setValid(false);
        }
      }
    }
    else if (isRequired()) {
      final FacesMessage msg;

      String requiredMessage = getRequiredMessage();

      if (requiredMessage != null)
        msg = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                               requiredMessage,
                               requiredMessage);
      else {
        String summary = Util.l10n(context,
                                   REQUIRED_MESSAGE_ID,
                                   "{0}: UIInput validation Error: Value is required.",
                                   Util.getLabel(context, this));

        String detail = summary;
        msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, summary, detail);
      }

      context.addMessage(getClientId(context), msg);

      setValid(false);
      return;
    }

  }

  //
  // state
  //

  public Object saveState(FacesContext context)
  {
    int offset = 6;
    
    Object []state = new Object[offset + 2 * _validators.length];
    
    state[0] = super.saveState(context);
    state[1] = _immediate;
    state[2] = _required;
    state[3] = _requiredMessage;
    state[4] = _converterMessage;
    state[5] = _validatorMessage;

    if (_validators.length > 0) {
      for (int i = 0; i < _validators.length; i++) {
        Validator validator = _validators[i];

        int index = offset + 2 * i;

        state[index] = validator.getClass();

        if (validator instanceof StateHolder) {
          StateHolder holder = (StateHolder) validator;

          state[index + 1] = holder.saveState(context);
        }
      }
    }

    return state;
  }

  public void restoreState(FacesContext context, Object value)
  {
    Object []state = (Object []) value;

    super.restoreState(context, state[0]);

    _immediate = (Boolean) state[1];
    _required = (Boolean) state[2];
    _requiredMessage = (String) state[3];
    _converterMessage = (String) state[4];
    _validatorMessage = (String) state[5];
    
    _valueExpr = super.getValueExpression("value");

    int offset = 6;

    if (offset < state.length) {
      _validators = new Validator[(state.length - offset) / 2];

      for (int i = 0; i < _validators.length; i++) {
        int index = offset + 2 * i;

        Class cl = (Class) state[index];

        try {
          Validator validator = (Validator) cl.newInstance();

          if (validator instanceof StateHolder) {
            StateHolder holder = (StateHolder) validator;

            holder.restoreState(context, state[index + 1]);
          }

          _validators[i] = validator;
        } catch (Exception e) {
          throw new FacesException(e);
        }
      }
    }
  }

  //
  // private helpers
  //

  private static enum PropEnum {
    IMMEDIATE,
    REQUIRED,
    VALUE,
    REQUIRED_MESSAGE,
    CONVERTER_MESSAGE,
    VALIDATOR_MESSAGE,
  }

  private static class ValueChangeListenerAdapter
    implements ValueChangeListener, StateHolder
  {
    private MethodBinding _binding;
    
    private boolean _transient;

    public ValueChangeListenerAdapter()
    {
    }

    public ValueChangeListenerAdapter(MethodBinding binding)
    {
      _binding = binding;
    }

    MethodBinding getBinding()
    {
      return _binding;
    }

    public void processValueChange(ValueChangeEvent event)
      throws AbortProcessingException
    {
      FacesContext facesContext = FacesContext.getCurrentInstance();

      _binding.invoke(facesContext, new Object[] { event }); 
    }

    public Object saveState(FacesContext context)
    {
      return _binding;
    }

    public void restoreState(FacesContext context, Object state)
    {
      _binding = (MethodBinding) state;
    }

    public boolean isTransient()
    {
      return _transient;
    }

    public void setTransient(boolean isTransient)
    {
      _transient = isTransient;
    }

    public String toString()
    {
      return "ValueChangeListenerAdapter[" + _binding + "]";
    }
  }

  private static class ValidatorAdapter
    implements Validator, StateHolder
  {
    private MethodBinding _binding;
    
    private boolean _transient;

    public ValidatorAdapter()
    {
    }

    public ValidatorAdapter(MethodBinding binding)
    {
      _binding = binding;
    }

    MethodBinding getBinding()
    {
      return _binding;
    }

    public void validate(FacesContext context,
                         UIComponent component,
                         Object value)
      throws ValidatorException
    {
      try {
        FacesContext facesContext = FacesContext.getCurrentInstance();

        _binding.invoke(facesContext, new Object[] { context, component, value });
      } catch (EvaluationException e) {
        if (e.getCause() instanceof ValidatorException)
          throw (ValidatorException) e.getCause();
        else if (e.getCause() instanceof RuntimeException)
          throw (RuntimeException) e.getCause();
        else
          throw e;
      }
    }

    public Object saveState(FacesContext context)
    {
      return _binding;
    }

    public void restoreState(FacesContext context, Object state)
    {
      _binding = (MethodBinding) state;
    }

    public boolean isTransient()
    {
      return _transient;
    }

    public void setTransient(boolean isTransient)
    {
      _transient = isTransient;
    }

    public String toString()
    {
      return "ValueChangeListenerAdapter[" + _binding + "]";
    }
  }

  static {
    _propMap.put("value", PropEnum.VALUE);
    _propMap.put("immediate", PropEnum.IMMEDIATE);
    _propMap.put("required", PropEnum.REQUIRED);
    _propMap.put("requiredMessage", PropEnum.REQUIRED_MESSAGE);
    _propMap.put("converterMessage", PropEnum.CONVERTER_MESSAGE);
    _propMap.put("validatorMessage", PropEnum.VALIDATOR_MESSAGE);
  }
}
