/**
 * One fragment to rule them all (Notes, that is)
 */
package org.wordpress.android.ui.notifications;

import android.app.ListFragment;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.R;
import org.wordpress.android.datasets.NotificationsTable;
import org.wordpress.android.datasets.ReaderCommentTable;
import org.wordpress.android.datasets.ReaderPostTable;
import org.wordpress.android.fluxc.model.CommentStatus;
import org.wordpress.android.models.Note;
import org.wordpress.android.ui.notifications.adapters.NoteBlockAdapter;
import org.wordpress.android.ui.notifications.blocks.BlockType;
import org.wordpress.android.ui.notifications.blocks.CommentUserNoteBlock;
import org.wordpress.android.ui.notifications.blocks.FooterNoteBlock;
import org.wordpress.android.ui.notifications.blocks.HeaderNoteBlock;
import org.wordpress.android.ui.notifications.blocks.NoteBlock;
import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan;
import org.wordpress.android.ui.notifications.blocks.UserNoteBlock;
import org.wordpress.android.ui.reader.ReaderActivityLauncher;
import org.wordpress.android.ui.reader.actions.ReaderPostActions;
import org.wordpress.android.ui.reader.services.ReaderCommentService;
import org.wordpress.android.ui.reader.utils.ReaderUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.JSONUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.widgets.WPNetworkImageView.ImageType;

import java.util.ArrayList;
import java.util.List;

public class NotificationsDetailListFragment extends ListFragment implements NotificationFragment {
    private static final String KEY_NOTE_ID = "noteId";
    private static final String KEY_LIST_POSITION = "listPosition";

    private int mRestoredListPosition;

    private Note mNote;
    private LinearLayout mRootLayout;
    private ViewGroup mFooterView;

    private String mRestoredNoteId;
    private int mBackgroundColor;
    private int mCommentListPosition = ListView.INVALID_POSITION;

    private CommentUserNoteBlock.OnCommentStatusChangeListener mOnCommentStatusChangeListener;
    private NoteBlockAdapter mNoteBlockAdapter;

    public NotificationsDetailListFragment() {
    }

