/*
 * 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.application.options.codeStyle.arrangement.match;

import com.intellij.application.options.codeStyle.arrangement.ArrangementConstants;
import com.intellij.application.options.codeStyle.arrangement.color.ArrangementColorsProvider;
import com.intellij.application.options.codeStyle.arrangement.match.tokens.ArrangementRuleAliasDialog;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.codeStyle.arrangement.ArrangementUtil;
import com.intellij.psi.codeStyle.arrangement.match.ArrangementSectionRule;
import com.intellij.psi.codeStyle.arrangement.match.StdArrangementMatchRule;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementAtomMatchCondition;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementCompositeMatchCondition;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchConditionVisitor;
import com.intellij.psi.codeStyle.arrangement.std.*;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static com.intellij.application.options.codeStyle.arrangement.match.ArrangementSectionRuleManager.ArrangementSectionRuleData;

/**
 * @author Denis Zhdanov
 * @since 10/31/12 1:23 PM
 */
public class ArrangementSectionRulesControl extends ArrangementMatchingRulesControl {
  @NotNull public static final DataKey<ArrangementSectionRulesControl> KEY = DataKey.create("Arrangement.Rule.Match.Control");
  @NotNull private static final Logger LOG = Logger.getInstance("#" + ArrangementSectionRulesControl.class.getName());
  @NotNull private final ArrangementColorsProvider myColorsProvider;
  @NotNull private final ArrangementStandardSettingsManager mySettingsManager;

  @Nullable private final ArrangementSectionRuleManager mySectionRuleManager;
  @Nullable private ArrangementStandardSettingsManager myExtendedSettingsManager;

  public ArrangementSectionRulesControl(@NotNull Language language,
                                        @NotNull ArrangementStandardSettingsManager settingsManager,
                                        @NotNull ArrangementColorsProvider colorsProvider,
                                        @NotNull RepresentationCallback callback) {
    super(settingsManager, colorsProvider, callback);
    mySectionRuleManager = ArrangementSectionRuleManager.getInstance(language, settingsManager, colorsProvider, this);
    mySettingsManager = settingsManager;
    myColorsProvider = colorsProvider;
  }

  private static void appendBufferedSectionRules(@NotNull List<ArrangementSectionRule> result,
                                                 @NotNull List<StdArrangementMatchRule> buffer,
                                                 @Nullable String currentSectionStart) {
    if (currentSectionStart == null) {
      return;
    }

    if (buffer.isEmpty()) {
      result.add(ArrangementSectionRule.create(currentSectionStart, null));
    }
    else {
      result.add(ArrangementSectionRule.create(currentSectionStart, null, buffer.get(0)));
      for (int j = 1; j < buffer.size(); j++) {
        result.add(ArrangementSectionRule.create(buffer.get(j)));
      }
      buffer.clear();
    }
  }

  @Override
  protected MatchingRulesRendererBase createRender() {
    return new MatchingRulesRenderer();
  }

  @NotNull
  @Override
  protected ArrangementMatchingRulesValidator createValidator() {
    return new ArrangementSectionRulesValidator(getModel(), mySectionRuleManager);
  }

  @Nullable
  public ArrangementSectionRuleManager getSectionRuleManager() {
    return mySectionRuleManager;
  }

  public List<ArrangementSectionRule> getSections() {
    if (getModel().getSize() <= 0) {
      return Collections.emptyList();
    }

    final List<ArrangementSectionRule> result = ContainerUtil.newArrayList();
    final List<StdArrangementMatchRule> buffer = ContainerUtil.newArrayList();
    String currentSectionStart = null;
    for (int i = 0; i < getModel().getSize(); i++) {
      final Object element = getModel().getElementAt(i);
      if (element instanceof StdArrangementMatchRule) {
        final ArrangementSectionRuleData sectionRule =
          mySectionRuleManager == null ? null : mySectionRuleManager.getSectionRuleData((StdArrangementMatchRule)element);
        if (sectionRule != null) {
          if (sectionRule.isSectionStart()) {
            appendBufferedSectionRules(result, buffer, currentSectionStart);
            currentSectionStart = sectionRule.getText();
          }
          else {
            result.add(ArrangementSectionRule.create(StringUtil.notNullize(currentSectionStart), sectionRule.getText(), buffer));
            buffer.clear();
            currentSectionStart = null;
          }
        }
        else {
          if (currentSectionStart == null) {
            result.add(ArrangementSectionRule.create((StdArrangementMatchRule)element));
          }
          else {
            buffer.add((StdArrangementMatchRule)element);
          }
        }
      }
    }

    appendBufferedSectionRules(result, buffer, currentSectionStart);
    return result;
  }

  public void setSections(@Nullable List<ArrangementSectionRule> sections) {
    final List<StdArrangementMatchRule> rules = sections == null ? null : ArrangementUtil.collectMatchRules(sections);
    myComponents.clear();
    getModel().clear();

    if (rules == null) {
      return;
    }

    for (StdArrangementMatchRule rule : rules) {
      getModel().add(rule);
    }

    if (ArrangementConstants.LOG_RULE_MODIFICATION) {
      LOG.info("Arrangement matching rules list is refreshed. Given rules:");
      for (StdArrangementMatchRule rule : rules) {
        LOG.info("  " + rule.toString());
      }
    }
  }

  @Nullable
  public Collection<StdArrangementRuleAliasToken> getRulesAliases() {
    return myExtendedSettingsManager == null ? null : myExtendedSettingsManager.getRuleAliases();
  }

  public void setRulesAliases(@Nullable Collection<StdArrangementRuleAliasToken> aliases) {
    if (aliases != null) {
      myExtendedSettingsManager = new ArrangementStandardSettingsManager(mySettingsManager.getDelegate(), myColorsProvider, aliases);
      myEditor = new ArrangementMatchingRuleEditor(myExtendedSettingsManager, myColorsProvider, this);
    }
  }

  public void showEditor(int rowToEdit) {
    if (mySectionRuleManager != null && mySectionRuleManager.isSectionRule(getModel().getElementAt(rowToEdit))) {
      mySectionRuleManager.showEditor(rowToEdit);
    }
    else {
      super.showEditor(rowToEdit);
    }
  }

  @NotNull
  public ArrangementRuleAliasDialog createRuleAliasEditDialog() {
    final Set<String> tokenIds = new THashSet<>();
    final List<ArrangementSectionRule> sections = getSections();
    for (ArrangementSectionRule section : sections) {
      for (StdArrangementMatchRule rule : section.getMatchRules()) {
        rule.getMatcher().getCondition().invite(new ArrangementMatchConditionVisitor() {
          @Override
          public void visit(@NotNull ArrangementAtomMatchCondition condition) {
            if (ArrangementUtil.isAliasedCondition(condition)) {
              tokenIds.add(condition.getType().getId());
            }
          }

          @Override
          public void visit(@NotNull ArrangementCompositeMatchCondition condition) {
            for (ArrangementMatchCondition operand : condition.getOperands()) {
              operand.invite(this);
            }
          }
        });
      }
    }

    final Collection<StdArrangementRuleAliasToken> aliases = getRulesAliases();
    assert aliases != null;
    return new ArrangementRuleAliasDialog(null, mySettingsManager, myColorsProvider, aliases, tokenIds);
  }

  private class MatchingRulesRenderer extends MatchingRulesRendererBase {
    @Override
    public boolean allowModifications(StdArrangementMatchRule rule) {
      return !(mySectionRuleManager != null && mySectionRuleManager.isSectionRule(rule));
    }
  }
}
