/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.master.holiday.impl;

import java.util.concurrent.ConcurrentHashMap;

import javax.time.calendar.LocalDate;

import com.opengamma.core.holiday.HolidayType;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.master.holiday.HolidayDocument;
import com.opengamma.master.holiday.HolidayMaster;
import com.opengamma.master.holiday.HolidaySearchRequest;
import com.opengamma.util.tuple.ObjectsPair;
import com.opengamma.util.tuple.Pair;

/**
 * PLAT-1015: A cache to optimize the results of {@code MasterHolidaySource}.
 */

public class ConcurrentMapCachingMasterHolidaySource extends MasterHolidaySource {
  //TODO timeout

  /**
   * Creates the cache around an underlying holiday source.
   * 
   * @param underlying  the underlying data, not null
   */
  public ConcurrentMapCachingMasterHolidaySource(final HolidayMaster underlying) {
    super(underlying);
  }

  private class Entry {
    long expiry; // CSIGNORE //TODO this
    // TODO PLAT-1308: I've set TTL short to hide the fact that we return stale data
    HolidayDocument value; // CSIGNORE
  }
  
  private ConcurrentHashMap<HolidaySearchRequest, Entry> _cache = new ConcurrentHashMap<HolidaySearchRequest, Entry>();
  
  
  private ConcurrentHashMap<Pair<HolidayType, ExternalIdBundle>, Entry> _cache2 = new ConcurrentHashMap<Pair<HolidayType, ExternalIdBundle>, Entry>();

  @Override
  public boolean isHoliday(final LocalDate dateToCheck, final HolidayType holidayType, final ExternalIdBundle regionOrExchangeIds) {
    if (isWeekend(dateToCheck)) {
      return true;
    }
    
    //PLAT-1015 : avoid cloning regionOrExchangeIds like the plague, we get nice cheap equals on hits then
    ObjectsPair<HolidayType, ExternalIdBundle> cacheKey = Pair.of(holidayType, regionOrExchangeIds);
    Entry loaded = _cache2.get(cacheKey);
    if (loaded == null) {
      HolidaySearchRequest searchRequest = getSearchRequest(holidayType, regionOrExchangeIds);
      HolidayDocument newDoc = getDocCached(searchRequest);
      Entry entry = new Entry();
      entry.value = newDoc;
      loaded = entry;
      _cache2.put(cacheKey, entry);
    }
    return isHoliday(loaded.value, dateToCheck);
  }
  
  //-------------------------------------------------------------------------
  @Override
  protected boolean isHoliday(final HolidaySearchRequest request, final LocalDate dateToCheck) {
    if (isWeekend(dateToCheck)) {
      return true;
    }
    HolidayDocument doc = getDocCached(request);
    return isHoliday(doc, dateToCheck);
  }

  private HolidayDocument getDocCached(final HolidaySearchRequest request) {
    HolidayDocument doc; 
    Entry entry = _cache.get(request);
    if (entry != null) {
      doc = entry.value;
    } else {
      doc = getMaster().search(request).getFirstDocument();
      
      entry = new Entry();
      entry.value = doc;
      _cache.putIfAbsent(request, entry);
    }
    return doc;
  }
}

@@ -223,16 +223,44 @@
 shMap;%0A%0A
+import javax.time.Duration;%0A
 import j
@@ -799,25 +799,260 @@
 e %7B%0A
+%0A
   /
-/TODO timeout%0A
+*%0A   * NOTE: this is only attempted%0A   */%0A  private final Duration _timeout = Duration.ofSeconds(10); // TODO PLAT-1308: I've set TTL short to hide the fact that we return stale data%0A  private final long _timeoutMillis = _timeout.toMillisLong();%0A  
 %0A  /
@@ -1319,16 +1319,22 @@
 y %7B%0A    
+final 
 long exp
@@ -1370,134 +1370,347 @@
     
-// TODO PLAT-1308: I've set TTL short to hide the fact that we return stale data%0A    HolidayDocument value; // CSIGNORE%0A  %7D%0A  
+final HolidayDocument value; // CSIGNORE%0A    %0A    public Entry(HolidayDocument value) %7B%0A      this(System.currentTimeMillis() + _timeoutMillis, value);%0A    %7D%0A    %0A    public Entry(long expiry, HolidayDocument value) %7B%0A      super();%0A      this.expiry = expiry;%0A      this.value = value;%0A    %7D%0A  %7D%0A  %0A  //TODO: need to expire unused entries
 %0A  p
@@ -1830,14 +1830,8 @@
 ();%0A
-  %0A  %0A
   pr
@@ -2455,24 +2455,44 @@
 aded == null
+ %7C%7C !isValid(loaded)
 ) %7B%0A      Ho
@@ -2672,37 +2672,15 @@
 try(
-);%0A      entry.value = 
 newDoc
+)
 ;%0A  
@@ -2793,24 +2793,26 @@
 ck);%0A  %7D%0A  %0A
+  
   //--------
@@ -3220,34 +3220,8 @@
 ) %7B%0A
-    HolidayDocument doc; %0A
     
@@ -3273,62 +3273,59 @@
 try 
-!
+=
 = null
-) %7B%0A      doc = entry.value;%0A    %7D else %7B%0A     
+ %7C%7C !isValid(entry)) %7B%0A      HolidayDocument
  doc
@@ -3375,22 +3375,16 @@
 ment();%0A
-      
 %0A      e
@@ -3400,16 +3400,19 @@
 w Entry(
+doc
 );%0A     
@@ -3408,24 +3408,77 @@
 doc);%0A      
+_cache.putIfAbsent(request, entry);%0A    %7D%0A    return 
 entry.value 
@@ -3480,82 +3480,283 @@
 alue
- = doc;
+;%0A  %7D
 %0A  
+%0A
   
-  _cache.putIfAbsent(request, entry);%0A    %7D%0A    return doc
+private boolean isValid(Entry entry) %7B%0A    //NOTE: when this returns false we can have several thread hitting the database at once, which is a bit stupid%0A    long expiry = entry.expiry;%0A    long allowed = System.currentTimeMillis();%0A    return expiry %3C= allowed
 ;%0A  %7D%0A%7D
-%0A

Expire old elements