/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/everything
 */

package net.ranides.assira.generic;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 * @param <T>
 */
public final class LazyReference {

    private LazyReference() {
        /* utility class */
    }
    
    /**
     * Initialization is unsynchronized. There is no guarantee about thread-safety.
     * @param <T>
     * @param supplier
     * @return 
     */
     
    public static <T> Supplier<T> unique(Supplier<T> supplier) {
        return new NonLocking<>(supplier);
    }
    
    /**
     * Initialization is done once per thread. Every thread will have different thread-local object.
     * @param <T>
     * @param supplier
     * @return 
     */
    public static <T> Supplier<T> local(Supplier<T> supplier) {
        return ThreadLocal.withInitial(supplier)::get;
    }

    /**
     * Initialization is synchronized using correctly implemented "double checked locking". 
	 * Requires JDK 1.5+ because uses new "volatile semantics".
     * @param <T>
     * @param supplier
     * @return 
     */
    public static <T> Supplier<T> sharedVolatile(Supplier<T> supplier) {
        return new SharedVolatile<>(supplier);
    }
	
	/**
     * Initialization is synchronized using correctly implemented "double checked locking".
	 * It uses wrapper object with "final" attribute instead of "volatile". I don't
	 * know anything about compatibility of that with JDK &lt; 1.5.
     * @param <T>
     * @param supplier
     * @return 
     */
	public static <T> Supplier<T> shared(Supplier<T> supplier) {
        return new Shared<>(supplier);
    }

    /**
     * Initialization is lock-free. There is a chance of spurious "double initialization".
     * @param <T>
     * @param supplier
     * @return 
     */
    @SuppressWarnings("unchecked")
    public static <T> Supplier<T> concurrent(Supplier<T> supplier) {
        AtomicReference<T> ref = new AtomicReference<>(null);
        return () -> {
			T temp = ref.get();
			if (temp == null) {
				temp = supplier.get();
				if (ref.compareAndSet(null, temp)) {
					return temp;
				} else {
					return ref.get();
				}
			} else {
				return temp;
			}
		};
    }
    
    private static class NonLocking<T> implements Supplier<T> {

        private final Supplier<T> supplier;
        
        private T ret;

        public NonLocking(Supplier<T> supplier) {
            this.supplier = supplier;
        }

        @Override
        public T get() {
            if( ret == null) {
                ret = supplier.get();
            }
            return ret;
        }

    }
    
    private static class SharedVolatile<T> implements Supplier<T> {
        
        // works in JDK5 +
        // http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
        
        private final Supplier<T> supplier;

        private volatile T ref = null;

        public SharedVolatile(Supplier<T> supplier) {
            this.supplier = supplier;
        }

        @Override
        public T get() {
            T temp = ref;
            if(null == temp) {
                synchronized(this) {
					temp = ref;
                    if(temp == null) {
                        ref = temp = supplier.get();
                    }
                }
            }
            return temp;
        }

    }
	
	private static final class SharedWrapper<T> {
		
		public final T value;

		public SharedWrapper(T value) {
			this.value = value;
		}
		
	}
	
	@SuppressWarnings({"DoubleCheckedLocking"})
	@SuppressFBWarnings({"DC_DOUBLECHECK","IS2_INCONSISTENT_SYNC"})
    private static final class Shared<T> implements Supplier<T>  {
		
		private final Supplier<T> supplier;
		
		private SharedWrapper<T> ref = null;

		public Shared(Supplier<T> supplier) {
			this.supplier = supplier;
		}

		@Override
		public T get() {
			SharedWrapper<T> temp = ref;
			if (temp == null) {
				synchronized(this) {
					temp = ref;
					if (temp == null) {
						ref = temp = new SharedWrapper<>(supplier.get());
					}
				}
			}
			return temp.value;
		}
		
	}
	
}
