/**
 * 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.hadoop.hive.ql.exec.persistence;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;

import org.apache.hadoop.hive.serde2.AbstractSerDe;
import org.apache.hadoop.hive.serde2.SerDeException;
import org.apache.hadoop.hive.serde2.io.ShortWritable;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils.ObjectInspectorCopyOption;
import org.apache.hadoop.io.Writable;

@SuppressWarnings("deprecation")
public class MapJoinEagerRowContainer
    implements MapJoinRowContainer, AbstractRowContainer.RowIterator<List<Object>> {
  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

  private final List<List<Object>> list;
  private byte aliasFilter = (byte) 0xff;
  private int index = 0;

  public MapJoinEagerRowContainer() {
    index = 0;
    list = new ArrayList<List<Object>>(1);
  } 

  @Override
  public void addRow(List<Object> t) {
    list.add(t);
  }

  @Override
  public void addRow(Object[] t) {
    addRow(toList(t));
  }

  @Override
  public AbstractRowContainer.RowIterator<List<Object>> rowIter() {
    return this;
  }

  @Override
  public List<Object> first() {
    index = 0;
    if (index < list.size()) {
      return list.get(index);
    }
    return null;
  }

  @Override
  public List<Object> next() {
    index++;
    if (index < list.size()) {
      return list.get(index);
    }
    return null;
  }

  @Override
  public boolean hasRows() {
    return list.size() > 0;
  }

  @Override
  public boolean isSingleRow() {
    return list.size() == 1;
  }

  /**
   * Get the number of elements in the RowContainer.
   *
   * @return number of elements in the RowContainer
   */
  @Override
  public int rowCount() {
    return list.size();
  }

  /**
   * Remove all elements in the RowContainer.
   */
  @Override
  public void clearRows() {
    list.clear();
  }

  @Override
  public byte getAliasFilter() {
    return aliasFilter;
  }

  @Override
  public MapJoinRowContainer copy() {
    MapJoinEagerRowContainer result = new MapJoinEagerRowContainer();
    for(List<Object> item : list) {
      result.addRow(item);
    }
    return result;
  }

  public void read(MapJoinObjectSerDeContext context, ObjectInputStream in, Writable container)
  throws IOException, SerDeException {
    long numRows = in.readLong();
    for (long rowIndex = 0L; rowIndex < numRows; rowIndex++) {
      container.readFields(in);
      read(context, container);
    }
  }

  @SuppressWarnings("unchecked")
  public void read(MapJoinObjectSerDeContext context, Writable currentValue) throws SerDeException {
    AbstractSerDe serde = context.getSerDe();
    List<Object> value = (List<Object>)ObjectInspectorUtils.copyToStandardObject(serde.deserialize(currentValue),
        serde.getObjectInspector(), ObjectInspectorCopyOption.WRITABLE);
    if(value == null) {
      addRow(toList(EMPTY_OBJECT_ARRAY));
    } else {
      Object[] valuesArray = value.toArray();
      if (context.hasFilterTag()) {
        aliasFilter &= ((ShortWritable)valuesArray[valuesArray.length - 1]).get();
      }
      addRow(toList(valuesArray));
    }
  }

  @Override
  public void write(MapJoinObjectSerDeContext context, ObjectOutputStream out)
  throws IOException, SerDeException {
    AbstractSerDe serde = context.getSerDe();
    ObjectInspector valueObjectInspector = context.getStandardOI();
    long numRows = rowCount();
    long numRowsWritten = 0L;
    out.writeLong(numRows);
    for (List<Object> row = first(); row != null; row = next()) {
      serde.serialize(row.toArray(), valueObjectInspector).write(out);
      ++numRowsWritten;      
    }
    if(numRows != rowCount()) {
      throw new ConcurrentModificationException("Values was modified while persisting");
    }
    if(numRowsWritten != numRows) {
      throw new IllegalStateException("Expected to write " + numRows + " but wrote " + numRowsWritten);
    }
  }
  
  private List<Object> toList(Object[] array) {
    return new NoCopyingArrayList(array);
  }
  /**
   * In this use case our objects will not be modified
   * so we don't care about copying in and out.
   */
  private static class NoCopyingArrayList extends AbstractList<Object> {
    private Object[] array;
    public NoCopyingArrayList(Object[] array) {
      this.array = array;
    }
    @Override
    public Object get(int index) {
      return array[index];
    }

    @Override
    public int size() {
      return array.length;
    }
    
    public Object[] toArray() {
      return array;
    }    
  }
}
