/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1999-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
package org.jacorb.orb.portableInterceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jacorb.orb.Delegate;
import org.jacorb.orb.Delegate.INVOCATION_KEY;
import org.jacorb.orb.etf.ProfileBase;
import org.jacorb.orb.giop.ClientConnection;
import org.jacorb.orb.giop.ReplyInputStream;
import org.jacorb.orb.giop.RequestOutputStream;
import org.omg.CORBA.ARG_IN;
import org.omg.CORBA.ARG_INOUT;
import org.omg.CORBA.ARG_OUT;
import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_INV_ORDER;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.INV_POLICY;
import org.omg.CORBA.NO_RESOURCES;
import org.omg.CORBA.NamedValue;
import org.omg.CORBA.ParameterMode;
import org.omg.CORBA.Policy;
import org.omg.CORBA.TypeCode;
import org.omg.Dynamic.Parameter;
import org.omg.ETF.Profile;
import org.omg.IOP.ServiceContext;
import org.omg.IOP.TaggedComponent;
import org.omg.IOP.TaggedProfile;
import org.omg.PortableInterceptor.ClientRequestInfo;
import org.omg.PortableInterceptor.LOCATION_FORWARD;
import org.omg.TimeBase.UtcT;
import org.slf4j.Logger;

/**
 * This class represents the type of info object,
 * that will be passed to the ClientRequestInterceptors. <br>
 * See PI Spec p.5-46ff
 *
 * @author Nicolas Noffke
 */

