/*
 * 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 javaobject;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Properties;
import java.util.Arrays;

import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.DataSerializable;
import org.apache.geode.Delta;
import org.apache.geode.DataSerializer;
import org.apache.geode.cache.Declarable;
import org.apache.geode.InvalidDeltaException;
import org.apache.geode.Instantiator;

/**
 * Sample test class which implements Delta.
 * 
 * @since 6.1
 */
public class DeltaTestImpl implements DataSerializable, Delta, Declarable {

  static {
     Instantiator.register(new Instantiator(DeltaTestImpl.class, (byte) 30) {
     public DataSerializable newInstance() {
        return new DeltaTestImpl();
     }
   });
  }
  
  private static final byte INT_MASK      = 0x1;
  private static final byte STR_MASK      = 0x2;
  private static final byte DOUBLE_MASK   = 0x4;
  private static final byte BYTE_ARR_MASK = 0x8;
  private static final byte TEST_OBJ_MASK = 0x10;
  private static final byte COMPLETE_MASK = 0x1F;  
  
  /*****************************************************************************
   * Below fields are not part of standard Delta implementation but are used for
   * testing purpose.
   */
  public static final String ERRONEOUS_STRING_FOR_FROM_DELTA = "ERRONEOUS_STRING";
  public static final int ERRONEOUS_INT_FOR_TO_DELTA = -101;
  private static long fromDeltaInvokations;
  private static long toDeltaInvokations;
  private static long toDeltaFailure;
  private static long fromDeltaFailure;
  public static boolean NEED_TO_RESET_T0_DELTA = true;
  /** *********************************************************************** */
  
  // Actual data fields of this instance.
  private int intVar = 0;                          // 0000 0001
  private String str = "";                         // 0000 0010
  private Double doubleVar = new Double(0);        // 0000 0100
  private byte[] byteArr = new byte[1];            // 0000 1000
  private TestObject1 testObj = new TestObject1(); // 0001 0000

  /**
   * Indicates the fields containing delta.
   */
  private byte deltaBits = 0x0;

  private boolean hasDelta = false;

  public DeltaTestImpl() {
  }

  public DeltaTestImpl(int intVal, String str) {
    this.intVar = intVal;
    this.str = str;
  }

  public DeltaTestImpl(int intVal, String string, Double doubleVal,
      byte[] bytes, TestObject1 testObj) {
    this.intVar = intVal;
    this.str = string;
    this.doubleVar = doubleVal;
    this.byteArr = bytes;
    this.testObj = testObj;
  }

  public void resetDeltaStatus() {
    this.deltaBits = 0x0;
    this.hasDelta = false;
  }

  public byte[] getByteArr() {
    return byteArr;
  }

  public void setByteArr(byte[] bytes) {
    this.byteArr = bytes;
    this.deltaBits |= BYTE_ARR_MASK;
    this.hasDelta = true;
  }

  public Double getDoubleVar() {
    return doubleVar;
  }

  public void setDoubleVar(Double doubleVar) {
    this.doubleVar = doubleVar;
    this.deltaBits |= DOUBLE_MASK;
    this.hasDelta = true;
  }

  public int getIntVar() {
    return intVar;
  }

  public void setIntVar(int intVar) {
    this.intVar = intVar;
    this.deltaBits |= INT_MASK;
    this.hasDelta = true;
  }

  public String getStr() {
    return str;
  }

  public void setStr(String str) {
    this.str = str;
    this.deltaBits |= STR_MASK;
    this.hasDelta = true;
  }

  public TestObject1 getTestObj() {
    return testObj;
  }

  public void setTestObj(TestObject1 testObj) {
    this.testObj = testObj;
    this.deltaBits |= TEST_OBJ_MASK;
    this.hasDelta = true;
  }

  /*****************************************************************************
   * Below methods are not part of standard Delta implementation but are used
   * for testing purpose.
   */
  public static void resetDeltaInvokationCounters() {
    resetToDeltaCounter();
    resetFromDeltaCounter();
    resetFailureCounter();
  }

  public static void resetToDeltaCounter() {
    toDeltaInvokations = 0;
  }

  public static void resetFromDeltaCounter() {
    fromDeltaInvokations = 0;
  }

