/*
 * WANDORA
 * Knowledge Extraction, Management, and Publishing Application
 * http://wandora.org
 * 
 * Copyright (C) 2004-2023 Wandora Team
 * 
 * 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 3 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/>.
 *
 *
 * 
 *
 * ZipPackageInput.java
 *
 * Created on 17. maaliskuuta 2006, 13:51
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.wandora.topicmap.packageio;

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

/**
 * <p>
 * Reads entries from a zip file. Entries are stored in ZIP file in the same
 * order they were entered with ZipPackageOutput. Searching entries is relatively
 * smart, gotoEntry will scan the file from the current position for the requested
 * entry, unless the entry has been seen already and it is known to be before
 * the current file position. However it is always best to read entries
 * in the order they appear in the file if possible. This avoids opening
 * the file several times which in the case of a remote file means transferring
 * the file several times over network.</p>
 *
 * @author olli
 */
public class ZipPackageInput implements PackageInput {
    
    private URL url;
    private ZipInputStream zis;
    private List<String> entries=null;
    private List<String> passedEntries=null;
    private int currentEntry=-1;

    
    
    /** Creates a new instance of ZipPackageInput */
    public ZipPackageInput(File file) throws IOException {
        this(file.toURI().toURL());
    }
    public ZipPackageInput(String file) throws IOException {
        this(new File(file).toURI().toURL());
    }
    
    public ZipPackageInput(URL url) throws IOException {
        this.url=url;
    }

    private void openStream() throws IOException {
        if(zis!=null) zis.close();
        zis=new ZipInputStream(url.openStream());
        currentEntry=-1;
        passedEntries=new ArrayList<String>();
    }
    
    

    
    /**
     * Moves to the entry with the specified name. Returns true if that entry was
     * found, false otherwise.
     */
    @Override
    public boolean gotoEntry(String name) throws IOException {
        if(zis==null) currentEntry=-1;
        boolean findFromStart=false;
        if(entries!=null) {
            int ind=entries.indexOf(name);
            if(ind==-1) return false;
            if(ind<=currentEntry) findFromStart=true;
        }
        else if(passedEntries!=null && passedEntries.contains(name)) {
            findFromStart=true;
        }
        if( (findFromStart && currentEntry>=0) || zis==null) {
            openStream();
        }
        String e=null;
        while(true) {
            e=gotoNextEntry();
            if(e==null) return false;
            if(e.equals(name)) return true;
        }
    }
    
    
    @Override
    public boolean gotoEntry(String path, String name) throws IOException {
        return gotoEntry(joinPath(path, name));
    }
    
    
    
    /**
     * Goes to next entry in the file.
     * 
     * @return String
     * @throws java.io.IOException
     */
    @Override
    public String gotoNextEntry() throws IOException{
        if(zis==null) {
            openStream();
        }
        currentEntry++;
        ZipEntry e=zis.getNextEntry();
        if(e==null) {
            return null;
        }
        passedEntries.add(e.getName());
        return e.getName();
    }
    
    
    /**
     * Gets the input stream for current entry.
     * 
     * @return InputStream
     * @throws java.io.IOException
     */
    @Override
    public InputStream getInputStream() throws IOException {
        return new InputStream(){
            @Override
            public int available() throws IOException {
                return zis.available();
            }
            public int read() throws IOException {
                return zis.read();
            }
            @Override
            public int read(byte[] b) throws IOException {
                return zis.read(b);
            }
            @Override
            public int read(byte[] b,int off,int len) throws IOException {
                return zis.read(b,off,len);
            }
            @Override
            public long skip(long n) throws IOException {
                return zis.skip(n);
            }
        };
    }
    
    
    /**
     * Gets the names of all entries in the file. This requires scanning
     * of the entire file and then reopening the file after the entries
     * are later actually read when gotoEntry or gotoNextEntry is called.
     */
    @Override
    public Collection<String> getEntries() throws IOException {
        if(entries!=null) return entries;
        if(zis!=null) openStream();
        ZipEntry e=null;
        entries=new ArrayList<String>();
        while( (e=zis.getNextEntry())!=null ){
            entries.add(e.getName());
        }
        zis.close();
        zis=null;
        return entries;
    }

    
    /**
     * Closes the file.
     */
    @Override
    public void close() throws IOException {
        zis.close();
        zis=null;
    }

    
    
    
    @Override
    public String getSeparator() {
        return "/";
    }
    
    @Override
    public String joinPath(String path, String name) {
        if(path != null && path.length()>0) {
            return path + getSeparator() + name;
        }
        else {
            return name;
        }
    }
    
}
