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

import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.List;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.Cell.Type;
import org.apache.hadoop.hbase.CellBuilderFactory;
import org.apache.hadoop.hbase.CellBuilderType;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.io.TimeRange;
import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category({ SmallTests.class, ClientTests.class })
public class TestMutation {

  @ClassRule
  public static final HBaseClassTestRule CLASS_RULE =
      HBaseClassTestRule.forClass(TestMutation.class);

  @Test
  public void testAppendCopyConstructor() throws IOException {
    Append origin = new Append(Bytes.toBytes("ROW-01"));
    origin.setPriority(100);
    byte[] family = Bytes.toBytes("CF-01");

    origin.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
      .setRow(origin.getRow())
      .setFamily(family)
      .setQualifier(Bytes.toBytes("q"))
      .setType(Type.Put)
      .setValue(Bytes.toBytes(100))
      .build());
    origin.addColumn(family, Bytes.toBytes("q0"), Bytes.toBytes("value"));
    origin.setTimeRange(100, 1000);
    Append clone = new Append(origin);
    assertEquals(origin, clone);
    origin.addColumn(family, Bytes.toBytes("q1"), Bytes.toBytes("value"));

    //They should have different cell lists
    assertNotEquals(origin.getCellList(family), clone.getCellList(family));
  }

  @Test
  public void testIncrementCopyConstructor() throws IOException {
    Increment origin = new Increment(Bytes.toBytes("ROW-01"));
    origin.setPriority(100);
    byte[] family = Bytes.toBytes("CF-01");

    origin.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
      .setRow(origin.getRow())
      .setFamily(family)
      .setQualifier(Bytes.toBytes("q"))
      .setType(Cell.Type.Put)
      .setValue(Bytes.toBytes(100))
      .build());
    origin.addColumn(family, Bytes.toBytes("q0"), 4);
    origin.setTimeRange(100, 1000);
    Increment clone = new Increment(origin);
    assertEquals(origin, clone);
    origin.addColumn(family, Bytes.toBytes("q1"), 3);

    //They should have different cell lists
    assertNotEquals(origin.getCellList(family), clone.getCellList(family));
  }

  @Test
  public void testDeleteCopyConstructor() throws IOException {
    Delete origin = new Delete(Bytes.toBytes("ROW-01"));
    origin.setPriority(100);
    byte[] family = Bytes.toBytes("CF-01");

    origin.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
      .setRow(origin.getRow())
      .setFamily(family)
      .setQualifier(Bytes.toBytes("q"))
      .setType(Type.Delete)
      .build());
    origin.addColumn(family, Bytes.toBytes("q0"));
    origin.addColumns(family, Bytes.toBytes("q1"));
    origin.addFamily(family);
    origin.addColumns(family, Bytes.toBytes("q2"), 100);
    origin.addFamilyVersion(family, 1000);
    Delete clone = new Delete(origin);
    assertEquals(origin, clone);
    origin.addColumn(family, Bytes.toBytes("q3"));

    //They should have different cell lists
    assertNotEquals(origin.getCellList(family), clone.getCellList(family));
  }

  @Test
  public void testPutCopyConstructor() throws IOException {
    Put origin = new Put(Bytes.toBytes("ROW-01"));
    origin.setPriority(100);
    byte[] family = Bytes.toBytes("CF-01");

    origin.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
      .setRow(origin.getRow())
      .setFamily(family)
      .setQualifier(Bytes.toBytes("q"))
      .setType(Cell.Type.Put)
      .setValue(Bytes.toBytes("value"))
      .build());
    origin.addColumn(family, Bytes.toBytes("q0"), Bytes.toBytes("V-01"));
    origin.addColumn(family, Bytes.toBytes("q1"), 100, Bytes.toBytes("V-01"));
    Put clone = new Put(origin);
    assertEquals(origin, clone);
    origin.addColumn(family, Bytes.toBytes("q2"), Bytes.toBytes("V-02"));

    //They should have different cell lists
    assertNotEquals(origin.getCellList(family), clone.getCellList(family));
  }

  private void assertEquals(Mutation origin, Mutation clone) {
    Assert.assertEquals(origin.getFamilyCellMap().size(), clone.getFamilyCellMap().size());
    for (byte[] family : origin.getFamilyCellMap().keySet()) {
      List<Cell> originCells = origin.getCellList(family);
      List<Cell> cloneCells = clone.getCellList(family);
      Assert.assertEquals(originCells.size(), cloneCells.size());
      for (int i = 0; i != cloneCells.size(); ++i) {
        Cell originCell = originCells.get(i);
        Cell cloneCell = cloneCells.get(i);
        assertTrue(CellUtil.equals(originCell, cloneCell));
        assertTrue(CellUtil.matchingValue(originCell, cloneCell));
      }
    }
    Assert.assertEquals(origin.getAttributesMap().size(), clone.getAttributesMap().size());
    for (String name : origin.getAttributesMap().keySet()) {
      byte[] originValue = origin.getAttributesMap().get(name);
      byte[] cloneValue = clone.getAttributesMap().get(name);
      assertTrue(Bytes.equals(originValue, cloneValue));
    }
    Assert.assertEquals(origin.getTimestamp(), clone.getTimestamp());
    Assert.assertEquals(origin.getPriority(), clone.getPriority());
    if (origin instanceof Append) {
      assertEquals(((Append)origin).getTimeRange(), ((Append)clone).getTimeRange());
    }
    if (origin instanceof Increment) {
      assertEquals(((Increment)origin).getTimeRange(), ((Increment)clone).getTimeRange());
    }
  }

  private static void assertEquals(TimeRange origin, TimeRange clone) {
    Assert.assertEquals(origin.getMin(), clone.getMin());
    Assert.assertEquals(origin.getMax(), clone.getMax());
  }

  // HBASE-14881
  @Test
  public void testRowIsImmutableOrNot() {
    byte[] rowKey = Bytes.toBytes("immutable");

    // Test when row key is immutable
    Put putRowIsImmutable = new Put(rowKey, true);
    assertTrue(rowKey == putRowIsImmutable.getRow());  // No local copy is made

    // Test when row key is not immutable
    Put putRowIsNotImmutable = new Put(rowKey, 1000L, false);
    assertTrue(rowKey != putRowIsNotImmutable.getRow());  // A local copy is made
  }

  // HBASE-14882
  @Test
  public void testAddImmutableToPut() throws IOException {
    byte[] row        = Bytes.toBytes("immutable-row");
    byte[] family     = Bytes.toBytes("immutable-family");

    byte[] qualifier0 = Bytes.toBytes("immutable-qualifier-0");
    byte[] value0     = Bytes.toBytes("immutable-value-0");

    byte[] qualifier1 = Bytes.toBytes("immutable-qualifier-1");
    byte[] value1     = Bytes.toBytes("immutable-value-1");
    long   ts1        = 5000L;

    // "true" indicates that the input row is immutable
    Put put = new Put(row, true);
    put.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
            .setRow(row)
            .setFamily(family)
            .setQualifier(qualifier0)
            .setTimestamp(put.getTimestamp())
            .setType(Type.Put)
            .setValue(value0)
            .build())
        .add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
            .setRow(row)
            .setFamily(family)
            .setQualifier(qualifier1)
            .setTimestamp(ts1)
            .setType(Type.Put)
            .setValue(value1)
            .build());

    // Verify the cell of family:qualifier0
    Cell cell0 = put.get(family, qualifier0).get(0);

    // Verify no local copy is made for family, qualifier or value
    assertTrue(cell0.getFamilyArray()    == family);
    assertTrue(cell0.getQualifierArray() == qualifier0);
    assertTrue(cell0.getValueArray()     == value0);

    // Verify timestamp
    assertTrue(cell0.getTimestamp()      == put.getTimestamp());

    // Verify the cell of family:qualifier1
    Cell cell1 = put.get(family, qualifier1).get(0);

    // Verify no local copy is made for family, qualifier or value
    assertTrue(cell1.getFamilyArray()    == family);
    assertTrue(cell1.getQualifierArray() == qualifier1);
    assertTrue(cell1.getValueArray()     == value1);

    // Verify timestamp
    assertTrue(cell1.getTimestamp()      == ts1);
  }
}