/*
 * 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.drill.exec.physical.resultSet.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.exec.physical.resultSet.ResultVectorCache;
import org.apache.drill.exec.physical.resultSet.impl.ColumnState.BaseContainerColumnState;
import org.apache.drill.exec.physical.resultSet.impl.SingleVectorState.FixedWidthVectorState;
import org.apache.drill.exec.record.metadata.ColumnMetadata;
import org.apache.drill.exec.record.metadata.VariantMetadata;
import org.apache.drill.exec.record.metadata.VariantSchema;
import org.apache.drill.exec.vector.accessor.ObjectWriter;
import org.apache.drill.exec.vector.accessor.VariantWriter;
import org.apache.drill.exec.vector.accessor.impl.HierarchicalFormatter;
import org.apache.drill.exec.vector.accessor.writer.AbstractObjectWriter;
import org.apache.drill.exec.vector.accessor.writer.UnionVectorShim;
import org.apache.drill.exec.vector.accessor.writer.UnionWriterImpl;
import org.apache.drill.exec.vector.complex.UnionVector;

/**
 * Represents the contents of a union vector (or a pseudo-union for lists).
 * Holds the column states for the "columns" that make up the type members
 * of the union, and implements the writer callbacks to add members to
 * a union. This class is used when a column is a (single, non-repeated)
 * UNION. See the {@link ListState} for when the union is used inside
 * a LIST (repeated union) type.
 */

public class UnionState extends ContainerState
  implements VariantWriter.VariantWriterListener {

  /**
   * Union or list (repeated union) column state.
   */

  public static class UnionColumnState extends BaseContainerColumnState {

    private final ContainerState unionState;

    public UnionColumnState(LoaderInternals loader,
        AbstractObjectWriter writer,
        VectorState vectorState,
        ContainerState unionState) {
      super(loader, writer, vectorState);
      this.unionState = unionState;
      unionState.bindColumnState(this);
    }

    @Override
    public boolean isProjected() {
      // Unions and lists are always projected
      return true;
    }

    /**
     * Get the output schema. For a primitive (non-structured) column,
     * the output schema is the same as the internal schema.
     */
    @Override
    public ColumnMetadata outputSchema() { return schema(); }

    @Override
    public ContainerState container() { return unionState; }
  }

  /**
   * Vector wrapper for a union vector. Union vectors contain a types
   * vector (which indicates the type of each row) along with member
   * vectors for each supported type. This class manages the types
   * vector. The member vectors are managed as children of the
   * {@link UnionState} class. The union vector itself is just a
   * holder; it has no state to be managed.
   */

  public static class UnionVectorState implements VectorState {

    private final UnionVector vector;
    private final VectorState typesVectorState;

    public UnionVectorState(UnionVector vector, UnionWriterImpl unionWriter) {
      this.vector = vector;
      if (vector == null) {
        typesVectorState = new NullVectorState();
      } else {
        typesVectorState = new FixedWidthVectorState(
            ((UnionVectorShim) unionWriter.shim()).typeWriter(), vector.getTypeVector());
      }
    }

    @Override
    public int allocate(int cardinality) {
      return typesVectorState.allocate(cardinality);
    }

    @Override
    public void rollover(int cardinality) {
      typesVectorState.rollover(cardinality);
    }

    @Override
    public void harvestWithLookAhead() {
      typesVectorState.harvestWithLookAhead();
    }

    @Override
    public void startBatchWithLookAhead() {
      typesVectorState.startBatchWithLookAhead();
    }

    @Override
    public void close() {
      typesVectorState.close();
    }

    @SuppressWarnings("unchecked")
    @Override
    public UnionVector vector() { return vector; }

    @Override
    public boolean isProjected() { return vector != null; }

    @Override
    public void dump(HierarchicalFormatter format) {
      // TODO Auto-generated method stub
    }
  }

  /**
   * Map of types to member columns, used to track the set of child
   * column states for this union. This map mimics the actual set of
   * vectors in the union,
   * and matches the set of child writers in the union writer.
   */
  private final Map<MinorType, ColumnState> columns = new HashMap<>();

  public UnionState(LoaderInternals events, ResultVectorCache vectorCache, ProjectionFilter projectionSet) {
    super(events, vectorCache, projectionSet);
  }

  public UnionWriterImpl writer() {
    return (UnionWriterImpl) parentColumn.writer.variant();
  }

  public VariantMetadata variantSchema() {
    return writer().variantSchema();
  }

  public UnionVector vector() {
    return parentColumn.vector();
  }

  @Override
  public ObjectWriter addType(MinorType type) {
    return addMember(VariantSchema.memberMetadata(type));
  }

  @Override
  public ObjectWriter addMember(ColumnMetadata member) {
    if (variantSchema().hasType(member.type())) {
      throw new IllegalArgumentException("Duplicate type: " + member.type().toString());
    }
    return addColumn(member).writer();
  }

  @Override
  protected void addColumn(ColumnState colState) {
    assert ! columns.containsKey(colState.schema().type());
    columns.put(colState.schema().type(), colState);
    if (vector() == null) {
      if (colState.vector() != null) {
        throw new IllegalStateException("Attempt to add a materialized vector to an unprojected vector");
      }
    } else {
      vector().addType(colState.vector());
    }
  }

  @Override
  protected Collection<ColumnState> columnStates() {
    return columns.values();
  }

  @Override
  public int innerCardinality() {
    return parentColumn.innerCardinality();
  }

  @Override
  protected boolean isVersioned() { return false; }
}
