/*
 * 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 org.junit.experimental.categories.Category;
import org.junit.Test;

import static org.junit.Assert.*;

import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.CacheListener;
import org.apache.geode.cache.CacheTransactionManager;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.EntryEvent;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.TransactionEvent;
import org.apache.geode.cache.TransactionListener;
import org.apache.geode.cache.util.CacheListenerAdapter;
import org.apache.geode.cache.util.TransactionListenerAdapter;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.SerializableCallable;
import org.apache.geode.test.dunit.VM;

/**
 * Test the getCallbackArgument in light of bug 34075.
 *
 * @since GemFire 5.0
 */
@Category(DistributedTest.class)
public class CallbackArgDUnitTest extends JUnit4CacheTestCase {

  // private transient Region r;
  // private transient DistributedMember otherId;
  protected transient int invokeCount;
  protected static String callbackArg;

  public CallbackArgDUnitTest() {
    super();
  }

  private VM getOtherVm() {
    Host host = Host.getHost(0);
    return host.getVM(0);
  }

  private void initOtherId() {
    VM vm = getOtherVm();
    vm.invoke(new CacheSerializableRunnable("Connect") {
      public void run2() throws CacheException {
        getCache();
      }
    });
    vm.invoke(() -> CallbackArgDUnitTest.getVMDistributedMember());
  }

  private void doCommitOtherVm() {
    VM vm = getOtherVm();
    vm.invoke(new CacheSerializableRunnable("create root") {
      public void run2() throws CacheException {
        AttributesFactory af = new AttributesFactory();
        CacheListener cl1 = new CacheListenerAdapter() {
          public void afterCreate(EntryEvent e) {
            assertEquals(callbackArg, e.getCallbackArgument());
          }
        };
        af.addCacheListener(cl1);
        af.setScope(Scope.DISTRIBUTED_ACK);
        Region r1 = createRootRegion("r1", af.create());
        Region r2 = r1.createSubregion("r2", af.create());
        Region r3 = r2.createSubregion("r3", af.create());
        CacheTransactionManager ctm = getCache().getCacheTransactionManager();
        TransactionListener tl1 = new TransactionListenerAdapter() {
          public void afterCommit(TransactionEvent e) {
            assertEquals(6, e.getEvents().size());
            Iterator it = e.getEvents().iterator();
            while (it.hasNext()) {
              EntryEvent ee = (EntryEvent) it.next();
              assertEquals(callbackArg, ee.getCallbackArgument());
              assertEquals(true, ee.isCallbackArgumentAvailable());
            }
          }
        };
        ctm.addListener(tl1);
        ctm.begin();
        r2.put("b", "value1", callbackArg);
        r3.put("c", "value2", callbackArg);
        r1.put("a", "value3", callbackArg);
        r1.put("a2", "value4", callbackArg);
        r3.put("c2", "value5", callbackArg);
        r2.put("b2", "value6", callbackArg);
        ctm.commit();
      }
    });
  }

  public static DistributedMember getVMDistributedMember() {
    return InternalDistributedSystem.getAnyInstance().getDistributedMember();
  }

  ////////////////////// Test Methods //////////////////////

  List expectedKeys;
  int clCount = 0;

  Object getCurrentExpectedKey() {
    Object result = this.expectedKeys.get(this.clCount);
    this.clCount += 1;
    return result;
  }

  /**
   * make sure callback arg is NOT_AVAILABLE in all the places it should be
   */
  @Test
  public void testForNA_CA() throws CacheException {
    doTest();
  }

  @Test
  public void testForCA() throws Exception {
    callbackArg = "cbArg";
    getOtherVm().invoke(new SerializableCallable() {
      public Object call() throws Exception {
        callbackArg = "cbArg";
        return null;
      }
    });
    doTest();
  }

  private void doTest() throws CacheException {
    initOtherId();
    AttributesFactory af = new AttributesFactory();
    af.setDataPolicy(DataPolicy.REPLICATE);
    af.setScope(Scope.DISTRIBUTED_ACK);
    CacheListener cl1 = new CacheListenerAdapter() {
      public void afterCreate(EntryEvent e) {
        assertEquals(getCurrentExpectedKey(), e.getKey());
        assertEquals(callbackArg, e.getCallbackArgument());
        assertEquals(true, e.isCallbackArgumentAvailable());
      }
    };
    af.addCacheListener(cl1);
    Region r1 = createRootRegion("r1", af.create());
    Region r2 = r1.createSubregion("r2", af.create());
    r2.createSubregion("r3", af.create());

    TransactionListener tl1 = new TransactionListenerAdapter() {
      public void afterCommit(TransactionEvent e) {
        assertEquals(6, e.getEvents().size());
        ArrayList keys = new ArrayList();
        Iterator it = e.getEvents().iterator();
        while (it.hasNext()) {
          EntryEvent ee = (EntryEvent) it.next();
          keys.add(ee.getKey());
          assertEquals(callbackArg, ee.getCallbackArgument());
          assertEquals(true, ee.isCallbackArgumentAvailable());
        }
        assertEquals(CallbackArgDUnitTest.this.expectedKeys, keys);
        CallbackArgDUnitTest.this.invokeCount = 1;
      }
    };
    CacheTransactionManager ctm = getCache().getCacheTransactionManager();
    ctm.addListener(tl1);

    this.invokeCount = 0;
    this.clCount = 0;
    this.expectedKeys = Arrays.asList(new String[] {"b", "c", "a", "a2", "c2", "b2"});
    doCommitOtherVm();
    assertEquals(1, this.invokeCount);
    assertEquals(6, this.clCount);
  }
}
