/*
 * 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.CarrierConfigManager;
import android.telephony.ims.RcsContactTerminatedReason;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.SipDetails;
import android.telephony.ims.aidl.ISubscribeResponseCallback;
import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode;

import com.android.ims.rcs.uce.eab.EabCapabilityResult;
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.pidfparser.RcsContactUceCapabilityWrapper;
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 com.android.internal.telephony.flags.FeatureFlags;

import java.util.ArrayList;
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 {
    public static final int MAX_RETRY_COUNT = 1;

    // 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(@NonNull SipDetails details) {
                    SubscribeRequest.this.onNetworkResponse(details);
                }
                @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;
    private final FeatureFlags mFeatureFlags;

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

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

    @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;
        }

        if (mFeatureFlags.enableSipSubscribeRetry()
                && cmdError == RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT
                && isRetryEnabled()) {
            int retryCount = getRetryCount();
            if (retryCount < MAX_RETRY_COUNT) {
                CapabilityRequest request = new SubscribeRequest(mSubId, mRequestType,
                        mRequestManagerCallback, mSubscribeController, mFeatureFlags);
                request.setContactUri(getContactUri());
                request.setRetryCount(retryCount + 1);
                // Do not use the cached capability to retry
                request.setSkipGettingFromCache(true);

                mRequestManagerCallback.sendSubscribeRetryRequest(request);
                logd("onCommandError: Retry subscribe request");
            } else {
                logd("onCommandError: Reached max retry");
            }
        }

        mRequestResponse.setCommandError(cmdError);
        mRequestManagerCallback.notifyCommandError(mCoordinatorId, mTaskId);
    }

    // Receive the network response callback which is triggered by ISubscribeResponseCallback.
    private void onNetworkResponse(@NonNull SipDetails details) {
        logd("onNetworkResponse: sip details=" + details.toString());

        if (mIsFinished) {
            logw("onNetworkResponse: request is already finished");
            return;
        }
        mRequestResponse.setSipDetails(details);
        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 RcsContactUceCapabilityWrapper
        List<RcsContactUceCapabilityWrapper> capabilityList = pidfXml.stream()
                .map(pidf -> PidfParser.getRcsContactUceCapabilityWrapper(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.
        List<RcsContactUceCapability> notReceivedCapabilityList = new ArrayList<>();
        if (capabilityList.isEmpty()) {
            logd("onCapabilitiesUpdate: The capabilities list is empty, Set to non-RCS user.");
            List<Uri> notReceiveCapUpdatedContactList =
                    mRequestResponse.getNotReceiveCapabilityUpdatedContact();
            notReceivedCapabilityList = notReceiveCapUpdatedContactList.stream()
                    .map(PidfParserUtils::getNotFoundContactCapabilities)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }

        List<RcsContactUceCapability> updateCapabilityList = new ArrayList<>();
        List<Uri> malformedListWithEntityURI = new ArrayList<>();
        for (RcsContactUceCapabilityWrapper capability : capabilityList) {
            if (!capability.isMalformed()) {
                updateCapabilityList.add(capability.toRcsContactUceCapability());
            } else {
                logw("onCapabilitiesUpdate: malformed capability was found and not saved.");
                malformedListWithEntityURI.add(capability.getEntityUri());
            }
        }
        logd("onCapabilitiesUpdate: PIDF size=" + pidfXml.size()
                + ", not received capability size=" + notReceivedCapabilityList.size()
                + ", normal capability size=" + updateCapabilityList.size()
                + ", malformed but entity uri is valid capability size="
                + malformedListWithEntityURI.size());

        for (RcsContactUceCapability emptyCapability : notReceivedCapabilityList) {
            updateCapabilityList.add(emptyCapability);
        }

        // All tuples in received xml are malformed but entity uri is valid.
        // The capability should be get from the DB and report it to callback.
        List<EabCapabilityResult> cachedCapabilityList =
                mRequestManagerCallback.getCapabilitiesFromCache(malformedListWithEntityURI);
        for (EabCapabilityResult cacheEabCapability : cachedCapabilityList) {
            RcsContactUceCapability cachedCapability = cacheEabCapability.getContactCapabilities();
            if (cachedCapability != null) {
                updateCapabilityList.add(cachedCapability);
            }
        }
        // Add these updated RcsContactUceCapability into the RequestResponse and notify
        // the RequestManager to process the RcsContactUceCapabilities updated.
        logd("onCapabilitiesUpdate: updatedCapability size=" + updateCapabilityList.size());
        mRequestResponse.addUpdatedCapabilities(updateCapabilityList);
        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;
    }
}
