/* DefaultGraphBundle.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.graphs;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.grinvin.gui.icons.DefaultGraphIconFactory;
import org.grinvin.gui.icons.GraphIconFactory;
import org.grinvin.invariants.Invariant;
import org.grinvin.invariants.InvariantComputer;
import org.grinvin.invariants.InvariantManager;
import org.grinvin.invariants.InvariantValue;
import org.grinvin.util.InternationalizedProperties;

/**
 * Default implementation of {@link GraphBundle}. Uses {@link DefaultGraph} and
 * {@link DefaultEmbedding} to represent the bundle.
 */
public class DefaultGraphBundle implements GraphBundle {
    
    //
    protected DefaultGraph graph;
    
    //
    protected List<DefaultEmbedding> embeddings;
    
    //
    protected List<DefaultAnnotation> annotations;
    
    //
    protected InternationalizedProperties properties;
    
    //TODO: might be better to use a special map here as InvariantValue contains Invariant as well
    protected Map<Invariant, InvariantValue> invariantValues;
    
    //
    protected List<GraphBundleListener> listeners;
    
    //
    protected GraphIconFactory graphIconFactory;

    //
    public GraphIconFactory getGraphIconFactory() {
        return graphIconFactory;
    }

    //
    public void setGraphIconFactory(GraphIconFactory graphIconFactory) {
        if (graphIconFactory == null)
            this.graphIconFactory = DefaultGraphIconFactory.getInstance();
        else
            this.graphIconFactory = graphIconFactory;
    }
    

    /**
     * Default constructor.
     */
    public DefaultGraphBundle() {
        this.graph = null;
        this.embeddings = new ArrayList<DefaultEmbedding>();
        this.annotations = new ArrayList<DefaultAnnotation>();
        invariantValues = new HashMap<Invariant, InvariantValue>();
        listeners = new ArrayList<GraphBundleListener>();
        this.graphIconFactory = DefaultGraphIconFactory.getInstance();
    }
    
    // implements GraphBundle
    public void setProperties(InternationalizedProperties properties) {
        this.properties = properties;
    }
    
    // implements GraphBundleView
    public DefaultEmbedding getEmbedding(int index) {
        return embeddings.get(index);
    }
    
    // implements GraphBundle
    public DefaultEmbedding createEmbedding() {
        DefaultEmbedding embedded = new DefaultEmbedding(graph, 0);
        embeddings.add(embedded);
        return embedded;
    }
    
    // implements GraphBundleView
    public int getEmbeddingCount() {
        return embeddings.size();
    }
    
    // implements GraphBundleView
    public DefaultEmbedding getEmbedding() {
        if (getEmbeddingCount() > 0)
            return getEmbedding(0);
        else
            return null;
    }
    
    // implements GraphBundle
    public DefaultGraph createGraph() {
        this.graph = new DefaultGraph();
        return graph;
    }
    
    // implements GraphBundle
    public DefaultAnnotation createAnnotation() {
        DefaultAnnotation annotation = new DefaultAnnotation(graph);
        annotations.add(annotation);
        return annotation;
    }

    // implements GraphBundleView
    public DefaultAnnotation getAnnotation() {
        if (getAnnotationCount() > 0)
            return getAnnotation(0);
        else
            return null;
    }
    
    // implements GraphBundleView
    public DefaultAnnotation getAnnotation(int index) {
        return annotations.get(index);
    }
    
    //implements GraphBundleView
    public int getAnnotationCount() {
        return annotations.size();
    }
    
    // implements GraphBundleView
    public InternationalizedProperties getProperties() {
        return properties;
    }
    
    // implements GraphBundleView
    public DefaultGraph getGraph() {
        return graph;
    }
    
    // implements GraphBundleView
    public String getName() {
        if (properties == null)
            return null;
        else
            return properties.getProperty("graph.name");
    }
    
    // implements GraphBundleView
    public String getDescription() {
        if (properties == null)
            return null;
        else
            return properties.getProperty("graph.description");
    }
    
    // implements getInvariantValue
    public InvariantValue getInvariantValue(Invariant invariant) {
        InvariantValue result = invariantValues.get(invariant);
        if (result == null || isOldInvariantValue(result)) {
            InvariantComputer v = InvariantManager.getInstance().getInvariantComputerFor(invariant);
            try {
                result = v.compute(this);
                addInvariantValue(result);
            } catch (Exception ex) {
                // makes sure Grinvin does not go down with defunct invariant computers
                Logger logger = Logger.getLogger("org.grinvin.invariants.computers");
                logger.log(Level.WARNING, "InvariantComputer " + v.getId() + " failed to compute invariant " + v.getInvariantId() + " for graph " + this.getName(), ex);
            }
        }
        return result;
    }
    
