package org.ardverk.gibson.dashboard;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.ardverk.gibson.Event;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Singleton
public class DefaultTrendService implements TrendService {

  private static final long TREND_FREQUENCY = 15;
  private static final TimeUnit TREND_TIMEUNIT = TimeUnit.SECONDS;

  private final Map<String, Callable<Long>> counterMap = new HashMap<String, Callable<Long>>();
  private final Map<String, Trend> trendMap = new HashMap<String, Trend>();

  private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

  @Inject
  private EventDAO eventDAO;

  @Inject
  public void init() {
    executorService.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        updateTrends();
      }
    }, TREND_FREQUENCY, TREND_FREQUENCY, TREND_TIMEUNIT);
  }

  @Override
  public Trend getTrendForEvent(final Event event) {
    return getTrendInfo(event.getSignature(), new Callable<Long>() {
      @Override
      public Long call() throws Exception {
        return eventDAO.getEventCount(event.getSignature());
      }
    });
  }

  @Override
  public Trend getTrendForType(final String type) {
    return getTrendInfo(type, new Callable<Long>() {
      @Override
      public Long call() throws Exception {
        return eventDAO.getTypeNameCount(type);
      }
    });
  }

  @Override
  public synchronized void clear() {
    trendMap.clear();
    counterMap.clear();
  }

  private Trend getTrendInfo(String key, Callable<Long> countGetter) {
    Trend trend;
    synchronized (this) {
      trend = trendMap.get(key);
    }
    if (trend == null) {
      try {
        long count = countGetter.call();
        trend = new Trend(count, count, count, System.currentTimeMillis());
        synchronized (this) {
          trendMap.put(key, trend);
          counterMap.put(key, countGetter);
        }
      } catch (Exception e) {
        // TODO better way to handle this?
        throw new RuntimeException(e);
      }
    }

    return trend;
  }

  private void updateTrends() {
    List<Pair<String, Trend>> trends = new ArrayList<Pair<String, Trend>>();
    synchronized (this) {
      for (Map.Entry<String, Trend> e : trendMap.entrySet()) {
        trends.add(new ImmutablePair<String, Trend>(e.getKey(), e.getValue()));
      }
    }
    for (Pair<String, Trend> p : trends) {
      String key = p.getKey();
      Trend trend = p.getValue();
      try {
        Callable<Long> counter;
        synchronized (this) {
          counter = counterMap.get(key);
        }
        if (counter != null) {
          long count = counter.call();
          Trend newTrend = Trend.create(count, trend);
          synchronized (this) {
            trendMap.put(key, newTrend);
          }
        }
      } catch (Exception ex) {
        throw new RuntimeException(ex);
      }
    }
  }
}

@@ -243,9 +243,11 @@
 til.
-*
+Map
 ;%0Aim
@@ -282,16 +282,63 @@
 llable;%0A
+import java.util.concurrent.ConcurrentHashMap;%0A
 import j
@@ -670,32 +670,44 @@
 nal Map%3CString, 
+Pair%3CTrend, 
 Callable%3CLong%3E%3E 
@@ -709,94 +709,8 @@
 ng%3E%3E
- counterMap = new HashMap%3CString, Callable%3CLong%3E%3E();%0A  private final Map%3CString, Trend
 %3E tr
@@ -722,16 +722,26 @@
 p = new 
+Concurrent
 HashMap%3C
@@ -748,21 +748,43 @@
 String, 
-Trend
+Pair%3CTrend, Callable%3CLong%3E%3E
 %3E();%0A%0A  
@@ -1688,29 +1688,16 @@
  public 
-synchronized 
 void cle
@@ -1729,32 +1729,8 @@
 ();%0A
-    counterMap.clear();%0A
   %7D%0A
@@ -1809,58 +1809,40 @@
     
-Trend trend;%0A    synchronized (this) %7B%0A      trend
+Pair%3CTrend, Callable%3CLong%3E%3E pair
  = t
@@ -1867,23 +1867,16 @@
     
-%7D%0A    if (trend
+if (pair
  == 
@@ -1940,24 +1940,30 @@
 ();%0A        
+Trend 
 trend = new 
@@ -2030,63 +2030,74 @@
     
-synchronized (this) %7B%0A          trendMap.put(key, trend
+pair = new ImmutablePair%3CTrend, Callable%3CLong%3E%3E(trend, countGetter
 );%0A 
@@ -2103,25 +2103,21 @@
         
-  counter
+trend
 Map.put(
@@ -2125,31 +2125,14 @@
 ey, 
-countGetter);%0A        %7D
+pair);
 %0A   
@@ -2266,21 +2266,30 @@
  return 
-trend
+pair.getLeft()
 ;%0A  %7D%0A%0A 
@@ -2327,303 +2327,83 @@
     
-List%3CPair%3CString, Trend%3E%3E trends = new ArrayList%3CPair%3CString, Trend%3E%3E();%0A    synchronized (this) %7B%0A      for (Map.Entry%3CString, Trend%3E e : trendMap.entrySet()) %7B%0A        trends.add(new ImmutablePair%3CString, Trend%3E(e.getKey(), e.getValue()));%0A      %7D%0A    %7D%0A    for (Pair%3CString, Trend%3E p : trends
+for (Map.Entry%3CString, Pair%3CTrend, Callable%3CLong%3E%3E%3E e : trendMap.entrySet()
 ) %7B%0A
@@ -2421,17 +2421,17 @@
 g key = 
-p
+e
 .getKey(
@@ -2453,17 +2453,17 @@
 trend = 
-p
+e
 .getValu
@@ -2465,16 +2465,26 @@
 tValue()
+.getLeft()
 ;%0A      
@@ -2523,90 +2523,35 @@
 nter
-;%0A        synchronized (this) %7B%0A          counter = counterMap.get(key);%0A        %7D
+ = e.getValue().getRight();
 %0A   
@@ -2686,82 +2686,87 @@
     
-synchronized (this) %7B%0A            trendMap.put(key, newTrend);%0A          %7D
+trendMap.put(key, new ImmutablePair%3CTrend, Callable%3CLong%3E%3E(newTrend, counter));
 %0A   

Use ConcurrentHashMap