/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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 android.net.sip;

import android.os.RemoteException;
import android.telephony.Rlog;

/**
 * Represents a SIP session that is associated with a SIP dialog or a standalone
 * transaction not within a dialog.
 * <p>You can get a {@link SipSession} from {@link SipManager} with {@link
 * SipManager#createSipSession createSipSession()} (when initiating calls) or {@link
 * SipManager#getSessionFor getSessionFor()} (when receiving calls).</p>
 */
public final class SipSession {
    private static final String TAG = "SipSession";

    /**
     * Defines SIP session states, such as "registering", "outgoing call", and "in call".
     */
    public static class State {
        /** When session is ready to initiate a call or transaction. */
        public static final int READY_TO_CALL = 0;

        /** When the registration request is sent out. */
        public static final int REGISTERING = 1;

        /** When the unregistration request is sent out. */
        public static final int DEREGISTERING = 2;

        /** When an INVITE request is received. */
        public static final int INCOMING_CALL = 3;

        /** When an OK response is sent for the INVITE request received. */
        public static final int INCOMING_CALL_ANSWERING = 4;

        /** When an INVITE request is sent. */
        public static final int OUTGOING_CALL = 5;

        /** When a RINGING response is received for the INVITE request sent. */
        public static final int OUTGOING_CALL_RING_BACK = 6;

        /** When a CANCEL request is sent for the INVITE request sent. */
        public static final int OUTGOING_CALL_CANCELING = 7;

        /** When a call is established. */
        public static final int IN_CALL = 8;

        /** When an OPTIONS request is sent. */
        public static final int PINGING = 9;

        /** When ending a call. @hide */
        public static final int ENDING_CALL = 10;

        /** Not defined. */
        public static final int NOT_DEFINED = 101;

        /**
         * Converts the state to string.
         */
        public static String toString(int state) {
            switch (state) {
                case READY_TO_CALL:
                    return "READY_TO_CALL";
                case REGISTERING:
                    return "REGISTERING";
                case DEREGISTERING:
                    return "DEREGISTERING";
                case INCOMING_CALL:
                    return "INCOMING_CALL";
                case INCOMING_CALL_ANSWERING:
                    return "INCOMING_CALL_ANSWERING";
                case OUTGOING_CALL:
                    return "OUTGOING_CALL";
                case OUTGOING_CALL_RING_BACK:
                    return "OUTGOING_CALL_RING_BACK";
                case OUTGOING_CALL_CANCELING:
                    return "OUTGOING_CALL_CANCELING";
                case IN_CALL:
                    return "IN_CALL";
                case PINGING:
                    return "PINGING";
                default:
                    return "NOT_DEFINED";
            }
        }

        private State() {
        }
    }

    /**
     * Listener for events relating to a SIP session, such as when a session is being registered
     * ("on registering") or a call is outgoing ("on calling").
     * <p>Many of these events are also received by {@link SipAudioCall.Listener}.</p>
     */
    public static class Listener {
        /**
         * Called when an INVITE request is sent to initiate a new call.
         *
         * @param session the session object that carries out the transaction
         */
        public void onCalling(SipSession session) {
        }

        /**
         * Called when an INVITE request is received.
         *
         * @param session the session object that carries out the transaction
         * @param caller the SIP profile of the caller
         * @param sessionDescription the caller's session description
         */
        public void onRinging(SipSession session, SipProfile caller,
                String sessionDescription) {
        }

        /**
         * Called when a RINGING response is received for the INVITE request sent
         *
         * @param session the session object that carries out the transaction
         */
        public void onRingingBack(SipSession session) {
        }

        /**
         * Called when the session is established.
         *
         * @param session the session object that is associated with the dialog
         * @param sessionDescription the peer's session description
         */
        public void onCallEstablished(SipSession session,
                String sessionDescription) {
        }

        /**
         * Called when the session is terminated.
         *
         * @param session the session object that is associated with the dialog
         */
        public void onCallEnded(SipSession session) {
        }

        /**
         * Called when the peer is busy during session initialization.
         *
         * @param session the session object that carries out the transaction
         */
        public void onCallBusy(SipSession session) {
        }

        /**
         * Called when the call is being transferred to a new one.
         *
         * @hide
         * @param newSession the new session that the call will be transferred to
         * @param sessionDescription the new peer's session description
         */
        public void onCallTransferring(SipSession newSession,
                String sessionDescription) {
        }

        /**
         * Called when an error occurs during session initialization and
         * termination.
         *
         * @param session the session object that carries out the transaction
         * @param errorCode error code defined in {@link SipErrorCode}
         * @param errorMessage error message
         */
        public void onError(SipSession session, int errorCode,
                String errorMessage) {
        }

