/*
  Copyright (C) 2007 Helge Hess

  This file is part of JOPE.

  JOPE 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, or (at your option) any
  later version.

  JOPE 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 JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
package org.opengroupware.jope.ofs;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.HashSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.appserver.publisher.JoInternalErrorException;
import org.opengroupware.jope.appserver.publisher.JoNotFoundException;
import org.opengroupware.jope.foundation.NSObject;
import org.opengroupware.jope.foundation.UString;

/*
 * OFSFileContainerChildInfo
 * 
 * This class represents a directory in the filesystem. It retrieves the
 * information in a form suitable for OFS processing.
 * 
 * Note: this consumes quite a bit of memory, possibly we want to optimize it,
 *       but then we don't have _that_ many containers per request either.
 *       
 * TODO: we could probably cache this based on the folder lastModified date?
 * 
 * THREAD: this is not supposed to be thread safe.
 */
public class OFSFileContainerChildInfo extends NSObject {
  protected static final Log log = LogFactory.getLog("JoOFS");
  
  protected File     directory;
  protected File[]   files;
  protected String[] fileIds;   /* index maps to files array */
  protected String[] fileTypes; /* index maps to files array */
  protected String[] ids;       /* there can be DUPs in the file ids! */
  
  public OFSFileContainerChildInfo(File _directory) {
    this.directory = _directory;
  }

  public static OFSFileContainerChildInfo infoForFile(File _file) {
    return _file != null ? new OFSFileContainerChildInfo(_file) : null;
  }
  
  /* accessors */
  
  public File[] files() {
    if (this.files == null) this.load();
    return this.files;
  }
  
  /* loading */
  
  protected static final String[] emptyStringArray = new String[0];
  
  protected Exception load() {
    if (this.files != null)
      return null; /* already loaded */
    
    if (this.directory == null) {
      log().warn("did not find base directory: " + this);
      return new JoNotFoundException("missing base directory for childinfo");
    }
    
    /* load subfiles */
    
    this.files = this.directory.listFiles(ofsFileNameFilter);
    if (this.files == null) {
      log().warn("directory returned no files: " + this);
      return new JoInternalErrorException
        ("could not list directory: " + this.directory.getName());
    }
    
    /* check if its empty */
    
    if (this.files.length == 0) {
      this.fileIds = emptyStringArray;
      this.ids     = this.fileIds; 
    }
    
    /* extract file information */
    
    HashSet<String> idUniquer = new HashSet<String>(this.files.length);
    this.fileIds   = new String[this.files.length];
    this.fileTypes = new String[this.files.length];
    
    for (int i = this.files.length - 1; i >= 0; i--) {
      String fn     = this.files[i].getName();
      int    dotIdx = fn != null ? fn.indexOf('.') : -1;
      
      if (dotIdx == 0) /* this is a .dot file, we never expose those */
        continue; // Note: this should be catched in the filename filter

      if (dotIdx == -1) /* not recommended, file has no extension (README) */
        this.fileIds[i] = fn;
      else {
        this.fileIds[i]   = fn.substring(0, dotIdx);
        this.fileTypes[i] = fn.substring(dotIdx + 1);
      }
      
      if (this.fileIds[i] != null)
        idUniquer.add(this.fileIds[i]);
    }
    
    /* check whether all files where unique and included */
    
    if (this.files.length == idUniquer.size()) {
      /* all IDs were unique */
      this.ids = this.fileIds;
    }
    else {
      /* we found DUPs */
      this.ids = idUniquer.toArray(emptyStringArray);
      Arrays.sort(this.ids);
    }
    
    return null; /* everything is awesome */
  }
  
  /* name lookup */
  
  public boolean hasKey(String _key) {
    if (this.files == null) this.load();
    
    if (this.ids == null || _key == null || _key.length() == 0)
      return false;
      
    int idx = _key.indexOf('.');
    if (idx > 0)
      _key = _key.substring(0, idx);
    
    for (int i = 0; i < this.ids.length; i++) {
      if (_key.equals(this.ids[i]))
        return true;
    }
    return false;
  }
  
  /* basic filter */
  
  protected static FilenameFilter ofsFileNameFilter = new FilenameFilter() {
    /* excluse common filenames */

    public boolean accept(File _directory, String _filename) {
      if (_filename == null || _filename.length() == 0)
        return false;
      
      if (_filename.charAt(0) == '.') { /* filter out all dotfiles */
        /* Note: this includes:
         *   .svn
         *   .DS_Store
         *   .attributes.plist
         */
        return false;
      }
      
      if ("CVS".equals(_filename))
        return false;
      
      return true;
    }
    
  };
  
  /* logging */
  
  public Log log() {
    return log;
  }
  
  /* description */

  @Override
  public void appendAttributesToDescription(StringBuffer _d) {
    super.appendAttributesToDescription(_d);
    
    if (this.directory != null)
      _d.append(" base=" + this.directory);
    
    if (this.files == null)
      _d.append(" not-loaded");
    else {
      _d.append(" #files=" + this.files.length);
      if (this.ids != null) {
        if (this.files.length != this.ids.length)
          _d.append(" has-dups");
          
        if (this.ids != null)
          _d.append(" ids=" + UString.componentsJoinedByString(this.ids, ","));
      }
      else
        _d.append(" no-ids");
    }
  }
}
