/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ws.tools.metadata;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.rmi.Remote;
import java.rmi.RemoteException;

import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.soap.SOAPBinding;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.holders.Holder;

import org.jboss.ws.WSException;
import org.jboss.ws.core.soap.Style;
import org.jboss.ws.metadata.umdm.FaultMetaData;
import org.jboss.ws.metadata.umdm.OperationMetaData;
import org.jboss.ws.metadata.umdm.ParameterMetaData;
import org.jboss.ws.metadata.wsdl.WSDLUtils;
import org.jboss.ws.tools.ToolsUtils;

/**
 *  Builds the ToolsEndpointMetaData using JSR-181 annotations
 *  on the endpoint
 *  @author <mailto:Anil.Saldhana@jboss.org>Anil Saldhana
 *  @since  Oct 20, 2005
 */
public class ToolsAnnotationMetaDataBuilder
{
   private ToolsEndpointMetaData tmd = null;
   private String targetNamespace = null;
   private String typeNamespace = null;

   private Class endpoint = null;

   public ToolsAnnotationMetaDataBuilder(ToolsEndpointMetaData tmd, String targetNamespace, String typeNamespace2)
   {
      this.tmd = tmd;
      this.targetNamespace = targetNamespace;
      this.typeNamespace = typeNamespace;
      endpoint = tmd.getServiceEndpointInterface();
   }

   public ToolsEndpointMetaData generate()
   {
      generateOperationMetaData();
      return tmd;
   }

   //PRIVATE METHODS
   private void generateOperationMetaData()
   {
      //    Generate the Operation Metadata
      Method[] marr = endpoint.getDeclaredMethods();
      if( marr != null)
      {
         int len = Array.getLength(marr);
         for(int i = 0; i < len ; i++)
         {
            Method m = marr[i];
            if (WSDLUtils.getInstance().checkIgnoreMethod(m))
               continue;
            tmd.addOperation(getOperationMetaData(m,tmd));
         }
      }
   }

   private OperationMetaData getOperationMetaData(Method m, ToolsEndpointMetaData em)
   {
      String opname = null;
      String soapAction = null;
      //Check if there are annotations
      WebMethod an = m.getAnnotation(WebMethod.class);
      if(an != null)
      {
         opname = an.operationName();
         soapAction = an.action();
      }
      if(opname == null || opname.length() == 0)
         opname = m.getName();

      OperationMetaData om = new OperationMetaData(em, new QName(targetNamespace,opname),
            m.getName());
      om.setSOAPAction(soapAction);

      Style style = Style.RPC;

      SOAPBinding sb = (SOAPBinding)endpoint.getAnnotation(SOAPBinding.class);
      if(sb != null)
      {
         String wsdlStyle = sb.style().name();
         if(wsdlStyle != null && wsdlStyle.equalsIgnoreCase("DOCUMENT"))
            style = Style.DOCUMENT;
      }
      em.setStyle(style);

      Class[] paramTypes = m.getParameterTypes();
      int lenparam = paramTypes != null ? paramTypes.length : 0;

      for (int j = 0; j < lenparam; j++)
      {
         Class paramType = paramTypes[j];
         if(Remote.class.isAssignableFrom(paramType))
            throw new WSException("OpName:" + opname + " param:" + paramType.getName() +
                  " should not extend Remote" );
         //Get the ParameterMetaData for the individual parameters
         om.addParameter(getParameterMetaData(paramType, om, j + 1));
      }


      //Oneway annotation
      Oneway ow = m.getAnnotation(Oneway.class);
      if(ow != null)
         om.setOneWay(true);

      Class ret = m.getReturnType();
      ParameterMetaData retPmd = getParameterMetaDataForReturnType(ret, om, 1);
      if(retPmd != null )
         om.setReturnParameter(retPmd);

      //Take care of exceptions also
      Class[] exarr = m.getExceptionTypes();
      if(exarr != null)
      {
         int len = Array.getLength(exarr);
         int i = 0;
         for(  i = 0 ; i < len ; i++)
         {
            Class exClass = exarr[i];
            if(!RemoteException.class.isAssignableFrom(exClass))
               om.addFault(getFaultMetaData(exClass,om));
         }
      }
      return om;
   }

   private ParameterMetaData getParameterMetaData(Class type, OperationMetaData om,
         int index)
   {
      WebParam wp = (WebParam)type.getAnnotation(WebParam.class);
      String tns = targetNamespace;
      String name = "";
      ParameterMode mode = ParameterMode.IN;

      if( wp != null)
      {
         tns = wp.targetNamespace();
         if(tns == null || tns == "")
            tns = targetNamespace;
         name = wp.name() == "" ? type.getName() + "_" + index : type.getName();
         if( wp.mode() == WebParam.Mode.INOUT )
            mode = ParameterMode.INOUT;
         else
            if(wp.mode() == WebParam.Mode.OUT)
               mode = ParameterMode.OUT;
      }

      if(name == null || name.length() == 0)
         name = this.getXMLName(type) + "_" +index;

      if(typeNamespace != null && typeNamespace.equals(tns) == false)
         tns = typeNamespace; //Types always deal with typeNamespace

      boolean header = wp != null ? wp.header() : false;
      QName xmlType = ToolsUtils.getXMLType(type, tns);
      ParameterMetaData pm = new ParameterMetaData(om, new QName(tns,name),
            xmlType, type.getName());

      return pm;
   }

   private ParameterMetaData getParameterMetaDataForReturnType(Class type,
         OperationMetaData om,  int index)
   {
      if(type == void.class)
         return null;

      if(Remote.class.isAssignableFrom( type ))
         throw new WSException(om.getJavaName() + " has return type which " +
               "should not extend java.rmi.Remote" );
      WebResult wr = (WebResult)type.getAnnotation(WebResult.class);
      String tns = targetNamespace;
      String name = "result";

      if( wr != null)
      {
         tns = wr.targetNamespace();
         if(tns == null || tns == "")
            tns = targetNamespace;
         name = wr.name() == "" ? type.getName() + "_" + index : type.getName();
      }
      else
      {
         //  Check if it is a Holder
         if (Holder.class.isAssignableFrom( type ))
         {
            type = WSDLUtils.getInstance().getJavaTypeForHolder( type );
         }
      }

      if(typeNamespace != null && tns.equals(typeNamespace) == false)
         tns = typeNamespace;
      QName xmlType = ToolsUtils.getXMLType(type, tns);
      ParameterMetaData pm = new ParameterMetaData(om, new QName(tns,name),
            xmlType, type.getName());
      return pm;
   }

   private FaultMetaData getFaultMetaData(Class exType, OperationMetaData om)
   {
      String exname = WSDLUtils.getInstance().getJustClassName(exType);
      QName xmlName = new QName( typeNamespace, exname);

      FaultMetaData fm =  new FaultMetaData(om, xmlName, xmlName, exType.getName());
      return fm;
   }

   private String getXMLName(Class javaClass)
   {
      String name = "";
      WSDLUtils utils = WSDLUtils.getInstance();
      if(Holder.class.isAssignableFrom(javaClass))
         javaClass = utils.getJavaTypeForHolder(javaClass);
      if(javaClass.isArray())
      {
         int len = utils.getArrayDimension(javaClass);
         for(int i = 0; i < len; i++)
            javaClass = javaClass.getComponentType();

         name = utils.getMessagePartForArray(javaClass);
      }
      else
         name = utils.getJustClassName(javaClass);
      return name;
   }
}