        /**
         * Called when an error occurs during session modification negotiation.
         *
         * @param session the session object that carries out the transaction
         * @param errorCode error code defined in {@link SipErrorCode}
         * @param errorMessage error message
         */
        public void onCallChangeFailed(SipSession session, int errorCode,
                String errorMessage) {
        }

        /**
         * Called when a registration request is sent.
         *
         * @param session the session object that carries out the transaction
         */
        public void onRegistering(SipSession session) {
        }

        /**
         * Called when registration is successfully done.
         *
         * @param session the session object that carries out the transaction
         * @param duration duration in second before the registration expires
         */
        public void onRegistrationDone(SipSession session, int duration) {
        }

        /**
         * Called when the registration fails.
         *
         * @param session the session object that carries out the transaction
         * @param errorCode error code defined in {@link SipErrorCode}
         * @param errorMessage error message
         */
        public void onRegistrationFailed(SipSession session, int errorCode,
                String errorMessage) {
        }

        /**
         * Called when the registration gets timed out.
         *
         * @param session the session object that carries out the transaction
         */
        public void onRegistrationTimeout(SipSession session) {
        }
    }

    private final ISipSession mSession;
    private Listener mListener;

    SipSession(ISipSession realSession) {
        mSession = realSession;
        if (realSession != null) {
            try {
                realSession.setListener(createListener());
            } catch (RemoteException e) {
                loge("SipSession.setListener:", e);
            }
        }
    }

    SipSession(ISipSession realSession, Listener listener) {
        this(realSession);
        setListener(listener);
    }

    /**
     * Gets the IP address of the local host on which this SIP session runs.
     *
     * @return the IP address of the local host
     */
    public String getLocalIp() {
        try {
            return mSession.getLocalIp();
        } catch (RemoteException e) {
            loge("getLocalIp:", e);
            return "127.0.0.1";
        }
    }

    /**
     * Gets the SIP profile that this session is associated with.
     *
     * @return the SIP profile that this session is associated with
     */
    public SipProfile getLocalProfile() {
        try {
            return mSession.getLocalProfile();
        } catch (RemoteException e) {
            loge("getLocalProfile:", e);
            return null;
        }
    }

    /**
     * Gets the SIP profile that this session is connected to. Only available
     * when the session is associated with a SIP dialog.
     *
     * @return the SIP profile that this session is connected to
     */
    public SipProfile getPeerProfile() {
        try {
            return mSession.getPeerProfile();
        } catch (RemoteException e) {
            loge("getPeerProfile:", e);
            return null;
        }
    }

    /**
     * Gets the session state. The value returned must be one of the states in
     * {@link State}.
     *
     * @return the session state
     */
    public int getState() {
        try {
            return mSession.getState();
        } catch (RemoteException e) {
            loge("getState:", e);
            return State.NOT_DEFINED;
        }
    }

    /**
     * Checks if the session is in a call.
     *
     * @return true if the session is in a call
     */
    public boolean isInCall() {
        try {
            return mSession.isInCall();
        } catch (RemoteException e) {
            loge("isInCall:", e);
            return false;
        }
    }

    /**
     * Gets the call ID of the session.
     *
     * @return the call ID
     */
    public String getCallId() {
        try {
            return mSession.getCallId();
        } catch (RemoteException e) {
            loge("getCallId:", e);
            return null;
        }
    }


    /**
     * Sets the listener to listen to the session events. A {@code SipSession}
     * can only hold one listener at a time. Subsequent calls to this method
     * override the previous listener.
     *
     * @param listener to listen to the session events of this object
     */
    public void setListener(Listener listener) {
        mListener = listener;
    }


    /**
     * Performs registration to the server specified by the associated local
     * profile. The session listener is called back upon success or failure of
     * registration. The method is only valid to call when the session state is
     * in {@link State#READY_TO_CALL}.
     *
     * @param duration duration in second before the registration expires
     * @see Listener
     */
    public void register(int duration) {
        try {
            mSession.register(duration);
        } catch (RemoteException e) {
            loge("register:", e);
        }
    }

    /**
     * Performs unregistration to the server specified by the associated local
     * profile. Unregistration is technically the same as registration with zero
     * expiration duration. The session listener is called back upon success or
     * failure of unregistration. The method is only valid to call when the
     * session state is in {@link State#READY_TO_CALL}.
     *
     * @see Listener
     */
    public void unregister() {
        try {
            mSession.unregister();
        } catch (RemoteException e) {
            loge("unregister:", e);
        }
    }

