/**
 * 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 com.ibm.mqlight.api.impl.timer;

import java.util.HashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.ibm.mqlight.api.ClientException;
import com.ibm.mqlight.api.Promise;
import com.ibm.mqlight.api.logging.Logger;
import com.ibm.mqlight.api.logging.LoggerFactory;
import com.ibm.mqlight.api.timer.TimerService;

// TODO: this could be re-written based on a ConcurrentHashMap to reduce the amount of lock
//       contention on the single 'promiseToTimer' HashMap used in this implementation.
public class TimerServiceImpl implements TimerService {

    private static final Logger logger = LoggerFactory.getLogger(TimerServiceImpl.class);
  
    private static final ScheduledThreadPoolExecutor executor;
    private static final ClientException failureException = new ClientException("Timer cancelled");

    static {
        executor = new ScheduledThreadPoolExecutor(1);
        executor.setKeepAliveTime(500, TimeUnit.MILLISECONDS);
        executor.allowCoreThreadTimeOut(true);
        executor.setRemoveOnCancelPolicy(true);
    }
    
    private static final HashMap<Promise<Void>, Timer> promiseToTimer = new HashMap<>();
    
    private static class Timer implements Runnable {
      
        private static final Logger logger = LoggerFactory.getLogger(Timer.class);
      
        private final Promise<Void> promise;
        private final HashMap<Promise<Void>, Timer> promiseToTimer;
        private ScheduledFuture<?> future;
        private Timer(Promise<Void> promise, HashMap<Promise<Void>, Timer> promiseToTimer) {
            final String methodName = "<init>";
            logger.entry(this, methodName, promise, promiseToTimer);
          
            this.promise = promise;
            this.promiseToTimer = promiseToTimer;
            
            logger.exit(this, methodName);
        }
        public void run() {
            final String methodName = "run";
            logger.entry(this, methodName);
          
            synchronized(promiseToTimer) {
                promiseToTimer.remove(promise);
            }
            promise.setSuccess(null);
            
            logger.exit(this, methodName);
        }
    }
    
    @Override
    public void schedule(long delay, Promise<Void> promise) {
        final String methodName = "schedule";
        logger.entry(this, methodName, delay, promise);
      
        Timer timer = new Timer(promise, promiseToTimer);
        synchronized(promiseToTimer) {
            ScheduledFuture<?> sf = executor.schedule(timer, delay, TimeUnit.MILLISECONDS);
            timer.future = sf;
            promiseToTimer.put(promise, timer);
        }
        
        logger.exit(this, methodName);
    }

    @Override
    public void cancel(Promise<Void> promise) {
        final String methodName = "cancel";
        logger.entry(this, methodName, promise);
        
        synchronized(promiseToTimer) {
            Timer timer = promiseToTimer.get(promise);
            if (timer != null) {
                if (timer.future.cancel(false)) {
                    promiseToTimer.remove(promise);
                    promise.setFailure(failureException);
                }
            }
        }
        
        logger.exit(this, methodName);
    }

}

@@ -860,16 +860,37 @@
 va.util.
+concurrent.Concurrent
 HashMap;
@@ -1256,188 +1256,8 @@
 e;%0A%0A
-// TODO: this could be re-written based on a ConcurrentHashMap to reduce the amount of lock%0A//       contention on the single 'promiseToTimer' HashMap used in this implementation.%0A
 publ
@@ -1799,24 +1799,142 @@
 %0A    %7D%0A    %0A
+    // Using a default ConcurrentHashMap, with concurrency level 16, which should be sufficient for most applications%0A
     private 
@@ -1942,24 +1942,34 @@
 tatic final 
+Concurrent
 HashMap%3CProm
@@ -2007,16 +2007,26 @@
 r = new 
+Concurrent
 HashMap%3C
@@ -2252,16 +2252,26 @@
 e final 
+Concurrent
 HashMap%3C
@@ -2396,16 +2396,26 @@
 romise, 
+Concurrent
 HashMap%3C
@@ -2866,55 +2866,8 @@
    %0A
-            synchronized(promiseToTimer) %7B%0A    
     
@@ -2906,30 +2906,16 @@
 omise);%0A
-            %7D%0A
         
@@ -3206,32 +3206,38 @@
 %0A      %0A        
+final 
 Timer timer = ne
@@ -3282,50 +3282,13 @@
     
-synchronized(promiseToTimer) %7B%0A           
+final
  Sch
@@ -3372,20 +3372,16 @@
         
-    
 timer.fu
@@ -3391,20 +3391,16 @@
 e = sf;%0A
-    
         
@@ -3435,26 +3435,16 @@
 timer);%0A
-        %7D%0A
         
@@ -3645,24 +3645,16 @@
 omise);%0A
-        
 %0A       
@@ -3658,50 +3658,13 @@
     
-synchronized(promiseToTimer) %7B%0A           
+final
  Tim
@@ -3703,36 +3703,32 @@
 omise);%0A        
-    
 if (timer != nul
@@ -3740,28 +3740,24 @@
             
- 
-   
 if (timer.fu
@@ -3786,36 +3786,32 @@
                 
-    
 promiseToTimer.r
@@ -3834,36 +3834,32 @@
                 
-   
- 
 promise.setFailu
@@ -3884,26 +3884,8 @@
 n);%0A
-                %7D%0A
     

Use ConcurrentHashMap instead of HashMap