package datalog_ra.evaluation;

import datalog_ra.base.TupleTransformation.*;
import datalog_ra.base.TupleTransformation.condition.*;
import datalog_ra.base.dataStructures.Instance;
import datalog_ra.base.operator.*;
import datalog_ra.base.dataStructures.Relation;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author Jakub
 */
public class Rule {
  private final String name;
  private final int arity;
  
  private final LinkedList<Term> head = new LinkedList<>();
  private final LinkedList<Subgoal> positiveOrder = new LinkedList<>();
  private final LinkedList<Subgoal> negativeOrder = new LinkedList<>();
  private final LinkedList<ComparisonSubgoal> comparisons = new LinkedList<>();
  
  private Instance positiveFactSource = new Instance();
  private Instance negativeFactSource = new Instance();
  
  private Operator operator;

  public Rule(String name, List<Term> head) {
    this.name = name;
    this.arity = head.size();
    this.head.addAll(head);
  }

  /**
   * Updates the internal operator used to calculate the result. Operator
   * contains relations in positive and negative fact source. Should be called
   * after either of fact sources is updated.
   */
  public void buildOperator() {
    // at least one positive subgoal should exist
    if (positiveOrder.size() == 0) {
      System.out.println("No positive subgoals in rule " + name);
    }

    // make join of all positive subgoals
    operator = buildJoin();

    // add antijoin for each negative subgoal
    for (int i = 0; i < negativeOrder.size(); i++) {
      Subgoal subgoal = negativeOrder.get(i);
      operator = new AntiJoin(
          operator,
          negativeFactSource.forceGet(
              subgoal.name,
              subgoal.arguments.size()
          ).operator(),
          buildAntijoinCondition(negativeOrder.get(i))
      );
    }

    operator = new Projection(operator, buildProjectionTransformation());
  }

  /**
   * Positive join is Join of all positive subgoals.
   */
  private Operator buildJoin() {
    Operator result = null;

    for (int i = 0; i < positiveOrder.size(); i ++) {
      Subgoal subgoal = positiveOrder.get(i);
      
      // begin with simple relation operator
      if (i == 0) {
        result = positiveFactSource.forceGet(
            subgoal.name,
            subgoal.arguments.size()
        ).operator();
      } 
      //make a cartesian product of all positive subgoals
      else { 
        result = new Join(
            result,
            positiveFactSource.forceGet(
                subgoal.name,
                subgoal.arguments.size()
            ).operator(),
            new TrueCondition()
        );
      }
    }
    
    //make a selection according to condition
    result = new Selection(result, buildJoinCondition());

    return result;
  }
  
  private TupleTransformation buildJoinCondition(){
    // create a row of all arguments in positive subgoals
    ArrayList<Term> arguments = new ArrayList<>();
    for (Subgoal subgoal : positiveOrder) {
      arguments.addAll(subgoal.arguments);
    }
    
    TransformationSequence result = new TransformationSequence();
    
    for (int i = 0; i < arguments.size(); i++){
      // compare the position to this constant
      if (arguments.get(i).isConstant()) {
        result.add(new CompareConstantCondition(i, arguments.get(i).getTerm()));
        continue;
      }
      
      // otherwise it's an actual variable
      // compare it to the next occurrence if it exists
      for (int j = i + 1; j < arguments.size(); j++) {
        if (arguments.get(i).equals(arguments.get(j))) {
          result.add(new CompareCondition(i, j));
          break;
        }
      }
    }

    // add comparisons filters
    for (ComparisonSubgoal comp : comparisons) {
      int lhs_pos = -1, rhs_pos = -1;

      // find position of lhs (must be variable)
      for (int j = 0; j < arguments.size(); j++) {
        if (arguments.get(j).equals(comp.lhs)) {
          lhs_pos = j;
          break;
        }
      }

      Condition cond;

      if (comp.rhs.isConstant()) {
        // if rhs is constant, we are done
        cond = new CompareConstantCondition(lhs_pos, comp.rhs.getTerm());
      } else {
        // otherwise it's an actual variable
        // find it's position too
        for (int j = 0; j < arguments.size(); j++) {
          if (arguments.get(j).equals(comp.rhs)) {
            rhs_pos = j;
            break;
          }
        }
        cond = new CompareCondition(lhs_pos, rhs_pos);
      }

      if (comp.negate) {
        cond = new NegateCondition(cond);
      }
      result.add(cond);
    }
    
    return result;
  }
  
