/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed 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.jboss.axis.message;

import org.jboss.axis.AxisFault;
import org.jboss.axis.NotImplementedException;
import org.jboss.axis.encoding.DeserializationContext;
import org.jboss.logging.Logger;
import org.w3c.dom.DOMException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
 * Abstracts an RPC parameter as SOAPElement
 * <p/>
 * This is a hack that bridges between the SOAPElement that represents a RPC parameter
 * and the actual RPCParam object which lives in a list inside the RPCElement. As
 * long as the RPCParam is not a true javax.xml.soap.Node, clients cannot use the saaj
 * api to modify its value.
 * <p/>
 * This class should eventually become the RPCParam.
 *
 * @author Thomas Diesler (thomas.diesler@jboss.org)
 */
public class RPCParamElementImpl extends SOAPElementAxisImpl
{

   private static Logger log = Logger.getLogger(RPCParamElementImpl.class.getName());

   // The parent of this rpc param
   private RPCElement rpcElement;
   private DeserializationContext context;

   public RPCParamElementImpl(String namespace, String localPart, String prefix, Attributes attributes, DeserializationContext context) throws AxisFault
   {
      super(namespace, localPart, prefix, attributes, context);

      SOAPElement curEl = context.getCurElement();
      if (curEl instanceof RPCParamElementImpl)
         rpcElement = ((RPCParamElementImpl)curEl).rpcElement;

      if (curEl instanceof RPCElement)
         rpcElement = (RPCElement)curEl;

      if (rpcElement == null)
      {
         IllegalArgumentException ex = new IllegalArgumentException("Unexpected element type: " + curEl.getClass().getName());
         log.error(ex.getMessage(), ex);
         throw ex;
      }

      this.context = context;
   }

   public RPCParamElementImpl(RPCParam rpcParam)
   {
      super(rpcParam.getQName(), rpcParam.getValue());
      rpcElement = rpcParam.myCall;

      if (rpcElement == null)
      {
         IllegalArgumentException ex = new IllegalArgumentException("RPCParam has no parent element");
         log.error(ex.getMessage(), ex);
         throw ex;
      }

      // Added to build the soap tree such that it contains SOAPElements for the parameters
      // Revisit to do this properly for the different encoding styles, arrays, value types, etc.
      // TDI 06-June-2006

      List supported = Arrays.asList(new Class[]{
         String.class, Integer.class, Float.class, Double.class, Long.class, Boolean.class,
         Short.class, Byte.class, BigInteger.class, BigDecimal.class
      });

      Object value = rpcParam.getValue();
      if (value != null)
      {
         try
         {
            if (supported.contains(value.getClass()))
            {
               super.addTextNode(value.toString());
            }
            else
            {
               log.debug("Cannot add text node for rpc parameter type: " + value.getClass().getName());
            }
         }
         catch (SOAPException e)
         {
            log.error("Cannot addTextNode: " + value, e);
         }
      }
   }

   public Iterator getChildElements()
   {
      if (!hasChildNodes() && objectValue != null)
      {
         throw new NotImplementedException("SOAPElement view of RPCParam not implemented");
      }
      else
      {
         return super.getChildElements();
      }
   }

   public Iterator getChildElements(Name name)
   {
      if (!hasChildNodes() && objectValue != null)
      {
         throw new NotImplementedException("SOAPElement view of RPCParam not implemented");
      }
      else
      {
         return super.getChildElements(name);
      }
   }

   /**
    * Try to keep the RPCParam object in sync
    */
   public SOAPElement addTextNode(String value) throws SOAPException
   {
      SOAPElement txtNode = super.addTextNode(value);
      setRPCParamValue(value);
      return txtNode;
   }

   /**
    * Try to keep the RPCParam object in sync
    */
   public void setValue(String value)
   {
      super.setValue(value);
      setRPCParamValue(value);
   }

   /**
    * Try to keep the RPCParam object in sync
    */
   public void setNodeValue(String value) throws DOMException
   {
      super.setNodeValue(value);
      setRPCParamValue(value);
   }

   /**
    * Set the value in the RPCParam object, if found
    */
   private void setRPCParamValue(String newValue)
   {
      // The context is null when the client side build this object
      // before it sends the message on the wire
      if (context == null || context.isDoneParsing())
      {
         RPCParam rpcParam = getRPCParam();
         Object paramValue = rpcParam.getValue();

         if (paramValue != null && !paramValue.getClass().isAssignableFrom(newValue.getClass()))
            log.warn("Trying to change the value type from " + paramValue.getClass().getName() + " to " + newValue.getClass().getName());

         rpcParam.setValue(newValue);
      }
   }

   /**
    * Search through the list of parameters in the rpcElement and return the one and only one
    * that has the same QName as this SOAPElement
    */
   private RPCParam getRPCParam()
   {

      RPCParam rpcParam = null;

      QName rpcParamElementName = getQName();
      ArrayList weHave = new ArrayList();
      try
      {
         Vector params = rpcElement.getParams();
         for (int i = 0; i < params.size(); i++)
         {

            RPCParam aux = (RPCParam)params.elementAt(i);
            QName rpcParamName = aux.getQName();
            weHave.add(rpcParamName);

            if (rpcParamName.equals(rpcParamElementName))
            {
               if (rpcParam != null)
                  log.error("Duplicate parameter: " + rpcParamName);
               else
                  rpcParam = aux;
            }
         }
      }
      catch (SAXException e)
      {
         log.error("Cannot get RPCParam " + rpcParamElementName, e);
      }

      if (rpcParam == null)
         log.warn("Cannot find parameter: " + getQName() + " we have " + weHave);

      return rpcParam;
   }
}
