// $Id: LocalDatasetSource.java,v 1.10 2004/11/30 22:49:12 edavis Exp $

package thredds.cataloggen.config;

import thredds.catalog.*;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

import org.apache.log4j.Logger;
/**
 * <p>Title: Catalog Generator</p>
 * <p>Description: Tool for generating THREDDS catalogs.</p>
 * <p>Copyright: Copyright (c) 2001</p>
 * <p>Company: UCAR/Unidata</p>
 * @author Ethan Davis
 * @version $Id: LocalDatasetSource.java,v 1.10 2004/11/30 22:49:12 edavis Exp $
 */

public class LocalDatasetSource extends DatasetSource
{
  private static Logger logger = Logger.getLogger( LocalDatasetSource.class.getName());

  private URI accessPointHeaderUri = null;

  LocalDatasetSource()
  {
      this.type = DatasetSourceType.getType( "Local");
  }

  /**
   * Build an unnamed InvCatalog for this DatasetSource and return the
   * top-level InvDataset. The ResultService for this DatasetSource is
   * used to create the InvService for the new InvCatalog. Each InvDataset
   * in the catalog is named with the location of the object they represent
   * on the dataset source.
   *
   * @return the top-level dataset of the newly constructed InvCatalog.
   */
  protected InvDataset getTopLevelDataset()
  {
    // Some setup stuff
    accessPointHeaderUri = new File( this.getResultService().getAccessPointHeader()).toURI();

    // Create catalog.
    InvCatalogImpl catalog = new InvCatalogImpl( null, null, null );
                                                 //this.getName(), null, null);

    // Create service.
    InvService service = new InvService( this.getResultService().getName(),
                                         this.getResultService().getServiceType().toString(),
                                         this.getResultService().getBase(),
                                         this.getResultService().getSuffix(),
                                         this.getResultService().getDescription());
    for ( Iterator it = this.getResultService().getProperties().iterator(); it.hasNext(); )
    {
      service.addProperty( (InvProperty) it.next() );
    }
    for ( Iterator it = this.getResultService().getServices().iterator(); it.hasNext(); )
    {
      service.addService( (InvService) it.next() );
    }

    // Add service to catalog.
    catalog.addService( service);

    // Create top-level dataset.
    LocalInvDataset topDs = new LocalInvDataset( null, new File( this.getAccessPoint()));

    // Set the serviceName (inherited by all datasets) in top-level dataset.
    ThreddsMetadata tm = new ThreddsMetadata( false);
    tm.setServiceName( service.getName());
    InvMetadata md = new InvMetadata( topDs, null, JaxpFactory.CATALOG_NAMESPACE_10, "", true, true, null, tm);
    ThreddsMetadata tm2 = new ThreddsMetadata( false);
    tm2.addMetadata( md);
    topDs.setLocalMetadata(tm2);

    // Check that the top-level dataset is a collection dataset.
    if ( ! topDs.isDirectory())
    {
      String tmpMsg = "The DatasetSource \"" + this.getName() +
                      "\" could not be expanded. Its accessPoint (" + this.getAccessPoint() +
                      ") is not a directory.";
      logger.warn( "getTopLevelDataset(): " + tmpMsg);
      topDs = new LocalInvDataset( null, tmpMsg, this.getAccessPoint());
    }

    // Add top-level dataset to catalog.
    catalog.addDataset( topDs);

    // Tie up any loose ends in catalog with finish().
    ((InvCatalogImpl) catalog).finish();

    return( topDs);
  }

  /**
   * Return true if the given dataset is a collection dataset, false otherwise.
   *
   * @param dataset - the InvDataset to test for being a collection dataset.
   * @return true if the given dataset is a collection dataset, false otherwise.
   * @throws NullPointerException if the given InvDataset is null.
   * @throws ClassCastException if the given InvDataset is not a LocalInvDataset.
   */
  protected boolean isCollection( InvDataset dataset)
  {
    return( ((LocalInvDataset) dataset).isDirectory());
  }

  /**
   * Return a list of the InvDatasets contained in the given collection dataset
   * on this DatasetSource.
   *
   * @param dataset - the collection dataset to be expanded.
   * @return A list of the InvDatasets contained in the given collection dataset.
   * @throws IllegalArgumentException when given dataset is not a collection dataset.
   * @throws NullPointerException if given dataset is null.
   * @throws ClassCastException if the given InvDataset is not a LocalInvDataset.
   */
  protected List expandThisLevel( InvDataset dataset)
  {
    if ( ! isCollection( dataset)) throw new IllegalArgumentException( "Dataset \"" + dataset.getName() + "\" is not a collection dataset.");

    //-----
    // Check that the given location string is URI compliant.
    //-----

    // Deal with all files in this directory.
    File theDir = new File( ((LocalInvDataset) dataset).getLocalPath() );
    File[] allFiles = theDir.listFiles();
    InvDataset curDs = null;
    ArrayList list = new ArrayList();
    for ( int i = 0; i < allFiles.length; i++)
    {
      curDs = new LocalInvDataset( dataset, allFiles[ i]);

      list.add( curDs);
    }

    return( list);
  }

