/*
 *  Copyright (C) 2011-2012 The Animo Project
 *  http://animotron.org
 *  	
 *  This file is part of Animotron.
 *  
 *  Animotron is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as 
 *  published by the Free Software Foundation, either version 3 of 
 *  the License, or (at your option) any later version.
 *  
 *  Animotron 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 Affero General Public License for more details.
 *  
 *  You should have received a copy of 
 *  the GNU Affero General Public License along with Animotron.  
 *  If not, see <http://www.gnu.org/licenses/>.
 */
package org.animotron.graph.builder;

import org.animotron.exception.AnimoException;
import org.animotron.expression.AbstractExpression;
import org.animotron.graph.index.Order;
import org.animotron.graph.serializer.DigestSerializer;
import org.animotron.manipulator.Manipulators;
import org.animotron.statement.Statement;
import org.animotron.statement.animo.update.CHANGE;
import org.animotron.statement.link.LINK;
import org.animotron.statement.operator.AN;
import org.animotron.statement.operator.DEF;
import org.animotron.statement.operator.REF;
import org.animotron.statement.value.VALUE;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.kernel.DeadlockDetectedException;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.Stack;

import static org.animotron.graph.AnimoGraph.beginTx;
import static org.animotron.graph.AnimoGraph.finishTx;
import static org.animotron.graph.Properties.HASH;

/**
 * Animo graph builder, it do optimization/compression and
 * inform listeners on store/delete events.
 *
 * Direct graph as input from top element to bottom processing strategy.
 *
 * Methods to use:
 *
 * endGraph()
 *
 * start(VALUE prefix, VALUE ns, VALUE reference, VALUE value)
 * start(Statement statement, VALUE prefix, VALUE ns, VALUE reference, VALUE value)
 * end()
 *
 * relationship()
 *
 * @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
 * @author <a href="mailto:gazdovsky@gmail.com">Evgeny Gazdovsky</a>
 */
public abstract class GraphBuilder {

    Transaction tx;
    private int order;
    protected final boolean ignoreNotFound;
    private Manipulators.Catcher catcher;
    private Stack<Object[]> stack;

    private Relationship relationship;

    public GraphBuilder() {
        this(true);
    }

    public GraphBuilder(boolean ignoreNotFound) {
        this.ignoreNotFound = ignoreNotFound;
    }

    protected final int order(){
        return order;
    }

    protected final void order(Relationship r){
        order(r, order);
    }

    protected final void order(Relationship r, int order){
        if (order > 0) {
            Order._.add(r, order);
        }
    }

    public final Relationship relationship() {
        return relationship;
    };

    protected abstract Relationship endGraph() throws AnimoException, IOException;

    public final void start(Statement statement) throws AnimoException, IOException {
        start(statement, null);
    }

    public final void start(Object value) throws AnimoException, IOException {
        start(VALUE._, value);
    }

   private Statement s;
   private Object r;

    public final void start(Statement statement, Object reference) throws AnimoException, IOException {
        if (statement instanceof DEF) {
            relationship = DEF._.get(reference);
            if (relationship != null) {
                start(CHANGE._);
                    start(AN._);
                        _(REF._,  reference);
                    end();
                    start(LINK._);
                return;
            }
        }
    	if (s != null) {
    		stack.push(start(s, r, true));
        }
        s = statement;
        r = reference;
    }

    protected abstract Object[] start(Statement statement, Object reference, boolean ready) throws AnimoException, IOException;

    public final void end() throws AnimoException, IOException {
    	byte[] hash;
    	if (s != null) {
    		hash = end(start(s, r, false));
    		s = null; r = null;
    	} else {
    		Object[] p = popParent();
    		hash = end(p);
    	}
        if (hasParent()) {
            ((MessageDigest) peekParent()[0]).update(hash);
        }
    }

	protected abstract byte[] end(Object[] o) throws AnimoException;

    public final void bind(Relationship e) throws IOException, AnimoException {
        Object[] o;
        byte[] hash = HASH.has(e) ? (byte[]) HASH.get(e) : DigestSerializer._.serialize(e);
        if (s != null) {
            o = start(s, r, true);
            stack.push(o);
            bind(e, o, hash);
            s = null; r = null;
        } else {
            o = peekParent();
            bind(e, o, hash);
        }
        if (o != null) {
            ((MessageDigest) o[0]).update(hash);
        }
    };

    protected abstract void bind(Relationship r, Object[] o, byte[] hash) throws IOException;

    public final void _(Statement statement) throws AnimoException, IOException {
        _(statement, null);
    }

    public final void _(Object value) throws AnimoException, IOException {
        _(VALUE._, value);
    }

    public final void _(Statement statement, Object reference) throws AnimoException, IOException {
        start(statement, reference);
        end();
    }

    protected final boolean hasParent() {
        return !stack.empty();
    }

    protected final Object[] popParent() {
        return stack.pop();
    }

    protected final Object[] peekParent() {
        return stack.peek();
    }

    public final void build(AbstractExpression exp) throws Throwable {
        boolean deadlock;
        order = 0;
        catcher = Manipulators.getCatcher();
        do {
            deadlock = false;
            tx = beginTx();
            try {
                s = null; r = null;
                stack = new Stack<Object[]>();
                exp.build();
                while (!stack.empty()) {
                    end();
                }
                Relationship c = endGraph();
                if (relationship == null) {
                    relationship = c;
                } else {
                    evaluative(c);
                }
                tx.success();
                finishTx(tx);
            } catch (Throwable t) {
                finishTx(tx);
                if (t instanceof DeadlockDetectedException) {
                    System.out.println("Deadlock");
                    t.printStackTrace();
                    deadlock = true;
                } else {
                    tx = beginTx();
                    try {
                        catcher.clear();
                    } finally {
                        finishTx(tx);
                    }
                    throw t;
                }
            } finally {
                catcher.push();
            }
        } while (deadlock);
    }

    protected final void step() {
        order++;
//        if (order % (10000) == 0) {
//            tx.success();
//            finishTx(tx);
//            tx = beginTx();
//        }
    }

    protected final void evaluative(Relationship r) {
        catcher.evaluative(r);
    }

    protected final void preparative(Relationship r) {
        catcher.preparative(r);
    }

    protected final void modificative(Relationship r) {
        catcher.modificative(r);
    }

    protected final void destructive(Relationship r) {
        catcher.destructive(r);
    }

    protected final void destructive(Node n) {
        catcher.destructive(n);
    }

}

@@ -3774,91 +3774,27 @@
     
-start(AN._);%0A                        _(REF._,  reference);%0A                    end(
+_(REF._,  reference
 );%0A 

remove AN