package trunk.coref.nativ;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.sparql.algebra.Op;
import com.hp.hpl.jena.sparql.algebra.TransformCopy;
import com.hp.hpl.jena.sparql.algebra.op.Op0;
import com.hp.hpl.jena.sparql.algebra.op.Op1;
import com.hp.hpl.jena.sparql.algebra.op.Op2;
import com.hp.hpl.jena.sparql.algebra.op.OpAssign;
import com.hp.hpl.jena.sparql.algebra.op.OpBGP;
import com.hp.hpl.jena.sparql.algebra.op.OpConditional;
import com.hp.hpl.jena.sparql.algebra.op.OpDatasetNames;
import com.hp.hpl.jena.sparql.algebra.op.OpDiff;
import com.hp.hpl.jena.sparql.algebra.op.OpDisjunction;
import com.hp.hpl.jena.sparql.algebra.op.OpDistinct;
import com.hp.hpl.jena.sparql.algebra.op.OpExt;
import com.hp.hpl.jena.sparql.algebra.op.OpExtend;
import com.hp.hpl.jena.sparql.algebra.op.OpFilter;
import com.hp.hpl.jena.sparql.algebra.op.OpGraph;
import com.hp.hpl.jena.sparql.algebra.op.OpGroup;
import com.hp.hpl.jena.sparql.algebra.op.OpJoin;
import com.hp.hpl.jena.sparql.algebra.op.OpLabel;
import com.hp.hpl.jena.sparql.algebra.op.OpLeftJoin;
import com.hp.hpl.jena.sparql.algebra.op.OpList;
import com.hp.hpl.jena.sparql.algebra.op.OpMinus;
import com.hp.hpl.jena.sparql.algebra.op.OpN;
import com.hp.hpl.jena.sparql.algebra.op.OpNull;
import com.hp.hpl.jena.sparql.algebra.op.OpOrder;
import com.hp.hpl.jena.sparql.algebra.op.OpPath;
import com.hp.hpl.jena.sparql.algebra.op.OpProcedure;
import com.hp.hpl.jena.sparql.algebra.op.OpProject;
import com.hp.hpl.jena.sparql.algebra.op.OpPropFunc;
import com.hp.hpl.jena.sparql.algebra.op.OpQuadPattern;
import com.hp.hpl.jena.sparql.algebra.op.OpReduced;
import com.hp.hpl.jena.sparql.algebra.op.OpSequence;
import com.hp.hpl.jena.sparql.algebra.op.OpService;
import com.hp.hpl.jena.sparql.algebra.op.OpSlice;
import com.hp.hpl.jena.sparql.algebra.op.OpTable;
import com.hp.hpl.jena.sparql.algebra.op.OpTriple;
import com.hp.hpl.jena.sparql.algebra.op.OpUnion;
import com.hp.hpl.jena.sparql.core.Var;
import com.hp.hpl.jena.sparql.engine.binding.Binding;

public class OpSaTransform extends TransformCopy {

	private Binding binding;
	
	public OpSaTransform(Binding binding) {
		this.binding = binding;
	}
	
	public Op transform(Op op)										{return dispatch(op);}
    public Op transform(OpTable opTable)                            { return xform(opTable) ; }
    public Op transform(OpBGP opBGP)                                { return xform(opBGP) ; }
    public Op transform(OpQuadPattern opQuadPattern)                { return xform(opQuadPattern) ; }
    public Op transform(OpTriple opTriple)                          { return xform(opTriple) ; }
    public Op transform(OpPath opPath)                              { return xform(opPath) ; }

    public Op transform(OpProcedure opProc, Op subOp)               { return xform(opProc, subOp) ; }
    public Op transform(OpPropFunc opPropFunc, Op subOp)            { return xform(opPropFunc, subOp) ; }
    public Op transform(OpDatasetNames opDatasetNames)              { return xform(opDatasetNames) ; }

    public Op transform(OpFilter opFilter, Op subOp)                { return xform(opFilter, subOp) ; }
    public Op transform(OpGraph opGraph, Op subOp)                  { return xform(opGraph, subOp) ; }
    public Op transform(OpService opService, Op subOp)              { return xform(opService, subOp) ; }

    public Op transform(OpAssign opAssign, Op subOp)                { return xform(opAssign, subOp) ; }
    public Op transform(OpExtend opExtend, Op subOp)                { return xform(opExtend, subOp) ; }
    
