/* GraphBundleLoader.java
 * =========================================================================
 * This file is part of the GrInvIn project - http://www.grinvin.org
 * 
 * Copyright (C) 2005-2008 Universiteit Gent
 * 
 * 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 2 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.
 * 
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 * 
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.grinvin.io.graphs;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import org.grinvin.graphs.Annotation;
import org.grinvin.graphs.Embedding;
import org.grinvin.graphs.Graph;
import org.grinvin.graphs.GraphBundle;
import org.grinvin.gui.icons.DefaultGraphIconFactory;
import org.grinvin.gui.icons.GraphIconFactory;
import org.grinvin.io.DirectorySectionLoader;
import org.grinvin.io.IOFormatException;
import org.grinvin.io.InvariantValuesLoader;
import org.grinvin.io.SectionLoader;
import org.grinvin.io.ZipFileSectionLoader;
import org.grinvin.io.ZipInputStreamSectionLoader;
import org.grinvin.io.compat.GraphLoader_1_0;
import org.grinvin.util.InternationalizedProperties;

/**
 * Loads a {@link GraphBundle} from a zip file.
 */
public class GraphBundleLoader {
    
    //
    private static final Logger LOGGER = Logger.getLogger("org.grinvin.io");
    
    //
    private final GraphBundle bundle;
    
    //
    private final SectionLoader sloader;
    
    //
    private final String path;
    
    // meta-information stored in the file
    private Properties meta;
    
    /**
     * Default constructor.
     */
    private GraphBundleLoader(GraphBundle bundle, SectionLoader sloader, String path) {
        this.bundle = bundle;
        this.sloader = sloader;
        this.path = path;
    }
    
    private GraphBundleLoader(GraphBundle bundle, SectionLoader sloader) {
        this(bundle, sloader, null);
    }
    
    /**
     * Get the entry with the given name.
     */
    private InputStream openEntry(String name) throws IOException {
        InputStream in;
        if (path != null) {
            in = sloader.openSection(path + "/" + name);
        } else {
            in = sloader.openSection(name);
        }
        if (in == null)
            throw new IOFormatException("Expected section: " + path + "/" + name);
        return in;
    }
    
    /**
     * Load the meta-information for the graph.
     */
    private void loadMeta() throws IOException {
        meta = new Properties();
        InputStream in = openEntry("meta-info.xml");
        meta.loadFromXML(in);
        in.close();
    }
    
    /**
     * Load the graph properties.
     */
    private void loadProperties() throws IOException {
        InputStream in = openEntry("resources.xml");
        InternationalizedProperties props = new InternationalizedProperties();
        props.load(in);
        bundle.setProperties(props);
        in.close();
    }

    /**
     * Load the graph properties without validating the XML schema.
     */
    private void loadPropertiesNonValidating() throws IOException {
        InputStream in = openEntry("resources.xml");
        InternationalizedProperties props = new InternationalizedProperties();
        props.loadNonValidating(in);
        bundle.setProperties(props);
        in.close();
    }

    
    /**
     * Load the abstract graph.
     */
    private void loadGraph() throws IOException {
        InputStream in = openEntry("graph.xml");
        Graph graph = bundle.createGraph();
        GraphLoader.load(graph, in);
        in.close();
    }
    
    /**
     * Load the abstract graph (version 1.0).
     */
    private void loadGraph_1_0() throws IOException {
        InputStream in = openEntry("graph.xml");
        Graph graph = bundle.createGraph();
        Annotation annotation = bundle.createAnnotation();
        GraphLoader_1_0.load(graph, annotation, in);
        in.close();
    }
    
    private void loadEmbeddings(boolean validate) throws IOException {
        try {
            int embeddingsCount = Integer.parseInt(meta.getProperty("nrOfEmbeddings", "2"));
            for (int i = 0; i < embeddingsCount; i++) {
                if (validate) {
                    addEmbedding(i);
                } else {
                    addEmbeddingNonValidating(i);
                }
            }
        } catch (NumberFormatException nfex) {
            throw new IOFormatException("number of embeddings is not an integer", nfex);
        }
    }
            
    /**
     * Add the embedding with the given index as the next embedding in the bundle.
     */
    private void addEmbedding(int index) throws IOException {
        InputStream in = openEntry("embedding_"+index+".xml");
        Embedding embedding = bundle.createEmbedding();
        EmbeddingLoader.load(embedding, in);
        in.close();
    }

    /**
     * Add the embedding with the given index as the next embedding in the bundle
     * without validating the XML schema.
     */
    private void addEmbeddingNonValidating(int index) throws IOException {
        InputStream in = openEntry("embedding_"+index+".xml");
        Embedding embedding = bundle.createEmbedding();
        EmbeddingLoader.loadNonValidating(embedding, in);
        in.close();
    }

