package ru.yandex.webmaster3.core.util.concurrent.graph;

import ru.yandex.webmaster3.core.util.concurrent.ExhaustableQueue;

import java.util.List;
import java.util.function.ToIntFunction;

/**
 * @author avhaliullin
 */
public abstract class GraphOutQueue<T> {
    public abstract void put(T elem) throws InterruptedException;

    public static <T> GraphOutQueue<T> devnull() {
        return (GraphOutQueue<T>) DEV_NULL;
    }

    public static <T> GraphOutQueue<T> fromExhaustableQueue(ExhaustableQueue<T> queue) {
        return new ExhaustableOutQueue<T>(queue);
    }

    public static final class ExhaustableOutQueue<T> extends GraphOutQueue<T> {
        private final ExhaustableQueue<T> delegate;

        public ExhaustableOutQueue(ExhaustableQueue<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void put(T elem) throws InterruptedException {
            delegate.put(elem);
        }
    }

    public static final class ShardedOutQueue<T> extends GraphOutQueue<T> {
        private final List<ExhaustableQueue<T>> shards;
        private final ToIntFunction<T> hashF;

        public ShardedOutQueue(List<ExhaustableQueue<T>> shards, ToIntFunction<T> hashF) {
            this.shards = shards;
            this.hashF = hashF;
        }

        @Override
        public void put(T elem) throws InterruptedException {
            int shard = hashF.applyAsInt(elem);
            if (shard >= shards.size() || shard < 0) {
                throw new RuntimeException("Hash function returned shard " + shard + " for elem " + elem + " while there is only " + shards.size() + "shards");
            }
            shards.get(shard).put(elem);
        }
    }

    public static final class MultiplexingOutQueue<T> extends GraphOutQueue<T> {
        private final List<GraphOutQueue<T>> delegates;

        public MultiplexingOutQueue(List<GraphOutQueue<T>> delegates) {
            this.delegates = delegates;
        }

        @Override
        public void put(T elem) throws InterruptedException {
            for (GraphOutQueue<T> delegate : delegates) {
                delegate.put(elem);
            }
        }
    }

    public static final GraphOutQueue<Object> DEV_NULL = new GraphOutQueue<Object>() {
        @Override
        public void put(Object elem) throws InterruptedException {
        }
    };
}
