/*
 * Copyright (C) 2015 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.messaging.sms;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.mms.MmsManager;
import android.telephony.SmsManager;

import com.android.messaging.datamodel.MmsFileProvider;
import com.android.messaging.datamodel.action.SendMessageAction;
import com.android.messaging.datamodel.data.MessageData;
import com.android.messaging.mmslib.InvalidHeaderValueException;
import com.android.messaging.mmslib.pdu.AcknowledgeInd;
import com.android.messaging.mmslib.pdu.EncodedStringValue;
import com.android.messaging.mmslib.pdu.GenericPdu;
import com.android.messaging.mmslib.pdu.NotifyRespInd;
import com.android.messaging.mmslib.pdu.PduComposer;
import com.android.messaging.mmslib.pdu.PduHeaders;
import com.android.messaging.mmslib.pdu.PduParser;
import com.android.messaging.mmslib.pdu.RetrieveConf;
import com.android.messaging.mmslib.pdu.SendConf;
import com.android.messaging.mmslib.pdu.SendReq;
import com.android.messaging.receiver.SendStatusReceiver;
import com.android.messaging.util.Assert;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.PhoneUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Class that sends chat message via MMS.
 *
 * The interface emulates a blocking send similar to making an HTTP request.
 */
public class MmsSender {
    private static final String TAG = LogUtil.BUGLE_TAG;

    /**
     * Send an MMS message.
     *
     * @param context Context
     * @param messageUri The unique URI of the message for identifying it during sending
     * @param sendReq The SendReq PDU of the message
     * @throws MmsFailureException
     */
    public static void sendMms(final Context context, final int subId, final Uri messageUri,
            final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException {
        sendMms(context,
                subId,
                messageUri,
                null /* locationUrl */,
                sendReq,
                true /* responseImportant */,
                sentIntentExras);
    }

    /**
     * Send NotifyRespInd (response to mms auto download).
     *
     * @param context Context
     * @param subId subscription to use to send the response
     * @param transactionId The transaction id of the MMS message
     * @param contentLocation The url of the MMS message
     * @param status The status to send with the NotifyRespInd
     * @throws MmsFailureException
     * @throws InvalidHeaderValueException
     */
    public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
            final byte[] transactionId, final String contentLocation, final int status)
            throws MmsFailureException, InvalidHeaderValueException {
        // Create the M-NotifyResp.ind
        final NotifyRespInd notifyRespInd = new NotifyRespInd(
                PduHeaders.CURRENT_MMS_VERSION, transactionId, status);
        final Uri messageUri = Uri.parse(contentLocation);
        // Pack M-NotifyResp.ind and send it
        sendMms(context,
                subId,
                messageUri,
                MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
                notifyRespInd,
                false /* responseImportant */,
                null /* sentIntentExtras */);
    }

