/*
 * Copyright 2015 Ritesh Kapoor
 *
 * 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.ritesh.idea.plugin.ui.diff;

import com.intellij.openapi.diff.DiffRequest;
import com.intellij.openapi.diff.impl.DiffPanelImpl;
import com.intellij.openapi.diff.impl.DiffUtil;
import com.intellij.openapi.diff.impl.external.FrameDiffTool;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.EditorMouseAdapter;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseEventArea;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.MarkupModel;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.ui.FrameWrapper;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.ritesh.idea.plugin.reviewboard.Review;
import com.ritesh.idea.plugin.reviewboard.Review.File.Comment;
import com.ritesh.idea.plugin.ui.panels.CommentPanel;
import com.ritesh.idea.plugin.ui.panels.CommentsListViewPanel;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JList;
import javax.swing.ListCellRenderer;

/**
 * @author Ritesh
 */
public class CommentsDiffTool extends FrameDiffTool {
    private Review.File file;
    private List<Comment> comments;
    private List<RangeHighlighter> newCommentHighlighters = new ArrayList<>();
    private ListCellRenderer<Comment> listCellRenderer;
    private ActionListener actionListener;

    public CommentsDiffTool(Review.File file, List<Comment> comments) {
        this.file = file;
        this.comments = new ArrayList<>(comments);
        this.listCellRenderer = new ListCellRenderer<Comment>() {
            @Override
            public Component getListCellRendererComponent(JList list, final Comment value, final int index, boolean isSelected, boolean cellHasFocus) {
                return new CommentPanel(value.user, value.text, value.timestamp).getPanel();
            }
        };
    }

    @Override
    public void show(DiffRequest request) {
        final FrameWrapper frameWrapper = new FrameWrapper(request.getProject(), request.getGroupKey());
        final DiffPanelImpl diffPanel = createDiffPanelImpl(request, frameWrapper.getFrame(), frameWrapper);
        final Editor editor = diffPanel.getEditor2();
        updateHighLights(editor);

        editor.addEditorMouseListener(new EditorMouseAdapter() {
            @Override
            public void mouseClicked(EditorMouseEvent e) {
                if (e.getArea() != null && e.getArea().equals(EditorMouseEventArea.LINE_MARKERS_AREA)) {
                    final Point locationOnScreen = e.getMouseEvent().getLocationOnScreen();
                    final int lineNumber = EditorUtil.yPositionToLogicalLine(editor, e.getMouseEvent()) + 1;
                    showCommentsView(locationOnScreen, lineNumber, editor, e);
                }
            }
        });

        DiffUtil.initDiffFrame(request.getProject(), frameWrapper, diffPanel, diffPanel.getComponent());
        frameWrapper.setTitle(request.getWindowTitle());
        frameWrapper.setPreferredFocusedComponent(diffPanel.getComponent());
        frameWrapper.show();
    }

    public void setActionListener(ActionListener actionListener) {
        this.actionListener = actionListener;
    }

    private void showCommentsView(Point locationOnScreen, final int lineNumber, final Editor editor,
            EditorMouseEvent mouseEvent) {
        List<Comment> comments = lineComments(CommentsDiffTool.this.comments).get(lineNumber);

        final CommentsListViewPanel<Comment> commentsListViewPanel = new CommentsListViewPanel(comments, listCellRenderer);
        final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(commentsListViewPanel, null)
                .setTitle("Comment")
                .setMovable(true)
                .setRequestFocus(true)
                .setCancelOnWindowDeactivation(false)
                .setAdText("Hit Ctrl+Enter to add comment & double click comment to delete.")
                .setResizable(true)
                .createPopup();
        popup.show(new RelativePoint(mouseEvent.getMouseEvent()));


        commentsListViewPanel.setListener(new CommentsListViewPanel.CommentListener<Comment>() {
            @Override
            public void onAdd(String value, boolean issueOpened) {
                popup.dispose();
                Comment newComment = new Comment();
                newComment.text = value;
                newComment.firstLine = lineNumber;
                newComment.numberOfLines = 1;
                newComment.file = file;
                newComment.issueOpened = issueOpened;
                CommentsDiffTool.this.comments.add(newComment);
                updateHighLights(editor);
                actionListener.actionPerformed(new ActionEvent(this, 0, null));
            }

            @Override
            public void onDelete(Comment value) {
                if (value != null && value.id == null) {
                    CommentsDiffTool.this.comments.remove(value);
                    updateHighLights(editor);
                }
                popup.dispose();
            }
        });
    }

    private void updateHighLights(Editor editor) {
        MarkupModel markup = editor.getMarkupModel();

        for (RangeHighlighter customRangeHighlighter : newCommentHighlighters) {
            markup.removeHighlighter(customRangeHighlighter);
        }
        newCommentHighlighters.clear();

        int lineCount = markup.getDocument().getLineCount();

        Map<Integer, List<Comment>> lineComments = lineComments(comments);
        for (Map.Entry<Integer, List<Comment>> entry : lineComments.entrySet()) {
            if (entry.getKey() > lineCount) continue;

            boolean hasNewComments = false;
            for (Comment comment : entry.getValue()) {
                if (comment.id == null) {
                    hasNewComments = true;
                    break;
                }
            }

            TextAttributes attributes = new TextAttributes();
            if (hasNewComments) attributes.setBackgroundColor(JBColor.PINK);
            else attributes.setBackgroundColor(JBColor.YELLOW);

            RangeHighlighter rangeHighlighter = markup
                    .addLineHighlighter(entry.getKey() - 1, HighlighterLayer.SELECTION + (hasNewComments ? 2 : 1), attributes);
            rangeHighlighter.setGutterIconRenderer(new CommentGutterIconRenderer());
            newCommentHighlighters.add(rangeHighlighter);
        }
    }

    private Map<Integer, List<Comment>> lineComments(List<Comment> comments) {
        Map<Integer, List<Comment>> result = new HashMap<>();
        for (Comment comment : comments) {
            if (result.get(comment.firstLine) == null) {
                result.put(comment.firstLine, new ArrayList<Comment>());
            }
            result.get(comment.firstLine).add(comment);
        }
        return result;
    }

    public List<Comment> getNewComments() {
        final List<Review.File.Comment> newComments = new ArrayList<>();
        for (Comment comment : comments) {
            if (comment.id == null) newComments.add(comment);
        }
        return newComments;
    }
}