public class ClientRequestInfoImpl
    extends RequestInfoImpl
    implements ClientRequestInfo
{
    private final Logger logger;

    //from ClientRequestInfo
    private org.omg.CORBA.Object target = null;
    private final org.omg.CORBA.Object effective_target;
    private TaggedProfile effective_profile = null;

    private final TaggedComponent[] effective_components;
    private final Map<INVOCATION_KEY, UtcT> invocationContext;

    private final ClientConnection connection;

    protected final Any received_exception;
    protected Delegate delegate;
    protected RequestOutputStream request_os;
    protected String received_exception_id;
    protected ReplyInputStream reply_is;


    public ClientRequestInfoImpl
                      ( org.jacorb.orb.ORB orb,
                        ClientRequestInfoImpl original,
                        org.jacorb.orb.giop.RequestOutputStream ros,
                        org.omg.CORBA.Object self,
                        org.jacorb.orb.Delegate delegate,
                        org.jacorb.orb.ParsedIOR piorOriginal,
                        org.jacorb.orb.giop.ClientConnection connection,
                        Map<INVOCATION_KEY, UtcT> invocationContext)
    {
        super(orb);

        logger = orb.getConfiguration().getLogger("org.jacorb.orb.interceptors");

        this.operation = ros.operation();
        this.response_expected = ros.response_expected();
        this.received_exception = orb.create_any();

        sync_scope = ros.syncScope();

        if ( ros.getRequest() != null )
        {
            this.setRequest( ros.getRequest() );
        }

        this.effective_target = self;

        org.jacorb.orb.ParsedIOR pior = delegate.getParsedIOR();

        if ( piorOriginal == null )
        {
            this.target = self;
        }
        else
        {
            this.target = orb._getDelegate(piorOriginal);
        }

        Profile profile = pior.getEffectiveProfile();

        // If this ParsedIOR is using a profile that extends ProfileBase e.g. IIOPProfile
        // and WIOP (within the regression suite) then grab the effective profile and the
        // possibly null effective_components.
        if (profile instanceof ProfileBase)
        {
            this.effective_profile    = ((ProfileBase)profile).asTaggedProfile();
            this.effective_components =
                (
                        ((ProfileBase)profile).getComponents() == null ?
                                new org.omg.IOP.TaggedComponent[0]             :
                                    ((ProfileBase)profile).getComponents().asArray()
                );
        }
        else
        {
            this.effective_components = new org.omg.IOP.TaggedComponent[ 0 ];
        }

        this.delegate = delegate;

        this.request_id = ros.requestId();

        this.current = orb.getInterceptorManager().getCurrent();

        //allow interceptors access to request output stream
        this.request_os = ros;

        //allow (BiDir) interceptor to inspect the connection
        this.connection = connection;

        // If the original ClientRequestInfo is not null and the forward_reference
        // is not null then copy it over.
        if (original != null && original.forward_reference != null)
        {
            forward_reference = original.forward_reference;
        }

        this.invocationContext = invocationContext;

        if (logger.isDebugEnabled())
        {
            logger.debug("created with invocationContext: " + invocationContext);
        }
    }

    /**
     * Constructor for local calls involving portable interceptors.  With
     * local calls there is no Request/streams so we need to set the data
     * directly
     */
    public ClientRequestInfoImpl ( org.jacorb.orb.ORB orb,
                                   String operation,
                                   boolean response_expected,
                                   short sync_scope,
                                   org.omg.CORBA.Object self,
                                   org.jacorb.orb.Delegate delegate,
                                   org.jacorb.orb.ParsedIOR piorOriginal,
                                   Map<INVOCATION_KEY, UtcT> invocationContext)
    {
        super(orb);

        logger = orb.getConfiguration().getLogger("org.jacorb.orb.interceptors");

        this.operation = operation;
        this.response_expected = response_expected;
        this.received_exception = orb.create_any();
        this.sync_scope = sync_scope;

        this.effective_target = self;

        org.jacorb.orb.ParsedIOR pior = delegate.getParsedIOR();

        if ( piorOriginal == null )
        {
            this.target = self;
        }
        else
        {
            this.target = orb._getDelegate (piorOriginal);
        }

        Profile profile = pior.getEffectiveProfile();

        // If this ParsedIOR is using a profile that extends ProfileBase e.g. IIOPProfile
        // and WIOP (within the regression suite) then grab the effective profile and the
        // possibly null effective_components.
        if (profile instanceof ProfileBase)
        {
            final ProfileBase profileBase = (ProfileBase)profile;

            this.effective_profile    = (profileBase).asTaggedProfile();
            this.effective_components =
                (
                        (profileBase).getComponents() == null ?
                                new org.omg.IOP.TaggedComponent[0]             :
                                    (profileBase).getComponents().asArray()
                );
        }
        else
        {
            this.effective_components = new org.omg.IOP.TaggedComponent[ 0 ];
            effective_profile = null;
        }

        this.delegate = delegate;
        this.invocationContext = invocationContext;

        InterceptorManager manager = orb.getInterceptorManager();

        this.current = manager.getCurrent();

        /* The following do not exist with a local call so nullify them for compilation
         * reasons because they are declared as final
         */
        connection = null;

    }

    public final void setRequest(org.jacorb.orb.dii.Request request)
    {
        arguments = new org.omg.Dynamic.Parameter[request.arguments.count()];
        for (int i = 0; i < arguments.length; i++)
        {
            try
            {
                NamedValue value = request.arguments.item(i);

                ParameterMode mode = null;
                if (value.flags() == ARG_IN.value)
                {
                    mode = ParameterMode.PARAM_IN;
                }
                else if (value.flags() == ARG_OUT.value)
                {
                    mode = ParameterMode.PARAM_OUT;
                }
                else if (value.flags() == ARG_INOUT.value)
                {
                    mode = ParameterMode.PARAM_INOUT;
                }

                arguments[i] = new org.omg.Dynamic.Parameter(value.value(), mode);
            }
            catch (Exception e)
            {
                logger.debug("unexpected exception", e);
            }
        }
        //exceptions will be set when available
    }


    // implementation                        of
    // org.omg.PortableInterceptor.RequestInfoOperations interface

    public Parameter[] arguments()
    {
        if (! (caller_op == ClientInterceptorIterator.SEND_REQUEST) &&
            ! (caller_op == ClientInterceptorIterator.RECEIVE_REPLY))
        {
            throw new BAD_INV_ORDER("The attribute \"arguments\" is currently invalid!",
                                    10, CompletionStatus.COMPLETED_MAYBE);
        }

        if (arguments == null)
        {
            throw new NO_RESOURCES("Stream-based skeletons/stubs do not support this op",
                                   1, CompletionStatus.COMPLETED_MAYBE);
        }

        return arguments;
    }

    public TypeCode[] exceptions()
    {
        if (caller_op == ClientInterceptorIterator.SEND_POLL)
        {
            throw new BAD_INV_ORDER("The attribute \"exceptions\" is currently invalid!",
                                    10, CompletionStatus.COMPLETED_MAYBE);
        }

        if (exceptions == null)
        {
            throw new NO_RESOURCES("Stream-based skeletons/stubs do not support this op",
                                   1, CompletionStatus.COMPLETED_MAYBE);
        }

        return exceptions;
    }

    public Any result()
    {
        if (caller_op != ClientInterceptorIterator.RECEIVE_REPLY)
        {
            throw new BAD_INV_ORDER("The attribute \"result\" is currently invalid!",
                                    10, CompletionStatus.COMPLETED_MAYBE);
        }

        if (result == null)
        {
            throw new NO_RESOURCES("Stream-based skeletons/stubs do not support this op",
                                   1, CompletionStatus.COMPLETED_MAYBE);
        }

        return result;
    }

    public short sync_scope()
    {
        if (caller_op == ClientInterceptorIterator.SEND_POLL)
        {
            throw new BAD_INV_ORDER("The attribute \"sync_scope\" is currently invalid!",
                                    10, CompletionStatus.COMPLETED_MAYBE);
        }

        return sync_scope;
    }

    public short reply_status()
    {
        if ((caller_op == ClientInterceptorIterator.SEND_REQUEST) ||
            (caller_op == ClientInterceptorIterator.SEND_POLL))
        {
            throw new BAD_INV_ORDER("The attribute \"reply_status\" is currently invalid!",
                                    10, CompletionStatus.COMPLETED_MAYBE);
        }

        return reply_status;
    }

    /**
     * <code>forward_reference</code> returns the forward reference for the client request. Note
     * that the current version of the specification does not permit this to be accessed by
     * SendRequest; this modification is a PrismTech enhancement complying one of the suggested
     * portable solutions within http://www.omg.org/issues/issue5266.txt.
     *
     * @return an <code>org.omg.CORBA.Object</code> value
     */
    public org.omg.CORBA.Object forward_reference()
    {
        if (  caller_op != ClientInterceptorIterator.SEND_REQUEST &&
                (caller_op != ClientInterceptorIterator.RECEIVE_OTHER &&
                 (reply_status != LOCATION_FORWARD.value)) )
        {
            throw new BAD_INV_ORDER("The attribute \"forward_reference\" is currently " +
                                    "invalid!", 10, CompletionStatus.COMPLETED_MAYBE);
        }
        return forward_reference;
    }

    public ServiceContext get_request_service_context(int id)
    {
        if (caller_op == ClientInterceptorIterator.SEND_POLL)
        {
            throw new BAD_INV_ORDER("The attribute \"operation_context\" is currently " +
                                    "invalid!", 10, CompletionStatus.COMPLETED_MAYBE);
        }

        return super.get_request_service_context(id);
    }

    public ServiceContext get_reply_service_context(int id)
    {
        if ((caller_op == ClientInterceptorIterator.SEND_REQUEST) ||
            (caller_op == ClientInterceptorIterator.SEND_POLL))
        {
            throw new BAD_INV_ORDER("The attribute \"reply_status\" is currently invalid!",
                                    10, CompletionStatus.COMPLETED_MAYBE);
        }

        return super.get_reply_service_context(id);
    }

    // implementation of ClientRequestInfoOperations interface
    public org.omg.CORBA.Object target()
    {
        return target;
    }

    public org.omg.CORBA.Object effective_target()
    {
        return effective_target;
    }

    public TaggedProfile effective_profile() {
        return effective_profile;
    }

    public Any received_exception()
    {
        if (caller_op != ClientInterceptorIterator.RECEIVE_EXCEPTION)
        {
            throw new BAD_INV_ORDER("The attribute \"received_exception\" is currently " +
                                    "invalid!", 10, CompletionStatus.COMPLETED_MAYBE);
        }

        return received_exception;
    }

    public String received_exception_id()
    {
        if (caller_op != ClientInterceptorIterator.RECEIVE_EXCEPTION)
        {
            throw new BAD_INV_ORDER("The attribute \"received_exception_id\" is " +
                                    "currently invalid!", 10,
                                    CompletionStatus.COMPLETED_MAYBE);
        }

        return received_exception_id;
    }

    public TaggedComponent get_effective_component(int id)
    {
        if (caller_op == ClientInterceptorIterator.SEND_POLL)
        {
            throw new BAD_INV_ORDER("The operation \"get_effective_component\" is " +
                                    "currently invalid!", 10,
                                    CompletionStatus.COMPLETED_MAYBE);
        }

        for(int _i = 0; _i < effective_components.length; _i++)
        {
            if (effective_components[_i].tag == id)
            {
                return effective_components[_i];
            }
        }

        throw new BAD_PARAM("No TaggedComponent with id " + id + " found",
                            25, CompletionStatus.COMPLETED_MAYBE);
    }

    public TaggedComponent[] get_effective_components(int id)
    {
        if (caller_op == ClientInterceptorIterator.SEND_POLL)
        {
            throw new BAD_INV_ORDER("The operation \"get_effective_components\" is " +
                                    "currently invalid!", 10,
                                    CompletionStatus.COMPLETED_MAYBE);
        }

        List<TaggedComponent> _store = new ArrayList<TaggedComponent>();
        for(int _i = 0; _i < effective_components.length; _i++)
        {
            if (effective_components[_i].tag == id)
            {
                _store.add(effective_components[_i]);
            }
        }

        if (_store.size() == 0)
        {
            throw new BAD_PARAM("No TaggedComponents with id " + id + " found",
                                25, CompletionStatus.COMPLETED_MAYBE);
        }

        TaggedComponent[] _result = new TaggedComponent[_store.size()];
        for (int _i = 0; _i < _result.length; _i++)
        {
                _result[_i] = _store.get(_i);
        }

        return _result;
    }

    /**
     * WARNING: This method relies on the DomainService to be available.
     * Make sure that the DS is running, if you want to call this method.
     */

    public Policy get_request_policy(int type)
    {
        if (caller_op == ClientInterceptorIterator.SEND_POLL)
        {
            throw new BAD_INV_ORDER("The operation \"get_request_policy\" is currently " +
                                    "invalid!", 10, CompletionStatus.COMPLETED_MAYBE);
        }

        if (!orb.hasPolicyFactoryForType(type))
        {
            throw new INV_POLICY("No PolicyFactory for type " + type +
                                 " has been registered!", 1,
                                 CompletionStatus.COMPLETED_MAYBE);
        }

        try
        {
            return delegate.get_policy (target, type);
        }
        catch(INV_POLICY e)
        {
            e.minor = 1;
            throw e;
        }
    }

    public void add_request_service_context(ServiceContext service_context,
                                            boolean replace)
    {

        if (caller_op != ClientInterceptorIterator.SEND_REQUEST)
        {
            throw new BAD_INV_ORDER("The operation \"add_request_service_context\" is " +
                                    "currently invalid!", 10,
                                    CompletionStatus.COMPLETED_MAYBE);
        }

        Integer _id = Integer.valueOf(service_context.context_id);

        if (! replace && request_ctx.containsKey(_id))
        {
            throw new BAD_INV_ORDER("The ServiceContext with id " + _id.toString()
                                    + " has already been set!", 11,
                                    CompletionStatus.COMPLETED_MAYBE);
        }

        request_ctx.put(_id, service_context);
    }


    /**
     * Public accessor to return the connection being used by this ClientRequestInfoImpl.
     *
     * @return the connection (maybe null if local call and no connection has been used).
     */
    public ClientConnection getConnection()
    {
        return connection;
    }

    /**
     * Public accessor to determine if this interceptor is being used in a local call.
     *
     * @return true if this is a local interceptor.
     */
    public boolean isLocalInterceptor()
    {
        return (getConnection() == null);
    }

    // These functions should NOT be used internally and are only provided
    // to allow users to interrogate the streams.
    /**
     * Public accessor to access the internal reply stream. Using this API it
     * may be possible to corrupt the inputstream and therebye the call
     * chain. Use at your own risk.
     */
    public ReplyInputStream getReplyStream ()
    {
        return reply_is;
    }

    /**
     * Public accessor to access the internal request stream. Using this API it
     * may be possible to corrupt the outputstream and thereby the call
     * chain. Use at your own risk.
     */
    public RequestOutputStream getRequestStream ()
    {
        return request_os;
    }

    /**
     * Public accessor to access the Delegate for this client request. Use at your
     * own risk
     */
    public Delegate getDelegate ()
    {
        return delegate;
    }
} // ClientRequestInfoImpl
