/**
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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.apache.openejb.server.axis.client;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.axis.client.Call;
import org.apache.axis.description.ParameterDesc;
import org.apache.axis.utils.JavaUtils;
import org.apache.openejb.server.ServerRuntimeException;
import org.apache.openejb.server.webservices.saaj.SaajUniverse;

import javax.wsdl.OperationType;
import javax.xml.rpc.holders.Holder;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class ServiceEndpointMethodInterceptor implements MethodInterceptor {
    private final GenericServiceEndpoint stub;
    private final OperationInfo[] operations;
    private final String credentialsName;

    public ServiceEndpointMethodInterceptor(GenericServiceEndpoint stub, OperationInfo[] operations, String credentialsName) {
        this.stub = stub;
        this.operations = operations;
        this.credentialsName = credentialsName;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        SaajUniverse universe = new SaajUniverse();
        universe.set(SaajUniverse.AXIS1);
        try {
            return doIntercept(method, objects, methodProxy);
        } finally {
            universe.unset();
        }
    }

    private Object doIntercept(Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        int index = methodProxy.getSuperIndex();
        OperationInfo operationInfo = operations[index];
        if (operationInfo == null) {
            throw new ServerRuntimeException("Operation not mapped: " + method.getName() + " index: " + index + "\n OperationInfos: " + Arrays.asList(operations));
        }
        stub.checkCachedEndpoint();

        Call call = stub.createCall();

        operationInfo.prepareCall(call);

        stub.setUpCall(call);
        if (credentialsName != null) {
            throw new UnsupportedOperationException("Client side auth is not implementd");
//            Subject subject = ContextManager.getNextCaller();
//            if (subject == null) {
//                throw new IllegalStateException("Subject missing but authentication turned on");
//            } else {
//                Set creds = subject.getPrivateCredentials(NamedUsernamePasswordCredential.class);
//                boolean found = false;
//                for (Iterator iterator = creds.iterator(); iterator.hasNext();) {
//                    NamedUsernamePasswordCredential namedUsernamePasswordCredential = (NamedUsernamePasswordCredential) iterator.next();
//                    if (credentialsName.equals(namedUsernamePasswordCredential.getName())) {
//                        call.setUsername(namedUsernamePasswordCredential.getUsername());
//                        call.setPassword(new String(namedUsernamePasswordCredential.getPassword()));
//                        found = true;
//                        break;
//                    }
//                }
//                if (!found) {
//                    throw new IllegalStateException("no NamedUsernamePasswordCredential found for name " + credentialsName);
//                }
//            }
        }
        Object response = null;
        List parameterDescs = operationInfo.getOperationDesc().getParameters();
        Object[] unwrapped = extractFromHolders(objects, parameterDescs, operationInfo.getOperationDesc().getNumInParams());
        if (operationInfo.getOperationDesc().getMep() == OperationType.REQUEST_RESPONSE) {
            try {
                response = call.invoke(unwrapped);
            } catch (RemoteException e) {
                throw operationInfo.unwrapFault(e);
            }

            if (response instanceof RemoteException) {
                throw operationInfo.unwrapFault((RemoteException) response);
            } else {
                stub.extractAttachments(call);
                Map outputParameters = call.getOutputParams();
                putInHolders(outputParameters, objects, parameterDescs);
                Class returnType = operationInfo.getOperationDesc().getReturnClass();
                //return type should never be null... but we are not objecting if wsdl-return-value-mapping is not set.
                if (response == null || returnType == null || returnType.isAssignableFrom(response.getClass())) {
                    return response;
                } else {
                    return JavaUtils.convert(response, returnType);
                }
            }
        } else if (operationInfo.getOperationDesc().getMep() == OperationType.ONE_WAY) {
            //one way
            call.invokeOneWay(unwrapped);
            return null;
        } else {
            throw new ServerRuntimeException("Invalid messaging style: " + operationInfo.getOperationDesc().getMep());
        }
    }

    private Object[] extractFromHolders(Object[] objects, List parameterDescs, int inParameterCount) throws JavaUtils.HolderException {
        if (objects.length != parameterDescs.size()) {
            throw new IllegalArgumentException("Mismatch parameter count: expected: " + parameterDescs.size() + ", actual: " + objects.length);
        }
        Object[] unwrapped = new Object[inParameterCount];
        int j = 0;
        for (int i = 0; objects != null && i < objects.length; i++) {
            Object parameter = objects[i];
            ParameterDesc parameterDesc = (ParameterDesc) parameterDescs.get(i);

            if (parameterDesc.getMode() == ParameterDesc.INOUT) {
                unwrapped[j++] = JavaUtils.getHolderValue(parameter);
            } else if (parameterDesc.getMode() == ParameterDesc.IN) {
                unwrapped[j++] = parameter;
            }
        }
        return unwrapped;
    }

    private void putInHolders(Map outputParameters, Object[] objects, List parameterDescs) throws JavaUtils.HolderException {
        for (int i = 0; i < objects.length; i++) {
            Object parameter = objects[i];
            ParameterDesc parameterDesc = (ParameterDesc) parameterDescs.get(i);
            if ((parameterDesc.getMode() == ParameterDesc.INOUT) || (parameterDesc.getMode() == ParameterDesc.OUT)) {
                Object returned = outputParameters.get(parameterDesc.getQName());
                if (returned instanceof Holder) {
                    //TODO this must be a bug somewhere!!!!
                    returned = JavaUtils.getHolderValue(returned);
                }
                JavaUtils.setHolderValue(parameter, returned);
            }
        }
    }
}
