/*
 * 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.datamodel.action;

import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;

import com.android.messaging.Factory;
import com.android.messaging.datamodel.BugleDatabaseOperations;
import com.android.messaging.datamodel.BugleNotifications;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DatabaseWrapper;
import com.android.messaging.datamodel.MmsFileProvider;
import com.android.messaging.datamodel.data.MessageData;
import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.data.ParticipantData;
import com.android.messaging.mmslib.pdu.SendConf;
import com.android.messaging.sms.MmsConfig;
import com.android.messaging.sms.MmsSender;
import com.android.messaging.sms.MmsUtils;
import com.android.messaging.util.Assert;
import com.android.messaging.util.LogUtil;

import java.io.File;
import java.util.ArrayList;

/**
* Update message status to reflect success or failure
* Can also update the message itself if a "final" message is now available from telephony db
*/
public class ProcessSentMessageAction extends Action {
    private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;

    // These are always set
    private static final String KEY_SMS = "is_sms";
    private static final String KEY_SENT_BY_PLATFORM = "sent_by_platform";

    // These are set when we're processing a message sent by the user. They are null for messages
    // sent automatically (e.g. a NotifyRespInd/AcknowledgeInd sent in response to a download).
    private static final String KEY_MESSAGE_ID = "message_id";
    private static final String KEY_MESSAGE_URI = "message_uri";
    private static final String KEY_UPDATED_MESSAGE_URI = "updated_message_uri";
    private static final String KEY_SUB_ID = "sub_id";

    // These are set for messages sent by the platform (L+)
    public static final String KEY_RESULT_CODE = "result_code";
    public static final String KEY_HTTP_STATUS_CODE = "http_status_code";
    private static final String KEY_CONTENT_URI = "content_uri";
    private static final String KEY_RESPONSE = "response";
    private static final String KEY_RESPONSE_IMPORTANT = "response_important";

    // These are set for messages we sent ourself (legacy), or which we fast-failed before sending.
    private static final String KEY_STATUS = "status";
    private static final String KEY_RAW_STATUS = "raw_status";

