/*
 * Copyright 2003-2015 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 jetbrains.mps.ide.ui.tree.module;

import com.intellij.icons.AllIcons.Nodes;
import jetbrains.mps.extapi.model.TransientSModel;
import jetbrains.mps.icons.MPSIcons.Nodes.Models;
import jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.ide.ui.tree.SortUtil.SModelComparator;
import jetbrains.mps.ide.ui.tree.TextTreeNode;
import jetbrains.mps.ide.ui.tree.TreeNodeTextSource;
import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode;
import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode.LongModelNameText;
import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode.ShortModelNameText;
import jetbrains.mps.smodel.LanguageID;
import jetbrains.mps.smodel.SModelStereotype;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.util.NameUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.module.SModule;

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

/**
 * Facility to build set of tree nodes to represent SModel.
 * Could be configured to group tree nodes by model namespace (default),
 * and to utilize model qualified name as namespace for other models.
 */
public class SModelsSubtree {
  private final MPSTreeNode myRootTreeNode;
  private final boolean myWithNamespaceNodes;
  private final boolean myWithModelsAsNamespace;
  private TreeNodeTextSource<SModelTreeNode> myRootModelsText;
  private TreeNodeTextSource<SModelTreeNode> myChildModelsText;

  public SModelsSubtree(@NotNull MPSTreeNode rootTreeNode) {
    this(rootTreeNode, true, true);
  }

  /**
   * @param rootTreeNode          tree node to populate with children
   * @param withNamespaceNodes    <code>true</code> to group models according to their namespace under dedicated text (aka namespace) nodes
   * @param withModelsAsNamespace <code>true</code> group models according to their namespace under model with shorter namespace
   */
  public SModelsSubtree(@NotNull MPSTreeNode rootTreeNode, boolean withNamespaceNodes, boolean withModelsAsNamespace) {
    myRootTreeNode = rootTreeNode;
    myWithNamespaceNodes = withNamespaceNodes;
    myWithModelsAsNamespace = withModelsAsNamespace;
  }

  public void create(SModule module) {
    create(IterableUtil.asCollection(module.getModels()));
  }

  public void create(Collection<SModel> models) {
    ModelUnderNamespaceText nsText = new ModelUnderNamespaceText();
    ShortModelNameText shortText = new ShortModelNameText();
    myRootModelsText = myWithNamespaceNodes ? nsText : shortText;
    myChildModelsText = myWithModelsAsNamespace ? nsText : shortText;
    List<SModelTreeNode> treeNodes = getRootModelTreeNodes(models);
    if (treeNodes.isEmpty()) {
      return;
    }
    if (myWithNamespaceNodes) {
      SModelNamespaceTreeBuilder builder = new SModelNamespaceTreeBuilder();
      for (SModelTreeNode treeNode : treeNodes) {
        builder.addNode(treeNode);
      }
      builder.fillNode(myRootTreeNode);
    } else {
      for (SModelTreeNode treeNode : treeNodes) {
        myRootTreeNode.add(treeNode);
      }
    }
  }

  private List<SModelTreeNode> getRootModelTreeNodes(Collection<SModel> models) {
    List<SModelTreeNode> result = new ArrayList<SModelTreeNode>();
    ArrayList<SModel> sortedModels = new ArrayList<SModel>(models);
    Collections.sort(sortedModels, new SModelComparator());
    if (!sortedModels.isEmpty()) {
      int rootIndex = 0;
      while (rootIndex < sortedModels.size()) {
        SModel rootModelDescriptor = sortedModels.get(rootIndex);
        SModelTreeNode treeNode = new SModelTreeNode(rootModelDescriptor, myRootModelsText);
        result.add(treeNode);
        rootIndex = myWithModelsAsNamespace ? buildChildModels(treeNode, sortedModels, rootIndex) : rootIndex + 1;
      }
    }
    return result;
  }

  private int buildChildModels(SModelTreeNode treeNode, List<SModel> candidates, int rootIndex) {
    int index = rootIndex + 1;
    while (index < candidates.size()) {
      SModel candidate = candidates.get(index);
      if (treeNode.isSubfolderModel(candidate)) {
        SModelTreeNode newChildModel = new SModelTreeNode(candidate, myChildModelsText);
        treeNode.addChildModel(newChildModel);
        index = buildChildModels(newChildModel, candidates, index);
      } else {
        return index;
      }
    }
    return index;
  }

  public static int getCountNamePart(SModel md, String baseName) {
    String modelLongName = NameUtil.getModelLongName(md);
    String shortName = md instanceof TransientSModel ? modelLongName : modelLongName.replace(baseName + '.', "");
    return shortName.split("\\.").length - 1;
  }

  public static class StubsTreeNode extends TextTreeNode implements StereotypeProvider {
    public StubsTreeNode() {
      super("stubs");

      setIcon(Nodes.PpLibFolder);
    }

    @Override
    public String getStereotype() {
      return SModelStereotype.getStubStereotypeForId(LanguageID.JAVA);
    }

    @Override
    public boolean isStrict() {
      return true;
    }
  }

  public static class TestsTreeNode extends TextTreeNode implements StereotypeProvider {
    public TestsTreeNode() {
      super("tests");

      setIcon(Models.TestsModel);
    }

    @Override
    public String getStereotype() {
      return SModelStereotype.TESTS;
    }

    @Override
    public boolean isStrict() {
      return true;
    }
  }

  private static class ModelUnderNamespaceText implements TreeNodeTextSource<SModelTreeNode> {
    private final LongModelNameText myFullText = new LongModelNameText();

    @Override
    public String calculateText(SModelTreeNode treeNode) {
      String longName = myFullText.calculateText(treeNode);
      if (treeNode.getModel() instanceof TransientSModel) {
        // this is a hack derived from legacy code, shall configure TreeNodeTextSource from TransientModelsTreeNode instead
        return longName;
      }
      String namespace;
      // FIXME Perhaps, shall introduce interface NamespaceNode, which either gives a string or is capable to tell text for a given child
      if (treeNode.getParent() instanceof NamespaceTextNode) {
        NamespaceTextNode parent = (NamespaceTextNode) treeNode.getParent();
        namespace = parent.getNamespace();
      } else if (treeNode.getParent() instanceof SModelTreeNode) {
        SModelTreeNode parent = (SModelTreeNode) treeNode.getParent();
        // code inspired by #getCountNamePart()
        namespace = NameUtil.getModelLongName(parent.getModel());
      } else {
        return longName;
      }
      // if namespace + '.' + non empty tail fits into longName
      if (longName.length() > namespace.length() + 1 && longName.startsWith(namespace) && longName.charAt(namespace.length()) == '.') {
        return longName.substring(namespace.length() + 1);
      }
      return longName;
    }
  }
}