    /**
     * Send AcknowledgeInd (response to mms manual download). Ignore failures.
     *
     * @param context Context
     * @param subId The SIM's subId we are currently using
     * @param transactionId The transaction id of the MMS message
     * @param contentLocation The url of the MMS message
     * @throws MmsFailureException
     * @throws InvalidHeaderValueException
     */
    public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
            final byte[] transactionId, final String contentLocation)
            throws MmsFailureException, InvalidHeaderValueException {
        final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
        // Create the M-Acknowledge.ind
        final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION,
                transactionId);
        acknowledgeInd.setFrom(new EncodedStringValue(selfNumber));
        final Uri messageUri = Uri.parse(contentLocation);
        // Sending
        sendMms(context,
                subId,
                messageUri,
                MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
                acknowledgeInd,
                false /*responseImportant*/,
                null /* sentIntentExtras */);
    }

    /**
     * Send a generic PDU.
     *
     * @param context Context
     * @param messageUri The unique URI of the message for identifying it during sending
     * @param locationUrl The optional URL to send to
     * @param pdu The PDU to send
     * @param responseImportant If the sending response is important. Responses to the
     * Sending of AcknowledgeInd and NotifyRespInd are not important.
     * @throws MmsFailureException
     */
    private static void sendMms(final Context context, final int subId, final Uri messageUri,
            final String locationUrl, final GenericPdu pdu, final boolean responseImportant,
            final Bundle sentIntentExtras) throws MmsFailureException {
        // Write PDU to temporary file to send to platform
        final Uri contentUri = writePduToTempFile(context, pdu, subId);

        // Construct PendingIntent that will notify us when message sending is complete
        final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
                messageUri,
                context,
                SendStatusReceiver.class);
        sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
        sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant);
        if (sentIntentExtras != null) {
            sentIntent.putExtras(sentIntentExtras);
        }
        final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
                context,
                0 /*request code*/,
                sentIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Send the message
        MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl,
                sentPendingIntent);
    }

    private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId)
            throws MmsFailureException {
        final Uri contentUri = MmsFileProvider.buildRawMmsUri();
        final File tempFile = MmsFileProvider.getFile(contentUri);
        FileOutputStream writer = null;
        try {
            // Ensure rawmms directory exists
            tempFile.getParentFile().mkdirs();
            writer = new FileOutputStream(tempFile);
            final byte[] pduBytes = new PduComposer(context, pdu).make();
            if (pduBytes == null) {
                throw new MmsFailureException(
                        MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU");
            }
            if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) {
                throw new MmsFailureException(
                        MmsUtils.MMS_REQUEST_NO_RETRY,
                        MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
            }
            writer.write(pduBytes);
        } catch (final IOException e) {
            if (tempFile != null) {
                tempFile.delete();
            }
            LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e);
            throw new MmsFailureException(
                    MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file");
        } catch (final OutOfMemoryError e) {
            if (tempFile != null) {
                tempFile.delete();
            }
            LogUtil.e(TAG, "Out of memory in composing PDU", e);
            throw new MmsFailureException(
                    MmsUtils.MMS_REQUEST_MANUAL_RETRY,
                    MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (final IOException e) {
                    // no action we can take here
                }
            }
        }
        return contentUri;
    }

    public static SendConf parseSendConf(byte[] response, int subId) {
        if (response != null) {
            final GenericPdu respPdu = new PduParser(
                    response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
            if (respPdu != null) {
                if (respPdu instanceof SendConf) {
                    return (SendConf) respPdu;
                } else {
                    LogUtil.e(TAG, "MmsSender: send response not SendConf");
                }
            } else {
                // Invalid PDU
                LogUtil.e(TAG, "MmsSender: send invalid response");
            }
        }
        // Empty or invalid response
        return null;
    }

    /**
     * Download an MMS message.
     *
     * @param context Context
     * @param contentLocation The url of the MMS message
     * @throws MmsFailureException
     * @throws InvalidHeaderValueException
     */
    public static void downloadMms(final Context context, final int subId,
            final String contentLocation, Bundle extras) throws MmsFailureException,
            InvalidHeaderValueException {
        final Uri requestUri = Uri.parse(contentLocation);
        final Uri contentUri = MmsFileProvider.buildRawMmsUri();

        final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION,
                requestUri,
                context,
                SendStatusReceiver.class);
        downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
        if (extras != null) {
            downloadedIntent.putExtras(extras);
        }
        final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast(
                context,
                0 /*request code*/,
                downloadedIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri,
                downloadedPendingIntent);
    }

    public static RetrieveConf parseRetrieveConf(byte[] data, int subId) {
        if (data != null) {
            final GenericPdu pdu = new PduParser(
                    data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
            if (pdu != null) {
                if (pdu instanceof RetrieveConf) {
                    return (RetrieveConf) pdu;
                } else {
                    LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: "
                            + pdu.getClass().getName());
                }
            } else {
                LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)");
            }
        }
        LogUtil.e(TAG, "MmsSender: downloaded pdu is empty");
        return null;
    }

    // Process different result code from platform MMS service
    public static int getErrorResultStatus(int resultCode, int httpStatusCode) {
        Assert.isFalse(resultCode == Activity.RESULT_OK);
        switch (resultCode) {
            case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS:
            case SmsManager.MMS_ERROR_IO_ERROR:
                return MmsUtils.MMS_REQUEST_AUTO_RETRY;
            case SmsManager.MMS_ERROR_INVALID_APN:
            case SmsManager.MMS_ERROR_CONFIGURATION_ERROR:
            case SmsManager.MMS_ERROR_NO_DATA_NETWORK:
            case SmsManager.MMS_ERROR_UNSPECIFIED:
                return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
            case SmsManager.MMS_ERROR_HTTP_FAILURE:
                if (httpStatusCode == 404) {
                    return MmsUtils.MMS_REQUEST_NO_RETRY;
                } else {
                    return MmsUtils.MMS_REQUEST_AUTO_RETRY;
                }
            default:
                return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
        }
    }
}
