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

package net.ranides.assira.generic;

import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

/**
 *
 * @author Mariusz Sieron <m.sieron@samsung.com>
 * @param <T>
 */
public abstract class LazyRef<T> {

    final Callable<T> generator;

    LazyRef(Callable<T> generator) {
        this.generator = generator;
    }

    public abstract T get();

    public static <T> LazyRef<T> nonlocking(Callable<T> generator) {
        return new NonLocking<>(generator);
    }

    public static <T> LazyRef<T> locking(Callable<T> generator) {
        return new Locking<>(generator);
    }

    public static <T> LazyRef<T> lockfree(Callable<T> generator) {
        return new LockFree<>(generator);
    }

    private static class NonLocking<T> extends LazyRef<T> {

        private T ret;

        public NonLocking(Callable<T> generator) {
            super(generator);
        }

        @Override
        public T get() {
            try {
                if( ret == null) {
                    ret = generator.call();
                }
                return ret;
            } catch(Exception cause) {
                throw new RuntimeException(cause);
            }
        }

    }

    private static class Locking<T> extends LazyRef<T> {

        private volatile T ref = null;

        public Locking(Callable<T> generator) {
            super(generator);
        }

        @Override
        public T get() {
            try {
                T temp = ref;
                if(null == temp) {
                    synchronized(this) {
                        if(null == ref) {
                            ref = temp = generator.call();
                        }
                    }
                }
                return temp;
            } catch(Exception cause) {
                throw new RuntimeException(cause);
            }
        }

    }

    private static class LockFree<T> extends LazyRef<T> {

        private static enum State { UNDEFINED_1, UNDEFINED_2 }

        private final AtomicReference<Object> ref = new AtomicReference<Object>(State.UNDEFINED_1);

        public LockFree(Callable<T> generator) {
            super(generator);
        }

        @Override
        public T get() {
            try {
                if( ref.compareAndSet(State.UNDEFINED_1, State.UNDEFINED_2) ) {
                    ref.set( generator.call() );
                    return (T)ref.get();
                }
                if( ref.compareAndSet(State.UNDEFINED_2, State.UNDEFINED_1) ) {
                    ref.set( generator.call() );
                    return (T)ref.get();
                }
                return (T)ref.get();
            } catch(Exception cause) {
                throw new RuntimeException(cause);
            }
        }

    }

}
