/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.client.solrj.io.stream;

import java.io.IOException;
import java.util.LinkedList;

import org.apache.solr.client.solrj.io.Tuple;
import org.apache.solr.client.solrj.io.comp.StreamComparator;
import org.apache.solr.client.solrj.io.eq.StreamEqualitor;
import org.apache.solr.client.solrj.io.stream.expr.Expressible;
import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;

/**
 * Joins leftStream with rightStream based on a Equalitor. Both streams must be sorted by the fields being joined on.
 * Resulting stream is sorted by the equalitor.
 * @since 6.0.0
 **/

public class LeftOuterJoinStream extends BiJoinStream implements Expressible {
  
  private LinkedList<Tuple> joinedTuples = new LinkedList<Tuple>();
  private LinkedList<Tuple> leftTupleGroup = new LinkedList<Tuple>();
  private LinkedList<Tuple> rightTupleGroup = new LinkedList<Tuple>();
  
  public LeftOuterJoinStream(TupleStream leftStream, TupleStream rightStream, StreamEqualitor eq) throws IOException {
    super(leftStream, rightStream, eq);
  }
  
  public LeftOuterJoinStream(StreamExpression expression, StreamFactory factory) throws IOException {
    super(expression, factory);
  }
  
  public Tuple read() throws IOException {
    // if we've already figured out the next joined tuple then just return it
    if (joinedTuples.size() > 0) {
      return joinedTuples.removeFirst();
    }
    
    // keep going until we find something to return or left stream is empty
    while (true) {
      if (0 == leftTupleGroup.size()) {
        Tuple firstMember = loadEqualTupleGroup(leftStream, leftTupleGroup, leftStreamComparator);
        
        // if first member of group is EOF then we're done
        if (firstMember.EOF) {
          return firstMember;
        }
      }
      
      if (0 == rightTupleGroup.size()) {
        // Load the right tuple group, but don't end if it's EOF
        loadEqualTupleGroup(rightStream, rightTupleGroup, rightStreamComparator);
      }
      
      // If the right stream is at the EOF, we just return the next element from the left stream
      if (0 == rightTupleGroup.size() || rightTupleGroup.get(0).EOF) {
        return leftTupleGroup.removeFirst();
      }
      
      // At this point we know both left and right groups have at least 1 member
      if (eq.test(leftTupleGroup.get(0), rightTupleGroup.get(0))) {
        // The groups are equal. Join em together and build the joinedTuples
        for (Tuple left : leftTupleGroup) {
          for (Tuple right : rightTupleGroup) {
            Tuple clone = left.clone();
            clone.merge(right);
            joinedTuples.add(clone);
          }
        }
        
        // Cause each to advance next time we need to look
        leftTupleGroup.clear();
        rightTupleGroup.clear();
        
        return joinedTuples.removeFirst();
      } else {
        int c = iterationComparator.compare(leftTupleGroup.get(0), rightTupleGroup.get(0));
        if (c < 0) {
          // If there's no match, we still advance the left stream while returning every element.
          // Because it's a left-outer join we still return the left tuple if no match on right.
          return leftTupleGroup.removeFirst();
        } else {
          // advance right
          rightTupleGroup.clear();
        }
      }
    }
  }
  
  @Override
  public StreamComparator getStreamSort() {
    return iterationComparator;
  }
}
