/*
 * 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.internal.cache.tier;

import static org.junit.Assert.*;

import java.util.Properties;

import org.apache.geode.test.junit.categories.ClientServerTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.internal.AvailablePort;
import org.apache.geode.internal.cache.tier.sockets.DeltaEOFException;
import org.apache.geode.internal.cache.tier.sockets.FaultyDelta;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.Invoke;
import org.apache.geode.test.dunit.SerializableRunnable;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;

/**
 * Test delta propagation for faulty delta implementation
 * 
 * @since GemFire 6.1
 */
@Category({DistributedTest.class, ClientServerTest.class})
public class Bug40396DUnitTest extends JUnit4DistributedTestCase {

  private static Cache cache;
  private static final String REGION_NAME = "Bug40396DUnitTest_region";

  private static final String END_OF_FILE_EX = "eofe";
  private static final String ARRAY_INDEX_OUT_BOUND_EX = "aiob";

  private static int counter;

  private VM server;
  private VM server2;

  private static final int PUT_COUNT = 10;

  public Bug40396DUnitTest() {
    super();
  }

  @Override
  public final void postSetUp() throws Exception {
    disconnectAllFromDS();
    final Host host = Host.getHost(0);
    server = host.getVM(0);
    server2 = host.getVM(2);
  }

  /*
   * create server cache
   */
  public static Integer createServerCache() throws Exception {
    new Bug40396DUnitTest().createCache(new Properties());
    AttributesFactory factory = new AttributesFactory();
    factory.setScope(Scope.DISTRIBUTED_ACK);
    factory.setDataPolicy(DataPolicy.REPLICATE);
    RegionAttributes attrs = factory.create();
    Region region = cache.createRegion(REGION_NAME, attrs);
    region.getAttributesMutator().setCloningEnabled(false);
    CacheServer server = cache.addCacheServer();
    addExceptions();
    int port = AvailablePort.getRandomAvailablePort(AvailablePort.SOCKET);
    server.setPort(port);
    // ensures updates to be sent instead of invalidations
    server.setNotifyBySubscription(true);
    server.start();
    return new Integer(server.getPort());

  }

  public static void addExceptions() throws Exception {
    if (cache != null && !cache.isClosed()) {
      cache.getLogger()
          .info("<ExpectedException action=add>" + "java.io.EOFException" + "</ExpectedException>");
      cache.getLogger().info("<ExpectedException action=add>"
          + "java.lang.ArrayIndexOutOfBoundsException" + "</ExpectedException>");
    }
  }

  public static void removeExceptions() {
    if (cache != null && !cache.isClosed()) {
      cache.getLogger().info(
          "<ExpectedException action=remove>" + "java.io.EOFException" + "</ExpectedException>");
      cache.getLogger().info("<ExpectedException action=remove>"
          + "java.lang.ArrayIndexOutOfBoundsException" + "</ExpectedException>");
    }
  }

  /*
   * create cache with properties
   */
  private void createCache(Properties props) throws Exception {
    DistributedSystem ds = getSystem(props);
    cache = CacheFactory.create(ds);
    assertNotNull(cache);
  }

  public static void closeCache() {
    if (cache != null && !cache.isClosed()) {
      cache.close();
      cache.getDistributedSystem().disconnect();
    }
  }

  public static Exception putDelta(String regName, String type) {
    Region reg = cache.getRegion(Region.SEPARATOR + regName);
    try {
      if (type.equals(END_OF_FILE_EX)) {
        DeltaEOFException obj = new DeltaEOFException();
        for (int i = 0; i < PUT_COUNT; i++) {
          obj.setIntVal(i);
          obj.setBigObj(new byte[] {(byte) (i + 3), (byte) (i + 3)});
          reg.put("key", obj);
        }
      } else if (type.equals(ARRAY_INDEX_OUT_BOUND_EX)) {
        FaultyDelta obj = new FaultyDelta();
        for (int i = 0; i < PUT_COUNT; i++) {
          obj.setIntVal(i);
          obj.setBigObj(new byte[] {(byte) (i + 3), (byte) (i + 3)});
          reg.put("key", obj);
        }
      }
    } catch (Exception ex) {
      return ex;
    }
    // this make tests fail
    return new Exception();
  }

  /**
   * This test does the following 1)send faulty implementation (Reading more in fromDelta then what
   * sent by toDelta) of delta raises EOF exception<br>
   */
  @Test
  public void testForFaultyDeltaImplementationForEOFEX() {
    boolean matched = false;
    ((Integer) server.invoke(() -> Bug40396DUnitTest.createServerCache())).intValue();
    ((Integer) server2.invoke(() -> Bug40396DUnitTest.createServerCache())).intValue();
    Exception xp =
        (Exception) server.invoke(() -> Bug40396DUnitTest.putDelta(REGION_NAME, END_OF_FILE_EX));
    StackTraceElement[] st = xp.getCause().getStackTrace();
    matched = getMatched(st);

    assertTrue("pattern not found", matched);
  }

  private boolean getMatched(StackTraceElement[] ste) {
    boolean mched = false;
    for (int i = 0; i < ste.length; i++) {
      if (mched)
        break;
      if (ste[i].toString().indexOf("fromDelta") != -1)
        mched = true;
    }
    return mched;
  }

  /**
   * This test does the following 1)send faulty implementation when reading incorrect order from
   * toDelta, raises delta raises array index out of bound exception<br>
   */
  @Test
  public void testForFaultyDeltaImplementationForAIOBEX() {
    boolean matched = false;
    ((Integer) server.invoke(() -> Bug40396DUnitTest.createServerCache())).intValue();
    ((Integer) server2.invoke(() -> Bug40396DUnitTest.createServerCache())).intValue();
    Exception xp = (Exception) server
        .invoke(() -> Bug40396DUnitTest.putDelta(REGION_NAME, ARRAY_INDEX_OUT_BOUND_EX));

    StackTraceElement[] st = xp.getStackTrace();
    matched = getMatched(st);

    assertTrue("pattern not found", matched);
  }

  @Override
  public final void preTearDown() throws Exception {
    // then close the servers
    server.invoke(() -> Bug40396DUnitTest.removeExceptions());
    server.invoke(() -> Bug40396DUnitTest.closeCache());
    server2.invoke(() -> Bug40396DUnitTest.closeCache());
    cache = null;
    Invoke.invokeInEveryVM(new SerializableRunnable() {
      public void run() {
        cache = null;
      }
    });
  }
}
