package de.renew.formalism.fs;

import java.util.Vector;

import de.uni_hamburg.fs.FeatureStructure;
import de.uni_hamburg.fs.JavaObject;
import de.uni_hamburg.fs.Path;
import de.uni_hamburg.fs.UnificationFailure;

import de.renew.unify.IStateRecorder;
import de.renew.unify.Impossible;
import de.renew.unify.Notifiable;
import de.renew.unify.StateRestorer;
import de.renew.unify.Unify;
import de.renew.unify.Variable;


class FSUnifier {
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(FSUnifier.class);
    Variable _result;
    private Vector<Variable> _variables = new Vector<Variable>();
    private FeatureStructure _current;
    private int _open = 0;

    //  private boolean resultSet=false;
    //  private boolean debug=false;
    //  FSUnifier(FeatureStructure template, Vector paths, StateRecorder recorder,
    //  boolean debug) throws Impossible {
    //      this(template,paths,recorder);
    //      this.debug=debug;
    //  }
    FSUnifier(FeatureStructure template, Vector<Path> paths, IStateRecorder recorder)
        throws Impossible
    {
        _result = new Variable();
        _current = template;

        // Create listeners.
        for (int i = 0; i < paths.size(); i++) {
            Variable variable = new Variable();
            _variables.addElement(variable);
            setupListener(variable, paths.elementAt(i), recorder);
            _open++;
        }
        setupResultListener(paths, recorder);
    }

    Variable getVariable(int i) {
        return _variables.elementAt(i);
    }

    private void setupListener(final Variable variable, final Path path, IStateRecorder recorder)
        throws Impossible
    {
        variable.addListener(new Notifiable() {
            @Override
            public void boundNotify(IStateRecorder irecorder) throws Impossible {
                //      if (resultSet) {
                //          return;
                //      }
                Object val = variable.getValue();
                final FeatureStructure oldCurrent = _current;
                if (irecorder != null) {
                    irecorder.record(new StateRestorer() {
                        @Override
                        public void restore() {
                            _current = oldCurrent;
                            _open++;
                        }
                    });
                }
                _open--;
                FeatureStructure fs;
                if (val instanceof FeatureStructure) {
                    fs = (FeatureStructure) val;
                } else {
                    fs = new FeatureStructure(JavaObject.getJavaType(val));
                }


                //if (debug)
                //      logger.debug("Trying to unify "+fs+" into "+current+" at "+path);
                try {
                    _current = _current.unify(fs, path);
                } catch (UnificationFailure uff) {
                    // logger.debug(" failed!");
                    throw new Impossible();
                }


                // logger.debug("succeeded with result "+current);
                if (_open == 0) {
                    // logger.debug("all expressions evaluated:"+current);
                    Unify.unify(_result, _current, irecorder);
                }
            }
        }, recorder);
    }

    private void setupResultListener(final Vector<Path> paths, IStateRecorder recorder)
        throws Impossible
    {
        _result.addListener(new Notifiable() {
            @Override
            public void boundNotify(IStateRecorder irecorder) throws Impossible {
                Object val = _result.getValue();
                if (!(val instanceof FeatureStructure)) {
                    throw new Impossible();
                }
                FeatureStructure resultfs = (FeatureStructure) val;
                if (!_current.subsumes(resultfs)) {
                    // they can never become equal!
                    LOGGER.debug("Current: " + _current + "resultfs:" + resultfs);
                    throw new Impossible();
                }


                //  final FeatureStructure oldCurrent=current;
                //  irecorder.record(new StateRestorer() {
                //      public void restore() {
                //          current=oldCurrent;
                //          resultSet=false;
                //      }
                //  });
                //  resultSet=true;
                //  try {
                //      current=((FeatureStructure)val).unify(current);
                //  } catch (UnificationFailure uff) {
                //      throw new Impossible();
                //  }
                for (int i = 0; i < paths.size(); i++) {
                    Variable variable = _variables.elementAt(i);
                    if (!Unify.isBound(variable)) {
                        Path path = paths.elementAt(i);


                        //  Unify.unify(variable,current.unpackingAt(path),irecorder);
                        Unify.unify(variable, resultfs.unpackingAt(path), irecorder);
                    }
                }
            }
        }, recorder);
    }
}