/*
 * Copyright (C) 2011 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.dialer.voicemail;

import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_OK;
import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;

import android.database.Cursor;
import android.net.Uri;
import android.provider.VoicemailContract.Status;

import com.android.contacts.common.util.UriUtils;
import com.android.dialer.R;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/** Implementation of {@link VoicemailStatusHelper}. */
public class VoicemailStatusHelperImpl implements VoicemailStatusHelper {
    private static final int SOURCE_PACKAGE_INDEX = 0;
    private static final int CONFIGURATION_STATE_INDEX = 1;
    private static final int DATA_CHANNEL_STATE_INDEX = 2;
    private static final int NOTIFICATION_CHANNEL_STATE_INDEX = 3;
    private static final int SETTINGS_URI_INDEX = 4;
    private static final int VOICEMAIL_ACCESS_URI_INDEX = 5;
    private static final int NUM_COLUMNS = 6;
    /** Projection on the voicemail_status table used by this class. */
    public static final String[] PROJECTION = new String[NUM_COLUMNS];
    static {
        PROJECTION[SOURCE_PACKAGE_INDEX] = Status.SOURCE_PACKAGE;
        PROJECTION[CONFIGURATION_STATE_INDEX] = Status.CONFIGURATION_STATE;
        PROJECTION[DATA_CHANNEL_STATE_INDEX] = Status.DATA_CHANNEL_STATE;
        PROJECTION[NOTIFICATION_CHANNEL_STATE_INDEX] = Status.NOTIFICATION_CHANNEL_STATE;
        PROJECTION[SETTINGS_URI_INDEX] = Status.SETTINGS_URI;
        PROJECTION[VOICEMAIL_ACCESS_URI_INDEX] = Status.VOICEMAIL_ACCESS_URI;
    }

    /** Possible user actions. */
    public static enum Action {
        NONE(-1),
        CALL_VOICEMAIL(R.string.voicemail_status_action_call_server),
        CONFIGURE_VOICEMAIL(R.string.voicemail_status_action_configure);

        private final int mMessageId;
        private Action(int messageId) {
            mMessageId = messageId;
        }

        public int getMessageId() {
            return mMessageId;
        }
    }

    /**
     * Overall state of the source status. Each state is associated with the corresponding display
     * string and the corrective action. The states are also assigned a relative priority which is
     * used to order the messages from different sources.
     */
    private static enum OverallState {
        // TODO: Add separate string for call details and call log pages for the states that needs
        // to be shown in both.
        /** Both notification and data channel are not working. */
        NO_CONNECTION(0, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
                R.string.voicemail_status_audio_not_available),
        /** Notifications working, but data channel is not working. Audio cannot be downloaded. */
        NO_DATA(1, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
                R.string.voicemail_status_audio_not_available),
        /** Messages are known to be waiting but data channel is not working. */
        MESSAGE_WAITING(2, Action.CALL_VOICEMAIL, R.string.voicemail_status_messages_waiting,
                R.string.voicemail_status_audio_not_available),
        /** Notification channel not working, but data channel is. */
        NO_NOTIFICATIONS(3, Action.CALL_VOICEMAIL,
                R.string.voicemail_status_voicemail_not_available),
        /** Invite user to set up voicemail. */
        INVITE_FOR_CONFIGURATION(4, Action.CONFIGURE_VOICEMAIL,
                R.string.voicemail_status_configure_voicemail),
        /**
         * No detailed notifications, but data channel is working.
         * This is normal mode of operation for certain sources. No action needed.
         */
        NO_DETAILED_NOTIFICATION(5, Action.NONE, -1),
        /** Visual voicemail not yet set up. No local action needed. */
        NOT_CONFIGURED(6, Action.NONE, -1),
        /** Everything is OK. */
        OK(7, Action.NONE, -1),
        /** If one or more state value set by the source is not valid. */
        INVALID(8, Action.NONE, -1);

        private final int mPriority;
        private final Action mAction;
        private final int mCallLogMessageId;
        private final int mCallDetailsMessageId;

        private OverallState(int priority, Action action, int callLogMessageId) {
            this(priority, action, callLogMessageId, -1);
        }

        private OverallState(int priority, Action action, int callLogMessageId,
                int callDetailsMessageId) {
            mPriority = priority;
            mAction = action;
            mCallLogMessageId = callLogMessageId;
            mCallDetailsMessageId = callDetailsMessageId;
        }

        public Action getAction() {
            return mAction;
        }

        public int getPriority() {
            return mPriority;
        }

        public int getCallLogMessageId() {
            return mCallLogMessageId;
        }

        public int getCallDetailsMessageId() {
            return mCallDetailsMessageId;
        }
    }

    /** A wrapper on {@link StatusMessage} which additionally stores the priority of the message. */
    private static class MessageStatusWithPriority {
        private final StatusMessage mMessage;
        private final int mPriority;

