/*
 * Copyright (C) 2012 Google Inc.
 * Licensed to 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.mail.browse;

import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;

import com.android.mail.content.ObjectCursor;
import com.android.mail.providers.Account;
import com.android.mail.providers.Attachment;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.UIProvider.CursorExtraKeys;
import com.android.mail.providers.UIProvider.CursorStatus;
import com.android.mail.ui.ConversationUpdater;

import com.google.common.collect.Lists;

import java.util.List;

/**
 * MessageCursor contains the messages within a conversation; the public methods within should
 * only be called by the UI thread, as cursor position isn't guaranteed to be maintained
 */
public class MessageCursor extends ObjectCursor<ConversationMessage> {
    /**
     * The current controller that this cursor can use to reference the owning {@link Conversation},
     * and a current {@link ConversationUpdater}. Since this cursor will survive a rotation, but
     * the controller does not, whatever the new controller is MUST update this reference before
     * using this cursor.
     */
    private ConversationController mController;

    private Integer mStatus;

    public interface ConversationController {
        Conversation getConversation();
        ConversationUpdater getListController();
        MessageCursor getMessageCursor();
        Account getAccount();
    }

    public MessageCursor(Cursor inner) {
        super(inner, ConversationMessage.FACTORY);
    }

    public void setController(ConversationController controller) {
        mController = controller;
    }

    public ConversationMessage getMessage() {
        final ConversationMessage m = getModel();
        // ALWAYS set up each ConversationMessage with the latest controller.
        // Rotation invalidates everything except this Cursor, its Loader and the cached Messages,
        // so if we want to continue using them after rotate, we have to ensure their controller
        // references always point to the current controller.
        m.setController(mController);
        return m;
    }

    public Conversation getConversation() {
        return mController != null ? mController.getConversation() : null;
    }

    // Is the conversation starred?
    public boolean isConversationStarred() {
        int pos = -1;
        while (moveToPosition(++pos)) {
            if (getMessage().starred) {
                return true;
            }
        }
        return false;
    }


    public boolean isConversationRead() {
        int pos = -1;
        while (moveToPosition(++pos)) {
            if (!getMessage().read) {
                return false;
            }
        }
        return true;
    }
    public void markMessagesRead() {
        int pos = -1;
        while (moveToPosition(++pos)) {
            getMessage().read = true;
        }
    }

    public ConversationMessage getMessageForId(long id) {
        if (isClosed()) {
            return null;
        }

        int pos = -1;
        while (moveToPosition(++pos)) {
            final ConversationMessage m = getMessage();
            if (id == m.id) {
                return m;
            }
        }
        return null;
    }

    public int getStateHashCode() {
        return getStateHashCode(0);
    }

    /**
     * Calculate a hash code that compactly summarizes the state of the messages in this cursor,
     * with respect to the way the messages are displayed in conversation view. This is not a
     * general-purpose hash code. When the state hash codes of a new cursor differs from the
     * existing cursor's hash code, the conversation view will re-render from scratch.
     *
     * @param exceptLast optional number of messages to exclude iterating through at the end of the
     * cursor. pass zero to iterate through all messages (or use {@link #getStateHashCode()}).
     * @return state hash code of the selected messages in this cursor
     */
    public int getStateHashCode(int exceptLast) {
        int hashCode = 17;
        int pos = -1;
        final int stopAt = getCount() - exceptLast;
        while (moveToPosition(++pos) && pos < stopAt) {
            hashCode = 31 * hashCode + getMessage().getStateHashCode();
        }
        return hashCode;
    }

    public int getStatus() {
        if (mStatus != null) {
            return mStatus;
        }

        mStatus = CursorStatus.LOADED;
        final Bundle extras = getExtras();
        if (extras != null && extras.containsKey(CursorExtraKeys.EXTRA_STATUS)) {
            mStatus = extras.getInt(CursorExtraKeys.EXTRA_STATUS);
        }
        return mStatus;
    }

    /**
     * Returns true if the cursor is fully loaded. Returns false if the cursor is expected to get
     * new messages.
     * @return
     */
    public boolean isLoaded() {
        return !CursorStatus.isWaitingForResults(getStatus());
    }

    public String getDebugDump() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("conv='%s' status=%d messages:\n",
                mController.getConversation(), getStatus()));
        int pos = -1;
        while (moveToPosition(++pos)) {
            final ConversationMessage m = getMessage();
            final List<Uri> attUris = Lists.newArrayList();
            for (Attachment a : m.getAttachments()) {
                attUris.add(a.uri);
            }
            sb.append(String.format(
                    "[Message #%d hash=%s uri=%s id=%s serverId=%s from='%s' draftType=%d" +
                    " sendingState=%s read=%s starred=%s attUris=%s]\n",
                    pos, m.getStateHashCode(), m.uri, m.id, m.serverId, m.getFrom(), m.draftType,
                    m.sendingState, m.read, m.starred, attUris));
        }
        return sb.toString();
    }

}