/*
 * Licensed 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 org.jctools.queues;

import static org.jctools.util.UnsafeAccess.UNSAFE;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Use an SPSC per producer.
 */
abstract class MpscOnSpscL0Pad<E> extends AbstractQueue<E> {
	long p00, p01, p02, p03, p04, p05, p06, p07;
	long p30, p31, p32, p33, p34, p35, p36, p37;
}

abstract class MpscOnSpscFields<E> extends MpscOnSpscL0Pad<E> {
	private final static long QUEUES_OFFSET;

	static {
		try {
			QUEUES_OFFSET = UNSAFE.objectFieldOffset(MpscOnSpscFields.class.getDeclaredField("queues"));
		} catch (NoSuchFieldException e) {
			throw new RuntimeException(e);
		}
	}

	protected final ThreadLocal<Queue<E>> producerQueue;
	ReferenceQueue<Thread> refQ = new ReferenceQueue<Thread>();
	protected volatile Queue<E>[] queues;
	private static class ThreadWeakRef extends WeakReference<Thread> {
		final Object r;
		public ThreadWeakRef(ReferenceQueue<? super Thread> q, Object r) {
			super(Thread.currentThread(), q);
			this.r = r;
		}
	}
	private List<ThreadWeakRef> weakRefHolder = Collections.synchronizedList(new ArrayList<ThreadWeakRef>());

	@SuppressWarnings("unchecked")
	public MpscOnSpscFields(final int capacity) {
		producerQueue = new ThreadLocal<Queue<E>>() {
			@Override
			protected Queue<E> initialValue() {
				Queue<E> q = new SpscArrayQueue<E>(capacity);
				addQueue(q);
				return q;
			}
		};
		Thread refProcessor = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
						ThreadWeakRef remove = (ThreadWeakRef)refQ.remove();
						removeQueue(remove.r);
						weakRefHolder.remove(remove);
					} catch (InterruptedException e) {
						if (Thread.interrupted()) {
							return;
						}
					}
				}
			}
		};
		refProcessor.setDaemon(false);
		refProcessor.start();
		queues = new Queue[0];
	}

	@SuppressWarnings("rawtypes")
	protected final void addQueue(Queue<E> q) {
		Queue[] oldQs;
		Queue[] newQs;
		do {
			oldQs = queues;
			newQs = new Queue[oldQs.length + 1];
			System.arraycopy(oldQs, 0, newQs, 0, oldQs.length);
			newQs[oldQs.length] = q;
		} while (!UNSAFE.compareAndSwapObject(this, QUEUES_OFFSET, oldQs, newQs));
		weakRefHolder.add(new ThreadWeakRef(refQ, q));
	}

	@SuppressWarnings("rawtypes")
	protected final void removeQueue(Object q) {
		Queue[] oldQs;
		Queue[] newQs;
		do {
			oldQs = queues;
			int i = 0;
			final int length = oldQs.length;
			for (; i < length; i++) {
				if (q == oldQs[i]) {
					break;
				}
			}
			// not here...
			if (i == length) {
				return;
			}
			// copy over all but that element
			newQs = new Queue[length - 1];
			System.arraycopy(oldQs, 0, newQs, 0, i);
			System.arraycopy(oldQs, i + 1, newQs, i, length - i - 1);
		} while (!UNSAFE.compareAndSwapObject(this, QUEUES_OFFSET, oldQs, newQs));
	}

	int numberOfQueues(){
		return queues.length;
	}
}

public final class MpscOnSpscQueue<E> extends MpscOnSpscFields<E>implements Queue<E> {
	long p40, p41, p42, p43, p44, p45, p46;
	long p30, p31, p32, p33, p34, p35, p36, p37;

	public MpscOnSpscQueue(final int capacity) {
		super(capacity);
	}

	@Override
	public boolean offer(final E e) {
		return producerQueue.get().offer(e);
	}

	@Override
	public E poll() {
		Queue<E>[] qs = this.queues;
		final int start = ThreadLocalRandom.current().nextInt(qs.length);
		for (int i = start; i < qs.length; i++) {
			E e = qs[i].poll();
			if (e != null)
				return e;
		}
		for (int i = 0; i < start; i++) {
			E e = qs[i].poll();
			if (e != null)
				return e;
		}
		return null;
	}

	@Override
	public E peek() {
		throw new UnsupportedOperationException();
	}

	@Override
	public int size() {
		Queue<E>[] queues = this.queues;
		int sum = 0;
		for (Queue<E> q : queues) {
			sum += q.size();
		}
		return sum;
	}

	@Override
	public boolean isEmpty() {
		Queue<E>[] queues = this.queues;
		for (Queue<E> q : queues) {
			if (!q.isEmpty()) {
				return false;
			}
		}
		return true;
	}

	@Override
	public Iterator<E> iterator() {
		throw new UnsupportedOperationException();
	}
}
