/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS
 */
package org.opends.server.backends.pluggable;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.opends.server.ConfigurationMock.*;
import static org.opends.server.backends.pluggable.State.IndexFlag.*;
import static org.opends.server.util.CollectionUtils.*;

import java.util.UUID;

import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.BackendIndexCfgDefn.IndexType;
import org.opends.server.admin.std.server.BackendIndexCfg;
import org.opends.server.admin.std.server.PDBBackendCfg;
import org.opends.server.backends.pdb.PDBStorage;
import org.opends.server.backends.pluggable.State.IndexFlag;
import org.opends.server.backends.pluggable.spi.AccessMode;
import org.opends.server.backends.pluggable.spi.ReadOperation;
import org.opends.server.backends.pluggable.spi.ReadableTransaction;
import org.opends.server.backends.pluggable.spi.TreeName;
import org.opends.server.backends.pluggable.spi.WriteOperation;
import org.opends.server.backends.pluggable.spi.WriteableTransaction;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.MemoryQuota;
import org.opends.server.core.ServerContext;
import org.opends.server.extensions.DiskSpaceMonitor;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

@Test(groups = { "precommit", "pluggablebackend" }, sequential = true)
public class StateTest extends DirectoryServerTestCase
{
  private static final IndexFlag DEFAULT_FLAG = COMPACTED;

  private final TreeName stateTreeName = new TreeName("base-dn", "index-id");
  private TreeName indexTreeName;
  private PDBStorage storage;
  private State state;

  @BeforeClass
  public void startServer() throws Exception {
    TestCaseUtils.startServer();
  }

  @BeforeMethod
  public void setUp() throws Exception
  {
    indexTreeName = new TreeName("index-base-dn", "index-index-id-" + UUID.randomUUID().toString());

    ServerContext serverContext = mock(ServerContext.class);
    when(serverContext.getMemoryQuota()).thenReturn(new MemoryQuota());
    when(serverContext.getDiskSpaceMonitor()).thenReturn(mock(DiskSpaceMonitor.class));

    storage = new PDBStorage(createBackendCfg(), serverContext);
    storage.open(AccessMode.READ_WRITE);
    storage.write(new WriteOperation()
    {
      @Override
      public void run(WriteableTransaction txn) throws Exception
      {
        txn.openTree(stateTreeName, true);
      }
    });

    state = new State(stateTreeName);
  }

  @AfterMethod
  public void tearDown() {
    storage.close();
    storage.removeStorageFiles();
  }

  @Test
  public void testDefaultValuesForNotExistingEntries() throws Exception
  {
    assertThat(getFlags()).containsExactly(DEFAULT_FLAG);
  }

  @Test
  public void testCreateNewFlagHasDefaultValue() throws Exception
  {
    addFlags();
    assertThat(getFlags()).containsExactly(DEFAULT_FLAG);
  }

  @Test
  public void testCreateStateTrustedIsAlsoCompacted() throws Exception
  {
    addFlags(TRUSTED);
    assertThat(getFlags()).containsExactly(TRUSTED, DEFAULT_FLAG);
  }

  @Test
  public void testCreateWithTrustedAndCompacted() throws Exception
  {
    addFlags(TRUSTED, COMPACTED);
    assertThat(getFlags()).containsExactly(TRUSTED, COMPACTED);
  }

  @Test
  public void testUpdateNotSetDefault() throws Exception
  {
    createFlagWith();

    addFlags(TRUSTED);
    assertThat(getFlags()).containsExactly(TRUSTED);
  }

  @Test
  public void testAddFlags() throws Exception
  {
    createFlagWith(TRUSTED);

    addFlags(COMPACTED);
    assertThat(getFlags()).containsExactly(TRUSTED, COMPACTED);
  }

  @Test
  public void testRemoveFlags() throws Exception
  {
    addFlags(COMPACTED, TRUSTED);
    assertThat(getFlags()).containsExactly(TRUSTED, COMPACTED);

    removeFlags(TRUSTED);
    assertThat(getFlags()).containsExactly(COMPACTED);

    removeFlags(COMPACTED);
    assertThat(getFlags()).containsExactly();
  }

  @Test
  public void testDeleteRecord() throws Exception
  {
    addFlags(COMPACTED, TRUSTED);

    storage.write(new WriteOperation()
    {
      @Override
      public void run(WriteableTransaction txn) throws Exception
      {
        state.deleteRecord(txn, indexTreeName);
      }
    });

    assertThat(getFlags()).containsExactly(COMPACTED);
  }

  private PDBBackendCfg createBackendCfg() throws ConfigException, DirectoryException
  {
    String homeDirName = "pdb_test";
    PDBBackendCfg backendCfg = legacyMockCfg(PDBBackendCfg.class);

    when(backendCfg.getBackendId()).thenReturn("persTest" + homeDirName);
    when(backendCfg.getDBDirectory()).thenReturn(homeDirName);
    when(backendCfg.getDBDirectoryPermissions()).thenReturn("755");
    when(backendCfg.getDBCacheSize()).thenReturn(0L);
    when(backendCfg.getDBCachePercent()).thenReturn(20);
    when(backendCfg.getBaseDN()).thenReturn(newTreeSet(DN.valueOf("dc=test,dc=com")));
    when(backendCfg.dn()).thenReturn(DN.valueOf("dc=test,dc=com"));
    when(backendCfg.listBackendIndexes()).thenReturn(new String[] { "sn" });
    when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[0]);

    BackendIndexCfg indexCfg = legacyMockCfg(BackendIndexCfg.class);
    when(indexCfg.getIndexType()).thenReturn(newTreeSet(IndexType.PRESENCE, IndexType.EQUALITY));
    when(indexCfg.getAttribute()).thenReturn(DirectoryServer.getAttributeTypeOrNull("sn"));
    when(backendCfg.getBackendIndex("sn")).thenReturn(indexCfg);

    return backendCfg;
  }

  private void createFlagWith(IndexFlag... flags) throws Exception
  {
    createEmptyFlag();
    addFlags(flags);
  }

  private void createEmptyFlag() throws Exception {
    removeFlags(DEFAULT_FLAG);
  }

  private void addFlags(final IndexFlag... flags) throws Exception
  {
    storage.write(new WriteOperation()
    {
      @Override
      public void run(WriteableTransaction txn) throws Exception
      {
        state.addFlagsToIndex(txn, indexTreeName, flags);
      }
    });
  }

  private void removeFlags(final IndexFlag... flags) throws Exception
  {
    storage.write(new WriteOperation()
    {
      @Override
      public void run(WriteableTransaction txn) throws Exception
      {
        state.removeFlagsFromIndex(txn, indexTreeName, flags);
      }
    });
  }

  private IndexFlag[] getFlags() throws Exception
  {
    return storage.read(new ReadOperation<IndexFlag[]>()
    {
      @Override
      public IndexFlag[] run(ReadableTransaction txn) throws Exception
      {
        return state.getIndexFlags(txn, indexTreeName).toArray(new IndexFlag[0]);
      }
    });
  }
}