  protected void expandThisType( InvDatasetImpl parent, String givenLocation )
  {
    logger.debug( "expandThisType(): expand the given location (" + givenLocation + ").");

    File[] allFiles;     // Array for all files in this directory.
    File curFile;
    URI curUri;
    String curDsRelativePath;
    String curDsAbsolutePath;

    InvDatasetImpl curDs = null; // Current dataset.

    String accessPointHeader =   //
            this.getResultService().getAccessPointHeader();
    File accessPointHeaderFile = new File( accessPointHeader);
    URI accessPointHeaderUri = accessPointHeaderFile.toURI();

    //-----
    // Test whether the access point header is a directory.
    //-----
    logger.debug( "expandThisType(): Test whether the access point header is a directory.");
    if ( ! accessPointHeaderFile.isDirectory())
    {
      String tmpMsg = "The DatasetSource \"" + this.getName() +
                      "\" could not be expanded. The accessPointHeader (" +
                      accessPointHeader + ") is not a directory.";
      logger.warn( "expandThisType(): " + tmpMsg );
      InvDatasetImpl tmpDs = new InvDatasetImpl( null, tmpMsg);
      parent.addDataset( tmpDs);
      return;
    }


    //-----
    // Check that the given location string is URI compliant.
    //-----
    logger.debug( "expandThisType(): Check if given location (" + givenLocation + ") string is URI compliant.");
    String givenLocationAsURI;
    if ( givenLocation.startsWith( "file:"))
    {
      logger.debug( "expandThisType(): given location starts with \"file:\".");
      givenLocationAsURI = givenLocation;
    }
    else
    {
      logger.debug( "expandThisType(): given location does not start with \"file:\".");
      File tmpFile = new File( givenLocation);
      logger.debug( "expandThisType():     tmpFile name - \"" + tmpFile.getPath() + "\".");
      givenLocationAsURI = tmpFile.toURI().toString();
      logger.debug( "expandThisType():     tmpFileURI name - \"" + givenLocationAsURI + "\".");
    }
    File theDir;
    try
    {
      logger.debug( "expandThisType(): Given location is \"" + givenLocation + "\".");
      URI theDirURI = new URI( givenLocationAsURI);
      logger.debug( "expandThisType(): Given location as URI is \"" + theDirURI.toString() + "\" (" + theDirURI.getScheme() + ").");
      theDir = new File( theDirURI);
      logger.debug( "expandThisType(): Given location as File is \"" + theDir.getAbsolutePath() + "\".");
    }
    catch ( URISyntaxException e )
    {
      logger.warn( "expandThisType(): The DatasetSource \"" + this.getName() +
              "\" could not be expanded. The given location (" + givenLocation + ")" +
              " resulted in a URISyntaxException.");
      InvDatasetImpl tmpDs = null;
      tmpDs = new InvDatasetImpl( null, "The DatasetSource \"" + this.getName() +
              "\" could not be expanded. The given location (" + givenLocation + ")" +
              " resulted in a URISyntaxException.");
      parent.addDataset( tmpDs);
      return;
    }

    //-----
    // Check that the given location is actually a directory.
    //-----
    logger.debug( "expandThisType(): Check that the given location is actually a directory.");
    if ( ! theDir.isDirectory())
    {
      logger.warn( "expandThisType(): The DatasetSource \"" + this.getName() +
              "\" could not be expanded. The given location (" + givenLocation + " - " +
              theDir.getAbsolutePath() + ")" +
              " is not a directory.");
      InvDatasetImpl tmpDs = null;
      tmpDs = new InvDatasetImpl( null, "The DatasetSource \"" + this.getName() +
              "\" could not be expanded. The given location (" + givenLocation + " - " +
              theDir.getAbsolutePath() + ")" +
              " is not a directory.");
      parent.addDataset( tmpDs);
      return;
    }

    // Deal with all files in this directory.
    allFiles = theDir.listFiles();
    logger.debug( "expandThisType(): Deal with all files in this directory (" + allFiles.length + ").");
    for ( int i = 0; i < allFiles.length; i++)
    {
      //-----
      // Determine the datasets urlPath. Use URIs to handle encoding and slash vs backslash issues.
      // Shorten path appropriately using ResultService.accessPointHeader
      // @todo Should the removal of the accessPointHeader be handled in DatasetSource.expand() when optimizing service stuff?
      //-----
      curFile = allFiles[ i];
      curUri = curFile.toURI();
      curDsAbsolutePath = curUri.toString();
      logger.debug( "expandThisType(): handling current file ([" + i + "] " + curDsAbsolutePath + ")");
      if ( curDsAbsolutePath.startsWith( accessPointHeaderUri.toString()))
      {
        curDsRelativePath = curDsAbsolutePath.substring( accessPointHeaderUri.toString().length());
      }
      else
      {
        // If the dataset path doesn't start with the accessPointHeader then skip it.
        logger.warn( "expandThisType(): current path (" + curDsAbsolutePath + ") not accessible" +
                " from the ResultService (" + this.getResultService().getName() + " - "
                + accessPointHeader +"). THIS SHOULDN'T HAPPEN!");
        continue;
      }

      //-----
      // If the current file is a directory, recurse through directories.
      //-----
      if ( curFile.isDirectory())
      {
        logger.debug( "expandThisType(): current file is directory, expand (" + curDsAbsolutePath + ").");
        // If building a hierarchy based on the directory structure,
        // create dataset representing directory and populate.
        if ( ! this.isFlatten())
        {
          // Name the new dataset with the dataset path.
          // @todo How allow naming of directories?
          logger.debug( "expandThisType(): add level in directory tree");
          curDs = new InvDatasetImpl( null, curDsRelativePath, null, null, null );
          this.expandThisType( curDs, curDsAbsolutePath);
          parent.addDataset( curDs);
        }
        // If not building a hierarchy based on the directory structure,
        // populate parent dataset with files in directory structure.
        else
        {
          this.expandThisType( parent, curDsAbsolutePath);
        }
      }
      //-----
      // If the current file is not a directory.
      //-----
      else
      {
        // Name the new dataset "" and set the urlPath to the dataset path.
        curDs = new InvDatasetImpl( null, "", null, null, curDsRelativePath);

        if ( ! this.getDatasetFilterList().isEmpty())
        {
          // Loop through filters in this.datasetFilterList to check that
          // curDs satisfies one of the filters.
          DatasetFilter curFilter = null;
          for ( int j = 0; j < this.getDatasetFilterList().size(); j++)
          {
            curFilter = (DatasetFilter) this.getDatasetFilterList().get( j);

            if ( curFilter.accept( curDs))
            {
              logger.debug( "expandThisType(): the current dataset (" + curDs.getUrlPath() + ") was" +
                      " accepted by the filter \"" + curFilter.getName() + "\"");
              parent.addDataset( curDs);
              break;
            }
          } // END - filter loop
        } // END - if there are filters
        else
        {
          parent.addDataset( curDs);
        } // END - if there are no filters
      } // END - if not a directory
    } // END - loop through files in current directory

    return;
  }

