/*
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This code 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA  02111-1307, USA.
 */
package no.geosoft.cc.directory;



import java.util.*;



/**
 * A class representing a generic folder node within a directory
 * structure. A folder may contain DirectoryEnty children and may
 * have a client specified item back-end object.
 * 
 * @author <a href="mailto:info@geosoft.no">GeoSoft</a>
 */   
public class Folder extends DirectoryEntry
{
  private List     entries_;  // of DirectoryEntry
  private boolean  isExpanded_;
  
  
  
  /**
   * Create a new folder entry.
   * 
   * @param item  Client specific item of the folder.
   * @param name  Name of the folder.
   */
  public Folder (Object item, String name)
  {
    super (item, name);

    entries_    = new ArrayList();
    isExpanded_ = false;
  }

  

  /**
   * Create a folder with name according to item toString().
   * 
   * @param item  Client specific item of the folder.
   */
  public Folder (Object item)
  {
    this (item, item.toString());
  }


  
  /**
   * Create a namelsee and itemless folder entry.
   * 
   * @return 
   */
  public Folder()
  {
    this (null, null);
  }

  

  /**
   * Add an entry to this folder at a specific position.
   * NOTE: Entries can have one parent only.
   * 
   * @param entry  Entry to add.
   * @param index  Entry position, 0 is first, etc.
   */
  public void add (DirectoryEntry entry, int index)
  {
    entry.setParent (this);
    entries_.add (index, entry);
  }

  
  
  /**
   * Add an entry to the end of this folder.
   * 
   * @param entry  Entry to add.
   */
  public void add (DirectoryEntry entry)
  {
    add (entry, getNEntries());
  }


  
  /**
   * Add a set of entries to this folder.
   * 
   * @param entries  Entries to add.
   */
  public void add (Collection entries)
  {
    for (Iterator i = entries.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      add (entry);
    }
  }


  
  /**
   * Add an entry sorted alphabetically according to the entrys
   * toString() method.
   * 
   * @param entry  Entry to add.
   */
  public void addSorted (DirectoryEntry entry)
  {
    int index = 0;
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry existingEntry = (DirectoryEntry) i.next();
      if (existingEntry.compareTo (entry) > 0)
        break;
      index++;
    }