    public static NotificationsDetailListFragment newInstance(final String noteId) {
        NotificationsDetailListFragment fragment = new NotificationsDetailListFragment();
        fragment.setNote(noteId);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_NOTE_ID)) {
            // The note will be set in onResume()
            // See WordPress.deferredInit()
            mRestoredNoteId = savedInstanceState.getString(KEY_NOTE_ID);
            mRestoredListPosition = savedInstanceState.getInt(KEY_LIST_POSITION, 0);
        }
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.notifications_fragment_detail_list, container, false);
        mRootLayout = (LinearLayout)view.findViewById(R.id.notifications_list_root);

        return view;
    }

    @Override
    public void onActivityCreated(Bundle bundle) {
        super.onActivityCreated(bundle);

        mBackgroundColor = getResources().getColor(R.color.white);

        ListView listView = getListView();
        listView.setDivider(null);
        listView.setDividerHeight(0);
        listView.setHeaderDividersEnabled(false);

        if (mFooterView != null) {
           listView.addFooterView(mFooterView);
        }

        reloadNoteBlocks();
    }

    @Override
    public void onResume() {
        super.onResume();

        // Set the note if we retrieved the noteId from savedInstanceState
        if (!TextUtils.isEmpty(mRestoredNoteId)) {
            setNote(mRestoredNoteId);
            reloadNoteBlocks();
            mRestoredNoteId = null;
        }

        if (getNote() == null) {
            showErrorToastAndFinish();
        }
    }

    @Override
    public void onPause() {
        // Stop the reader comment service if it is running
        ReaderCommentService.stopService(getActivity());

        super.onPause();
    }

    @Override
    public Note getNote() {
        return mNote;
    }

    @Override
    public void setNote(String noteId) {
        if (noteId == null) {
            showErrorToastAndFinish();
            return;
        }

        Note note = NotificationsTable.getNoteById(noteId);
        if (note == null) {
            showErrorToastAndFinish();
            return;
        }
        mNote = note;
    }

    private void showErrorToastAndFinish() {
        AppLog.e(AppLog.T.NOTIFS, "Note could not be found.");
        if (getActivity() != null) {
            ToastUtils.showToast(getActivity(), R.string.error_notification_open);
            getActivity().finish();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (mNote != null) {
            outState.putString(KEY_NOTE_ID, mNote.getId());
            outState.putInt(KEY_LIST_POSITION, getListView().getFirstVisiblePosition());
        }

        super.onSaveInstanceState(outState);
    }

    private void reloadNoteBlocks() {
        new LoadNoteBlocksTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    public void setFooterView(ViewGroup footerView) {
        mFooterView = footerView;
    }

    private final NoteBlock.OnNoteBlockTextClickListener mOnNoteBlockTextClickListener = new NoteBlock.OnNoteBlockTextClickListener() {
        @Override
        public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan) {
            if (!isAdded() || !(getActivity() instanceof NotificationsDetailActivity)) return;

            handleNoteBlockSpanClick((NotificationsDetailActivity) getActivity(), clickedSpan);
        }

        @Override
        public void showDetailForNoteIds() {
            if (!isAdded() || mNote == null || !(getActivity() instanceof NotificationsDetailActivity)) {
                return;
            }

            NotificationsDetailActivity detailActivity = (NotificationsDetailActivity)getActivity();
            if (mNote.isCommentReplyType() || (!mNote.isCommentType() && mNote.getCommentId() > 0)) {
                long commentId = mNote.isCommentReplyType() ? mNote.getParentCommentId() : mNote.getCommentId();

                // show comments list if it exists in the reader
                if (ReaderUtils.postAndCommentExists(mNote.getSiteId(), mNote.getPostId(), commentId)) {
                    detailActivity.showReaderCommentsList(mNote.getSiteId(), mNote.getPostId(), commentId);
                } else {
                    detailActivity.showWebViewActivityForUrl(mNote.getUrl());
                }
            } else if (mNote.isFollowType()) {
                detailActivity.showBlogPreviewActivity(mNote.getSiteId());
            } else {
                // otherwise, load the post in the Reader
                detailActivity.showPostActivity(mNote.getSiteId(), mNote.getPostId());
            }
        }

        @Override
        public void showReaderPostComments() {
            if (!isAdded() || mNote == null || mNote.getCommentId() == 0) return;

            ReaderActivityLauncher.showReaderComments(getActivity(), mNote.getSiteId(), mNote.getPostId(), mNote.getCommentId());
        }

        @Override
        public void showSitePreview(long siteId, String siteUrl) {
            if (!isAdded() || mNote == null || !(getActivity() instanceof NotificationsDetailActivity)) {
                return;
            }

            NotificationsDetailActivity detailActivity = (NotificationsDetailActivity)getActivity();
            if (siteId != 0) {
                detailActivity.showBlogPreviewActivity(siteId);
            } else if (!TextUtils.isEmpty(siteUrl)) {
                detailActivity.showWebViewActivityForUrl(siteUrl);
            }
        }

        public void handleNoteBlockSpanClick(NotificationsDetailActivity activity, NoteBlockClickableSpan clickedSpan) {
            switch (clickedSpan.getRangeType()) {
                case SITE:
                    // Show blog preview
                    activity.showBlogPreviewActivity(clickedSpan.getId());
                    break;
                case USER:
                    // Show blog preview
                    activity.showBlogPreviewActivity(clickedSpan.getSiteId());
                    break;
                case POST:
                    // Show post detail
                    activity.showPostActivity(clickedSpan.getSiteId(), clickedSpan.getId());
                    break;
                case COMMENT:
                    // Load the comment in the reader list if it exists, otherwise show a webview
                    if (ReaderUtils.postAndCommentExists(clickedSpan.getSiteId(), clickedSpan.getPostId(), clickedSpan.getId())) {
                        activity.showReaderCommentsList(clickedSpan.getSiteId(), clickedSpan.getPostId(), clickedSpan.getId());
                    } else {
                        activity.showWebViewActivityForUrl(clickedSpan.getUrl());
                    }
                    break;
                case STAT:
                case FOLLOW:
                    // We can open native stats if the site is a wpcom or Jetpack sites
                    activity.showStatsActivityForSite(clickedSpan.getSiteId(), clickedSpan.getRangeType());
                    break;
                case LIKE:
                    if (ReaderPostTable.postExists(clickedSpan.getSiteId(), clickedSpan.getId())) {
                        activity.showReaderPostLikeUsers(clickedSpan.getSiteId(), clickedSpan.getId());
                    } else {
                        activity.showPostActivity(clickedSpan.getSiteId(), clickedSpan.getId());
                    }
                    break;
                default:
                    // We don't know what type of id this is, let's see if it has a URL and push a webview
                    if (!TextUtils.isEmpty(clickedSpan.getUrl())) {
                        activity.showWebViewActivityForUrl(clickedSpan.getUrl());
                    }
            }
        }
    };

    private final UserNoteBlock.OnGravatarClickedListener mOnGravatarClickedListener = new UserNoteBlock.OnGravatarClickedListener() {
        @Override
        public void onGravatarClicked(long siteId, long userId, String siteUrl) {
            if (!isAdded() || !(getActivity() instanceof NotificationsDetailActivity)) return;

            NotificationsDetailActivity detailActivity = (NotificationsDetailActivity)getActivity();
            if (siteId == 0 && !TextUtils.isEmpty(siteUrl)) {
                detailActivity.showWebViewActivityForUrl(siteUrl);
            } else if (siteId != 0) {
                detailActivity.showBlogPreviewActivity(siteId);
            }
        }
    };

    private boolean hasNoteBlockAdapter() {
        return mNoteBlockAdapter != null;
    }


    // Loop through the 'body' items in this note, and create blocks for each.
    private class LoadNoteBlocksTask extends AsyncTask<Void, Boolean, List<NoteBlock>> {

        private boolean mIsBadgeView;

        @Override
        protected List<NoteBlock> doInBackground(Void... params) {
            if (mNote == null) return null;

            requestReaderContentForNote();

            JSONArray bodyArray = mNote.getBody();
            final List<NoteBlock> noteList = new ArrayList<>();

            // Add the note header if one was provided
            if (mNote.getHeader() != null) {
                ImageType imageType = mNote.isFollowType() ? ImageType.BLAVATAR : ImageType.AVATAR;
                HeaderNoteBlock headerNoteBlock = new HeaderNoteBlock(
                        getActivity(),
                        mNote.getHeader(),
                        imageType,
                        mOnNoteBlockTextClickListener,
                        mOnGravatarClickedListener
                );

                headerNoteBlock.setIsComment(mNote.isCommentType());
                noteList.add(headerNoteBlock);
            }

            if (bodyArray != null && bodyArray.length() > 0) {
                for (int i=0; i < bodyArray.length(); i++) {
                    try {
                        JSONObject noteObject = bodyArray.getJSONObject(i);
                        // Determine NoteBlock type and add it to the array
                        NoteBlock noteBlock;
                        String noteBlockTypeString = JSONUtils.queryJSON(noteObject, "type", "");

                        if (BlockType.fromString(noteBlockTypeString) == BlockType.USER) {
                            if (mNote.isCommentType()) {
                                // Set comment position so we can target it later
                                // See refreshBlocksForCommentStatus()
                                mCommentListPosition = i + noteList.size();

                                // We'll snag the next body array item for comment user blocks
                                if (i + 1 < bodyArray.length()) {
                                    JSONObject commentTextBlock = bodyArray.getJSONObject(i + 1);
                                    noteObject.put("comment_text", commentTextBlock);
                                    i++;
                                }

                                // Add timestamp to block for display
                                noteObject.put("timestamp", mNote.getTimestamp());

                                noteBlock = new CommentUserNoteBlock(
                                        getActivity(),
                                        noteObject,
                                        mOnNoteBlockTextClickListener,
                                        mOnGravatarClickedListener
                                );

                                // Set listener for comment status changes, so we can update bg and text colors
                                CommentUserNoteBlock commentUserNoteBlock = (CommentUserNoteBlock)noteBlock;
                                mOnCommentStatusChangeListener = commentUserNoteBlock.getOnCommentChangeListener();
                                commentUserNoteBlock.setCommentStatus(mNote.getCommentStatus());
                                commentUserNoteBlock.configureResources(getActivity());
                            } else {
                                noteBlock = new UserNoteBlock(
                                        getActivity(),
                                        noteObject,
                                        mOnNoteBlockTextClickListener,
                                        mOnGravatarClickedListener
                                );
                            }
                        } else if (isFooterBlock(noteObject)) {
                            noteBlock = new FooterNoteBlock(noteObject, mOnNoteBlockTextClickListener);
                            ((FooterNoteBlock)noteBlock).setClickableSpan(
                                    JSONUtils.queryJSON(noteObject, "ranges[last]", new JSONObject()),
                                    mNote.getType()
                            );
                        } else {
                            noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener);
                        }

                        // Badge notifications apply different colors and formatting
                        if (isAdded() && noteBlock.containsBadgeMediaType()) {
                            mIsBadgeView = true;
                            mBackgroundColor = getActivity().getResources().getColor(R.color.transparent);
                        }

                        if (mIsBadgeView) {
                            noteBlock.setIsBadge();
                        }

                        noteList.add(noteBlock);
                    } catch (JSONException e) {
                        AppLog.e(AppLog.T.NOTIFS, "Invalid note data, could not parse.");
                    }
                }
            }

            return noteList;
        }

        @Override
        protected void onPostExecute(List<NoteBlock> noteList) {
            if (!isAdded() || noteList == null) return;

            if (mIsBadgeView) {
                mRootLayout.setGravity(Gravity.CENTER_VERTICAL);
            }

            if (!hasNoteBlockAdapter()) {
                mNoteBlockAdapter = new NoteBlockAdapter(getActivity(), noteList, mBackgroundColor);
                setListAdapter(mNoteBlockAdapter);
            } else {
                mNoteBlockAdapter.setNoteList(noteList);
            }

            if (mRestoredListPosition > 0) {
                getListView().setSelectionFromTop(mRestoredListPosition, 0);
                mRestoredListPosition = 0;
            }
        }
    }

    private boolean isFooterBlock(JSONObject blockObject) {
        if (mNote == null || blockObject == null) return false;

        if (mNote.isCommentType()) {
            // Check if this is a comment notification that has been replied to
            // The block will not have a type, and its id will match the comment reply id in the Note.
            return (JSONUtils.queryJSON(blockObject, "type", null) == null &&
                    mNote.getCommentReplyId() == JSONUtils.queryJSON(blockObject, "ranges[1].id", 0));
        } else if (mNote.isFollowType() || mNote.isLikeType() ||
                mNote.isCommentLikeType() || mNote.isReblogType()) {
            // User list notifications have a footer if they have 10 or more users in the body
            // The last block will not have a type, so we can use that to determine if it is the footer
            return JSONUtils.queryJSON(blockObject, "type", null) == null;
        }

        return false;
    }

    public void refreshBlocksForCommentStatus(CommentStatus newStatus) {
        if (mOnCommentStatusChangeListener != null) {
            mOnCommentStatusChangeListener.onCommentStatusChanged(newStatus);
            ListView listView = getListView();
            if (listView == null || mCommentListPosition == ListView.INVALID_POSITION) {
                return;
            }

            // Redraw the comment row if it is visible so that the background and text colors update
            // See: http://stackoverflow.com/questions/4075975/redraw-a-single-row-in-a-listview/9987616#9987616
            int firstPosition = listView.getFirstVisiblePosition();
            int endPosition = listView.getLastVisiblePosition();
            for (int i = firstPosition; i < endPosition; i++) {
                if (mCommentListPosition == i) {
                    View view = listView.getChildAt(i - firstPosition);
                    listView.getAdapter().getView(i, view, listView);
                    break;
                }
            }
        }
    }

    // Requests Reader content for certain notification types
    private void requestReaderContentForNote() {
        if (mNote == null || !isAdded()) return;

        // Request the reader post so that loading reader activities will work.
        if (mNote.isUserList() && !ReaderPostTable.postExists(mNote.getSiteId(), mNote.getPostId())) {
            ReaderPostActions.requestBlogPost(mNote.getSiteId(), mNote.getPostId(), null);
        }

        // Request reader comments until we retrieve the comment for this note
        if ((mNote.isCommentLikeType() || mNote.isCommentReplyType() || mNote.isCommentWithUserReply()) &&
                !ReaderCommentTable.commentExists(mNote.getSiteId(), mNote.getPostId(), mNote.getCommentId())) {
            ReaderCommentService.startServiceForComment(getActivity(), mNote.getSiteId(), mNote.getPostId(), mNote.getCommentId());
        }
    }
}
