/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009 Red Hat Inc. and/or its affiliates and other
 * contributors as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.infinispan.eviction;

import org.infinispan.commands.write.EvictCommand;
import org.infinispan.config.Configuration;
import org.infinispan.container.InternalEntryFactoryImpl;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.MarshalledValueInterceptor;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.marshall.MarshalledValue;
import org.infinispan.marshall.StreamingMarshaller;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.jgroups.util.Util;
import org.testng.annotations.Test;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

@Test(groups = "functional", testName = "eviction.MarshalledValuesEvictionTest", enabled = false, description = "Is this test even valid?  Evictions don't go thru the marshalled value interceptor when initiated form the data container!")
public class MarshalledValuesEvictionTest extends SingleCacheManagerTest {
   
   private static final int CACHE_SIZE=128;


   @Override
   protected EmbeddedCacheManager createCacheManager() throws Exception {
      Configuration cfg = new Configuration().fluent()
         .eviction().strategy(EvictionStrategy.LRU).maxEntries(CACHE_SIZE) // CACHE_SIZE max entries
         .expiration().wakeUpInterval(100L)
         .locking().useLockStriping(false) // to minimise chances of deadlock in the unit test
         .storeAsBinary()
         .build();
      EmbeddedCacheManager cm = TestCacheManagerFactory.createCacheManager(cfg);
      cache = cm.getCache();
      StreamingMarshaller marshaller = TestingUtil.extractComponent(cache, StreamingMarshaller.class);
      MockMarshalledValueInterceptor interceptor = new MockMarshalledValueInterceptor(marshaller);
      assert TestingUtil.replaceInterceptor(cache, interceptor, MarshalledValueInterceptor.class);
      return cm;
   }
   
   public void testEvictCustomKeyValue() {
      for (int i = 0; i<CACHE_SIZE*2;i++) {
         EvictionPojo p1 = new EvictionPojo();
         p1.i = (int)Util.random(2000);
         EvictionPojo p2 = new EvictionPojo();
         p2.i = 24;
         cache.put(p1, p2);         
      }   

      // wait for the cache size to drop to CACHE_SIZE, up to a specified amount of time.
      long giveupTime = System.currentTimeMillis() + (1000 * 10); // 10 sec
      while (cache.getAdvancedCache().getDataContainer().size() > CACHE_SIZE && System.currentTimeMillis() < giveupTime) {
         TestingUtil.sleepThread(100);
      }
      
      assert cache.getAdvancedCache().getDataContainer().size() <= CACHE_SIZE : "Expected 1, was " + cache.size(); 

      //let eviction manager kick in
      Util.sleep(3000);
      MockMarshalledValueInterceptor interceptor = (MockMarshalledValueInterceptor) TestingUtil.findInterceptor(cache, MarshalledValueInterceptor.class);
      assert !interceptor.marshalledValueCreated;
   }

   public void testEvictPrimitiveKeyCustomValue() {
      for (int i = 0; i<CACHE_SIZE*2;i++) {
         EvictionPojo p1 = new EvictionPojo();
         p1.i = (int)Util.random(2000);
         EvictionPojo p2 = new EvictionPojo();
         p2.i = 24;
         cache.put(p1, p2);         
      }

      // wait for the cache size to drop to CACHE_SIZE, up to a specified amount of time.
      long giveupTime = System.currentTimeMillis() + (1000 * 10); // 10 sec
      while (cache.getAdvancedCache().getDataContainer().size() > CACHE_SIZE && System.currentTimeMillis() < giveupTime) {
         TestingUtil.sleepThread(100);
      }
      
      assert cache.getAdvancedCache().getDataContainer().size() <= CACHE_SIZE : "Expected 1, was " + cache.size(); 
      //let eviction manager kick in
      Util.sleep(3000);      
      MockMarshalledValueInterceptor interceptor = (MockMarshalledValueInterceptor) TestingUtil.findInterceptor(cache, MarshalledValueInterceptor.class);
      assert !interceptor.marshalledValueCreated;
   }
   
   static class MockMarshalledValueInterceptor extends MarshalledValueInterceptor {
      boolean marshalledValueCreated;
      
      MockMarshalledValueInterceptor(StreamingMarshaller marshaller) {
         injectMarshaller(marshaller, new InternalEntryFactoryImpl());
      }

      @Override
      protected MarshalledValue createMarshalledValue(Object toWrap, InvocationContext ctx) {
         marshalledValueCreated = true;
         return super.createMarshalledValue(toWrap, ctx);
      }

      @Override
      public Object visitEvictCommand(InvocationContext ctx, EvictCommand command) throws Throwable {
         // Reset value so that changes due to invocation can be asserted
         if (marshalledValueCreated) marshalledValueCreated = false;
         return super.visitEvictCommand(ctx, command);
      }
   }

   static class EvictionPojo implements Externalizable {
      int i;

      public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         EvictionPojo pojo = (EvictionPojo) o;
         return i == pojo.i;
      }

      public int hashCode() {
         int result;
         result = i;
         return result;
      }

      @Override
      public void writeExternal(ObjectOutput out) throws IOException {
         out.writeInt(i);
      }

      @Override
      public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
         i = in.readInt();
      }

   }
}