  public static void resetFailureCounter() {
    toDeltaFailure = 0;
    fromDeltaFailure = 0;
  }

  public static Boolean deltaFeatureUsed() {
    return (toDeltaInvokations > 0) || (fromDeltaInvokations > 0);
  }

  public static Boolean toDeltaFeatureUsed() {
    return (toDeltaInvokations > 0);
  }

  public static Long getFromDeltaInvokations() {
    return fromDeltaInvokations;
  }

  public static Long getToDeltaInvokations() {
    return toDeltaInvokations;
  }

  public static Boolean fromDeltaFeatureUsed() {
    return (fromDeltaInvokations > 0);
  }

  public static Boolean isFromDeltaFailure() {
    return (fromDeltaFailure > 0);
  }

  public static Boolean isToDeltaFailure() {
    return (toDeltaFailure > 0);
  }

  public static Long getFromDeltaFailures() {
    return fromDeltaFailure;
  }

  public static Long getToDeltaFailures() {
    return toDeltaFailure;
  }
  
  protected void checkInvalidString(String str) {
    if (ERRONEOUS_STRING_FOR_FROM_DELTA.equals(str)) {
      fromDeltaFailure++;
      throw new InvalidDeltaException("Delta could not be applied. "
          + this);
    }
  }

  protected void checkInvalidInt(int intVal) {
    if (ERRONEOUS_INT_FOR_TO_DELTA == intVal) {
      toDeltaFailure++;
      throw new InvalidDeltaException("Delta could not be extracted. "
          + this);
    }
  }

  protected void checkInvalidInt2(int intVal) {}
  /** ********************************************************************** */

  public String toString() {
    StringBuffer bytes = new StringBuffer("");
    if (byteArr != null) {
      for (int i = 0; i < byteArr.length; i++) {
        bytes.append(byteArr[i]);
      }
    }
    return "DeltaTestImpl[hasDelta=" + this.hasDelta + ",int=" + this.intVar
        + ",double=" + this.doubleVar + ",str=" + this.str + ",bytes={"
        + bytes.toString() + "},testObj="
        + ((this.testObj != null) ? this.testObj.hashCode() : "") + "]";
  }