  private TupleTransformation buildAntijoinCondition(Subgoal negativeSubgoal){
    // create a row of all arguments in positive subgoals
    ArrayList<Term> arguments = new ArrayList<>();
    for (Subgoal subgoal : positiveOrder) {
      arguments.addAll(subgoal.arguments);
    }
    int positiveArgumentsCount = arguments.size();
    // add the negative subgoal's arguments at the end
    arguments.addAll(negativeSubgoal.arguments);
    
    TransformationSequence result = new TransformationSequence();
    
    for (int i = 0; i < negativeSubgoal.arguments.size(); i++){
      // compare the position to this constant
      if (negativeSubgoal.arguments.get(i).isConstant()) {
        result.add(new CompareConstantCondition(
            positiveArgumentsCount + i,
            negativeSubgoal.arguments.get(i).getTerm())
        );
        continue;
      }
      
      // otherwise it's an actual variable
      // compare it to the first occurrence in positive subgoals
      for (int j = 0; j < positiveArgumentsCount + i; j++) {
        if (negativeSubgoal.arguments.get(i).equals(arguments.get(j))) {
          result.add(new CompareCondition(positiveArgumentsCount + i, j));
          break;
        }
      }
    }
    
    return result;
  }
  
  private TupleTransformation buildProjectionTransformation(){
    // create a row of all arguments in positive subgoals
    ArrayList<Term> arguments = new ArrayList<>();
    for (Subgoal subgoal : positiveOrder) {
      arguments.addAll(subgoal.arguments);
    }
    
    LinkedList<Integer> indexes = new LinkedList<>();
    
    // for all arguments in head
    for (int i = 0; i < head.size(); i++){
      // add index for first occurrence in positive subgoals
      for (int j = 0; j < arguments.size(); j++) {
        if (head.get(i).equals(arguments.get(j))) {
          indexes.add(j);
          break;
        }
      }
    }
    
    return new ProjectionTransformation(indexes);
  }

  /**
   * Calculates and returns the result of rule and returns it as a relation.
   */
  public Relation result() {
    Relation result = new Relation(operator, name);
    return result;
  }

  /**
   * A struct containing all the necessary information about a subgoal.
   */
  private class Subgoal {
    public String name;
    public ArrayList<Term> arguments;

    public Subgoal(String name, List<Term> arguments) {
      this.name = name;
      this.arguments = new ArrayList<>();
      this.arguments.addAll(arguments);
    }
  }

  private class ComparisonSubgoal {
    public Term lhs;
    public Term rhs;
    boolean negate;

    public ComparisonSubgoal(Term lhs, Term rhs, boolean negate) {
      this.lhs = lhs;
      this.rhs = rhs;
      this.negate = negate;
    }
  }

  // Datalog program simplified input
  
  public void addSubgoal(String name, List<Term> arguments, boolean isNegative) {
    if (isNegative) {
      negativeOrder.add(new Subgoal(name, arguments));
      negativeFactSource.add(new Relation(name, arguments.size()));
    } else {
      positiveOrder.add(new Subgoal(name, arguments));
      positiveFactSource.add(new Relation(name, arguments.size()));
    }
  }

  public void addPositiveSubgoal(String name, List<Term> arguments) {
    addSubgoal(name, arguments, false);
  }

  public void addNegativeSubgoal(String name, List<Term> arguments) {
    addSubgoal(name, arguments, true);
  }

  public void addComparison(Term lhs, Term rhs, boolean isInequality) {
    if (lhs.isConstant() && rhs.isConstant()) {
      throw new IllegalArgumentException("comparing constants is not supported");
    }
    // variable is always first
    if (lhs.isConstant() && rhs.isVariable()) {
      Term tmp = lhs;
      lhs = rhs;
      rhs = tmp;
    }
    comparisons.add(new ComparisonSubgoal(lhs, rhs, isInequality));
  }
  
  // getters 
  public String getName() { return name; }
  public int getArity() { return arity; }
  public List<Term> getHead() { return head; }
  public boolean hasBody() {
    return !(positiveOrder.isEmpty() && negativeOrder.isEmpty() && comparisons.isEmpty());
  }

  public String explain() {
    StringBuilder sb = new StringBuilder();
    sb.append(name);
    sb.append(" := ");
    sb.append(operator.toString(0));
    sb.append(".\n");
    return sb.toString();
  }

  // setters
    
  /**
   * Updates sources of facts for positive subgoals.
   *
   * @param instance - the source instance, all relations in subgoal, that 
   * are not present in this instance will be replaced with new empty relations.
   */
  public void setPositiveFactSource(Instance instance) {
    this.positiveFactSource = instance;
  }

  /**
   * Updates sources of facts for negative subgoals.
   *
   * @param instance - the source instance, all relations in subgoal, that 
   * are not present in this instance will be replaced with new empty relations.
   */
  public void setNegativeFactSource(Instance instance) {
    this.negativeFactSource = instance;
  }
}