    private void loadAnnotations() throws IOException {
        try {
            int count = Integer.parseInt(meta.getProperty("nrOfAnnotations"));
            for (int i = 0; i < count; i++) {
                addAnnotation(i);
            }
        } catch (NumberFormatException nfex) {
            throw new IOFormatException("number of annotations is not an integer", nfex);
        }
    }

    
    /**
     * Add the embedding with the given index as the next embedding in the bundle.
     */
    private void addAnnotation(int index) throws IOException {
        InputStream in = openEntry("annotation_"+index+".xml");
        Annotation annotation = bundle.createAnnotation();
        AnnotationLoader.load(annotation, in);
        in.close();
    }
    
    //
    private void loadInvariantValues() throws IOException {
        InputStream in;
        try {
            in = openEntry("invariantvalues.xml");
        } catch (IOFormatException ex) {
            // the given bundle does not contain invariant values
            //TODO: log failure somewhere
            return;
        }
        InvariantValuesLoader.load(bundle, in);
        in.close();
    }

    //
    private void loadInvariantValues_1_0() throws IOException {
        InputStream in;
        try {
            in = openEntry("invariantvalues.xml");
        } catch (IOFormatException ex) {
            // the given bundle does not contain invariant values
            //TODO: log failure somewhere
            return;
        }
        InvariantValuesLoader.load_1_0(bundle, in);
        in.close();
    }

    
    /**
     * Load the bundle and close the inputstream.
     */
    private void load() throws IOException {
        loadMeta();
        String versionStr = meta.getProperty("version");
        if (versionStr.equals("1.0")) {
            loadPropertiesNonValidating();
            loadGraph_1_0();
            loadEmbeddings(false);
            loadInvariantValues_1_0();
        } else {
            loadProperties();
            loadGraph();
            loadEmbeddings(true);
            loadAnnotations();
            loadInvariantValues();
        }
        initGraphIconFactory();
    }
    
    /**
     * Initialize the graph icon factory.
     */
    private void initGraphIconFactory() {
        String name = meta.getProperty("graphIconFactory");
        if (name == null) {
            bundle.setGraphIconFactory(DefaultGraphIconFactory.getInstance());
        } else {
            try {
                Class<?> clazz = Class.forName(name);
                Method method = clazz.getMethod("getInstance");
                GraphIconFactory gif = (GraphIconFactory)method.invoke(null); // static method
                bundle.setGraphIconFactory(gif);
            } catch (Exception ex) {
                LOGGER.log(Level.WARNING, "unable to instantiate graph icon factory", ex);
            }
        }
    }
    
    /**
     * Load the bundle from the given file.
     * @param bundle Empty bundle which will hold the result of the load operation.
     * @param file File which contains the graph bundle in Zip-format
     */
    public static void loadFromZip(GraphBundle bundle, File file) throws IOException {
        ZipFile zip = new ZipFile(file);
        GraphBundleLoader loader = new GraphBundleLoader(bundle, new ZipFileSectionLoader(zip));
        loader.load();
        zip.close();
    }
    
    /**
     * Load the bundle from the given input stream.
     * @param bundle Empty bundle which will hold the result of the load operation.
     * @param in Input stream which contains the graph bundle in Zip-format
     */
    public static void loadFromZip(GraphBundle bundle, InputStream in) throws IOException {
        ZipInputStream zip = new ZipInputStream(in);
        GraphBundleLoader loader = new GraphBundleLoader(bundle, new ZipInputStreamSectionLoader(zip));
        loader.load();
        in.close();
    }
    
    /**
     * Load the bundle from the given directory. The directory should
     * contain the uncompressed contents of a ZIP-archive.<p>
     * <b>Note:</b> The preferred representation of graph bundles on disk
     * is by means of compressed ZIP-archives. This method is provided
     * primarily for debugging purposes.
     *
     * @param bundle Empty bundle which will hold the result of the load operation.
     * @param dir Directory which contains the graph bundle as
     * uncompressed contents of a ZIP-archive.
     */
    public static void loadFromDirectory(GraphBundle bundle, File dir) throws IOException {
        GraphBundleLoader loader = new GraphBundleLoader(bundle, new DirectorySectionLoader(dir));
        loader.load();
    }
    
    //
    public static void load(GraphBundle bundle, SectionLoader sloader, String path) throws IOException {
        GraphBundleLoader loader = new GraphBundleLoader(bundle, sloader, path);
        loader.load();
    }

}