  private class LocalInvDataset extends InvDatasetImpl
  {
    private String localPath = null;
    private boolean directory = false;

    LocalInvDataset( InvDataset parent, File file)
    {
      super( (InvDatasetImpl) parent, null, null, null, null );
      this.localPath = file.getAbsolutePath();
      this.directory = file.isDirectory();

      // Determine the datasets urlPath by removing accessPointHeader from localPath.
      // (Use URIs to handle encoding and slash vs backslash issues.)
      String dsAbsolutePath = file.toURI().toString();
      String dsRelativePath = null;
      if ( dsAbsolutePath.startsWith( accessPointHeaderUri.toString()))
      {
        dsRelativePath = dsAbsolutePath.substring( accessPointHeaderUri.toString().length());
      }
      //else
      //{
        //@todo Otherwise, do what??
      //}


      if ( ! this.directory ) this.setUrlPath( dsRelativePath );
      this.setName( dsRelativePath);
    }

    LocalInvDataset( InvDataset parent, String name, String localPath)
    {
      super( parent, name);
      this.localPath = localPath;
      this.directory = new File( this.localPath ).isDirectory();
    }

    String getLocalPath() { return( this.localPath); }
    void setLocalPath( String localPath ) { this.localPath = localPath; }
    boolean isDirectory() { return( this.directory); }

  }
}
/*
 * $Log: LocalDatasetSource.java,v $
 * Revision 1.10  2004/11/30 22:49:12  edavis
 * Start changing DatasetSource into a more usable API.
 *
 * Revision 1.9  2004/06/03 20:27:24  edavis
 * Update for thredds.catalog changes for InvCatalog 1.0.
 *
 * Revision 1.8  2004/05/11 20:38:46  edavis
 * Update for changes to thredds.catalog object model (still InvCat 0.6).
 * Start adding some logging statements.
 *
 * Revision 1.7  2003/09/24 19:36:31  edavis
 * Fix how "givenLocation" is handled in expandThisType().
 *
 * Revision 1.6  2003/09/12 20:57:26  edavis
 * Deal with possibility that the "givenLocation" string handed to
 * expandThisType() might be either a local file name or a "file:" URI.
 *
 * Revision 1.5  2003/09/05 22:06:24  edavis
 * Add more logging.
 *
 */