    add (entry, index);
  }
  

  
  /**
   * Add a set of entries sorted alphabetically according to the entries'
   * toString() method.
   * 
   * @param entry  Entry to add.
   */
  public void addSorted (Collection entries)
  {
    for (Iterator i = entries.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      addSorted (entry);
    }
  }


  
  /**
   * Remove entry from this folder.
   * 
   * @param entry  Entry to remove.
   */
  public void remove (DirectoryEntry entry)
  {
    // Everybody that "is" this instance must be removed as well
    entry.removeSymbolicLinks();

    // Remove from child list
    entries_.remove (entry);
  }


  
  /**
   * Return true if this is a leaf node. A folder is per definition not
   * a leaf node even if empty, so this method always return false.
   * 
   * @return  False always.
   */
  public boolean isLeaf()
  {
    return false; // Per definition, even if it is empty
  }



  /**
   * Return true if this is a root node. A node is root if it doesn't
   * have a parent node.
   * 
   * @return  True if this is a root node, false otherwise.
   */
  public boolean isRoot()
  {
    return getParent() == null;
  }
  

  
  /**
   * Return the child entry at the specific position. Return null
   * if the entry does not exists.
   * 
   * @param index  Position of entry to return.
   * @return 
   */
  public DirectoryEntry getEntry (int index)
  {
    if (index < 0 || index > entries_.size()-1)
      return null;
    else
      return (DirectoryEntry) entries_.get (index);
  }

  

  /**
   * Staring from this node, return the index'th element in the
   * tree, traversing depth first.
   *
   * @param entryNo  Entry number to find.
   * @return 
   */
  public DirectoryEntry getGlobalEntry (int entryNo)
  {
    Vector list = new Vector();    
    return getGlobalEntry (entryNo, list);
  }

  
  
  private DirectoryEntry getGlobalEntry (int entryNo, Vector list)
  {
    if (list.size() == entryNo) return this;
    list.add (this);

    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry subEntry = (DirectoryEntry) i.next();
      if (subEntry.isFolder()) {
        Folder folder = (Folder) subEntry;
        DirectoryEntry foundEntry = folder.getGlobalEntry (entryNo, list);
        if (foundEntry != null) return foundEntry;
      }
      else {
        if (list.size() == entryNo) return subEntry;
        list.add (subEntry);
      }
    }

    return null;
  }

  

  /**
   * Starting from here, return the entry number of the specified
   * entry traversing depth first. Return -1 if not found.
   *
   * @param entry  Entry to find entry number of.
   * @return       Entry number of specified entry (or -1 if not found).
   */
  public int getGlobalIndexOfEntry (DirectoryEntry entry)
  {
    Vector list = new Vector();
    boolean isFound = getGlobalIndexOfEntry (entry, list);
    return isFound ? list.size() : -1;
  }


  
  private boolean getGlobalIndexOfEntry (DirectoryEntry entry, Vector list)
  {
    if (this == entry) return true;
    list.add (this);

    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry subEntry = (DirectoryEntry) i.next();
      if (subEntry.isFolder()) {
        Folder folder = (Folder) subEntry;
        boolean isFound = folder.getGlobalIndexOfEntry (entry, list);
        if (isFound) return true;
      }
      else {
        list.add (subEntry);
        if (subEntry == entry) return true;
      }
    }

    return false;
  }

  
  
  /**
   * Return position of the specified entry. Null if entry does not
   * exist.
   * 
   * @param entry  Entry to find index of.
   * @return 
   */
  public int getIndexOfEntry (DirectoryEntry entry)
  {
    return entries_.indexOf (entry);
  }


  
  /**
   * Check if the folder is empty (has no children).
   * 
   * @return  True if the folder is empty, false otherwise.
   */
  public boolean isEmpty()
  {
    return getNEntries() == 0;
  }

  

  /**
   * Return number of entries in this folder.
   * 
   * @return  Number of entries in this folder.
   */
  public int getNEntries()
  {
    return entries_.size();
  }


  
  /**
   * Get the entries of this folder.
   * 
   * @return  The entries of this folder (List of DirectoryEntry)
   */
  public List getEntries()
  {
    return entries_;
  }


  
  /**
   * Recursively check if an entry is descendant of this folder.
   * 
   * @param entry  Entry to check.
   * @return       True if the entry is a descendant of this folder,
   *               false otherwise.
   */
  public boolean contains (DirectoryEntry entry)
  {
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry subEntry = (DirectoryEntry) i.next();
      if (subEntry == entry) return true;
      if (subEntry instanceof Folder &&
          ((Folder)subEntry).contains (entry))
        return true;
    }

    return false;
  }



  /**
   * Find a given node among the immediate children of this
   * folder.
   *
   * @param name  Name of child to find.
   * @return      Directory entry of searched node (or null if not found).
   */
  public DirectoryEntry find (String name)
  {
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      if (entry.getName().equals (name))
        return entry;
    }

    return null;
  }

  

  /**
   * Find a given node within the entire subtree of this folder.
   *
   * @param name  Name of node to find.
   * @return      Directory entry of searched node (or null if not found).
   */
  public DirectoryEntry findGlobal (String name)
  {
    if (getName().equals (name)) return this;

    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      
      if (entry instanceof Folder) {
        DirectoryEntry foundEntry = ((Folder) entry).findGlobal (name);
        if (foundEntry != null) return foundEntry;
      }
      else {
        if (entry.getName().equals (name))
          return entry;
      }
    }

    // Not found
    return null;
  }


  
  /**
   * Find a given folder among the immediate children of this
   * folder.
   *
   * @param name  Name of folder to find.
   * @return      The searched folder (or null if not found).
   */
  public Folder findFolder (String name)
  {
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      if (entry.isFolder() && entry.getName().equals (name))
        return (Folder) entry;
    }

    return null;
  }

  
  private boolean isItemsEqual (Object o1, Object o2)
  {
    if (o1 == null)
      return o2 == null;
    else
      return o1.equals (o2);
  }

  
  /**
   * Return sub folder based on item value. Search the entire
   * sub tree from this folder.
   *
   * @param item  Item of the folder to find.
   * @return      Searched folder (or null if not found).
   */
  public Folder findFolder (Object item)
  {
    if (getItem() == item) return this;
    
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      if (entry.isFolder()) {
        Folder folder = (Folder) entry;
        Folder found = folder.findFolder (item);
        if (found != null) return found;
      }
    }

    // Not found
    return null;
  }
  

  
  /**
   * Return entry based on item value. Search the entire
   * sub tree from this folder.
   *
   * @param item  Item of the entry to find.
   * @return      Searched entry (or null if not found).
   */
  public DirectoryEntry findEntry (Object item)
  {
    if (isItemsEqual (item, getItem())) return this;
    
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();

      if (entry.isFolder()) {
        Folder folder = (Folder) entry;
        DirectoryEntry found = folder.findEntry (item);
        if (found != null) return found;
      }
      else {
        if (isItemsEqual (item, entry.getItem()))
          return entry;        
      }
    }

    // Not found
    return null;
  }

  

  /**
   * Identify this folder as "expanded". This setting may be used
   * in a GUI front-end module.
   */
  public void expand()
  {
    isExpanded_ = true;
  }


  
  /**
   * Identify this folder and all sub folders as "expanded".
   * This setting may be used in a GUI front-end module.
   */
  public void expandAll()
  {
    // Expand this
    expand();

    // Expand children folders
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      if (entry.isFolder()) {
        Folder folder = (Folder) entry;
        folder.expandAll();
      }
    }
  }


  
  /**
   * Identify this folder as "collapsed". This setting may be used
   * in a GUI front-end module.
   */
  public void collapse()
  {
    isExpanded_ = false;
  }

  

  /**
   * Identify this folder and all sub folders as "collapsed".
   * This setting may be used in a GUI front-end module.
   */
  public void collapseAll()
  {
    // Expand this
    collapse();

    // Expand children folders
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      if (entry.isFolder()) {
        Folder folder = (Folder) entry;
        folder.collapseAll();
      }
    }
  }


  // DEBUG
  public void print (int indent)
  {
    for (int i = 0; i < indent; i++)
      System.out.print (" ");
    System.out.println ("Folder: " + getName());
    
    for (Iterator i = entries_.iterator(); i.hasNext(); ) {
      DirectoryEntry entry = (DirectoryEntry) i.next();
      entry.print (indent + 2);
    }
  }
}