/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.test.virtual.support;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.jboss.virtual.VirtualFile;
import org.jboss.virtual.VirtualFileFilter;
import org.jboss.virtual.plugins.vfs.VirtualFileURLConnection;

/**
 * ClassPathIterator logic used by UCL package mapping
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision:$
 */
public class ClassPathIterator
{
   ZipInputStream zis;
   FileIterator fileIter;
   File file;
   VirtualFileIterator vfIter;
   VirtualFile vf;
   int rootLength;

   public ClassPathIterator(URL url) throws IOException
   {
      String protocol = url != null ? url.getProtocol() : null;
      if( protocol == null )
      {
      }
      else if( protocol.equals("file") )
      {
         File tmp = new File(url.getFile());
         if( tmp.isDirectory() )
         {
            rootLength = tmp.getPath().length() + 1;
            fileIter = new FileIterator(tmp);
         }
         else
         {
            // Assume this is a jar archive
            InputStream is = new FileInputStream(tmp);
            zis = new ZipInputStream(is);
         }
      }
      else if( protocol.startsWith("vfs") )
      {
         VirtualFileURLConnection conn = (VirtualFileURLConnection)url.openConnection();
         vf = conn.getContent();
         rootLength = vf.getPathName().length() + 1;
         vfIter = new VirtualFileIterator(vf);
      }
      else
      {
         // Assume this points to a jar
         InputStream is = url.openStream();
         zis = new ZipInputStream(is);
      }
   }

   public ClassPathEntry getNextEntry() throws IOException
   {
      ClassPathEntry entry = null;
      if( zis != null )
      {
         ZipEntry zentry = zis.getNextEntry();
         if( zentry != null )
            entry = new ClassPathEntry(zentry);
      }
      else if( fileIter != null )
      {
         File fentry = fileIter.getNextEntry();
         if( fentry != null )
            entry = new ClassPathEntry(fentry, rootLength);
         file = fentry;
      }
      else if( vfIter != null )
      {
         VirtualFile fentry = vfIter.getNextEntry();
         if( fentry != null )
            entry = new ClassPathEntry(fentry, rootLength);
         vf = fentry;
      }

      return entry;
   }

   InputStream getInputStream() throws IOException
   {
      InputStream is = zis;
      if( zis == null )
      {
         is = new FileInputStream(file);
      }
      return is;
   }

   public void close() throws IOException
   {
      if( zis != null )
         zis.close();
   }

   static class FileIterator
   {
      LinkedList subDirectories = new LinkedList();
      FileFilter filter;
      File[] currentListing;
      int index = 0;

      FileIterator(File start)
      {
         String name = start.getName();
         // Don't recurse into wars
         boolean isWar = name.endsWith(".war");
         if( isWar )
            currentListing = new File[0];
         else
            currentListing = start.listFiles();
      }
      FileIterator(File start, FileFilter filter)
      {
         String name = start.getName();
         // Don't recurse into wars
         boolean isWar = name.endsWith(".war");
         if( isWar )
            currentListing = new File[0];
         else
            currentListing = start.listFiles(filter);
         this.filter = filter;
      }

      File getNextEntry()
      {
         File next = null;
         if( index >= currentListing.length && subDirectories.size() > 0 )
         {
            do
            {
               File nextDir = (File) subDirectories.removeFirst();
               currentListing = nextDir.listFiles(filter);
            } while( currentListing.length == 0 && subDirectories.size() > 0 );
            index = 0;
         }
         if( index < currentListing.length )
         {
            next = currentListing[index ++];
            if( next.isDirectory() )
               subDirectories.addLast(next);
         }
         return next;
      }
   }

   static class VirtualFileIterator
   {
      LinkedList<VirtualFile> subDirectories = new LinkedList<VirtualFile>();
      VirtualFileFilter filter;
      List<VirtualFile> currentListing;
      int index = 0;

      VirtualFileIterator(VirtualFile start) throws IOException
      {
         this(start, null);
      }
      VirtualFileIterator(VirtualFile start, VirtualFileFilter filter)  throws IOException
      {
         String name = start.getName();
         // Don't recurse into wars
         boolean isWar = name.endsWith(".war");
         if( isWar )
            currentListing = new ArrayList<VirtualFile>();
         else
            currentListing = start.getChildren();
         this.filter = filter;
      }

      VirtualFile getNextEntry()
         throws IOException
      {
         VirtualFile next = null;
         if( index >= currentListing.size() && subDirectories.size() > 0 )
         {
            do
            {
               VirtualFile nextDir = subDirectories.removeFirst();
               currentListing = nextDir.getChildren(filter);
            } while( currentListing.size() == 0 && subDirectories.size() > 0 );
            index = 0;
         }
         if( index < currentListing.size() )
         {
            next = currentListing.get(index);
            index ++;
            if( next.isLeaf() == false )
               subDirectories.addLast(next);
         }
         return next;
      }
   }

   public static class ClassPathEntry
   {
      public String name;
      public ZipEntry zipEntry;
      public File fileEntry;
      public VirtualFile vfEntry;

      ClassPathEntry(ZipEntry zipEntry)
      {
         this.zipEntry = zipEntry;
         this.name = zipEntry.getName();
      }
      ClassPathEntry(File fileEntry, int rootLength)
      {
         this.fileEntry = fileEntry;
         this.name = fileEntry.getPath().substring(rootLength);
      }
      ClassPathEntry(VirtualFile vfEntry, int rootLength)
      {
         this.vfEntry = vfEntry;
         this.name = vfEntry.getPathName().substring(rootLength);
      }

      String getName()
      {
         return name;
      }
      /** Convert the entry path to a package name
       */
      String toPackageName()
      {
         String pkgName = name;
         char separatorChar = zipEntry != null ? '/' : File.separatorChar;
         int index = name.lastIndexOf(separatorChar);
         if( index > 0 )
         {
            pkgName = name.substring(0, index);
            pkgName = pkgName.replace(separatorChar, '.');
         }
         else
         {
            // This must be an entry in the default package (e.g., X.class)
            pkgName = "";
         }
         return pkgName;
      }

      boolean isDirectory()
      {
         boolean isDirectory = false;
         if( zipEntry != null )
            isDirectory = zipEntry.isDirectory();
         else
            isDirectory = fileEntry.isDirectory();
         return isDirectory;
      }
   }

}

