/*
 * 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 jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.util.ToStringComparator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

public abstract class NamespaceTreeBuilder<N extends MPSTreeNode, T extends MPSTreeNode> {
  private T myRootNamespace;
  private NamespaceNodeBuilder<T> myBuilder;

  protected NamespaceTreeBuilder(NamespaceNodeBuilder<T> builder) {
    myBuilder = builder;
    myRootNamespace = myBuilder.createNamespaceNode("");
  }

  /**
   * @return never <code>null</code>, empty string for root namespace
   */
  protected abstract String getNamespace(N node);

  public void addNode(N node) {
    String namespace = getNamespace(node);
    List<String> pathElements = new ArrayList<String>(Arrays.asList(namespace.split("\\.")));

    if (pathElements.size() == 1 && pathElements.get(0).equals("")) {
      pathElements.remove(0);
    }

    addNode(node, getSubnamespace(myRootNamespace, pathElements));
  }

  protected void addNode(N node, T namespace) {
    namespace.add(node);
  }

  public void fillNode(MPSTreeNode root) {
    sortTree(myRootNamespace);
    compactNodes(myRootNamespace);

    Enumeration children = myRootNamespace.children();
    List<MPSTreeNode> oldChildren = new ArrayList<MPSTreeNode>();
    while (children.hasMoreElements()) {
      oldChildren.add((MPSTreeNode) children.nextElement());
    }
    for (MPSTreeNode node : oldChildren) {
      myRootNamespace.remove(node);
      root.add(node);
    }
  }

  private void sortTree(T node) {
    List<MPSTreeNode> nodes = new ArrayList<MPSTreeNode>();
    List<T> namespaces = new ArrayList<T>();
    for (int i = 0; i < node.getChildCount(); i++) {
      MPSTreeNode child = (MPSTreeNode) node.getChildAt(i);
      if (myBuilder.isNamespaceNode(child)) {
        @SuppressWarnings("unchecked")
        T nsNode = (T) child;
        sortTree(nsNode);
        namespaces.add(nsNode);
      } else {
        nodes.add(child);
      }
    }

    Collections.sort(namespaces, new ToStringComparator());
    Collections.sort(nodes, new ModuleTreeNodeComparator());

    node.removeAllChildren();

    for (T ns : namespaces) {
      node.add(ns);
    }

    for (MPSTreeNode n : nodes) {
      node.add(n);
    }
  }

  private void compactNodes(T node) {
    for (int i = 0; i < node.getChildCount(); i++) {
      MPSTreeNode child = (MPSTreeNode) node.getChildAt(i);
      if (myBuilder.isNamespaceNode(child)) {
        @SuppressWarnings("unchecked")
        T nsNode = (T) child;
        compactNodes(nsNode);
      }
    }


    if (node.getParent() != null && //skip root
      node.getChildCount() == 1 && myBuilder.isNamespaceNode((MPSTreeNode) node.getChildAt(0))) {
      @SuppressWarnings("unchecked")
      T child = (T) node.getChildAt(0);
      myBuilder.setName(node, myBuilder.getName(node) + "." + myBuilder.getName(child));

      Enumeration children = child.children();
      List<MPSTreeNode> oldChildren = new ArrayList<MPSTreeNode>();
      while (children.hasMoreElements()) {
        oldChildren.add((MPSTreeNode) children.nextElement());
      }
      for (MPSTreeNode c : oldChildren) {
        child.remove(c);
        node.add(c);
      }

      node.remove(child);
    }
  }

  private T getSubnamespace(T sourceNode, List<String> pathElements) {
    if (pathElements.size() == 0) return sourceNode;

    String first = pathElements.get(0);
    List<String> otherElements = pathElements.subList(1, pathElements.size());

    for (int i = 0; i < sourceNode.getChildCount(); i++) {
      if (myBuilder.isNamespaceNode((MPSTreeNode) sourceNode.getChildAt(i))) {
        @SuppressWarnings("unchecked")
        T child = (T) sourceNode.getChildAt(i);
        if (first.equals(myBuilder.getName(child))) {
          return getSubnamespace(child, otherElements);
        }
      }
    }

    T newChild = myBuilder.createNamespaceNode(first);

    sourceNode.add(newChild);

    return getSubnamespace(newChild, otherElements);
  }

  public interface NamespaceNodeBuilder<N extends MPSTreeNode> {
    N createNamespaceNode(String text);

    String getName(N node);

    void setName(N node, String name);

    boolean isNamespaceNode(MPSTreeNode n);
  }

}
