/*
 * Copyright (c) 2020 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 com.android.ims.rcs.uce.request;

import android.annotation.NonNull;
import android.net.Uri;
import android.os.RemoteException;
import android.telephony.ims.RcsContactTerminatedReason;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.aidl.ISubscribeResponseCallback;
import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode;

import com.android.ims.rcs.uce.presence.pidfparser.PidfParser;
import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils;
import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * The UceRequest to request the capabilities when the presence mechanism is supported by the
 * network.
 */
public class SubscribeRequest extends CapabilityRequest {

    // The result callback of the capabilities request from IMS service.
    private final ISubscribeResponseCallback mResponseCallback =
            new ISubscribeResponseCallback.Stub() {
                @Override
                public void onCommandError(int code) {
                    SubscribeRequest.this.onCommandError(code);
                }
                @Override
                public void onNetworkResponse(int code, String reason) {
                    SubscribeRequest.this.onNetworkResponse(code, reason);
                }
                @Override
                public void onNetworkRespHeader(int code, String reasonPhrase,
                        int reasonHeaderCause, String reasonHeaderText) {
                    SubscribeRequest.this.onNetworkResponse(code, reasonPhrase, reasonHeaderCause,
                            reasonHeaderText);
                }
                @Override
                public void onNotifyCapabilitiesUpdate(List<String> pidfXmls) {
                    SubscribeRequest.this.onCapabilitiesUpdate(pidfXmls);
                }
                @Override
                public void onResourceTerminated(List<RcsContactTerminatedReason> terminatedList) {
                    SubscribeRequest.this.onResourceTerminated(terminatedList);
                }
                @Override
                public void onTerminated(String reason, long retryAfterMillis) {
                    SubscribeRequest.this.onTerminated(reason, retryAfterMillis);
                }
            };

    private SubscribeController mSubscribeController;

    public SubscribeRequest(int subId, @UceRequestType int requestType,
            RequestManagerCallback taskMgrCallback, SubscribeController subscribeController) {
        super(subId, requestType, taskMgrCallback);
        mSubscribeController = subscribeController;
        logd("SubscribeRequest created");
    }

    @VisibleForTesting
    public SubscribeRequest(int subId, @UceRequestType int requestType,
            RequestManagerCallback taskMgrCallback, SubscribeController subscribeController,
            CapabilityRequestResponse requestResponse) {
        super(subId, requestType, taskMgrCallback, requestResponse);
        mSubscribeController = subscribeController;
    }

    @Override
    public void onFinish() {
        mSubscribeController = null;
        super.onFinish();
        logd("SubscribeRequest finish");
    }

    @Override
    public void requestCapabilities(@NonNull List<Uri> requestCapUris) {
        SubscribeController subscribeController = mSubscribeController;
        if (subscribeController == null) {
            logw("requestCapabilities: request is finished");
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId);
            return;
        }

