/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * 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.intellij.psi.codeStyle.arrangement;

import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.arrangement.match.ArrangementSectionRule;
import com.intellij.psi.codeStyle.arrangement.std.ArrangementSettingsToken;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Section.END_SECTION;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Section.START_SECTION;

/**
 * Class that is able to detect arrangement section start/end from comment element.
 * <p/>
 * The detection is based on arrangement settings.
 *
 * @author Svetlana.Zemlyanskaya
 */
public class ArrangementSectionDetector {
  private final Document myDocument;
  private final ArrangementSettings mySettings;
  private final Consumer<ArrangementSectionEntryTemplate> mySectionEntryProducer;
  private Stack<String> myOpenedSections = ContainerUtil.newStack();

  public ArrangementSectionDetector(@Nullable Document document,
                                    @NotNull ArrangementSettings settings,
                                    @NotNull Consumer<ArrangementSectionEntryTemplate> producer) {
    myDocument = document;
    mySettings = settings;
    mySectionEntryProducer = producer;
  }

  /**
   * Check if comment can be recognized as section start/end
   * @return true for section comment, false otherwise
   */
  public boolean processComment(@NotNull PsiComment comment) {
    final TextRange range = comment.getTextRange();
    final TextRange expandedRange = myDocument == null ? range : ArrangementUtil.expandToLineIfPossible(range, myDocument);
    final TextRange sectionTextRange = new TextRange(expandedRange.getStartOffset(), expandedRange.getEndOffset());

    final String commentText = comment.getText().trim();
    final boolean start = isSectionStartComment(mySettings, commentText);
    String lastOpenedSection = myOpenedSections.isEmpty() ? null : myOpenedSections.peek();
    if (start) {
      mySectionEntryProducer.consume(new ArrangementSectionEntryTemplate(comment, START_SECTION, sectionTextRange, commentText));
      myOpenedSections.push(commentText);
      return true;
    }

    if (lastOpenedSection != null && isSectionEndComment(mySettings, commentText)) {
      mySectionEntryProducer.consume(new ArrangementSectionEntryTemplate(comment, END_SECTION, sectionTextRange, commentText));
      myOpenedSections.pop();
      return true;
    }
    return false;
  }

  public static boolean isSectionStartComment(@NotNull ArrangementSettings settings, @NotNull String comment) {
    for (ArrangementSectionRule rule : settings.getSections()) {
      if (rule.getStartComment() != null && StringUtil.equals(comment, rule.getStartComment())) {
        return true;
      }
    }
    return false;
  }

  public static boolean isSectionEndComment(@NotNull ArrangementSettings settings, @NotNull String comment) {
    for (ArrangementSectionRule rule : settings.getSections()) {
      if (rule.getEndComment() != null && StringUtil.equals(comment, rule.getEndComment())) {
        return true;
      }
    }
    return false;
  }

  public static class ArrangementSectionEntryTemplate {
    private PsiElement myElement;
    private ArrangementSettingsToken myToken;
    private TextRange myTextRange;
    private String myText;

    public ArrangementSectionEntryTemplate(@NotNull PsiElement element,
                                           @NotNull ArrangementSettingsToken token,
                                           @NotNull TextRange range,
                                           @NotNull String text) {
      myElement = element;
      myToken = token;
      myTextRange = range;
      myText = text;
    }

    public PsiElement getElement() {
      return myElement;
    }

    public ArrangementSettingsToken getToken() {
      return myToken;
    }

    public TextRange getTextRange() {
      return myTextRange;
    }

    public String getText() {
      return myText;
    }
  }
}

@@ -1832,20 +1832,42 @@
 ate 
-Stack%3CString
+final Stack%3CArrangementSectionRule
 %3E my
@@ -2802,21 +2802,46 @@
 nal 
-boolean start
+ArrangementSectionRule openSectionRule
  = i
@@ -2895,109 +2895,35 @@
     
-String lastO
+if (o
 pen
-ed
 Section
- = myOpenedSections.isEmpty() ? null : myOpenedSections.peek();%0A    if (start
+Rule != null
 ) %7B%0A
@@ -3080,27 +3080,31 @@
 ns.push(
-commentText
+openSectionRule
 );%0A     
@@ -3136,12 +3136,11 @@
 if (
-last
+!my
 Open
@@ -3152,29 +3152,122 @@
 tion
- != null && is
+s.isEmpty()) %7B%0A      final ArrangementSectionRule lastSection = myOpenedSections.peek();%0A      if (last
 Section
+.get
 EndC
@@ -3277,36 +3277,86 @@
 ent(
-mySettings, commentText
+) != null && StringUtil.equals(commentText, lastSection.getEndComment()
 )) %7B%0A
+  
     
@@ -3477,32 +3477,34 @@
 ntText));%0A      
+  
 myOpenedSections
@@ -3509,32 +3509,34 @@
 ns.pop();%0A      
+  
 return true;%0A   
@@ -3532,22 +3532,30 @@
 n true;%0A
+  
     %7D%0A
+    %7D%0A
     retu
@@ -3575,375 +3575,71 @@
 %0A%0A  
-public static boolean isSectionStartComment(@NotNull ArrangementSettings settings, @NotNull String comment) %7B%0A    for (ArrangementSectionRule rule : settings.getSections()) %7B%0A      if (rule.getStartComment() != null && StringUtil.equals(comment, rule.getStartComment())) %7B%0A        return true;%0A      %7D%0A    %7D%0A    return false;%0A  %7D%0A%0A  public static boolean isSectionEnd
+@Nullable%0A  public static ArrangementSectionRule isSectionStart
 Comm
@@ -3783,35 +3783,37 @@
     if (rule.get
-End
+Start
 Comment() != nul
@@ -3848,27 +3848,29 @@
 nt, rule.get
-End
+Start
 Comment())) 
@@ -3878,35 +3878,35 @@
 %0A        return 
-t
 ru
+l
 e;%0A      %7D%0A    %7D
@@ -3909,37 +3909,36 @@
    %7D%0A    return 
-false
+null
 ;%0A  %7D%0A%0A  public 

store opened section rules instead of start comments