<commit_before>/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2010-2014 Carnegie Mellon University
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.cmu.cylab.starslinger.view;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import android.app.Activity;
import android.app.Dialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.SQLException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import edu.cmu.cylab.starslinger.GeneralException;
import edu.cmu.cylab.starslinger.MyLog;
import edu.cmu.cylab.starslinger.R;
import edu.cmu.cylab.starslinger.SafeSlinger;
import edu.cmu.cylab.starslinger.SafeSlingerConfig;
import edu.cmu.cylab.starslinger.SafeSlingerConfig.extra;
import edu.cmu.cylab.starslinger.SafeSlingerPrefs;
import edu.cmu.cylab.starslinger.crypto.CryptTools;
import edu.cmu.cylab.starslinger.crypto.CryptoMsgException;
import edu.cmu.cylab.starslinger.model.InboxDbAdapter;
import edu.cmu.cylab.starslinger.model.MessageData;
import edu.cmu.cylab.starslinger.model.MessageDateAscendingComparator;
import edu.cmu.cylab.starslinger.model.MessageDbAdapter;
import edu.cmu.cylab.starslinger.model.MessagePacket;
import edu.cmu.cylab.starslinger.model.MessageRow;
import edu.cmu.cylab.starslinger.model.MessageRow.MsgAction;
import edu.cmu.cylab.starslinger.model.RecipientDbAdapter;
import edu.cmu.cylab.starslinger.model.RecipientRow;
import edu.cmu.cylab.starslinger.model.ThreadData;
import edu.cmu.cylab.starslinger.model.ThreadDateDecendingComparator;
import edu.cmu.cylab.starslinger.util.SSUtil;

public class MessagesFragment extends Fragment {
    private static final String TAG = SafeSlingerConfig.LOG_TAG;
    public static final int RESULT_GETFILE = 7124;
    public static final int RESULT_GETMESSAGE = 7125;
    public static final int RESULT_EDITMESSAGE = 7126;
    public static final int RESULT_FWDMESSAGE = 7127;
    public static final int RESULT_SEND = 7128;
    public static final int RESULT_SAVE = 7129;
    public static final int RESULT_PROCESS_SSMIME = 7130;

    private List<ThreadData> mThreadList = new ArrayList<ThreadData>();
    private List<MessageRow> mMessageList = new ArrayList<MessageRow>();
    private TextView mTvInstruct;
    private ListView mListViewMsgs;
    private ListView mListViewThreads;
    private MessagesAdapter mAdapterMsg;
    private ThreadsAdapter mAdapterThread;
    private NotificationManager mNm;
    private OnMessagesResultListener mResult;
    private static RecipientRow mRecip;
    private static int mListMsgVisiblePos;
    private static int mListMsgTopOffset;
    private static int mListThreadVisiblePos;
    private static int mListThreadTopOffset;
    private static EditText mEditTextMessage;
    private Button mButtonSend;
    private LinearLayout mComposeWidget;
    private static MessageData mDraft;
    // private updateListTask mListUpdateTask = null;
    private Handler mMsgFragmentHandler = new Handler();

    private boolean recentMsg = false;

    private enum LIST_TYPE {
        THREAD, MSG
    }

    private enum MESSAGE_OPERATIONS {
        UPDATE_NOTIFICATION, CLEAR_LIST, ADD_DRAFT, REMOVE_DRAFT, ADD_THREADS, ADD_MESSAGES, SORT_THREADS, SORT_MESSAGES
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // updateValues(savedInstanceState); TODO: check for any side effects
        // un-comment if necessary

        String ns = Context.NOTIFICATION_SERVICE;
        mNm = (NotificationManager) this.getActivity().getSystemService(ns);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        doSave(mEditTextMessage.getText().toString(), mRecip != null);

        // save
        if (mDraft != null && mDraft.getRowId() != -1) {
            outState.putLong(extra.MESSAGE_ROW_ID, mDraft.getRowId());
        }
        if (mRecip != null && mRecip.getRowId() != -1) {
            outState.putLong(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
        }
    }