    // This is called when MMS lib API returns via PendingIntent
    public static void processMmsSent(final int resultCode, final Uri messageUri,
            final Bundle extras) {
        final ProcessSentMessageAction action = new ProcessSentMessageAction();
        final Bundle params = action.actionParameters;
        params.putBoolean(KEY_SMS, false);
        params.putBoolean(KEY_SENT_BY_PLATFORM, true);
        params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID));
        params.putParcelable(KEY_MESSAGE_URI, messageUri);
        params.putParcelable(KEY_UPDATED_MESSAGE_URI,
                extras.getParcelable(SendMessageAction.EXTRA_UPDATED_MESSAGE_URI));
        params.putInt(KEY_SUB_ID,
                extras.getInt(SendMessageAction.KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
        params.putInt(KEY_RESULT_CODE, resultCode);
        params.putInt(KEY_HTTP_STATUS_CODE, extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
        params.putParcelable(KEY_CONTENT_URI,
                extras.getParcelable(SendMessageAction.EXTRA_CONTENT_URI));
        params.putByteArray(KEY_RESPONSE, extras.getByteArray(SmsManager.EXTRA_MMS_DATA));
        params.putBoolean(KEY_RESPONSE_IMPORTANT,
                extras.getBoolean(SendMessageAction.EXTRA_RESPONSE_IMPORTANT));
        action.start();
    }

    public static void processMessageSentFastFailed(final String messageId,
            final Uri messageUri, final Uri updatedMessageUri, final int subId, final boolean isSms,
            final int status, final int rawStatus, final int resultCode) {
        final ProcessSentMessageAction action = new ProcessSentMessageAction();
        final Bundle params = action.actionParameters;
        params.putBoolean(KEY_SMS, isSms);
        params.putBoolean(KEY_SENT_BY_PLATFORM, false);
        params.putString(KEY_MESSAGE_ID, messageId);
        params.putParcelable(KEY_MESSAGE_URI, messageUri);
        params.putParcelable(KEY_UPDATED_MESSAGE_URI, updatedMessageUri);
        params.putInt(KEY_SUB_ID, subId);
        params.putInt(KEY_STATUS, status);
        params.putInt(KEY_RAW_STATUS, rawStatus);
        params.putInt(KEY_RESULT_CODE, resultCode);
        action.start();
    }

    private ProcessSentMessageAction() {
        // Callers must use one of the static methods above
    }

    /**
    * Update message status to reflect success or failure
    * Can also update the message itself if a "final" message is now available from telephony db
    */
    @Override
    protected Object executeAction() {
        final Context context = Factory.get().getApplicationContext();
        final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
        final Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
        final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI);
        final boolean isSms = actionParameters.getBoolean(KEY_SMS);
        final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM);

        int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY);
        int rawStatus = actionParameters.getInt(KEY_RAW_STATUS,
                MmsUtils.PDU_HEADER_VALUE_UNDEFINED);
        final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);

        if (sentByPlatform) {
            // Delete temporary file backing the contentUri passed to MMS service
            final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
            Assert.isTrue(contentUri != null);
            final File tempFile = MmsFileProvider.getFile(contentUri);
            long messageSize = 0;
            if (tempFile.exists()) {
                messageSize = tempFile.length();
                tempFile.delete();
                if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
                    LogUtil.v(TAG, "ProcessSentMessageAction: Deleted temp file with outgoing "
                            + "MMS pdu: " + contentUri);
                }
            }

            final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
            final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT);
            if (resultCode == Activity.RESULT_OK) {
                if (responseImportant) {
                    // Get the status from the response PDU and update telephony
                    final byte[] response = actionParameters.getByteArray(KEY_RESPONSE);
                    final SendConf sendConf = MmsSender.parseSendConf(response, subId);
                    if (sendConf != null) {
                        final MmsUtils.StatusPlusUri result =
                                MmsUtils.updateSentMmsMessageStatus(context, messageUri, sendConf);
                        status = result.status;
                        rawStatus = result.rawStatus;
                    }
                }
            } else {
                String errorMsg = "ProcessSentMessageAction: Platform returned error resultCode: "
                        + resultCode;
                final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
                if (httpStatusCode != 0) {
                    errorMsg += (", HTTP status code: " + httpStatusCode);
                }
                LogUtil.w(TAG, errorMsg);
                status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);

                // Check for MMS messages that failed because they exceeded the maximum size,
                // indicated by an I/O error from the platform.
                if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) {
                    if (messageSize > MmsConfig.get(subId).getMaxMessageSize()) {
                        rawStatus = MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG;
                    }
                }
            }
        }
        if (messageId != null) {
            final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
            final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
            processResult(
                    messageId, updatedMessageUri, status, rawStatus, isSms, this, subId,
                    resultCode, httpStatusCode);
        } else {
            if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
                LogUtil.v(TAG, "ProcessSentMessageAction: No sent message to process (it was "
                        + "probably a notify response for an MMS download)");
            }
        }
        return null;
    }

    static void processResult(final String messageId, Uri updatedMessageUri, int status,
            final int rawStatus, final boolean isSms, final Action processingAction,
            final int subId, final int resultCode, final int httpStatusCode) {
        final DatabaseWrapper db = DataModel.get().getDatabase();
        MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
        final MessageData originalMessage = message;
        if (message == null) {
            LogUtil.w(TAG, "ProcessSentMessageAction: Sent message " + messageId
                    + " missing from local database");
            ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
                    true /* failed */, processingAction);
            return;
        }
        final String conversationId = message.getConversationId();
        if (updatedMessageUri != null) {
            // Update message if we have newly written final message in the telephony db
            final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri,
                    conversationId, message.getParticipantId(), message.getSelfId());
            if (update != null) {
                // Set message Id of final message to that of the existing place holder.
                update.updateMessageId(message.getMessageId());
                // Update image sizes.
                update.updateSizesForImageParts();
                // Temp attachments are no longer needed
                for (final MessagePartData part : message.getParts()) {
                    part.destroySync();
                }
                message = update;
                // processResult will rewrite the complete message as part of update
            } else {
                updatedMessageUri = null;
                status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
                LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message");
            }
        }

        final long timestamp = System.currentTimeMillis();
        boolean failed;
        if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) {
            message.markMessageSent(timestamp);
            failed = false;
        } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY
                && message.getInResendWindow(timestamp)) {
            message.markMessageNotSent(timestamp);
            message.setRawTelephonyStatus(rawStatus);
            failed = false;
        } else {
            message.markMessageFailed(timestamp);
            message.setRawTelephonyStatus(rawStatus);
            message.setMessageSeen(false);
            failed = true;
        }

        // We have special handling for when a message to an emergency number fails. In this case,
        // we notify immediately of any failure (even if we auto-retry), and instruct the user to
        // try calling the emergency number instead.
        if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) {
            final ArrayList<String> recipients =
                    BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
            for (final String recipient : recipients) {
                if (PhoneNumberUtils.isEmergencyNumber(recipient)) {
                    BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId);
                    message.markMessageFailedEmergencyNumber(timestamp);
                    failed = true;
                    break;
                }
            }
        }

        // Update the message status and optionally refresh the message with final parts/values.
        if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) {
            // We shouldn't show any notifications if we're not allowed to modify Telephony for
            // this message.
            if (failed) {
                BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
            }
            BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
                    conversationId, !failed, status, isSms, subId, true/*isSend*/);
        }

        LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS")
                + " message " + message.getMessageId()
                + " in conversation " + conversationId
                + "; status is " + MmsUtils.getRequestStatusDescription(status));

        // Whether we succeeded or failed we will check and maybe schedule some more work
        ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
                status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction);
    }

    private ProcessSentMessageAction(final Parcel in) {
        super(in);
    }

    public static final Parcelable.Creator<ProcessSentMessageAction> CREATOR
            = new Parcelable.Creator<ProcessSentMessageAction>() {
        @Override
        public ProcessSentMessageAction createFromParcel(final Parcel in) {
            return new ProcessSentMessageAction(in);
        }

        @Override
        public ProcessSentMessageAction[] newArray(final int size) {
            return new ProcessSentMessageAction[size];
        }
    };

    @Override
    public void writeToParcel(final Parcel parcel, final int flags) {
        writeActionToParcel(parcel, flags);
    }
}