        logi("requestCapabilities: size=" + requestCapUris.size());
        try {
            // Send the capabilities request.
            subscribeController.requestCapabilities(requestCapUris, mResponseCallback);
            // Setup the timeout timer.
            setupRequestTimeoutTimer();
        } catch (RemoteException e) {
            logw("requestCapabilities exception: " + e);
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId);
        }
    }

    // Receive the command error callback which is triggered by ISubscribeResponseCallback.
    private void onCommandError(@CommandCode int cmdError) {
        logd("onCommandError: error code=" + cmdError);
        if (mIsFinished) {
            logw("onCommandError: request is already finished");
            return;
        }
        mRequestResponse.setCommandError(cmdError);
        mRequestManagerCallback.notifyCommandError(mCoordinatorId, mTaskId);
    }

    // Receive the network response callback which is triggered by ISubscribeResponseCallback.
    private void onNetworkResponse(int sipCode, String reason) {
        logd("onNetworkResponse: code=" + sipCode + ", reason=" + reason);
        if (mIsFinished) {
            logw("onNetworkResponse: request is already finished");
            return;
        }
        mRequestResponse.setNetworkResponseCode(sipCode, reason);
        mRequestManagerCallback.notifyNetworkResponse(mCoordinatorId, mTaskId);
    }

    // Receive the network response callback which is triggered by ISubscribeResponseCallback.
    private void onNetworkResponse(int sipCode, String reasonPhrase,
        int reasonHeaderCause, String reasonHeaderText) {
        logd("onNetworkResponse: code=" + sipCode + ", reasonPhrase=" + reasonPhrase +
                ", reasonHeaderCause=" + reasonHeaderCause +
                ", reasonHeaderText=" + reasonHeaderText);
        if (mIsFinished) {
            logw("onNetworkResponse: request is already finished");
            return;
        }
        mRequestResponse.setNetworkResponseCode(sipCode, reasonPhrase, reasonHeaderCause,
                reasonHeaderText);
        mRequestManagerCallback.notifyNetworkResponse(mCoordinatorId, mTaskId);
    }

    // Receive the resource terminated callback which is triggered by ISubscribeResponseCallback.
    private void onResourceTerminated(List<RcsContactTerminatedReason> terminatedResource) {
        if (mIsFinished) {
            logw("onResourceTerminated: request is already finished");
            return;
        }

        if (terminatedResource == null) {
            logw("onResourceTerminated: the parameter is null");
            terminatedResource = Collections.emptyList();
        }

        logd("onResourceTerminated: size=" + terminatedResource.size());

        // Add the terminated resource into the RequestResponse and notify the RequestManager
        // to process the RcsContactUceCapabilities update.
        mRequestResponse.addTerminatedResource(terminatedResource);
        mRequestManagerCallback.notifyResourceTerminated(mCoordinatorId, mTaskId);
    }

    // Receive the capabilities update callback which is triggered by ISubscribeResponseCallback.
    private void onCapabilitiesUpdate(List<String> pidfXml) {
        if (mIsFinished) {
            logw("onCapabilitiesUpdate: request is already finished");
            return;
        }

        if (pidfXml == null) {
            logw("onCapabilitiesUpdate: The parameter is null");
            pidfXml = Collections.EMPTY_LIST;
        }

        // Convert from the pidf xml to the list of RcsContactUceCapability
        List<RcsContactUceCapability> capabilityList = pidfXml.stream()
                .map(pidf -> PidfParser.getRcsContactUceCapability(pidf))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        // When the given PIDF xml is empty, set the contacts who have not received the
        // capabilities updated as non-RCS user.
        if (capabilityList.isEmpty()) {
            logd("onCapabilitiesUpdate: The capabilities list is empty, Set to non-RCS user.");
            List<Uri> notReceiveCapUpdatedContactList =
                    mRequestResponse.getNotReceiveCapabilityUpdatedContact();
            capabilityList = notReceiveCapUpdatedContactList.stream()
                    .map(PidfParserUtils::getNotFoundContactCapabilities)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }

        logd("onCapabilitiesUpdate: PIDF size=" + pidfXml.size()
                + ", contact capability size=" + capabilityList.size());

        // Add these updated RcsContactUceCapability into the RequestResponse and notify
        // the RequestManager to process the RcsContactUceCapabilities updated.
        mRequestResponse.addUpdatedCapabilities(capabilityList);
        mRequestManagerCallback.notifyCapabilitiesUpdated(mCoordinatorId, mTaskId);
    }

    // Receive the terminated callback which is triggered by ISubscribeResponseCallback.
    private void onTerminated(String reason, long retryAfterMillis) {
        logd("onTerminated: reason=" + reason + ", retryAfter=" + retryAfterMillis);
        if (mIsFinished) {
            logd("onTerminated: This request is already finished");
            return;
        }
        mRequestResponse.setTerminated(reason, retryAfterMillis);
        mRequestManagerCallback.notifyTerminated(mCoordinatorId, mTaskId);
    }

    @VisibleForTesting
    public ISubscribeResponseCallback getResponseCallback() {
        return mResponseCallback;
    }
}
