package org.apache.lucene.util;

/**
* 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.
*/

import org.apache.lucene.util.LuceneTestCase;

public class TestDoubleBarrelLRUCache extends LuceneTestCase {

  private void testCache(DoubleBarrelLRUCache<CloneableInteger,Object> cache, int n) throws Exception {
    Object dummy = new Object();
    
    for (int i = 0; i < n; i++) {
      cache.put(new CloneableInteger(i), dummy);
    }
    
    // access every 2nd item in cache
    for (int i = 0; i < n; i+=2) {
      assertNotNull(cache.get(new CloneableInteger(i)));
    }
    
    // add n/2 elements to cache, the ones that weren't
    // touched in the previous loop should now be thrown away
    for (int i = n; i < n + (n / 2); i++) {
      cache.put(new CloneableInteger(i), dummy);
    }
    
    // access every 4th item in cache
    for (int i = 0; i < n; i+=4) {
      assertNotNull(cache.get(new CloneableInteger(i)));
    }

    // add 3/4n elements to cache, the ones that weren't
    // touched in the previous loops should now be thrown away
    for (int i = n; i < n + (n * 3 / 4); i++) {
      cache.put(new CloneableInteger(i), dummy);
    }
    
    // access every 4th item in cache
    for (int i = 0; i < n; i+=4) {
      assertNotNull(cache.get(new CloneableInteger(i)));
    }
  }
    
  public void testLRUCache() throws Exception {
    final int n = 100;
    testCache(new DoubleBarrelLRUCache<CloneableInteger,Object>(n), n);
  }

  private class CacheThread extends Thread {
    private final CloneableObject[] objs;
    private final DoubleBarrelLRUCache<CloneableObject,Object> c;
    private final long endTime;
    volatile boolean failed;

    public CacheThread(DoubleBarrelLRUCache<CloneableObject,Object> c,
                       CloneableObject[] objs, long endTime) {
      this.c = c;
      this.objs = objs;
      this.endTime = endTime;
    }

    @Override
    public void run() {
      try {
        long count = 0;
        long miss = 0;
        long hit = 0;
        final int limit = objs.length;

        while(true) {
          final CloneableObject obj = objs[(int) ((count/2) % limit)];
          Object v = c.get(obj);
          if (v == null) {
            c.put(new CloneableObject(obj), obj);
            miss++;
          } else {
            assert obj == v;
            hit++;
          }
          if ((++count % 10000) == 0) {
            if (System.currentTimeMillis() >= endTime)  {
              break;
            }
          }
        }

        addResults(miss, hit);
      } catch (Throwable t) {
        failed = true;
        throw new RuntimeException(t);
      }
    }
  }

  long totMiss, totHit;
  void addResults(long miss, long hit) {
    totMiss += miss;
    totHit += hit;
  }

  public void testThreadCorrectness() throws Exception {
    final int NUM_THREADS = 4;
    final int CACHE_SIZE = 512;
    final int OBJ_COUNT = 3*CACHE_SIZE;

    DoubleBarrelLRUCache<CloneableObject,Object> c = new DoubleBarrelLRUCache<CloneableObject,Object>(1024);

    CloneableObject[] objs = new CloneableObject[OBJ_COUNT];
    for(int i=0;i<OBJ_COUNT;i++) {
      objs[i] = new CloneableObject(new Object());
    }
    
    final CacheThread[] threads = new CacheThread[NUM_THREADS];
    final long endTime = System.currentTimeMillis()+1000L;
    for(int i=0;i<NUM_THREADS;i++) {
      threads[i] = new CacheThread(c, objs, endTime);
      threads[i].start();
    }
    for(int i=0;i<NUM_THREADS;i++) {
      threads[i].join();
      assert !threads[i].failed;
    }
    //System.out.println("hits=" + totHit + " misses=" + totMiss);
  }
  
  private static class CloneableObject extends DoubleBarrelLRUCache.CloneableKey {
    private Object value;

    public CloneableObject(Object value) {
      this.value = value;
    }

    @Override
    public boolean equals(Object other) {
      return this.value.equals(((CloneableObject) other).value);
    }

    @Override
    public int hashCode() {
      return value.hashCode();
    }

    @Override
    public Object clone() {
      return new CloneableObject(value);
    }
  }

  protected static class CloneableInteger extends DoubleBarrelLRUCache.CloneableKey {
    private Integer value;

    public CloneableInteger(Integer value) {
      this.value = value;
    }

    @Override
    public boolean equals(Object other) {
      return this.value.equals(((CloneableInteger) other).value);
    }

    @Override
    public int hashCode() {
      return value.hashCode();
    }

    @Override
    public Object clone() {
      return new CloneableInteger(value);
    }
  }


}
