/*
 * #%L
 * OME Bio-Formats manual and automated test suite.
 * %%
 * Copyright (C) 2006 - 2017 Open Microscopy Environment:
 *   - Board of Regents of the University of Wisconsin-Madison
 *   - Glencoe Software, Inc.
 *   - University of Dundee
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

package loci.tests.testng;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;

import javax.swing.tree.DefaultMutableTreeNode;

import loci.common.Constants;
import loci.common.IniList;
import loci.common.IniParser;
import loci.common.IniTable;
import loci.common.Location;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Stores configuration data about files in a directory structure.
 * This class is not designed to be thread safe, so should be synchronized
 * externally for proper thread safety.
 */
public class ConfigurationTree {

  // -- Constants --

  private static final Logger LOGGER =
    LoggerFactory.getLogger(ConfigurationTree.class);

  // -- Fields --

  /** Directory on the file system associated with the tree root. */
  private String rootDir;

  /** Base directory on the file system associated with the configuration
   * files.
   */
  private String configDir;

  /** Base directory on the file system associated with the memo files.
   */
  private String cacheDir;

  /**
   * Root of tree structure containing configuration data.
   * Each node's user object is a hashtable of key/value pairs.
   */
  private DefaultMutableTreeNode root;

  // -- Constructor --

  /**
   *  Constructs a new configuration tree rooted at the given directory in the
   *  file system.
   *
   *  @param rootDir a string specifying the root directory
   *
   */
  public ConfigurationTree(String rootDir) {
    this(rootDir, null);
  }

  /**
   *  Constructs a new configuration tree rooted at the given directory in the
   *  file system with a custom configuration directory.
   *
   *  @param rootDir a string specifying the root directory
   *  @param configDir a string specifying the base configuration directory
   *
   */
  public ConfigurationTree(String rootDir, String configDir) {
    this(rootDir, configDir, null);
  }

  /**
   *  Constructs a new configuration tree rooted at the given directory in the
   *  file system with a custom configuration directory anc cache directory.
   *
   *  @param rootDir a string specifying the root directory
   *  @param configDir a string specifying the base configuration directory
   *  @param cacheDir a string specifying the cache  directory
   */
  public ConfigurationTree(String rootDir, String configDir, String cacheDir) {
    if (rootDir == null) {
      throw new IllegalArgumentException("rootDir cannot be null.");
    }
    this.rootDir = new File(rootDir).getAbsolutePath();
    if (configDir != null) {
        this.configDir = new File(configDir).getAbsolutePath();
    }
    if (cacheDir != null) {
      this.cacheDir = new File(cacheDir).getAbsolutePath();
    }
    root = new DefaultMutableTreeNode();
  }

  // -- ConfigurationTree API methods --

  /**
   *  Returns the toot directory holding the data
   */
  public String getRootDirectory() {
    return this.rootDir;
  }

  /**
   *  Returns the base directory holding the configuration files
   */
  public String getConfigDirectory() {
    return this.configDir;
  }

  /**
   *  Returns the base directory holding the configuration files
   */
  public String getCacheDirectory() {
    return this.cacheDir;
  }

  /**
   *  Relocate a path from an base directory into a target directory
   */
  public String relocate(String path, String oldRoot, String newRoot) {
    if (!path.startsWith(oldRoot)) {
      return path;
    }
    String subPath = path.substring((int) Math.min(
      oldRoot.length() + 1, path.length()));
    if (subPath.length() == 0) {
      return newRoot;
    }
    else {
      return new Location(newRoot, subPath).getAbsolutePath();
    }
  }

  /**
   *  Relocate a path under the root directory to the configuration directory
   */
  public String relocateToConfig(String path) {
    return relocate(path, this.rootDir, this.configDir);
  }

  /**
   *  Relocate a path under the configuration directory to the root directory
   */
  public String relocateToRoot(String path) {
    return relocate(path, this.configDir, this.rootDir);
  }

  /** Retrieves the Configuration object corresponding to the given file. */
  public Configuration get(String id) throws IOException {
    DefaultMutableTreeNode pos = findNode(id, false, null);
    if (pos == null) return null;
    Hashtable table = (Hashtable) pos.getUserObject();
    return (Configuration) table.get("configuration");
  }

  public void parseConfigFile(String configFile) throws IOException {
    File file = new File(configFile);
    if (file.isDirectory()) {
      return;
    }
    String parent = file.getParent();
    if (configDir != null) {
      parent = relocateToRoot(parent);
    }

    configFile = file.getAbsolutePath();
    String dir = file.getParentFile().getAbsolutePath();

    IniParser parser = new IniParser();
    parser.setCommentDelimiter(null);
    FileInputStream stream = new FileInputStream(configFile);
    IniList iniList = parser.parseINI(new BufferedReader(
      new InputStreamReader(stream, Constants.ENCODING)));
    for (IniTable table : iniList) {
      String id = table.get(IniTable.HEADER_KEY);
      id = id.substring(0, id.lastIndexOf(" "));

      id = new File(parent, id).getAbsolutePath();

      DefaultMutableTreeNode node = findNode(id, true, configFile);
      if (node == null) {
        LOGGER.warn("config file '{}' has invalid filename '{}'",
          configFile, id);
        continue;
      }
    }
  }

  // -- Helper methods --

  /** Gets the tree node associated with the given file. */
  private DefaultMutableTreeNode findNode(String id, boolean create, String configFile) {
    String baseID = id;
    if (!id.startsWith(rootDir)) return null;
    id = id.substring(rootDir.length());
    StringTokenizer st = new StringTokenizer(id, "\\/");
    DefaultMutableTreeNode node = root;
    while (st.hasMoreTokens()) {
      String token = st.nextToken();
      Enumeration en = node.children();
      DefaultMutableTreeNode next = null;
      while (en.hasMoreElements()) {
        DefaultMutableTreeNode child =
          (DefaultMutableTreeNode) en.nextElement();
        Hashtable table = (Hashtable) child.getUserObject();
        if (token.equals(table.get("name"))) {
          next = child;
          break;
        }
      }
      if (next == null) {
        // create node, if applicable
        if (!create) return null;
        next = new DefaultMutableTreeNode();
        Hashtable table = new Hashtable();
        table.put("name", token);
        try {
          table.put("configuration", new Configuration(baseID, configFile));
        }
        catch (IOException e) { }
        next.setUserObject(table);
        node.add(next);
      }
      node = next;
    }
    return node;
  }

}