    public void updateValues(Bundle extras) {
        long msgRowId = -1;
        long recipRowId = 1;

        if (extras != null) {
            msgRowId = extras.getLong(extra.MESSAGE_ROW_ID, -1L);
            recipRowId = extras.getLong(extra.RECIPIENT_ROW_ID, -1L);

            // set position to top when incoming message in
            // background...
            InboxDbAdapter dbInbox = InboxDbAdapter.openInstance(this.getActivity());
            MessageDbAdapter dbMessage = MessageDbAdapter.openInstance(this.getActivity());
            int inCount = dbInbox.getUnseenInboxCount();
            int msgCount = dbMessage.getUnseenMessageCount();
            int allCount = inCount + msgCount;
            if (allCount > 0) {
                mListThreadTopOffset = 0;
                mListThreadVisiblePos = 0;
            }

            RecipientDbAdapter dbRecipient = RecipientDbAdapter.openInstance(this.getActivity());
            if (recipRowId != -1) {
                Cursor c = dbRecipient.fetchRecipient(recipRowId);
                if (c != null) {
                    try {
                        if (c.moveToFirst()) {
                            mRecip = new RecipientRow(c);
                        }
                    } finally {
                        c.close();
                    }
                }
            } else if (msgRowId == -1) {
                mRecip = null;
            }
        }

        recentMsg = msgRowId != -1;
        new updateListTask(recentMsg).execute();
        recentMsg = false;
        // updateMessageList(msgRowId != -1);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View vFrag = inflater.inflate(R.layout.messagelist, container, false);

        mTvInstruct = (TextView) vFrag.findViewById(R.id.tvInstruct);

        mListViewThreads = (ListView) vFrag.findViewById(R.id.listThread);
        mListViewThreads.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // nothing to do...
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                    int totalItemCount) {
                // save list position
                if (visibleItemCount != 0) {
                    mListThreadVisiblePos = firstVisibleItem;
                    View v = mListViewThreads.getChildAt(0);
                    mListThreadTopOffset = (v == null) ? 0 : v.getTop();
                }
            }
        });

        mListViewMsgs = (ListView) vFrag.findViewById(R.id.listMessage);
        mListViewMsgs.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // nothing to do...
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                    int totalItemCount) {
                // save list position
                if (visibleItemCount != 0) {
                    mListMsgVisiblePos = firstVisibleItem;
                    View v = mListViewMsgs.getChildAt(0);
                    mListMsgTopOffset = (v == null) ? 0 : v.getTop();
                }
            }
        });

        mComposeWidget = (LinearLayout) vFrag.findViewById(R.id.ComposeLayout);

        mEditTextMessage = (EditText) vFrag.findViewById(R.id.SendEditTextMessage);

        mButtonSend = (Button) vFrag.findViewById(R.id.SendButtonSend);
        mButtonSend.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // requested send from send button...
                doSend(mEditTextMessage.getText().toString(), mRecip != null);
            }
        });

        mEditTextMessage.setOnEditorActionListener(new OnEditorActionListener() {

            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEND) {
                    // requested send from keyboard...
                    doSend(mEditTextMessage.getText().toString(), mRecip != null);
                    return true;
                }
                return false;
            }
        });

        recentMsg = false;
        // updateMessageList(false);
        // new updateListTask(false).execute();
        return vFrag;
    }

    private void doSave(String text, boolean save) {
        Intent intent = new Intent();
        if (save) {
            intent.putExtra(extra.TEXT_MESSAGE, text.trim());
            if (mDraft != null) {
                intent.putExtra(extra.MESSAGE_ROW_ID, mDraft.getRowId());
            }
            if (mRecip != null) {
                intent.putExtra(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
            }
            // always keep local version, unless we need to delete
            if (mDraft != null && !isSendableText()) {
                mDraft = null;
                mEditTextMessage.setTextKeepState("");
            }
            sendResultToHost(RESULT_SAVE, intent.getExtras());
        }
    }

    private void doSend(String text, boolean send) {
        Intent intent = new Intent();
        if (send && isSendableText()) {
            // recipient required to send anything
            if (mDraft != null) {
                intent.putExtra(extra.MESSAGE_ROW_ID, mDraft.getRowId());
            }
            intent.putExtra(extra.TEXT_MESSAGE, text.trim());
            if (mRecip != null) {
                intent.putExtra(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
            }
            // remove local version after sending
            mDraft = null;
            mEditTextMessage.setTextKeepState("");
            sendResultToHost(RESULT_SEND, intent.getExtras());
        }
    }

    private void doGetMessage(MessageRow inbox) {

        long inboxRowId = -1;
        if (inbox.isInboxTable()) {
            inboxRowId = inbox.getRowId();
        } else {
            // TODO deprecate this old scheme handling in next release
            // move message from old table if we still need to download,
            MessageDbAdapter dbMessage = MessageDbAdapter.openInstance(this.getActivity());
            InboxDbAdapter dbInbox = InboxDbAdapter.openInstance(this.getActivity());
            final int seen = inbox.isSeen() ? MessageDbAdapter.MESSAGE_IS_SEEN
                    : MessageDbAdapter.MESSAGE_IS_NOT_SEEN;
            inboxRowId = dbInbox.createRecvEncInbox(inbox.getMsgHash(), inbox.getStatus(), seen);
            dbMessage.deleteMessage(inbox.getRowId());
        }

        Intent intent = new Intent();
        intent.putExtra(extra.PUSH_MSG_HASH, inbox.getMsgHash());
        intent.putExtra(extra.INBOX_ROW_ID, inboxRowId);
        sendResultToHost(RESULT_GETMESSAGE, intent.getExtras());
    }

    private void doForward(MessageRow msg) {
        Intent intent = new Intent();
        intent.putExtra(extra.MESSAGE_ROW_ID, msg.getRowId());
        sendResultToHost(RESULT_FWDMESSAGE, intent.getExtras());
    }

    public void doEditMessage(MessageRow msg) {
        Intent intent = new Intent();
        intent.putExtra(extra.MESSAGE_ROW_ID, msg.getRowId());
        if (mRecip != null)
            intent.putExtra(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
        sendResultToHost(RESULT_EDITMESSAGE, intent.getExtras());
    }

    public void doGetFile(MessageRow msg) {
        Intent intent = new Intent();
        intent.putExtra(extra.PUSH_MSG_HASH, msg.getMsgHash());
        intent.putExtra(extra.PUSH_FILE_NAME, msg.getFileName());
        intent.putExtra(extra.PUSH_FILE_TYPE, msg.getFileType());
        intent.putExtra(extra.PUSH_FILE_SIZE, msg.getFileSize());
        intent.putExtra(extra.MESSAGE_ROW_ID, msg.getRowId());
        sendResultToHost(RESULT_GETFILE, intent.getExtras());
    }

    private void setThreadListClickListener() {
        mListViewThreads.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {

                ThreadData t = mThreadList.get(pos);
                if (mRecip == null) {
                    // requested messages list
                    // assign recipient
                    if (TextUtils.isEmpty(t.getMsgRow().getKeyId())) {
                        // able to view null key id messages
                        mRecip = RecipientRow.createEmptyRecipient();
                    } else {
                        RecipientDbAdapter dbRecipient = RecipientDbAdapter
                                .openInstance(getActivity().getApplicationContext());
                        Cursor c = dbRecipient.fetchRecipientByKeyId(t.getMsgRow().getKeyId());
                        if (c != null) {
                            try {
                                if (c.moveToFirst()) {
                                    // messages with matching key ids in
                                    // database
                                    mRecip = new RecipientRow(c);
                                } else {
                                    // messages without matching key ids
                                    mRecip = RecipientRow.createKeyIdOnlyRecipient(t.getMsgRow()
                                            .getKeyId());
                                }
                            } finally {
                                c.close();
                            }
                        }
                    }
                } else {
                    // requested threads list
                    // remove recipient
                    mRecip = null;
                }
                // updateMessageList(true);
                new updateListTask(true).execute();
            }
        });
    }

    private void setMessageListClickListener() {
        mListViewMsgs.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
                String pass = <PASSWORD>.<PASSWORD>PassPhrase(SafeSlingerPrefs.getKeyIdString());

                // construct activity to download message...
                MessageRow msg = mMessageList.get(pos);
                MsgAction action = msg.getMessageAction();

                // ensure last item remains fully in view
                if (pos == mMessageList.size() - 1) {
                    mListMsgTopOffset = 0;
                    mListMsgVisiblePos = pos;
                }

                switch (action) {
                    case DISPLAY_ONLY:
                    case MSG_PROGRESS:
                    case MSG_EXPIRED:
                        // no action...
                        break;
                    case MSG_EDIT:
                        doEditMessage(msg);
                        break;
                    case MSG_DOWNLOAD:
                        doGetMessage(msg);
                        break;
                    case MSG_DECRYPT:
                        doDecryptMessage(pass, msg);
                        break;
                    case FILE_DOWNLOAD_DECRYPT:
                        doGetFile(msg);
                        break;
                    case FILE_OPEN:
                        doOpenFile(msg);
                        break;
                    default:
                        break;
                }

                // updateMessageList(false);
                new updateListTask(false).execute();
            }
        });
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setThreadListClickListener();
        setMessageListClickListener();
    }

    private void setUpThreadListAdapters() {
        mMsgFragmentHandler.removeCallbacks(null);
        mMsgFragmentHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                unregisterForContextMenu(mListViewMsgs);
                unregisterForContextMenu(mListViewThreads);

                mAdapterThread = new ThreadsAdapter(getActivity(), mThreadList);
                mListViewThreads.setAdapter(mAdapterThread);

                mListViewThreads.setSelectionFromTop(mListThreadVisiblePos, mListThreadTopOffset);

                mAdapterMsg = new MessagesAdapter(getActivity(), mMessageList);
                mListViewMsgs.setAdapter(mAdapterMsg);

                mListViewMsgs.setSelectionFromTop(mListMsgVisiblePos, mListMsgTopOffset);

                registerForContextMenu(mListViewMsgs);
                registerForContextMenu(mListViewThreads);
            }
        }, 225);

    }

    private class updateListTask extends AsyncTask<Void, Object, Boolean> {

        boolean showCompose = false;
        MessageDbAdapter dbMessage;
        InboxDbAdapter dbInbox;
        ThreadData t = null;
        byte[] myPhoto = null;
        boolean recentMsg;

        public updateListTask(boolean recentMsg) {
            dbMessage = MessageDbAdapter.openInstance(getActivity());
            dbInbox = InboxDbAdapter.openInstance(getActivity());
            this.recentMsg = recentMsg;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // make sure view is already inflated...
            if (mListViewMsgs == null) {
                cancel(true);
                return;
            }
        }

        @Override
        protected Boolean doInBackground(Void... params) {

            if (isCancelled())
                return false;

            String contactLookupKey = SafeSlingerPrefs.getContactLookupKey();
            myPhoto = ((BaseActivity) getActivity()).getContactPhoto(contactLookupKey);

            // MessageDbAdapter dbMessage =
            // MessageDbAdapter.openInstance(getActivity());
            // InboxDbAdapter dbInbox =
            // InboxDbAdapter.openInstance(getActivity());

            if (isResumed() && mRecip != null) {
                // when shown, current thread all are now seen
                dbMessage.updateAllMessagesAsSeenByThread(mRecip.getKeyid());
                dbInbox.updateAllInboxAsSeenByThread(mRecip.getKeyid());

                // remove notify when every unseen thread has been seen
                int inCount = dbInbox.getUnseenInboxCount();
                int msgCount = dbMessage.getUnseenMessageCount();
                int allCount = inCount + msgCount;
                publishProgress(MESSAGE_OPERATIONS.UPDATE_NOTIFICATION, allCount);
            }
            manageTextViewVisibility(View.GONE);
            setUpThreadList();
            setUpMsgList();

            return true;
        }

        @Override
        protected void onProgressUpdate(final Object... values) {
            super.onProgressUpdate(values);
            if (values != null && values.length > 0) {
                Object o = values[0];
                if (o instanceof MESSAGE_OPERATIONS) {
                    switch ((MESSAGE_OPERATIONS) o) {
                        case UPDATE_NOTIFICATION:
                            updateNotification((Integer) values[1]);
                            break;
                        case CLEAR_LIST:
                            clearThreadsUI((LIST_TYPE) values[1]);
                            break;
                        case ADD_DRAFT:
                            mEditTextMessage.setTextKeepState(mDraft.getText());
                            mEditTextMessage.forceLayout();
                            break;
                        case REMOVE_DRAFT:
                            doSave(mEditTextMessage.getText().toString(), true);
                            mDraft = null;
                            mEditTextMessage.setTextKeepState("");
                            break;
                        case ADD_THREADS:
                            mergeInThreads((ThreadData) values[1]);
                            // if(values[1] instanceof Integer)
                            // {
                            // mThreadList.add((Integer)values[1],
                            // (ThreadData)values[2]);
                            // }
                            // else
                            // mThreadList.add((ThreadData)values[1]);
                            break;
                        case ADD_MESSAGES:
                            mMessageList.add((MessageRow) values[1]);
                            break;
                        case SORT_THREADS:
                            // mMsgFragmentHandler.removeCallbacks(null);
                            // mMsgFragmentHandler.postDelayed(new Runnable() {
                            //
                            // @Override
                            // public void run() {
                            Collections.sort(mThreadList, new ThreadDateDecendingComparator());
                            //
                            // }
                            // }, 1000);
                            break;

                        case SORT_MESSAGES:
                            // mMsgFragmentHandler.removeCallbacks(null);
                            // mMsgFragmentHandler.postDelayed(new Runnable() {
                            //
                            // @Override
                            // public void run() {
                            Collections.sort(mMessageList, new MessageDateAscendingComparator());
                            //
                            // }
                            // }, 100);
                            break;
                    }
                }

            }

        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);

            if (result) {
                if (showCompose) {
                    mComposeWidget.setVisibility(View.VISIBLE);
                } else {
                    mComposeWidget.setVisibility(View.GONE);
                }

                // set position to top when incoming message in foreground...
                if (recentMsg) {
                    mListMsgTopOffset = 0;
                    mListMsgVisiblePos = mMessageList.size() - 1;
                }

                setUpThreadListAdapters();
                // unregisterForContextMenu(mListViewMsgs);
                // unregisterForContextMenu(mListViewThreads);<commit_msg>added a progress for messages tab UI loading<commit_after>/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2010-2014 Carnegie Mellon University
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.cmu.cylab.starslinger.view;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import android.app.Activity;
import android.app.Dialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.SQLException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import edu.cmu.cylab.starslinger.GeneralException;
import edu.cmu.cylab.starslinger.MyLog;
import edu.cmu.cylab.starslinger.R;
import edu.cmu.cylab.starslinger.SafeSlinger;
import edu.cmu.cylab.starslinger.SafeSlingerConfig;
import edu.cmu.cylab.starslinger.SafeSlingerConfig.extra;
import edu.cmu.cylab.starslinger.SafeSlingerPrefs;
import edu.cmu.cylab.starslinger.crypto.CryptTools;
import edu.cmu.cylab.starslinger.crypto.CryptoMsgException;
import edu.cmu.cylab.starslinger.model.InboxDbAdapter;
import edu.cmu.cylab.starslinger.model.MessageData;
import edu.cmu.cylab.starslinger.model.MessageDateAscendingComparator;
import edu.cmu.cylab.starslinger.model.MessageDbAdapter;
import edu.cmu.cylab.starslinger.model.MessagePacket;
import edu.cmu.cylab.starslinger.model.MessageRow;
import edu.cmu.cylab.starslinger.model.MessageRow.MsgAction;
import edu.cmu.cylab.starslinger.model.RecipientDbAdapter;
import edu.cmu.cylab.starslinger.model.RecipientRow;
import edu.cmu.cylab.starslinger.model.ThreadData;
import edu.cmu.cylab.starslinger.model.ThreadDateDecendingComparator;
import edu.cmu.cylab.starslinger.util.SSUtil;

public class MessagesFragment extends Fragment {
    private static final String TAG = SafeSlingerConfig.LOG_TAG;
    public static final int RESULT_GETFILE = 7124;
    public static final int RESULT_GETMESSAGE = 7125;
    public static final int RESULT_EDITMESSAGE = 7126;
    public static final int RESULT_FWDMESSAGE = 7127;
    public static final int RESULT_SEND = 7128;
    public static final int RESULT_SAVE = 7129;
    public static final int RESULT_PROCESS_SSMIME = 7130;

    private List<ThreadData> mThreadList = new ArrayList<ThreadData>();
    private List<MessageRow> mMessageList = new ArrayList<MessageRow>();
    private TextView mTvInstruct;
    private LinearLayout mLayoutLoadProgress;
    private ListView mListViewMsgs;
    private ListView mListViewThreads;
    private MessagesAdapter mAdapterMsg;
    private ThreadsAdapter mAdapterThread;
    private NotificationManager mNm;
    private OnMessagesResultListener mResult;
    private static RecipientRow mRecip;
    private static int mListMsgVisiblePos;
    private static int mListMsgTopOffset;
    private static int mListThreadVisiblePos;
    private static int mListThreadTopOffset;
    private static EditText mEditTextMessage;
    private Button mButtonSend;
    private LinearLayout mComposeWidget;
    private static MessageData mDraft;
    // private updateListTask mListUpdateTask = null;
    private Handler mMsgFragmentHandler = new Handler();

    private boolean recentMsg = false;

    private enum LIST_TYPE {
        THREAD, MSG
    }

    private enum MESSAGE_OPERATIONS {
        UPDATE_NOTIFICATION, CLEAR_LIST, ADD_DRAFT, REMOVE_DRAFT, ADD_THREADS, ADD_MESSAGES, SORT_THREADS, SORT_MESSAGES
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // updateValues(savedInstanceState); TODO: check for any side effects
        // un-comment if necessary

        String ns = Context.NOTIFICATION_SERVICE;
        mNm = (NotificationManager) this.getActivity().getSystemService(ns);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        doSave(mEditTextMessage.getText().toString(), mRecip != null);

        // save
        if (mDraft != null && mDraft.getRowId() != -1) {
            outState.putLong(extra.MESSAGE_ROW_ID, mDraft.getRowId());
        }
        if (mRecip != null && mRecip.getRowId() != -1) {
            outState.putLong(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
        }
    }

    public void updateValues(Bundle extras) {
        long msgRowId = -1;
        long recipRowId = 1;

        if (extras != null) {
            msgRowId = extras.getLong(extra.MESSAGE_ROW_ID, -1L);
            recipRowId = extras.getLong(extra.RECIPIENT_ROW_ID, -1L);

            // set position to top when incoming message in
            // background...
            InboxDbAdapter dbInbox = InboxDbAdapter.openInstance(this.getActivity());
            MessageDbAdapter dbMessage = MessageDbAdapter.openInstance(this.getActivity());
            int inCount = dbInbox.getUnseenInboxCount();
            int msgCount = dbMessage.getUnseenMessageCount();
            int allCount = inCount + msgCount;
            if (allCount > 0) {
                mListThreadTopOffset = 0;
                mListThreadVisiblePos = 0;
            }

            RecipientDbAdapter dbRecipient = RecipientDbAdapter.openInstance(this.getActivity());
            if (recipRowId != -1) {
                Cursor c = dbRecipient.fetchRecipient(recipRowId);
                if (c != null) {
                    try {
                        if (c.moveToFirst()) {
                            mRecip = new RecipientRow(c);
                        }
                    } finally {
                        c.close();
                    }
                }
            } else if (msgRowId == -1) {
                mRecip = null;
            }
        }

        recentMsg = msgRowId != -1;
        new updateListTask(recentMsg).execute();
        recentMsg = false;
        // updateMessageList(msgRowId != -1);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View vFrag = inflater.inflate(R.layout.messagelist, container, false);

        mTvInstruct = (TextView) vFrag.findViewById(R.id.tvInstruct);

        mLayoutLoadProgress = (LinearLayout) vFrag.findViewById(R.id.layoutLoadProgress);

        mListViewThreads = (ListView) vFrag.findViewById(R.id.listThread);
        mListViewThreads.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // nothing to do...
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                    int totalItemCount) {
                // save list position
                if (visibleItemCount != 0) {
                    mListThreadVisiblePos = firstVisibleItem;
                    View v = mListViewThreads.getChildAt(0);
                    mListThreadTopOffset = (v == null) ? 0 : v.getTop();
                }
            }
        });

        mListViewMsgs = (ListView) vFrag.findViewById(R.id.listMessage);
        mListViewMsgs.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // nothing to do...
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                    int totalItemCount) {
                // save list position
                if (visibleItemCount != 0) {
                    mListMsgVisiblePos = firstVisibleItem;
                    View v = mListViewMsgs.getChildAt(0);
                    mListMsgTopOffset = (v == null) ? 0 : v.getTop();
                }
            }
        });

        mComposeWidget = (LinearLayout) vFrag.findViewById(R.id.ComposeLayout);

        mEditTextMessage = (EditText) vFrag.findViewById(R.id.SendEditTextMessage);

        mButtonSend = (Button) vFrag.findViewById(R.id.SendButtonSend);
        mButtonSend.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // requested send from send button...
                doSend(mEditTextMessage.getText().toString(), mRecip != null);
            }
        });

        mEditTextMessage.setOnEditorActionListener(new OnEditorActionListener() {

            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEND) {
                    // requested send from keyboard...
                    doSend(mEditTextMessage.getText().toString(), mRecip != null);
                    return true;
                }
                return false;
            }
        });

        recentMsg = false;
        // updateMessageList(false);
        // new updateListTask(false).execute();
        return vFrag;
    }

    private void doSave(String text, boolean save) {
        Intent intent = new Intent();
        if (save) {
            intent.putExtra(extra.TEXT_MESSAGE, text.trim());
            if (mDraft != null) {
                intent.putExtra(extra.MESSAGE_ROW_ID, mDraft.getRowId());
            }
            if (mRecip != null) {
                intent.putExtra(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
            }
            // always keep local version, unless we need to delete
            if (mDraft != null && !isSendableText()) {
                mDraft = null;
                mEditTextMessage.setTextKeepState("");
            }
            sendResultToHost(RESULT_SAVE, intent.getExtras());
        }
    }

    private void doSend(String text, boolean send) {
        Intent intent = new Intent();
        if (send && isSendableText()) {
            // recipient required to send anything
            if (mDraft != null) {
                intent.putExtra(extra.MESSAGE_ROW_ID, mDraft.getRowId());
            }
            intent.putExtra(extra.TEXT_MESSAGE, text.trim());
            if (mRecip != null) {
                intent.putExtra(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
            }
            // remove local version after sending
            mDraft = null;
            mEditTextMessage.setTextKeepState("");
            sendResultToHost(RESULT_SEND, intent.getExtras());
        }
    }

    private void doGetMessage(MessageRow inbox) {

        long inboxRowId = -1;
        if (inbox.isInboxTable()) {
            inboxRowId = inbox.getRowId();
        } else {
            // TODO deprecate this old scheme handling in next release
            // move message from old table if we still need to download,
            MessageDbAdapter dbMessage = MessageDbAdapter.openInstance(this.getActivity());
            InboxDbAdapter dbInbox = InboxDbAdapter.openInstance(this.getActivity());
            final int seen = inbox.isSeen() ? MessageDbAdapter.MESSAGE_IS_SEEN
                    : MessageDbAdapter.MESSAGE_IS_NOT_SEEN;
            inboxRowId = dbInbox.createRecvEncInbox(inbox.getMsgHash(), inbox.getStatus(), seen);
            dbMessage.deleteMessage(inbox.getRowId());
        }

        Intent intent = new Intent();
        intent.putExtra(extra.PUSH_MSG_HASH, inbox.getMsgHash());
        intent.putExtra(extra.INBOX_ROW_ID, inboxRowId);
        sendResultToHost(RESULT_GETMESSAGE, intent.getExtras());
    }

    private void doForward(MessageRow msg) {
        Intent intent = new Intent();
        intent.putExtra(extra.MESSAGE_ROW_ID, msg.getRowId());
        sendResultToHost(RESULT_FWDMESSAGE, intent.getExtras());
    }

    public void doEditMessage(MessageRow msg) {
        Intent intent = new Intent();
        intent.putExtra(extra.MESSAGE_ROW_ID, msg.getRowId());
        if (mRecip != null)
            intent.putExtra(extra.RECIPIENT_ROW_ID, mRecip.getRowId());
        sendResultToHost(RESULT_EDITMESSAGE, intent.getExtras());
    }

    public void doGetFile(MessageRow msg) {
        Intent intent = new Intent();
        intent.putExtra(extra.PUSH_MSG_HASH, msg.getMsgHash());
        intent.putExtra(extra.PUSH_FILE_NAME, msg.getFileName());
        intent.putExtra(extra.PUSH_FILE_TYPE, msg.getFileType());
        intent.putExtra(extra.PUSH_FILE_SIZE, msg.getFileSize());
        intent.putExtra(extra.MESSAGE_ROW_ID, msg.getRowId());
        sendResultToHost(RESULT_GETFILE, intent.getExtras());
    }

    private void setThreadListClickListener() {
        mListViewThreads.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {

                ThreadData t = mThreadList.get(pos);
                if (mRecip == null) {
                    // requested messages list
                    // assign recipient
                    if (TextUtils.isEmpty(t.getMsgRow().getKeyId())) {
                        // able to view null key id messages
                        mRecip = RecipientRow.createEmptyRecipient();
                    } else {
                        RecipientDbAdapter dbRecipient = RecipientDbAdapter
                                .openInstance(getActivity().getApplicationContext());
                        Cursor c = dbRecipient.fetchRecipientByKeyId(t.getMsgRow().getKeyId());
                        if (c != null) {
                            try {
                                if (c.moveToFirst()) {
                                    // messages with matching key ids in
                                    // database
                                    mRecip = new RecipientRow(c);
                                } else {
                                    // messages without matching key ids
                                    mRecip = RecipientRow.createKeyIdOnlyRecipient(t.getMsgRow()
                                            .getKeyId());
                                }
                            } finally {
                                c.close();
                            }
                        }
                    }
                } else {
                    // requested threads list
                    // remove recipient
                    mRecip = null;
                }
                // updateMessageList(true);
                new updateListTask(true).execute();
            }
        });
    }

    private void setMessageListClickListener() {
        mListViewMsgs.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
                String pass = SafeSlinger.getCachedPassPhrase(SafeSlingerPrefs.getKeyIdString());

                // construct activity to download message...
                MessageRow msg = mMessageList.get(pos);
                MsgAction action = msg.getMessageAction();

                // ensure last item remains fully in view
                if (pos == mMessageList.size() - 1) {
                    mListMsgTopOffset = 0;
                    mListMsgVisiblePos = pos;
                }

                switch (action) {
                    case DISPLAY_ONLY:
                    case MSG_PROGRESS:
                    case MSG_EXPIRED:
                        // no action...
                        break;
                    case MSG_EDIT:
                        doEditMessage(msg);
                        break;
                    case MSG_DOWNLOAD:
                        doGetMessage(msg);
                        break;
                    case MSG_DECRYPT:
                        doDecryptMessage(pass, msg);
                        break;
                    case FILE_DOWNLOAD_DECRYPT:
                        doGetFile(msg);
                        break;
                    case FILE_OPEN:
                        doOpenFile(msg);
                        break;
                    default:
                        break;
                }

                // updateMessageList(false);
                new updateListTask(false).execute();
            }
        });
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setThreadListClickListener();
        setMessageListClickListener();
    }

    private void setUpThreadListAdapters() {
        mMsgFragmentHandler.removeCallbacks(null);
        mMsgFragmentHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                unregisterForContextMenu(mListViewMsgs);
                unregisterForContextMenu(mListViewThreads);

                mAdapterThread = new ThreadsAdapter(getActivity(), mThreadList);
                mListViewThreads.setAdapter(mAdapterThread);

                mListViewThreads.setSelectionFromTop(mListThreadVisiblePos, mListThreadTopOffset);

                mAdapterMsg = new MessagesAdapter(getActivity(), mMessageList);
                mListViewMsgs.setAdapter(mAdapterMsg);

                mListViewMsgs.setSelectionFromTop(mListMsgVisiblePos, mListMsgTopOffset);

                registerForContextMenu(mListViewMsgs);
                registerForContextMenu(mListViewThreads);
            }
        }, 225);

    }

    private class updateListTask extends AsyncTask<Void, Object, Boolean> {

        boolean showCompose = false;
        MessageDbAdapter dbMessage;
        InboxDbAdapter dbInbox;
        ThreadData t = null;
        byte[] myPhoto = null;
        boolean recentMsg;

        public updateListTask(boolean recentMsg) {
            dbMessage = MessageDbAdapter.openInstance(getActivity());
            dbInbox = InboxDbAdapter.openInstance(getActivity());
            this.recentMsg = recentMsg;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // show some progress when UI is slow to update
            if (mLayoutLoadProgress != null) {
                mLayoutLoadProgress.setVisibility(View.VISIBLE);
            }

            // make sure view is already inflated...
            if (mListViewMsgs == null) {
                cancel(true);
                return;
            }
        }

        @Override
        protected Boolean doInBackground(Void... params) {

            if (isCancelled())
                return false;

            String contactLookupKey = SafeSlingerPrefs.getContactLookupKey();
            myPhoto = ((BaseActivity) getActivity()).getContactPhoto(contactLookupKey);

            // MessageDbAdapter dbMessage =
            // MessageDbAdapter.openInstance(getActivity());
            // InboxDbAdapter dbInbox =
            // InboxDbAdapter.openInstance(getActivity());

            if (isResumed() && mRecip != null) {
                // when shown, current thread all are now seen
                dbMessage.updateAllMessagesAsSeenByThread(mRecip.getKeyid());
                dbInbox.updateAllInboxAsSeenByThread(mRecip.getKeyid());

                // remove notify when every unseen thread has been seen
                int inCount = dbInbox.getUnseenInboxCount();
                int msgCount = dbMessage.getUnseenMessageCount();
                int allCount = inCount + msgCount;
                publishProgress(MESSAGE_OPERATIONS.UPDATE_NOTIFICATION, allCount);
            }
            manageTextViewVisibility(View.GONE);
            setUpThreadList();
            setUpMsgList();

            return true;
        }

        @Override
        protected void onProgressUpdate(final Object... values) {
            super.onProgressUpdate(values);
            if (values != null && values.length > 0) {
                Object o = values[0];
                if (o instanceof MESSAGE_OPERATIONS) {
                    switch ((MESSAGE_OPERATIONS) o) {
                        case UPDATE_NOTIFICATION:
                            updateNotification((Integer) values[1]);
                            break;
                        case CLEAR_LIST:
                            clearThreadsUI((LIST_TYPE) values[1]);
                            break;
                        case ADD_DRAFT:
                            mEditTextMessage.setTextKeepState(mDraft.getText());
                            mEditTextMessage.forceLayout();
                            break;
                        case REMOVE_DRAFT:
                            doSave(mEditTextMessage.getText().toString(), true);
                            mDraft = null;
                            mEditTextMessage.setTextKeepState("");
                            break;
                        case ADD_THREADS:
                            mergeInThreads((ThreadData) values[1]);
                            // if(values[1] instanceof Integer)
                            // {
                            // mThreadList.add((Integer)values[1],
                            // (ThreadData)values[2]);
                            // }
                            // else
                            // mThreadList.add((ThreadData)values[1]);
                            break;
                        case ADD_MESSAGES:
                            mMessageList.add((MessageRow) values[1]);
                            break;
                        case SORT_THREADS:
                            // mMsgFragmentHandler.removeCallbacks(null);
                            // mMsgFragmentHandler.postDelayed(new Runnable() {
                            //
                            // @Override
                            // public void run() {
                            Collections.sort(mThreadList, new ThreadDateDecendingComparator());
                            //
                            // }
                            // }, 1000);
                            break;

                        case SORT_MESSAGES:
                            // mMsgFragmentHandler.removeCallbacks(null);
                            // mMsgFragmentHandler.postDelayed(new Runnable() {
                            //
                            // @Override
                            // public void run() {
                            Collections.sort(mMessageList, new MessageDateAscendingComparator());
                            //
                            // }
                            // }, 100);
                            break;
                    }
                }

            }

        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);

            // remove progress, UI update complete
            if (mLayoutLoadProgress != null) {
                mLayoutLoadProgress.setVisibility(View.GONE);
            }

            if (result) {
                if (showCompose) {
                    mComposeWidget.setVisibility(View.VISIBLE);
                } else {
                    mComposeWidget.setVisibility(View.GONE);
                }

                // set position to top when incoming message in foreground...
                if (recentMsg) {
                    mListMsgTopOffset = 0;
                    mListMsgVisiblePos = mMessageList.size() - 1;
                }

                setUpThreadListAdapters();
                // unregisterForContextMenu(mListViewMsgs);
                // unregisterForContextMenu(mListViewThreads);
{'fraction_non_alphanumeric': 0.0835244346038273, 'fraction_numerical': 0.003242131899414835, 'mean_word_length': 1.7655677655677655, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6544072', 'n_tokens_mistral': 13375, 'n_tokens_neox': 12399, 'n_words': 2758}