/* EngineRunner.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.conjecture.engine;

import java.awt.EventQueue;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;

import org.grinvin.conjecture.filter.GraphFilter;
import org.grinvin.expr.Expression;
import org.grinvin.generators.GeneratorException;
import org.grinvin.generators.graphs.FilteredInvariantListGeneratorSink;
import org.grinvin.generators.graphs.GraphGeneratorInstance;
import org.grinvin.generators.graphs.InvariantListGeneratorSink;
import org.grinvin.worksheet.WorksheetModel;
import org.grinvin.invariants.Invariant;
import org.grinvin.invariants.InvariantValue;
import org.grinvin.list.invariants.DefaultInvariantList;
import org.grinvin.list.graphs.GraphListElement;

/**
 * Object which can be used to run a given conjecturing engine in a background thread.
 * <p>
 * Allows listeners to be notified when the run starts and when it ends.
 */
public class EngineRunner implements Runnable {
    
    //
    private final Engine engine;
    
    //
    private final WorksheetModel worksheetModel;
    
    /**
     * Return the associated engine.
     */
    public Engine getEngine() {
        return engine;
    }
    
    //
    private final EventListenerList listenerList = new EventListenerList();
    
    //
    private static final Class<ChangeListener> CHANGE_LISTENER_CLASS = ChangeListener.class;
    
    //
    private final ChangeEvent EVENT = new ChangeEvent(this);
    
    /**
     * Register an object which is informed of changes in the
     * 'running' state of this runner.
     */
    public void addChangeListener(ChangeListener l) {
        listenerList.add(CHANGE_LISTENER_CLASS, l);
    }
    
    /**
     * Remove a change listener.
     */
    public void removeChangeListener(ChangeListener l) {
        listenerList.remove(CHANGE_LISTENER_CLASS, l);
    }
    
    /**
     * Notify every listener of a change in state.
     */
    protected void fireStateChanged() {
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length-2; i>=0; i-=2)
            // if (listeners[i]==CHANGE_LISTENER_CLASS)
            ((ChangeListener)listeners[i+1]).stateChanged(EVENT);
    }
    
    /**
     * Construct a runner object for the given engine.
     * 
     * @param engine Engine to be used
     * @param worksheetModel Model which determines the input for this EngineRunner
     */
    public EngineRunner(Engine engine,
            WorksheetModel worksheetModel) {
        this.engine = engine;
        this.worksheetModel = worksheetModel;
    }
    
    //
    private Expression result;
    
    /**
     * Return the latest result generated by the engine (may be null).
     */
    public Expression getResult() {
        return this.result;
    }
    
    //
    public enum Status {
        STOPPED,
        GENERATOR,
        INVARIANTS,
        CONJECTURE;
    }
    
    //
    private Status status = Status.STOPPED;
    
    //
    public Status getStatus() {
        return status;
    }
    
    //
    private void setStatus(Status status) {
        this.status = status;
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                fireStateChanged();
            }
        });
    }
    
    /**
     * Extract the invariant values from the table and run the engine. Changes are
     * reported to all listeners in the event dispatching thread, even when this
     * method is run in a different thread (which is generally a good idea).
     */
    public void run() {
        worksheetModel.writeStateToHistory(); //history
        this.result = null;
        List<Invariant> allowedInvariantList = new DefaultInvariantList();
        for(Invariant inv : worksheetModel.getInvariantList())
            if(engine.allows(inv))
                allowedInvariantList.add(inv);
        int numberOfInvariants = allowedInvariantList.size();
        int numberOfGenerators = worksheetModel.getGeneratorInstanceList().size();
        int numberOfRows = worksheetModel.getGraphList().size();
        
        if (numberOfInvariants == 0 || numberOfGenerators + numberOfRows == 0)
            return; // nothing to do
        
        setStatus(Status.GENERATOR);
        
        // gather invariants
        Invariant[] invariants = new Invariant[numberOfInvariants];
        for (int i = 0; i < numberOfInvariants; i++)
            invariants[i] = allowedInvariantList.get(i);

        // make sure the invariants are lexically sorted
        Arrays.sort(invariants, new InvariantIdComparator());
        
        // get filter
        GraphFilter filter = worksheetModel.getFilter();
        
        // gather invariant values from generators
        InvariantListGeneratorSink sink = new FilteredInvariantListGeneratorSink(invariants, filter);
        for (GraphGeneratorInstance instance : worksheetModel.getGeneratorInstanceList()) {
            try {
                instance.generate(sink);
            } catch (GeneratorException ex) {
                Logger.getLogger("org.grinvin.generators").log(Level.WARNING, "Failed to generate graphs from generator '" + instance.getName() + "' (" + instance.getId() + ")", ex);
                setStatus(Status.STOPPED);
                return;
            }
        }
        List<InvariantValue[]> values = sink.getListOfInvariantValues();

        setStatus(Status.INVARIANTS);
        
        // get invariant values from graph table
        for (GraphListElement gle : worksheetModel.getGraphList()) {
            if(filter.accepts(gle.getBundle())){
                InvariantValue[] graphvalues = new InvariantValue[numberOfInvariants];
                for (int i=0; i < numberOfInvariants; i++)
                    graphvalues[i] = gle.getInvariant(invariants[i]);
                values.add(graphvalues);
            }
        }
        
        // create array with values
        InvariantValue[][] valuesArray = new InvariantValue[values.size()][invariants.length];
        int i=0;
        for(InvariantValue[] ivs : values){
            valuesArray[i++] = ivs;
        }
               
        // compute conjecture
        setStatus(Status.CONJECTURE);
        this.result = engine.run(valuesArray);
        
        // that's all folks'
        setStatus(Status.STOPPED);
    }
    
    private static class InvariantIdComparator implements Comparator<Invariant> {
        
        InvariantIdComparator() {
            // avoids creating accessor type
        }
        
        public int compare(Invariant inv1, Invariant inv2) {
            return inv1.getId().compareTo(inv2.getId());
        }
        
    }

}