    public Op transform(OpJoin opJoin, Op left, Op right)           { return xform(opJoin, left, right) ; }
    public Op transform(OpLeftJoin opLeftJoin, Op left, Op right)   { return xform(opLeftJoin, left, right) ; }
    public Op transform(OpDiff opDiff, Op left, Op right)           { return xform(opDiff, left, right) ; }
    public Op transform(OpMinus opMinus, Op left, Op right)         { return xform(opMinus, left, right) ; }
    public Op transform(OpUnion opUnion, Op left, Op right)         { return xform(opUnion, left, right) ; }
    public Op transform(OpConditional opCond, Op left, Op right)    { return xform(opCond, left, right) ; }

    public Op transform(OpSequence opSequence, List<Op> elts)           { return xform(opSequence, elts) ; }
    public Op transform(OpDisjunction opDisjunction, List<Op> elts)     { return xform(opDisjunction, elts) ; }
    
    public Op transform(OpExt opExt)                                { return opExt ; }
    
    public Op transform(OpNull opNull)                              { return opNull.copy() ; }
    public Op transform(OpLabel opLabel, Op subOp)                  { return xform(opLabel, subOp) ; }
    
    public Op transform(OpList opList, Op subOp)                    { return xform(opList, subOp) ; }
    public Op transform(OpOrder opOrder, Op subOp)                  { return xform(opOrder, subOp) ; }
    public Op transform(OpProject opProject, Op subOp)              { return xform(opProject, subOp) ; }
    public Op transform(OpDistinct opDistinct, Op subOp)            { return xform(opDistinct, subOp) ; }
    public Op transform(OpReduced opReduced, Op subOp)              { return xform(opReduced, subOp) ; }
    public Op transform(OpSlice opSlice, Op subOp)                  { return xform(opSlice, subOp) ; }
    public Op transform(OpGroup opGroup, Op subOp)            { return xform(opGroup, subOp) ; }

    
    private Op dispatch(Op op) {
    	
    	if(op instanceof Op0) {
    		Op0 bgp = (Op0) op;
    		return xform(bgp);
    	}
    	
    	if(op instanceof Op1) {
    		Op1 op1 = (Op1)op;
    		return xform(op1,op1.getSubOp());
    	}
    	
    	if(op instanceof Op2) {
    		Op2 opc = (Op2) op;
    		return xform(opc,opc.getLeft(),opc.getRight());
    	}
    	
    	return op;
    }
    
    private Op xform(Op0 op)
    { 
    	OpBGP opb = new OpBGP();
    	if(op instanceof OpBGP) {
    		OpBGP bgp = (OpBGP) op;
    		List<Triple> satriples = sa_trans(bgp.getPattern().getList());
    		opb.getPattern().getList().addAll(satriples);
    	}else
    		System.err.println("Type "+op.getName()+" is not supported.");
        return opb;
    }

    private List<Triple> sa_trans(Collection<Triple> triples) {
    	List<Triple> lst = new ArrayList<Triple>();
    	for(Triple t:triples) {
    		lst.add(sa_trans(t));
    	}
    	return lst;
    }
    
	private Triple sa_trans(Triple t) {
		Node sub = t.getSubject();
		Node obj = t.getObject();
		
		if(hasSa(sub)) {
			sub = binding.get(Var.alloc(sub.getLocalName()+"sa"));
		}
		
		if(hasSa(obj)) {
			obj = binding.get(Var.alloc(obj.getLocalName()+"sa"));
		}
		return new Triple(sub,t.getPredicate(),obj);
	}
	
	private boolean hasSa(Node node) {
		if(node.isConcrete()) {
			return binding.contains(Var.alloc(node.getLocalName()+"sa"));
		} else
			return false;
	}

	private Op xform(Op1 op, Op subOp)
    { 
        return op.copy(dispatch(subOp)) ;
    }
    
    private Op xform(Op2 op, Op left, Op right)
    {
        return op.copy(dispatch(left), dispatch(right)) ;
    }
    private Op xform(OpN op, List<Op> elts)
    {
    	List<Op> tlts = new ArrayList<Op>();
    	for(Op e:elts) {
    		tlts.add(dispatch(e));
    	}
        return op.copy(tlts) ;
    }
}