        public MessageStatusWithPriority(StatusMessage message, int priority) {
            mMessage = message;
            mPriority = priority;
        }
    }

    @Override
    public List<StatusMessage> getStatusMessages(Cursor cursor) {
        List<MessageStatusWithPriority> messages =
            new ArrayList<VoicemailStatusHelperImpl.MessageStatusWithPriority>();
        cursor.moveToPosition(-1);
        while(cursor.moveToNext()) {
            MessageStatusWithPriority message = getMessageForStatusEntry(cursor);
            if (message != null) {
                messages.add(message);
            }
        }
        // Finally reorder the messages by their priority.
        return reorderMessages(messages);
    }

    @Override
    public int getNumberActivityVoicemailSources(Cursor cursor) {
        int count = 0;
        cursor.moveToPosition(-1);
        while(cursor.moveToNext()) {
            if (isVoicemailSourceActive(cursor)) {
                ++count;
            }
        }
        return count;
    }

    /** Returns whether the source status in the cursor corresponds to an active source. */
    private boolean isVoicemailSourceActive(Cursor cursor) {
        return cursor.getString(SOURCE_PACKAGE_INDEX) != null
                &&  cursor.getInt(CONFIGURATION_STATE_INDEX) == Status.CONFIGURATION_STATE_OK;
    }

    private List<StatusMessage> reorderMessages(List<MessageStatusWithPriority> messageWrappers) {
        Collections.sort(messageWrappers, new Comparator<MessageStatusWithPriority>() {
            @Override
            public int compare(MessageStatusWithPriority msg1, MessageStatusWithPriority msg2) {
                return msg1.mPriority - msg2.mPriority;
            }
        });
        List<StatusMessage> reorderMessages = new ArrayList<VoicemailStatusHelper.StatusMessage>();
        // Copy the ordered message objects into the final list.
        for (MessageStatusWithPriority messageWrapper : messageWrappers) {
            reorderMessages.add(messageWrapper.mMessage);
        }
        return reorderMessages;
    }

    /**
     * Returns the message for the status entry pointed to by the cursor.
     */
    private MessageStatusWithPriority getMessageForStatusEntry(Cursor cursor) {
        final String sourcePackage = cursor.getString(SOURCE_PACKAGE_INDEX);
        if (sourcePackage == null) {
            return null;
        }
        final OverallState overallState = getOverallState(cursor.getInt(CONFIGURATION_STATE_INDEX),
                cursor.getInt(DATA_CHANNEL_STATE_INDEX),
                cursor.getInt(NOTIFICATION_CHANNEL_STATE_INDEX));
        final Action action = overallState.getAction();

        // No source package or no action, means no message shown.
        if (action == Action.NONE) {
            return null;
        }

        Uri actionUri = null;
        if (action == Action.CALL_VOICEMAIL) {
            actionUri = UriUtils.parseUriOrNull(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
            // Even if actionUri is null, it is still be useful to show the notification.
        } else if (action == Action.CONFIGURE_VOICEMAIL) {
            actionUri = UriUtils.parseUriOrNull(cursor.getString(SETTINGS_URI_INDEX));
            // If there is no settings URI, there is no point in showing the notification.
            if (actionUri == null) {
                return null;
            }
        }
        return new MessageStatusWithPriority(
                new StatusMessage(sourcePackage, overallState.getCallLogMessageId(),
                        overallState.getCallDetailsMessageId(), action.getMessageId(),
                        actionUri),
                overallState.getPriority());
    }

    private OverallState getOverallState(int configurationState, int dataChannelState,
            int notificationChannelState) {
        if (configurationState == CONFIGURATION_STATE_OK) {
            // Voicemail is configured. Let's see how is the data channel.
            if (dataChannelState == DATA_CHANNEL_STATE_OK) {
                // Data channel is fine. What about notification channel?
                if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
                    return OverallState.OK;
                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
                    return OverallState.NO_DETAILED_NOTIFICATION;
                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
                    return OverallState.NO_NOTIFICATIONS;
                }
            } else if (dataChannelState == DATA_CHANNEL_STATE_NO_CONNECTION) {
                // Data channel is not working. What about notification channel?
                if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
                    return OverallState.NO_DATA;
                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
                    return OverallState.MESSAGE_WAITING;
                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
                    return OverallState.NO_CONNECTION;
                }
            }
        } else if (configurationState == CONFIGURATION_STATE_CAN_BE_CONFIGURED) {
            // Voicemail not configured. data/notification channel states are irrelevant.
            return OverallState.INVITE_FOR_CONFIGURATION;
        } else if (configurationState == Status.CONFIGURATION_STATE_NOT_CONFIGURED) {
            // Voicemail not configured. data/notification channel states are irrelevant.
            return OverallState.NOT_CONFIGURED;
        }
        // Will reach here only if the source has set an invalid value.
        return OverallState.INVALID;
    }
}
