/**
 *   This file is part of Serianalyzer.
 *
 *   Serianalyzer 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.
 *
 *   Serianalyzer 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 Serianalyzer.  If not, see <http://www.gnu.org/licenses/>.
 *   
 * Copyright 2015,2016 Moritz Bechler <[email protected]>
 * 
 * Created: 16.11.2015 by mbechler
 */
package serianalyzer;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.activation.DataSource;
import javax.activation.URLDataSource;

import org.apache.log4j.Logger;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;


/**
 * @author mbechler
 */
public class SerianalyzerInput {

    private static final Logger log = Logger.getLogger(SerianalyzerInput.class);

    /**
     * 
     */
    private SerianalyzerConfig config;
    /**
     * 
     */
    private Indexer indexer = new Indexer();
    /**
     * 
     */
    private Map<String, DataSource> classData = new HashMap<>();
    /**
     * 
     */
    private Index index;


    /**
     * @param cfg
     * 
     */
    public SerianalyzerInput ( SerianalyzerConfig cfg ) {
        this.config = cfg;
    }


    /**
     * @return the config
     */
    public SerianalyzerConfig getConfig () {
        return this.config;
    }


    /**
     * @return the index
     */
    public Index getIndex () {
        if ( this.index == null ) {
            this.index = this.indexer.complete();
        }
        return this.index;
    }


    /**
     * 
     * @param jarFile
     * @throws IOException
     */
    public void index ( File jarFile ) throws IOException {
        log.debug("Indexing file " + jarFile); //$NON-NLS-1$
        @SuppressWarnings ( "resource" )
        JarFile jar = new JarFile(jarFile);
        Enumeration<JarEntry> entries = jar.entries();
        while ( entries.hasMoreElements() ) {
            JarEntry entry = entries.nextElement();
            if ( entry.getName().endsWith(".class") ) { //$NON-NLS-1$
                index(new JARDataSource(jar, entry));
            }
            else if ( entry.getName().endsWith(".jar") ) { //$NON-NLS-1$
                log.error("Nested JARs not yet supported " + entry.getName()); //$NON-NLS-1$
            }
        }
    }


    /**
     * 
     * @param u
     * @throws IOException
     */
    public void index ( URL u ) throws IOException {
        try ( InputStream openStream = u.openStream() ) {
            index(new URLDataSource(u));
        }
    }


    /**
     * 
     * @param source
     * @throws IOException
     */
    public void index ( DataSource source ) throws IOException {
        ClassInfo ci = this.indexer.index(source.getInputStream());
        if ( log.isTraceEnabled() ) {
            log.trace("Adding " + ci.name()); //$NON-NLS-1$
        }
        if ( this.classData.put(ci.name().toString(), new ByteDataSource(source)) != null ) {
            log.warn("Duplicate class " + ci.name()); //$NON-NLS-1$
        }
    }


    /**
     * @param p
     * @throws IOException
     */
    public void index ( Path p ) throws IOException {
        if ( Files.isDirectory(p) ) {
            Files.find(p, 10, ( t, u ) -> t.getFileName().toString().endsWith(".jar")).forEach(r -> { //$NON-NLS-1$
                try {
                    index(r.toFile());
                }
                catch ( IOException e ) {
                    log.error("Failed to open file " + r, e); //$NON-NLS-1$
                }
            });
        }
        else {
            try {
                index(p.toFile());
            }
            catch ( IOException e ) {
                log.error("Failed to open file " + p, e); //$NON-NLS-1$
            }
        }
    }


    /**
     * @param typeName
     * @return class bytecode data
     * @throws IOException
     */
    public InputStream getClassData ( String typeName ) throws IOException {
        DataSource data = this.classData.get(typeName);
        if ( data == null ) {
            return null;
        }
        return data.getInputStream();
    }
}