package org.xbib.logging.ref;

import java.lang.ref.ReferenceQueue;

/**
 * A set of utility methods for reference types.
 */
public final class References {

    private References() {
    }

    private static final Reference<?, ?> NULL = new StrongReference<>(null);

    static final class BuildTimeHolder {
        static final ReferenceQueue<Object> REAPER_QUEUE = new ReferenceQueue<>();
    }

    static final class ReaperThread extends Thread {
        static ReferenceQueue<Object> getReaperQueue() {
            return BuildTimeHolder.REAPER_QUEUE;
        }

        static {
            startThreadAction(1);
        }


        private static Void startThreadAction(int id) {
            final ReaperThread thr = new ReaperThread();
            thr.setName("Reference Reaper #" + id);
            thr.setDaemon(true);
            thr.start();
            return null;
        }

        @Override
        public void run() {
            for (; ; )
                try {
                    final java.lang.ref.Reference<?> ref = ReaperThread.getReaperQueue().remove();
                    if (ref instanceof CleanerReference) {
                        ((CleanerReference<?, ?>) ref).clean();
                    }
                    if (ref instanceof Reapable) {
                        reap((Reapable<?, ?>) ref);
                    }
                } catch (InterruptedException ignored) {
                    // we consume interrupts.
                } catch (Throwable cause) {
                    // ignore failures.
                }
        }

        @SuppressWarnings({"unchecked"})
        private static <T, A> void reap(final Reapable<T, A> reapable) {
            reapable.getReaper().reap((Reference<T, A>) reapable);
        }
    }

    /**
     * Create a reference of a given type with the provided value and attachment. If the reference type is
     * {@link Reference.Type#STRONG} or {@link Reference.Type#NULL} then the reaper argument is ignored. If
     * the reference type is {@link Reference.Type#NULL} then the value and attachment arguments are ignored.
     *
     * @param type       the reference type
     * @param value      the reference value
     * @param attachment the attachment value
     * @param reaper     the reaper to use, if any
     * @param <T>        the reference value type
     * @param <A>        the reference attachment type
     * @return the reference
     */
    public static <T, A> Reference<T, A> create(Reference.Type type, T value, A attachment, Reaper<T, A> reaper) {
        if (value == null) {
            type = Reference.Type.NULL;
        }
        return switch (type) {
            case STRONG -> new StrongReference<T, A>(value, attachment);
            case WEAK -> new WeakReference<T, A>(value, attachment, reaper);
            case PHANTOM -> new PhantomReference<T, A>(value, attachment, reaper);
            case SOFT -> new SoftReference<T, A>(value, attachment, reaper);
            case NULL -> attachment == null ? getNullReference() : new StrongReference<>(null, attachment);
        };
    }

    /**
     * Create a reference of a given type with the provided value and attachment. If the reference type is
     * {@link Reference.Type#STRONG} or {@link Reference.Type#NULL} then the reference queue argument is ignored. If
     * the reference type is {@link Reference.Type#NULL} then the value and attachment arguments are ignored.
     *
     * @param type           the reference type
     * @param value          the reference value
     * @param attachment     the attachment value
     * @param referenceQueue the reference queue to use, if any
     * @param <T>            the reference value type
     * @param <A>            the reference attachment type
     * @return the reference
     */
    public static <T, A> Reference<T, A> create(Reference.Type type, T value, A attachment,
                                                ReferenceQueue<? super T> referenceQueue) {
        if (referenceQueue == null)
            return create(type, value, attachment);
        if (value == null) {
            type = Reference.Type.NULL;
        }
        return switch (type) {
            case STRONG -> new StrongReference<T, A>(value, attachment);
            case WEAK -> new WeakReference<T, A>(value, attachment, referenceQueue);
            case PHANTOM -> new PhantomReference<T, A>(value, attachment, referenceQueue);
            case SOFT -> new SoftReference<T, A>(value, attachment, referenceQueue);
            case NULL -> attachment == null ? getNullReference() : new StrongReference<>(null, attachment);
        };
    }

    /**
     * Create a reference of a given type with the provided value and attachment. If the reference type is
     * {@link Reference.Type#PHANTOM} then this method will return a {@code null} reference because
     * such references are not constructable without a queue or reaper. If the reference type is
     * {@link Reference.Type#NULL} then the value and attachment arguments are ignored.
     *
     * @param type       the reference type
     * @param value      the reference value
     * @param attachment the attachment value
     * @param <T>        the reference value type
     * @param <A>        the reference attachment type
     * @return the reference
     */
    public static <T, A> Reference<T, A> create(Reference.Type type, T value, A attachment) {
        if (value == null) {
            type = Reference.Type.NULL;
        }
        return switch (type) {
            case STRONG -> new StrongReference<T, A>(value, attachment);
            case WEAK -> new WeakReference<T, A>(value, attachment);
            case SOFT -> new SoftReference<T, A>(value, attachment);
            case PHANTOM, NULL -> attachment == null ? getNullReference() : new StrongReference<>(null, attachment);
        };
    }

    /**
     * Get a {@code null} reference. This reference type is always cleared and does not retain an attachment; as such
     * there is only one single instance of it.
     *
     * @param <T> the reference value type
     * @param <A> the attachment value type
     * @return the {@code null} reference
     */
    @SuppressWarnings({"unchecked"})
    public static <T, A> Reference<T, A> getNullReference() {
        return (Reference<T, A>) NULL;
    }
}
