/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.log4j.chainsaw;

import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;


/**
 *
 * A TreeModel that represents the Loggers for a given LogPanel
 *
 * @author Paul Smith &lt;psmith@apache.org&gt;
 */
class LogPanelLoggerTreeModel extends DefaultTreeModel
  implements LoggerNameListener {
  private Map fullPackageMap = new HashMap();
  private final Logger logger = LogManager.getLogger(LogPanelLoggerTreeModel.class);

  LogPanelLoggerTreeModel() {
    super(new LogPanelTreeNode("Root Logger"));
  }

  /* (non-Javadoc)
   * @see org.apache.log4j.chainsaw.LoggerNameListener#loggerNameAdded(java.lang.String)
   */
  public void loggerNameAdded(final String loggerName) {
    //invoke later, not on current EDT
    SwingUtilities.invokeLater(
      new Runnable() {
        public void run() {
          addLoggerNameInDispatchThread(loggerName);
        }
      });
  }

  public void reset() {
      DefaultMutableTreeNode current = (DefaultMutableTreeNode) getRoot();
      current.removeAllChildren();
      fullPackageMap.clear();
      nodeStructureChanged(current);
  }

  private void addLoggerNameInDispatchThread(final String loggerName) {
    String[] packages = tokenize(loggerName);

    /**
     * The packages array is effectively the tree
     * path that must exist within the tree, so
     * we walk the tree ensuring each level is present
     */
    DefaultMutableTreeNode current = (DefaultMutableTreeNode) getRoot();


/**
 * This label is used to break out when descending the
 * current tree hierachy, and it has matched a package name
 * with an already existing TreeNode.
 */
outerFor: 
    for (int i = 0; i < packages.length; i++) {
      String packageName = packages[i];
      Enumeration enumeration = current.children();

      while (enumeration.hasMoreElements()) {
        DefaultMutableTreeNode child =
          (DefaultMutableTreeNode) enumeration.nextElement();
        String childName = child.getUserObject().toString();

        if (childName.equals(packageName)) {
          /**
           * This the current known branch to descend
           */
          current = child;

          /**
           * we've found it, so break back to the outer
           * for loop to continue processing further
           * down the tree
           */
          continue outerFor;
        }
      }

      /*
       * So we haven't found this index in the current children,
       * better create the child
       */
      final LogPanelTreeNode newChild = new LogPanelTreeNode(packageName);

      StringBuffer fullPackageBuf = new StringBuffer();

      for (int j = 0; j <= i; j++) {
        fullPackageBuf.append(packages[j]);

        if (j < i) {
          fullPackageBuf.append(".");
        }
      }

      logger.debug("Adding to Map " + fullPackageBuf.toString());
      fullPackageMap.put(fullPackageBuf.toString(), newChild);

      final DefaultMutableTreeNode changedNode = current;

      changedNode.add(newChild);

      final int[] changedIndices = new int[changedNode.getChildCount()];

      for (int j = 0; j < changedIndices.length; j++) {
        changedIndices[j] = j;
      }

      nodesWereInserted(
        changedNode, new int[] { changedNode.getIndex(newChild) });
      nodesChanged(changedNode, changedIndices);
      current = newChild;
    }
  }

  LogPanelTreeNode lookupLogger(String newLogger) {
    if (fullPackageMap.containsKey(newLogger)) {
      return (LogPanelTreeNode) fullPackageMap.get(newLogger);
    }else{
        logger.debug("No logger found matching '" + newLogger + "'");
        logger.debug("Map Dump: " + fullPackageMap);
    }

    return null;
  }

  /**
     * Takes the loggerName and tokenizes it into it's
     * package name lements returning the elements
     * via the Stirng[]
     * @param loggerName
     * @return array of strings representing the package hierarchy
     */
  private String[] tokenize(String loggerName) {
    StringTokenizer tok = new StringTokenizer(loggerName, ".");

    String[] tokens = new String[tok.countTokens()];

    int index = 0;

    while (tok.hasMoreTokens()) {
      tokens[index++] = tok.nextToken();
    }

    return tokens;
  }

  private static class LogPanelTreeNode extends DefaultMutableTreeNode {
    protected static Comparator nodeComparator =
      new Comparator() {
        public int compare(Object o1, Object o2) {
          return o1.toString().compareToIgnoreCase(o2.toString());
        }
      };

    private LogPanelTreeNode(String logName) {
      super(logName);
    }

    public void insert(MutableTreeNode newChild, int childIndex) {
      //      logger.debug("[" + this.getUserObject() + "] inserting child " + newChild + " @ index " + childIndex);
      //      logger.debug("Children now: " + this.children);
      super.insert(newChild, childIndex);

      //	  logger.debug("Children after insert: " + this.children);
      Collections.sort(this.children, nodeComparator);

      //	  logger.debug("Children after sort: " + this.children);
    }
  }
}