    /**
     * Initiates a call to the specified profile. The session listener is called
     * back upon defined session events. The method is only valid to call when
     * the session state is in {@link State#READY_TO_CALL}.
     *
     * @param callee the SIP profile to make the call to
     * @param sessionDescription the session description of this call
     * @param timeout the session will be timed out if the call is not
     *        established within {@code timeout} seconds. Default value (defined
     *        by SIP protocol) is used if {@code timeout} is zero or negative.
     * @see Listener
     */
    public void makeCall(SipProfile callee, String sessionDescription,
            int timeout) {
        try {
            mSession.makeCall(callee, sessionDescription, timeout);
        } catch (RemoteException e) {
            loge("makeCall:", e);
        }
    }

    /**
     * Answers an incoming call with the specified session description. The
     * method is only valid to call when the session state is in
     * {@link State#INCOMING_CALL}.
     *
     * @param sessionDescription the session description to answer this call
     * @param timeout the session will be timed out if the call is not
     *        established within {@code timeout} seconds. Default value (defined
     *        by SIP protocol) is used if {@code timeout} is zero or negative.
     */
    public void answerCall(String sessionDescription, int timeout) {
        try {
            mSession.answerCall(sessionDescription, timeout);
        } catch (RemoteException e) {
            loge("answerCall:", e);
        }
    }

    /**
     * Ends an established call, terminates an outgoing call or rejects an
     * incoming call. The method is only valid to call when the session state is
     * in {@link State#IN_CALL},
     * {@link State#INCOMING_CALL},
     * {@link State#OUTGOING_CALL} or
     * {@link State#OUTGOING_CALL_RING_BACK}.
     */
    public void endCall() {
        try {
            mSession.endCall();
        } catch (RemoteException e) {
            loge("endCall:", e);
        }
    }

    /**
     * Changes the session description during a call. The method is only valid
     * to call when the session state is in {@link State#IN_CALL}.
     *
     * @param sessionDescription the new session description
     * @param timeout the session will be timed out if the call is not
     *        established within {@code timeout} seconds. Default value (defined
     *        by SIP protocol) is used if {@code timeout} is zero or negative.
     */
    public void changeCall(String sessionDescription, int timeout) {
        try {
            mSession.changeCall(sessionDescription, timeout);
        } catch (RemoteException e) {
            loge("changeCall:", e);
        }
    }

    ISipSession getRealSession() {
        return mSession;
    }

    private ISipSessionListener createListener() {
        return new ISipSessionListener.Stub() {
            @Override
            public void onCalling(ISipSession session) {
                if (mListener != null) {
                    mListener.onCalling(SipSession.this);
                }
            }

            @Override
            public void onRinging(ISipSession session, SipProfile caller,
                    String sessionDescription) {
                if (mListener != null) {
                    mListener.onRinging(SipSession.this, caller,
                            sessionDescription);
                }
            }

            @Override
            public void onRingingBack(ISipSession session) {
                if (mListener != null) {
                    mListener.onRingingBack(SipSession.this);
                }
            }

            @Override
            public void onCallEstablished(ISipSession session,
                    String sessionDescription) {
                if (mListener != null) {
                    mListener.onCallEstablished(SipSession.this,
                            sessionDescription);
                }
            }

            @Override
            public void onCallEnded(ISipSession session) {
                if (mListener != null) {
                    mListener.onCallEnded(SipSession.this);
                }
            }

            @Override
            public void onCallBusy(ISipSession session) {
                if (mListener != null) {
                    mListener.onCallBusy(SipSession.this);
                }
            }

            @Override
            public void onCallTransferring(ISipSession session,
                    String sessionDescription) {
                if (mListener != null) {
                    mListener.onCallTransferring(
                            new SipSession(session, SipSession.this.mListener),
                            sessionDescription);

                }
            }

            @Override
            public void onCallChangeFailed(ISipSession session, int errorCode,
                    String message) {
                if (mListener != null) {
                    mListener.onCallChangeFailed(SipSession.this, errorCode,
                            message);
                }
            }

            @Override
            public void onError(ISipSession session, int errorCode, String message) {
                if (mListener != null) {
                    mListener.onError(SipSession.this, errorCode, message);
                }
            }

            @Override
            public void onRegistering(ISipSession session) {
                if (mListener != null) {
                    mListener.onRegistering(SipSession.this);
                }
            }

            @Override
            public void onRegistrationDone(ISipSession session, int duration) {
                if (mListener != null) {
                    mListener.onRegistrationDone(SipSession.this, duration);
                }
            }

            @Override
            public void onRegistrationFailed(ISipSession session, int errorCode,
                    String message) {
                if (mListener != null) {
                    mListener.onRegistrationFailed(SipSession.this, errorCode,
                            message);
                }
            }

            @Override
            public void onRegistrationTimeout(ISipSession session) {
                if (mListener != null) {
                    mListener.onRegistrationTimeout(SipSession.this);
                }
            }
        };
    }

    private void loge(String s, Throwable t) {
        Rlog.e(TAG, s, t);
    }
}
