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

import static org.junit.Assert.*;

import java.io.Serializable;
import java.util.Arrays;

import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.EntryNotFoundException;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.Scope;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.VM;

/**
 * An instance of this class is delegated to by test classes that test disk regions.
 *
 * Used by: DiskDistributedNoAckRegionTestCase TODO: move this content into
 * DiskDistributedNoAckRegionTestCase
 */
public class DiskRegionTestImpl implements Serializable {

  private static final int NUM_ENTRIES = 1000;
  private static final int VALUE_SIZE = 2000;

  final RegionTestCase rtc;

  private CacheSerializableRunnable createRgnRunnable(final String name) {
    return new CacheSerializableRunnable("Create region") {
      public void run2() throws CacheException {
        DiskRegionTestImpl.this.rtc.createRegion(name);
      }
    };
  }

  /** Creates a new instance of DiskRegionTestImpl */
  public DiskRegionTestImpl(RegionTestCase rtc) {
    this.rtc = rtc;
  }

  /**
   * Tests that you can create a disk region
   */
  public void testCreateDiskRegion() throws CacheException {
    final String name = this.rtc.getUniqueName();
    Host host = Host.getHost(0);
    VM vm0 = host.getVM(0);

    vm0.invoke(createRgnRunnable(name));
  }

  /**
   * Tests fillValues on backup regions.
   *
   * Note: The regions in the following description all have the same unique name. 1) Create backup
   * region in VM0 and add some values so they get backed up 2) Close that region 3) Create
   * non-mirrored distributed region in VM1 and populate with over 1M of data 4) Create a mirrored
   * KEYS region in VM2. This will cause VM2 to have all the keys but no values. 5) Re-create the
   * backup region in VM0 with mirroring KEY_VALUES. This will get the keys from VM2 and the values
   * from VM1 using fillValues. The region should end up with the keys created in step 1, and they
   * should not be faulted into the VM.
   */
  public void testBackupFillValues() throws CacheException {
    RegionAttributes attrs = this.rtc.getRegionAttributes();
    assertTrue("This test not appropriate for non-backup regions", attrs.getPersistBackup());

    final String name = this.rtc.getUniqueName();
    final String key1 = "KEY1";
    final String key2 = "KEY2";
    final String value1 = "VALUE1";
    final String value2 = "VALUE2";

    Host host = Host.getHost(0);
    VM vm0 = host.getVM(0);
    VM vm1 = host.getVM(1);
    // VM vm2 = host.getVM(2);

    vm0.invoke(new CacheSerializableRunnable("Create backup Region in VM0") {
      public void run2() throws CacheException {
        Region rgn = DiskRegionTestImpl.this.rtc.createRegion(name);
        rgn.create(key1, value1);
        rgn.create(key2, value2);

        // create entries that will be overwritten by getInitialImage below
        rgn.create(new Integer(0), "TEMP-0");
        rgn.create(new Integer(1), "TEMP-1");

        // no longer to close cache in 6.5, otherwise the 2 vms will splitbrain
        // CacheTestCase.closeCache();
      }
    });

    vm1.invoke(new CacheSerializableRunnable("Create & Populate non-mirrored in VM1") {
      public void run2() throws CacheException {
        AttributesFactory factory = new AttributesFactory();
        // set scope to be same as test region
        Scope scope = DiskRegionTestImpl.this.rtc.getRegionAttributes().getScope();
        factory.setScope(scope);
        DataPolicy dataPolicy = DiskRegionTestImpl.this.rtc.getRegionAttributes().getDataPolicy();
        factory.setDataPolicy(dataPolicy);
        RegionAttributes attrs2 = factory.create();
        Region rgn = DiskRegionTestImpl.this.rtc.createRegion(name, attrs2);

        // Fill the region with some keys.
        for (int i = 0; i < NUM_ENTRIES; i++) {
          byte[] value = new byte[VALUE_SIZE];
          Arrays.fill(value, (byte) 0xAB);
          rgn.put(new Integer(i), value);
        }
        // just for sanity:
        assertEquals(NUM_ENTRIES + 2, rgn.keySet().size());
      }
    });

    vm0.invoke(new CacheSerializableRunnable("Close Cache in VM0") {
      public void run2() throws CacheException {
        CacheTestCase.closeCache();
      }
    });

    // vm2.invoke(new CacheSerializableRunnable("Create mirrored KEYS region in VM2") {
    // public void run2() throws CacheException {
    // AttributesFactory factory = new AttributesFactory();
    // // set scope to be same as test region
    // Scope scope = DiskRegionTestImpl.this.rtc.getRegionAttributes().getScope();
    // factory.setScope(scope);
    // // set mirror KEYS
    // factory.setMirrorType(MirrorType.KEYS);
    // RegionAttributes attrs2 = factory.create();
    // Region rgn = DiskRegionTestImpl.this.rtc.createRegion(name, attrs2);
    // }
    // });

    String runnableName =
        "Re-create backup region in VM0 with mirror " + "KEYS_VALUES and Do Verification";
    vm0.invoke(new CacheSerializableRunnable(runnableName) {
      public void run2() throws CacheException {
        AttributesFactory factory =
            new AttributesFactory(DiskRegionTestImpl.this.rtc.getRegionAttributes());
        factory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
        RegionAttributes attrs2 = factory.create();
        // DebuggerSupport.waitForJavaDebugger(rtc.getLogWriter(), "About to create region...");
        Region rgn = DiskRegionTestImpl.this.rtc.createRegion(name, attrs2);

        // verify
        assertEquals(NUM_ENTRIES + 2, rgn.keySet().size());

        boolean RECOVER_VALUES = true;

        if (RECOVER_VALUES) {
          assertEquals(value1, rgn.getEntry(key1).getValue());
          assertEquals(value2, rgn.getEntry(key2).getValue());
        } else {
          assertNull(valueInVM(rgn, key1));
          assertNull(valueInVM(rgn, key2));
        }
        assertEquals(value1, valueOnDisk(rgn, key1));
        assertEquals(value2, valueOnDisk(rgn, key2));

        // The following also verifies TEMP values were overwritten
        for (int i = 0; i < NUM_ENTRIES; i++) {
          Region.Entry entry = rgn.getEntry(new Integer(i));
          assertNotNull("No entry for key " + i, entry);
          byte[] v = (byte[]) entry.getValue();
          assertNotNull("Null value for key " + i, v);
          assertEquals(VALUE_SIZE, v.length);
          // test a byte
          assertEquals((byte) 0xAB, v[i % VALUE_SIZE]);
        }

        rgn.close();

        rgn = DiskRegionTestImpl.this.rtc.createRegion(name, attrs2);

        // verify
        assertEquals(NUM_ENTRIES + 2, rgn.keySet().size());

        if (RECOVER_VALUES) {
          assertEquals(value1, rgn.getEntry(key1).getValue());
          assertEquals(value2, rgn.getEntry(key2).getValue());
        } else {
          assertNull(valueInVM(rgn, key1));
          assertNull(valueInVM(rgn, key2));
        }
        assertEquals(value1, valueOnDisk(rgn, key1));
        assertEquals(value2, valueOnDisk(rgn, key2));

        // The following also verifies TEMP values were overwritten
        for (int i = 0; i < NUM_ENTRIES; i++) {
          Region.Entry entry = rgn.getEntry(new Integer(i));
          assertNotNull("No entry for key " + i, entry);
          byte[] v = (byte[]) entry.getValue();
          assertNotNull("Null value for key " + i, v);
          assertEquals(VALUE_SIZE, v.length);
          // test a byte
          assertEquals((byte) 0xAB, v[i % VALUE_SIZE]);
        }

      }



      private Object valueInVM(Region rgn, Object key) throws EntryNotFoundException {
        org.apache.geode.internal.cache.LocalRegion lrgn =
            (org.apache.geode.internal.cache.LocalRegion) rgn;
        return lrgn.getValueInVM(key);
      }

      private Object valueOnDisk(Region rgn, Object key) throws EntryNotFoundException {
        org.apache.geode.internal.cache.LocalRegion lrgn =
            (org.apache.geode.internal.cache.LocalRegion) rgn;
        return lrgn.getValueOnDisk(key);
      }
    });
  }

}
