/*
 * Copyright (C) 2014 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
 R* limitations under the License.
 */

package android.telecomm;

import android.content.ComponentName;
import android.net.Uri;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.telephony.DisconnectCause;

import android.text.TextUtils;

import com.android.internal.telecomm.ICallService;
import com.android.internal.telecomm.ICallServiceAdapter;
import com.android.internal.telecomm.RemoteServiceCallback;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

/**
 * Remote connection service which other connection services can use to place calls on their behalf.
 */
public class RemoteConnectionService implements DeathRecipient {
    private final ICallService mCallService;
    private final ComponentName mComponentName;

    private String mConnectionId;
    private ConnectionRequest mPendingRequest;
    private SimpleResponse<ConnectionRequest, RemoteConnection> mPendingResponse;
    // Remote connection services only support a single connection.
    private RemoteConnection mConnection;

    private final ICallServiceAdapter mAdapter = new ICallServiceAdapter.Stub() {

        /** ${inheritDoc} */
            @Override
        public void notifyIncomingCall(CallInfo callInfo) {
            Log.w(this, "notifyIncomingCall not implemented in Remote connection");
        }

        /** ${inheritDoc} */
            @Override
        public void handleSuccessfulOutgoingCall(String connectionId) {
            if (isPendingConnection(connectionId)) {
                mConnection = new RemoteConnection(mCallService, connectionId);
                mPendingResponse.onResult(mPendingRequest, mConnection);
                clearPendingInformation();
            }
        }

        /** ${inheritDoc} */
            @Override
        public void handleFailedOutgoingCall(
                ConnectionRequest request, int errorCode, String errorMessage) {
            if (isPendingConnection(request.getCallId())) {
                // Use mPendingRequest instead of request so that we use the same object that was
                // passed in to us.
                mPendingResponse.onError(request);
                mConnectionId = null;
                clearPendingInformation();
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setActive(String connectionId) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setState(Connection.State.ACTIVE);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setRinging(String connectionId) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setState(Connection.State.RINGING);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setDialing(String connectionId) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setState(Connection.State.DIALING);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setDisconnected(
                String connectionId, int disconnectCause, String disconnectMessage) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setDisconnected(disconnectCause, disconnectMessage);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setOnHold(String connectionId) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setState(Connection.State.HOLDING);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setRequestingRingback(String connectionId, boolean isRequestingRingback) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setRequestingRingback(isRequestingRingback);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void setCanConference(String connectionId, boolean canConference) {
            // not supported for remote connections.
        }

        /** ${inheritDoc} */
            @Override
        public void setIsConferenced(String connectionId, String conferenceConnectionId) {
            // not supported for remote connections.
        }

        /** ${inheritDoc} */
            @Override
        public void addConferenceCall(String connectionId, CallInfo callInfo) {
            // not supported for remote connections.
        }

        /** ${inheritDoc} */
            @Override
        public void removeCall(String connectionId) {
            if (isCurrentConnection(connectionId)) {
                destroyConnection();
            }
        }

        /** ${inheritDoc} */
            @Override
        public void onPostDialWait(String connectionId, String remainingDigits) {
            if (isCurrentConnection(connectionId)) {
                mConnection.setPostDialWait(remainingDigits);
            }
        }

        /** ${inheritDoc} */
            @Override
        public void handoffCall(String connectionId) {
            // unnecessary.
        }

        /** ${inheritDoc} */
        @Override
        public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
            try {
                // Not supported from remote connection service.
                callback.onError();
            } catch (RemoteException e) {
            }
        }
    };

    RemoteConnectionService(ComponentName componentName, ICallService callService)
            throws RemoteException {
        mComponentName = componentName;
        mCallService = callService;

        // TODO(santoscordon): Rename from setCallServiceAdapter to addCallServiceAdapter.
        mCallService.setCallServiceAdapter(mAdapter);
        mCallService.asBinder().linkToDeath(this, 0);
    }

    @Override
    public String toString() {
        return "[RemoteCS - " + mCallService.asBinder().toString() + "]";
    }

    /** ${inheritDoc} */
    @Override
    public void binderDied() {
        if (mConnection != null) {
            destroyConnection();
        }

        release();
    }

    /**
     * Places an outgoing call.
     */
    public void createOutgoingConnection(
            ConnectionRequest request,
            SimpleResponse<ConnectionRequest, RemoteConnection> response) {

        if (mConnectionId == null) {
            String id = UUID.randomUUID().toString();
            CallInfo callInfo = new CallInfo(id, CallState.NEW, request.getHandle());
            try {
                mCallService.call(callInfo);
                mConnectionId = id;
                mPendingResponse = response;
                mPendingRequest = request;
            } catch (RemoteException e) {
                response.onError(request);
            }
        } else {
            response.onError(request);
        }
    }

    // TODO(santoscordon): Handle incoming connections
    // public void handleIncomingConnection() {}

    public List<Subscription> lookupSubscriptions(Uri handle) {
        // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
        // each time.
        List<Subscription> subscriptions = new LinkedList<>();
        subscriptions.add(new Subscription(
                mComponentName,
                null /* id */,
                null /* handle */,
                0 /* labelResId */,
                0 /* shortDescriptionResId */,
                0 /* iconResId */,
                true /* isEnabled */,
                false /* isSystemDefault */));
        return subscriptions;
    }

    /**
     * Releases the resources associated with this Remote connection service. Should be called when
     * the remote service is no longer being used.
     */
    void release() {
        mCallService.asBinder().unlinkToDeath(this, 0);
    }

    private boolean isPendingConnection(String id) {
        return TextUtils.equals(mConnectionId, id) && mPendingResponse != null;
    }

    private boolean isCurrentConnection(String id) {
        return mConnection != null && TextUtils.equals(mConnectionId, id);
    }

    private void clearPendingInformation() {
        mPendingRequest = null;
        mPendingResponse = null;
    }

    private void destroyConnection() {
        mConnection.setDestroyed();
        mConnection = null;
        mConnectionId = null;
    }
}
