package com.zarbosoft.coroutines;

import com.zarbosoft.coroutinescore.SuspendExecution;
import com.zarbosoft.rendaw.common.Assertion;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.*;

public class ManualExecutor implements ScheduledExecutorService {
	private static class Scheduled {
		final LocalDateTime at;
		final Runnable runnable;
		final Duration repeat;

		private Scheduled(final LocalDateTime at, final Runnable runnable, final Duration repeat) {
			this.at = at;
			this.runnable = runnable;
			this.repeat = repeat;
		}
	}

	public List<Scheduled> scheduled = new ArrayList<>();
	public LocalDateTime now = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC);

	private void sortScheduled() {
		this.scheduled.sort(new Comparator<Scheduled>() {
			@Override
			public int compare(final Scheduled o1, final Scheduled o2) {
				return o1.at.compareTo(o2.at);
			}
		});
	}

	private <V> ScheduledFuture<V> push(final Scheduled event) {
		this.scheduled.add(event);
		sortScheduled();
		return new ScheduledFuture<V>() {
			@Override
			public long getDelay(final TimeUnit unit) {
				throw new AssertionError();
			}

			@Override
			public int compareTo(final Delayed o) {
				throw new AssertionError();
			}

			@Override
			public boolean cancel(final boolean mayInterruptIfRunning) {
				return scheduled.remove(event);
			}

			@Override
			public boolean isCancelled() {
				return scheduled.contains(event);
			}

			@Override
			public boolean isDone() {
				throw new AssertionError();
			}

			@Override
			public V get() throws InterruptedException, ExecutionException {
				throw new AssertionError();
			}

			@Override
			public V get(
					final long timeout, final TimeUnit unit
			) throws InterruptedException, ExecutionException, TimeoutException {
				throw new AssertionError();
			}
		};
	}

	public void advance(final Duration amount) throws SuspendExecution {
		final Coroutine coroutine = Coroutine.getActiveCoroutine();
		Coroutine.yieldThen(() -> {
			now = now.plus(amount);
			final List<Scheduled> reschedule = new ArrayList<>();
			for (final Scheduled event : scheduled) {
				if (event.at.compareTo(now) <= 0) {
					event.runnable.run();
					if (event.repeat != null)
						reschedule.add(event);
				} else {
					reschedule.add(event);
				}
			}
			scheduled = reschedule;
			sortScheduled();
			submit(() -> {
				try {
					coroutine.process(null);
				} catch (final Throwable e) {
					this.shutdown();
				}
			});
		});
	}

	private ChronoUnit toChronoUnit(final TimeUnit unit) {
		switch (unit) {
			case NANOSECONDS:
			case MICROSECONDS:
				return ChronoUnit.MICROS;
			case MILLISECONDS:
				return ChronoUnit.MILLIS;
			case SECONDS:
				return ChronoUnit.SECONDS;
			case MINUTES:
				return ChronoUnit.MINUTES;
			case HOURS:
				return ChronoUnit.HOURS;
			case DAYS:
				return ChronoUnit.DAYS;
			default:
				throw new AssertionError();
		}
	}

	@Override
	public ScheduledFuture<?> schedule(final Runnable command, final long delay, final TimeUnit unit) {
		return push(new Scheduled(now.plus(delay, toChronoUnit(unit)), command, null));
	}

	@Override
	public <V> ScheduledFuture<V> schedule(final Callable<V> callable, final long delay, final TimeUnit unit) {
		return push(new Scheduled(now.plus(delay, toChronoUnit(unit)), () -> {
			try {
				callable.call();
			} catch (final Exception e) {
				e.printStackTrace();
			}
		}, null));
	}

	@Override
	public ScheduledFuture<?> scheduleAtFixedRate(
			final Runnable command, final long initialDelay, final long period, final TimeUnit unit
	) {
		return push(new Scheduled(now.plus(initialDelay, toChronoUnit(unit)),
				command,
				Duration.of(period, toChronoUnit(unit))
		));
	}

	@Override
	public ScheduledFuture<?> scheduleWithFixedDelay(
			final Runnable command, final long initialDelay, final long delay, final TimeUnit unit
	) {
		return push(new Scheduled(now.plus(initialDelay, toChronoUnit(unit)),
				command,
				Duration.of(delay, toChronoUnit(unit))
		));
	}

	@Override
	public void shutdown() {
		throw new AssertionError("Shutdown");
	}

	@Override
	public List<Runnable> shutdownNow() {
		throw new Assertion();
	}

	@Override
	public boolean isShutdown() {
		throw new Assertion();
	}

	@Override
	public boolean isTerminated() {
		throw new Assertion();
	}

	@Override
	public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
		throw new Assertion();
	}

	@Override
	public <T> Future<T> submit(final Callable<T> task) {
		throw new Assertion();
	}

	@Override
	public <T> Future<T> submit(final Runnable task, final T result) {
		throw new Assertion();
	}

	@Override
	public Future<?> submit(final Runnable task) {
		task.run();
		return null;
	}

	@Override
	public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks) throws InterruptedException {
		throw new Assertion();
	}

	@Override
	public <T> List<Future<T>> invokeAll(
			final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit
	) throws InterruptedException {
		throw new Assertion();
	}

	@Override
	public <T> T invokeAny(final Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
		throw new Assertion();
	}

	@Override
	public <T> T invokeAny(
			final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit
	) throws InterruptedException, ExecutionException, TimeoutException {
		throw new Assertion();
	}

	@Override
	public void execute(final Runnable command) {
		command.run();
	}
}