  public boolean equals(Object other) {
    if (other == null || !(other instanceof DeltaTestImpl)) {
      return false;
    }
    DeltaTestImpl delta = (DeltaTestImpl)other;
    if (this.intVar == delta.intVar && this.doubleVar.equals(delta.doubleVar)
        && Arrays.equals(this.byteArr, delta.byteArr)
        && this.str.equals(delta.str)) {
      return true;
    }
    return false;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.geode.Delta#fromDelta(java.io.DataInput)
   */
  public void fromDelta(DataInput in) throws IOException {
    try {
      
      fromDeltaInvokations++;
      boolean tempHasDelta = false;
      byte tempDeltaBits = this.deltaBits;
      byte[] tempByteArr = this.byteArr;
      int tempIntVar = this.intVar;
      double tempDoubleVar = this.doubleVar;
      String tempStr = this.str;
      TestObject1 tempTestObj = this.testObj;

      tempDeltaBits = DataSerializer.readByte(in);
      if (tempDeltaBits != 0) {
        tempHasDelta = true;
        if ((tempDeltaBits & INT_MASK) == INT_MASK) {
          tempIntVar = DataSerializer.readPrimitiveInt(in);
          checkInvalidInt2(tempIntVar);
        }
        if ((tempDeltaBits & STR_MASK) == STR_MASK) {
          tempStr = DataSerializer.readString(in);
          checkInvalidString(tempStr); // Simulates exception
        }
        if ((tempDeltaBits & DOUBLE_MASK) == DOUBLE_MASK) {
          tempDoubleVar = DataSerializer.readDouble(in);
        }
        if ((tempDeltaBits & BYTE_ARR_MASK) == BYTE_ARR_MASK) {
          //tempByteArr = DataSerializer.readByteArray(in);
	  tempByteArr=(byte[])DataSerializer.readObject(in);
        }
        if ((tempDeltaBits & TEST_OBJ_MASK) == TEST_OBJ_MASK) {
          tempTestObj = (TestObject1)DataSerializer.readObject(in);
        }
        if ((deltaBits | COMPLETE_MASK) != COMPLETE_MASK) {
          throw new IllegalArgumentException("Unknown field code: "
              + tempDeltaBits);
        }
      }
      if (tempHasDelta) {
        //this.hasDelta = true;
        //this.deltaBits = tempDeltaBits;
        this.intVar = tempIntVar;
        this.str = tempStr;
        this.doubleVar = tempDoubleVar;
        this.byteArr = tempByteArr;
        this.testObj = tempTestObj;
      }
    }
    catch (IOException e) {
      GemFireCacheImpl.getInstance().getLogger().warning(
          "DeltaTestImpl.fromDelta(): " + e);
      throw e;
    }
    catch (IllegalArgumentException iae) {
      GemFireCacheImpl.getInstance().getLogger().warning(
          "DeltaTestImpl.fromDelta(): " + iae);
      throw new InvalidDeltaException(iae);
    }
    catch (ClassNotFoundException cnfe) {
      GemFireCacheImpl.getInstance().getLogger().warning(
          "DeltaTestImpl.fromDelta(): " + cnfe);
      throw new InvalidDeltaException(cnfe);
    }
  }
  
  /*
   * (non-Javadoc)
   * 
   * @see org.apache.geode.Delta#hasDelta()
   */
  public boolean hasDelta() {
    return this.hasDelta;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.geode.Delta#toDelta(java.io.DataOutput)
   */
  public void toDelta(DataOutput out) throws IOException {
    try {
      toDeltaInvokations++;
      DataSerializer.writeByte(this.deltaBits, out);

      if (deltaBits != 0) {
        if ((deltaBits & INT_MASK) == INT_MASK) {
          DataSerializer.writePrimitiveInt(this.intVar, out);
          checkInvalidInt(this.intVar); // Simulates exception
        }
        if ((deltaBits & STR_MASK) == STR_MASK) {
          DataSerializer.writeString(this.str, out);
        }
        if ((deltaBits & DOUBLE_MASK) == DOUBLE_MASK) {
          DataSerializer.writeDouble(this.doubleVar, out);
        }
        if ((deltaBits & BYTE_ARR_MASK) == BYTE_ARR_MASK) {
          DataSerializer.writeByteArray(this.byteArr, out);
        }
        if ((deltaBits & TEST_OBJ_MASK) == TEST_OBJ_MASK) {
          DataSerializer.writeObject(this.testObj, out);
        }
        if ((deltaBits | COMPLETE_MASK) != COMPLETE_MASK) {
          throw new IllegalArgumentException("Unknown field code: " + deltaBits);
        }
      }
    }
    catch (IOException ioe) {
      GemFireCacheImpl.getInstance().getLogger().warning(
          "DeltaTestImpl.toDelta(): " + ioe);
      throw ioe;
    }
    catch (IllegalArgumentException iae) {
      GemFireCacheImpl.getInstance().getLogger().warning(
          "DeltaTestImpl.toDelta(): " + iae);
      throw new InvalidDeltaException(iae);
    }
    finally {
      if (NEED_TO_RESET_T0_DELTA) {// No need to reset if secondary needs to
                                    // send delta again upon receiving
                                    // forceReattemptException
        this.deltaBits = 0x0;
        this.hasDelta = false;
      } 
    }
  }

  public void fromData(DataInput in) throws IOException, ClassNotFoundException {
    //this.deltaBits = DataSerializer.readByte(in);
    this.intVar = DataSerializer.readPrimitiveInt(in);
    this.str = DataSerializer.readString(in);
    this.doubleVar = DataSerializer.readDouble(in);
    this.byteArr = (byte[])DataSerializer.readObject(in);
    this.testObj = (TestObject1)DataSerializer.readObject(in);
    //if (deltaBits != 0) {
    //  this.hasDelta = true;
    //}
  }

  public void toData(DataOutput out) throws IOException {
    //DataSerializer.writeByte(this.deltaBits, out);
    DataSerializer.writePrimitiveInt(this.intVar, out);
    DataSerializer.writeString(this.str, out);
    DataSerializer.writeDouble(this.doubleVar, out);
    DataSerializer.writeObject(this.byteArr, out);
    DataSerializer.writeObject(this.testObj, out);
  }
  
  public void init(Properties props) {
  }
}
