/*
 * Queue - A Queueing system that can be used to handle labs in higher education
 * Copyright (C) 2016-2021  Delft University of Technology
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package nl.tudelft.queue.config;

import java.security.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

import nl.martijndwars.webpush.PushService;
import nl.martijndwars.webpush.cli.handlers.GenerateKeyHandler;
import nl.tudelft.queue.realtime.SubscriptionContainer;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

@EnableAsync
@Configuration
public class PushConfig extends AsyncConfigurerSupport {
	/**
	 * Creates a {@link SubscriptionContainer} to be used over the whole application.
	 *
	 * @return The {@link SubscriptionContainer} bean.
	 */
	@Bean
	public SubscriptionContainer subscriptionContainer() {
		return new SubscriptionContainer();
	}

	/**
	 * The push service as provided by the WebPush Java library. This object is created with a new key-pair
	 * everytime Queue restarts as this key-pair is only used to identify the current deploy of the
	 * application server and can safely be regenerated everytime without lasting effects.
	 *
	 * @return                                    The service bean.
	 * @throws InvalidAlgorithmParameterException when something goes wrong creating the key-pair.
	 * @throws NoSuchAlgorithmException           when something goes wrong creating the key-pair.
	 * @throws NoSuchProviderException            when something goes wrong creating the key-pair.
	 */
	@Bean
	public PushService pushService()
			throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
		Security.addProvider(new BouncyCastleProvider());
		KeyPair kp = new GenerateKeyHandler(null).generateKeyPair();

		return new PushService(kp);
	}

	@Bean
	@Override
	public Executor getAsyncExecutor() {
		return new ThreadPoolTaskExecutor() {
			private static final long serialVersionUID = 8285834444530205319L;

			@Override
			public <T> Future<T> submit(Callable<T> task) {
				return super.submit(
						new ContextAwareCallable<>(task, RequestContextHolder.currentRequestAttributes()));
			}

			@Override
			public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
				return super.submitListenable(
						new ContextAwareCallable<>(task, RequestContextHolder.currentRequestAttributes()));
			}
		};
	}

	public static class ContextAwareCallable<T> implements Callable<T> {
		private Callable<T> task;
		private RequestAttributes context;

		public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
			this.task = task;
			this.context = context;
		}

		@Override
		public T call() throws Exception {
			if (context != null) {
				RequestContextHolder.setRequestAttributes(context);
			}

			try {
				return task.call();
			} finally {
				RequestContextHolder.resetRequestAttributes();
			}
		}
	}
}
