/*
 * 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.vector;

import static org.junit.Assert.assertEquals;

import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.TypeProtos.MajorType;
import org.apache.drill.common.types.Types;
import org.apache.drill.exec.memory.RootAllocator;
import org.apache.drill.exec.record.MaterializedField;
import org.apache.drill.exec.record.TransferPair;
import org.apache.drill.exec.vector.complex.EmptyValuePopulator;
import org.apache.drill.exec.vector.complex.MapVector;
import org.apache.drill.exec.vector.complex.RepeatedMapVector;
import org.apache.drill.exec.vector.complex.impl.NullableVarCharWriterImpl;
import org.apache.drill.exec.vector.complex.impl.SingleMapWriter;
import org.apache.drill.exec.vector.complex.writer.BaseWriter.ListWriter;
import org.apache.drill.exec.vector.complex.writer.BaseWriter.MapWriter;
import org.apache.drill.exec.vector.complex.writer.FieldWriter;
import org.apache.drill.exec.vector.complex.writer.IntWriter;
import org.apache.drill.test.BaseTest;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import io.netty.buffer.DrillBuf;

import java.nio.charset.StandardCharsets;

public class VectorTest extends BaseTest {

  private static RootAllocator allocator;

  @BeforeClass
  public static void setup() {
    allocator = new RootAllocator(10_000_000);
  }

  @AfterClass
  public static void tearDown() {
    allocator.close();
  }

  @Test
  public void testVarChar() {
    MaterializedField field = MaterializedField.create("stringCol", Types.required(TypeProtos.MinorType.VARCHAR));
    try (VarCharVector v = new VarCharVector(field, allocator)) {
      v.allocateNew(1000, 128);
      VarCharVector.Accessor va = v.getAccessor();
      UInt4Vector ov = v.getOffsetVector();
      UInt4Vector.Accessor ova = ov.getAccessor();

      assertEquals(1024, v.getBuffer().capacity());
      // Note: offset vector size is (128 + 1) rounded up
      assertEquals(256 * 4, ov.getBuffer().capacity());

      // Zero fill so the following is deterministic

      v.getBuffer().setZero(0, 1024);
      ov.getBuffer().setZero(0, 256 * 4);

      // O-size is special

      v.getMutator().setValueCount(0);
      assertEquals(0, va.getValueCount());
      assertEquals(0, ova.getValueCount());

      // Fill-empties at 0 is also special.

      v.getMutator().fillEmpties(-1, 0);
      assertEquals(0, va.getValueCount());
      assertEquals(0, ova.getValueCount());

      // Write one value

      v.getMutator().setSafe(0, "foo".getBytes(StandardCharsets.UTF_8));
      v.getMutator().setValueCount(1);
      assertEquals(1, va.getValueCount());
      assertEquals(2, ova.getValueCount());
      assertEquals(0, ova.get(0));
      assertEquals(3, ova.get(1));

      // Pretend to skip a value

      v.getMutator().fillEmpties(0, 2);
      // fillEmpties does not change the value count
      assertEquals(0, ova.get(0));
      assertEquals(3, ova.get(1)); // First value
      assertEquals(3, ova.get(2)); // Filled
      assertEquals(0, ova.get(3)); // Should not be set

      // Write one more value

      v.getMutator().setSafe(2, "mumble".getBytes(StandardCharsets.UTF_8));
      v.getMutator().setValueCount(2);
      assertEquals(2, va.getValueCount());
      assertEquals(3, ova.getValueCount());
      assertEquals(3, ova.get(1));
      assertEquals(3, ova.get(2));
      assertEquals(9, ova.get(3));
    }
  }

  /**
   * Verify the delicate, fragile logic for setting the value count and
   * filling empty values. Some operators and readers first write values,
   * then set the value count at the end of the batch. The "complex writers"
   * set the value count after each value. The "lastSet" count tracks the
   * last value actually written, but is set in fillEmpties(), which is
   * also called when setting the value count.
   */

  @Test
  public void testNullableVarChar() {
    MaterializedField field = MaterializedField.create("stringCol", Types.optional(TypeProtos.MinorType.VARCHAR));
    try (NullableVarCharVector v = new NullableVarCharVector(field, allocator)) {
      int targetDataLength = 1000;
      int targetValueCount = 128;
      v.allocateNew(targetDataLength, targetValueCount);
      NullableVarCharVector.Accessor va = v.getAccessor();
      NullableVarCharVector.Mutator vm = v.getMutator();
      VarCharVector dv = v.getValuesVector();
      VarCharVector.Accessor da = dv.getAccessor();
      UInt4Vector ov = dv.getOffsetVector();
      UInt4Vector.Accessor ova = ov.getAccessor();
      UInt1Vector bv = v.getBitsVector();

      // Right at edge, so target offset vector is larger
      int actualDataLength = 1024;
      int targetOffsetCount = 2 * targetValueCount;
      assertEquals(actualDataLength, dv.getBuffer().capacity());
      assertEquals(targetOffsetCount * 4, ov.getBuffer().capacity());
      assertEquals(targetValueCount, bv.getBuffer().capacity());

      // Zero fill so the following is deterministic
      // Bits are already zero

      v.getBuffer().setZero(0, 1024);
      ov.getBuffer().setZero(0, 256 * 4);

      // Initial setup. Valid only because of zero-fill and
      // how intial values happen to be set up.

      assertEquals(0, va.getValueCount());
      assertEquals(-1, vm.getLastSet());
      assertEquals(0, bv.getAccessor().getValueCount());
      assertEquals(0, da.getValueCount());
      assertEquals(0, ova.getValueCount());
      assertEquals(0, ova.get(0));
      assertEquals(0, ova.get(1));

      // O-size is special

      v.getMutator().setValueCount(0);
      assertEquals(-1, vm.getLastSet());
      assertEquals(0, va.getValueCount());
      assertEquals(0, bv.getAccessor().getValueCount());
      assertEquals(0, da.getValueCount());
      assertEquals(0, ova.getValueCount());
      assertEquals(0, ova.get(0));
      assertEquals(0, ova.get(1));

      // Fill-empties at 0 is also special.

      v.getMutator().fillEmpties(0);
      assertEquals(0, vm.getLastSet());
      assertEquals(0, va.getValueCount());
      assertEquals(0, ova.getValueCount());
      assertEquals(0, ova.get(0));
      assertEquals(0, ova.get(1));

      // Write one value

      byte[] bytes = "foo".getBytes(StandardCharsets.UTF_8);
      vm.setSafe(0, bytes, 0, bytes.length);
      assertEquals(0, vm.getLastSet());
      vm.setValueCount(1);
      assertEquals(0, vm.getLastSet());
      assertEquals(1, va.getValueCount());
      assertEquals(2, ova.getValueCount());
      assertEquals(0, ova.get(0));
      assertEquals(bytes.length, ova.get(1));

      // Pretend to skip a value

      v.getMutator().fillEmpties(2);
      // Optimistically pre-set for value we're about to write
      assertEquals(2, vm.getLastSet());
      // fillEmpties does not change the value count
      assertEquals(0, ova.get(0));
      assertEquals(3, ova.get(1)); // First value
      assertEquals(3, ova.get(2)); // Filled
      assertEquals(0, ova.get(3)); // Should not be set

      // Calling fillEmpties() twice is idempotent

      v.getMutator().fillEmpties(2);
      assertEquals(2, v.getMutator().getLastSet());
      assertEquals(0, ova.get(0));
      assertEquals(3, ova.get(1)); // First value
      assertEquals(3, ova.get(2)); // Filled
      assertEquals(0, ova.get(3)); // Should not be set

      // Write one more value

      byte[] second = "mumble".getBytes(StandardCharsets.UTF_8);
      vm.setSafe(2, second, 0, second.length);
      assertEquals(2, v.getMutator().getLastSet());
      vm.setValueCount(3);
      assertEquals(2, v.getMutator().getLastSet());
      assertEquals(3, va.getValueCount());
      assertEquals(4, ova.getValueCount());
      assertEquals(3, ova.get(1));
      assertEquals(3, ova.get(2));
      assertEquals(9, ova.get(3));

      // Skip two values

      v.getMutator().setValueCount(5);
      assertEquals(4, v.getMutator().getLastSet());
      assertEquals(5, va.getValueCount());
      assertEquals(6, ova.getValueCount());
      assertEquals(9, ova.get(3));
      assertEquals(9, ova.get(4));
      assertEquals(9, ova.get(5));

      // Skip a large number of values. Finish the vector
      // right where the offset vector would have to increase
      // in length

      v.getMutator().setValueCount(targetOffsetCount);
      assertEquals(targetOffsetCount - 1, v.getMutator().getLastSet());
      assertEquals(targetOffsetCount, va.getValueCount());
      assertEquals(targetOffsetCount + 1, ova.getValueCount());
      assertEquals(9, ova.get(targetOffsetCount-1));
      assertEquals(9, ova.get(targetOffsetCount));
      assertEquals(0, ova.get(targetOffsetCount + 1));
    }
  }

  @Test
  public void testNullableVarCharWriter() throws Exception {
    MaterializedField field = MaterializedField.create("stringCol", Types.optional(TypeProtos.MinorType.VARCHAR));
    try (NullableVarCharVector v = new NullableVarCharVector(field, allocator);
         DrillBuf buf = allocator.buffer(100)) {
      v.allocateNew(1000, 128);
      @SuppressWarnings("resource")
      FieldWriter w = new NullableVarCharWriterImpl(v, null);

      // Write in locations 1 and 3.

      w.setPosition(0);
      buf.setBytes(0, "foo".getBytes(StandardCharsets.UTF_8));
      w.writeVarChar(0, 3, buf);

      w.setPosition(2);
      buf.setBytes(0, "mumble".getBytes(StandardCharsets.UTF_8));
      w.writeVarChar(0, 6, buf);

      // Don't close the writer; it clears the vector
      // w.close();

      VarCharVector dv = v.getValuesVector();
      UInt4Vector ov = dv.getOffsetVector();
      UInt4Vector.Accessor ova = ov.getAccessor();

      v.getMutator().setValueCount(2);
      assertEquals(2, v.getAccessor().getValueCount());
      assertEquals(3, ova.getValueCount());
      assertEquals(3, ova.get(1));
      assertEquals(3, ova.get(2));
      assertEquals(9, ova.get(3));
    }
  }

  @Test
  public void testEmptyValuePopulator() throws Exception {
    MaterializedField field = MaterializedField.create("offsets", Types.required(TypeProtos.MinorType.UINT4));
    try (UInt4Vector v = new UInt4Vector(field, allocator)) {
      EmptyValuePopulator pop = new EmptyValuePopulator(v);
      UInt4Vector.Accessor va = v.getAccessor();
      UInt4Vector.Mutator vm = v.getMutator();

      v.allocateNew(128);

      // Zero case; for zero-length batches

      vm.setValueCount(0);
      assertEquals(0, va.getValueCount());
      assertEquals(0, va.get(0));

      // Start a record 0 value.

      pop.populate(0);
      assertEquals(1, va.getValueCount());
      assertEquals(0, va.get(0));

      // Pretend batch count is 1. Offset vector is
      // currently in special 0-size state.

      pop.populate(1);
      vm.setValueCount(1);
      assertEquals(1, va.getValueCount());
      assertEquals(0, va.get(0));
      assertEquals(0, va.get(1));

      // Pretend values are [xx] and [xxx]

      vm.set(1, 2);
      vm.set(2, 5);
      vm.set(3, 0);
      vm.setValueCount(3);
      assertEquals(3, va.getValueCount());
      assertEquals(5, va.get(2));
      assertEquals(0, va.get(3));

      // Pretend that the record count is 2 for the two
      // values above.

      pop.populate(2);
      assertEquals(3, va.getValueCount());
      assertEquals(5, va.get(2));
      assertEquals(0, va.get(3));

      // Pretend, instead we skipped records 2, 3 and 4

      pop.populate(5);
      assertEquals(6, va.getValueCount());
      assertEquals(5, va.get(2));
      assertEquals(5, va.get(3));
      assertEquals(5, va.get(4));
      assertEquals(5, va.get(5));
      assertEquals(0, va.get(6));
    }
  }

  @Test
  public void testRepeatedMapCount() throws Exception {
    try (RepeatedMapVector v = buildRepeatedMap()) {

      IntVector iv = getInner(v);
      RepeatedMapVector.Accessor va = v.getAccessor();
      RepeatedMapVector.Mutator vm = v.getMutator();
      IntVector.Accessor ia = iv.getAccessor();
      IntVector.Mutator im = iv.getMutator();
      UInt4Vector ov = v.getOffsetVector();
      UInt4Vector.Accessor oa = ov.getAccessor();
      UInt4Vector.Mutator om = ov.getMutator();

      // Zero fill so the following is deterministic

      ov.getBuffer().setZero(0, 6 * 4);

      // Initial state

      assertEquals(0, va.getValueCount());
      assertEquals(0, ia.getValueCount());
      assertEquals(0, oa.getValueCount());
      assertEquals(0, oa.get(0));

      // Record size = 0

      vm.setValueCount(0);
      assertEquals(0, va.getValueCount());
      assertEquals(0, ia.getValueCount());
      assertEquals(0, oa.getValueCount());
      assertEquals(0, oa.get(0));

      // Record size = 1, so, implicit record 1 (1-based) of []

      vm.setValueCount(1);
      assertEquals(1, va.getValueCount());
      assertEquals(0, ia.getValueCount());
      assertEquals(2, oa.getValueCount());
      assertEquals(0, oa.get(0));
      assertEquals(0, oa.get(1));

      // Record 2 (1-based) is [10, 20]

      im.set(0, 10);
      im.set(1, 20);
      im.setValueCount(2);
      om.set(2, 2);
      om.setValueCount(3);
      vm.setValueCount(2);
      assertEquals(2, va.getValueCount());
      assertEquals(2, ia.getValueCount());
      assertEquals(3, oa.getValueCount());
      assertEquals(0, oa.get(0));
      assertEquals(0, oa.get(1));
      assertEquals(2, oa.get(2));
      assertEquals(0, oa.get(3));

      // Batch size = 4, so implicit record 2, 4 of []

      vm.setValueCount(4);
      assertEquals(4, va.getValueCount());
      assertEquals(2, ia.getValueCount());
      assertEquals(5, oa.getValueCount());
      assertEquals(0, oa.get(0));
      assertEquals(0, oa.get(1));
      assertEquals(2, oa.get(2));
      assertEquals(2, oa.get(3));
      assertEquals(2, oa.get(4));
      assertEquals(0, oa.get(5));
    }
  }

  @Test
  public void testRepeatedCopySafe() throws Exception {
    try (RepeatedMapVector v = buildRepeatedMap();
        RepeatedMapVector f = buildFromMap()) {

      RepeatedMapVector.Mutator vm = v.getMutator();
      UInt4Vector ov = v.getOffsetVector();
      UInt4Vector.Accessor oa = ov.getAccessor();
      UInt4Vector.Mutator om = ov.getMutator();

      TransferPair tp = f.makeTransferPair(v);

      tp.copyValueSafe(0, 0);

      // CopyValue does not change the value count
      //assertEquals(1, va.getValueCount());
      //assertEquals(2, oa.getValueCount());
      //assertEquals(2, ia.getValueCount());
      assertEquals(0, oa.get(0));
      assertEquals(2, oa.get(1));

      tp.copyValueSafe(1, 1);
      assertEquals(0, oa.get(0));
      assertEquals(2, oa.get(1));
      assertEquals(5, oa.get(2));

      tp.copyValueSafe(2, 2);
      assertEquals(2, oa.get(1));
      assertEquals(5, oa.get(2));
      assertEquals(5, oa.get(3));

     vm.setValueCount(3);

      // v should now be the same as f
      validateFrom(v);
    }
  }

  private class SpecialMapVector extends MapVector {

    private final RepeatedMapVector v;

    public SpecialMapVector(RepeatedMapVector v) {
      super("", null, null);
      this.v = v;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ValueVector> T addOrGet(String name, MajorType type, Class<T> clazz) {
      assert clazz == RepeatedMapVector.class;
      return (T) v;
    }
  }

  @Test
  public void testRepeatedMapWriter() throws Exception {

    // The writers will create a nullable int inner vector.

    MaterializedField field = MaterializedField.create("repeated_map", Types.repeated(TypeProtos.MinorType.MAP));
    try (RepeatedMapVector v = new RepeatedMapVector(field, allocator, null)) {

      SpecialMapVector mapVector = new SpecialMapVector(v);
      @SuppressWarnings("resource")
      SingleMapWriter mapRoot = new SingleMapWriter(mapVector, null, false);
      ListWriter lw = mapRoot.list("repeated_map");
      MapWriter mw = lw.map();

      // Record 1: [10, 20]

      lw.setPosition(0);
      lw.startList();
      mw.start();
      IntWriter iw = mw.integer("inner");
      iw.writeInt(10);
      mw.end();
      mw.start();
      iw.writeInt(20);
      mw.end();
      lw.endList();

      // Record 2: [30, 40, 50]

      lw.setPosition(1);
      lw.startList();
      mw.start();
      iw.writeInt(30);
      mw.end();
      mw.start();
      iw.writeInt(40);
      mw.end();
      mw.start();
      iw.writeInt(50);
      mw.end();
      lw.endList();

      // Record 3: []

      lw.setPosition(2);
      lw.startList();
      lw.endList();

      v.getMutator().setValueCount(3);

      assertEquals(3, v.getAccessor().getValueCount());
      UInt4Vector.Accessor oa = v.getOffsetVector().getAccessor();
      assertEquals(4, oa.getValueCount());
      assertEquals(0, oa.get(0));
      assertEquals(2, oa.get(1));
      assertEquals(5, oa.get(2));
      assertEquals(5, oa.get(3));
      assertEquals(0, oa.get(4)); // Past end

      NullableIntVector inner =  v.addOrGet("inner", Types.optional(TypeProtos.MinorType.INT), NullableIntVector.class);
      UInt1Vector.Accessor ba = inner.getBitsVector().getAccessor();
      assertEquals(1, ba.get(0));
      assertEquals(1, ba.get(1));
      assertEquals(1, ba.get(2));
      assertEquals(1, ba.get(3));
      assertEquals(1, ba.get(4));
      assertEquals(0, ba.get(5)); // Past end

      NullableIntVector.Accessor ia = inner.getAccessor();
      assertEquals(10, ia.get(0));
      assertEquals(20, ia.get(1));
      assertEquals(30, ia.get(2));
      assertEquals(40, ia.get(3));
      assertEquals(50, ia.get(4));
    }
  }

  private RepeatedMapVector buildRepeatedMap() {
    MaterializedField field = MaterializedField.create("repeated_map", Types.repeated(TypeProtos.MinorType.MAP));
    RepeatedMapVector v = new RepeatedMapVector(field, allocator, null);
    getInner(v);
    v.allocateNew(5, 10);
    return v;
  }

  private IntVector getInner(RepeatedMapVector v) {
    return v.addOrGet("inner", Types.required(TypeProtos.MinorType.INT), IntVector.class);
  }

  private RepeatedMapVector buildFromMap() {
    RepeatedMapVector v = buildRepeatedMap();

    // Can't figure out how to get the indexes to step for
    // inner and outer values.
    // If we get the int writer from the repeated map writer,
    // the column will be converted to nullable int.

    IntVector iv = getInner(v);
    IntVector.Mutator im = iv.getMutator();
    UInt4Vector ov = v.getOffsetVector();
    UInt4Vector.Mutator om = ov.getMutator();

    om.set(0, 0);

    // Record 1: [10, 20]

    im.set(0, 10);
    im.set(1, 20);
    om.set(1, 2);

    // Record 2: [30, 40, 50]

    im.set(2, 30);
    im.set(3, 40);
    im.set(4, 50);
    om.set(2, 5);

    // Record 3: []

    om.set(3, 5);

    om.setValueCount(4);
    v.getMutator().setValueCount(3);

    // Sanity check

    validateFrom(v);
    return v;
  }

  private void validateFrom(RepeatedMapVector v) {
    UInt4Vector.Accessor oa = v.getOffsetVector().getAccessor();
    assertEquals(3, v.getAccessor().getValueCount());
    assertEquals(4, oa.getValueCount());
    assertEquals(0, oa.get(0));
    assertEquals(2, oa.get(1));
    assertEquals(5, oa.get(2));
    assertEquals(5, oa.get(3));

    IntVector.Accessor ia = getInner(v).getAccessor();
    assertEquals(10, ia.get(0));
    assertEquals(20, ia.get(1));
    assertEquals(30, ia.get(2));
    assertEquals(40, ia.get(3));
    assertEquals(50, ia.get(4));
  }
}