    // implements getCachedInvariantValue
    public InvariantValue getCachedInvariantValue(Invariant invariant) {
        InvariantValue result = invariantValues.get(invariant);
        if (result != null && !isOldInvariantValue(result)) {
            return result;
        } else {
            return null;
        }
    }
    
    private boolean isOldInvariantValue(InvariantValue value) {
        List<InvariantComputer> computers = InvariantManager.getInstance().getInvariantComputersFor(value.getInvariant());
        for (InvariantComputer computer : computers) {
            if (computer.getId().equals(value.getComputerId()) && computer.getVersion().compareTo(value.getComputerVersion()) > 0) {
                return true;
            } else if (isStandardInvariantComputer(computer) && getOldInvariantComputerId(computer).equals(value.getComputerId()) &&
                    computer.getVersion().compareTo(value.getComputerVersion()) > 0) {
                return true;
            }
        }
        return false;
    }
    
    private String getOldInvariantComputerId(InvariantComputer computer) {
        if (isStandardInvariantComputer(computer)) {
            String computerId = computer.getId();
            int pos = computerId.lastIndexOf('.');
            return "org.grinvin.invariants" + computerId.substring(pos);
        } else {
            return computer.getId();
        }
    }
    
    private boolean isStandardInvariantComputer(InvariantComputer computer) {
        return computer.getId().startsWith("org.grinvin.invariants.");
    }
    
    // implements GraphBundleView
    public Collection<InvariantValue> getInvariantValues() {
        return invariantValues.values();
    }
    
    // implements GraphBundleView
    public Set<Invariant> getInvariants() {
        return invariantValues.keySet();
    }
    
    //implements addInvariantValue
    public void addInvariantValue(InvariantValue value) {
        invariantValues.put(value.getInvariant(), value);
        fireGraphBundleChanged();
    }
    
    //
    public void addGraphBundleListener(GraphBundleListener listener) {
        listeners.add(listener);
    }
    
    //
    public void removeGraphBundleListener(GraphBundleListener listener) {
        listeners.remove(listener);
    }
    
    //
    public void fireGraphBundleChanged() {
        for (GraphBundleListener listener : listeners)
            listener.graphBundleChanged(this);
    }
    
    /* ============================================================
     * CACHED VALUES
     * ============================================================ */
    
    //
    private int cachedModCount;
    
    //
    private boolean [][] booleanAdjacencyMatrix;
    
    //
    public boolean[][] booleanAdjacencyMatrix() {
        if (booleanAdjacencyMatrix == null || graph.getModCount() != cachedModCount) {
            invalidateCache();
            booleanAdjacencyMatrix = Graphs.booleanAdjacencyMatrix(graph);
            cachedModCount = graph.getModCount();
        }
        return booleanAdjacencyMatrix;
    }
    
    //
    private int[][] adjacencyList;
    
    //
    public int[][] adjacencyList() {
        if (adjacencyList == null || graph.getModCount() != cachedModCount) {
            invalidateCache();
            adjacencyList = Graphs.adjacencyList(graph);
            cachedModCount = graph.getModCount();
        }
        return adjacencyList;
    }
    
    //
    private int[][] distanceMatrix;
    
    //
    public int[][] distanceMatrix() {
        if (distanceMatrix == null || graph.getModCount() != cachedModCount) {
            invalidateCache();
            distanceMatrix = Graphs.distanceMatrix(graph);
            cachedModCount = graph.getModCount();
        }
        return distanceMatrix;
    }

    //
    private int[] eccentricityList;
    
    //
    public int[] eccentricityList() {
        if (eccentricityList == null || graph.getModCount() != cachedModCount) {
            invalidateCache();
            eccentricityList = Graphs.eccentricityList(graph);
            cachedModCount = graph.getModCount();
        }
        return eccentricityList;
    }
    
    // clear the cached values
    private void invalidateCache() {
        booleanAdjacencyMatrix = null;
        adjacencyList = null;
        distanceMatrix = null;
        eccentricityList = null;
    }
    
    // clear the cached values and removed the cached invariantvalues
    public void invalidate() {
        invalidateCache();
        invariantValues.clear();
        fireGraphBundleChanged();
    }

    public void invalidate(InvariantValue value) {
        invariantValues.remove(value.getInvariant());
        fireGraphBundleChanged();
    }
}
