/*
 * Copyright (c) 2011-2015 Pivotal Software Inc, All Rights Reserved.
 *
 * 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 reactor.io.net.impl.netty.http;

import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LoggingHandler;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.Environment;
import reactor.core.Dispatcher;
import reactor.io.buffer.Buffer;
import reactor.io.codec.Codec;
import reactor.io.net.ChannelStream;
import reactor.io.net.ReactorChannelHandler;
import reactor.io.net.config.ServerSocketOptions;
import reactor.io.net.config.SslOptions;
import reactor.io.net.http.HttpChannel;
import reactor.io.net.http.HttpServer;
import reactor.io.net.impl.netty.NettyChannelStream;
import reactor.io.net.impl.netty.tcp.NettyTcpServer;
import reactor.io.net.tcp.TcpServer;
import reactor.rx.Promise;
import reactor.rx.Streams;

import java.net.InetSocketAddress;

/**
 * A Netty-based {@code TcpServer} implementation
 *
 * @param <IN>  The type that will be received by this server
 * @param <OUT> The type that will be sent by this server
 * @author Jon Brisbin
 * @author Stephane Maldini
 */
public class NettyHttpServer<IN, OUT> extends HttpServer<IN, OUT> {

	private static final Logger log = LoggerFactory.getLogger(NettyHttpServer.class);

	protected final TcpServer<IN, OUT> server;

	protected NettyHttpServer(final Environment env,
	                          final Dispatcher dispatcher,
	                          final InetSocketAddress listenAddress,
	                          final ServerSocketOptions options,
	                          final SslOptions sslOptions,
	                          final Codec<Buffer, IN, OUT> codec) {

		super(env, dispatcher, codec);

		this.server = new NettyTcpServer<IN, OUT>(env,
				dispatcher,
				listenAddress,
				options,
				sslOptions,
				codec) {

			@Override
			protected void bindChannel(ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>> handler, SocketChannel
					nativeChannel) {
				NettyHttpServer.this.bindChannel(handler, nativeChannel);
			}
		};
	}

	@Override
	public InetSocketAddress getListenAddress() {
		return this.server.getListenAddress();
	}

	@Override
	protected Promise<Void> doStart(final ReactorChannelHandler<IN, OUT, HttpChannel<IN, OUT>> handler) {
		return server.start(new ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>>() {
			@Override
			public Publisher<Void> apply(ChannelStream<IN, OUT> ch) {
				NettyHttpChannel<IN, OUT> request = (NettyHttpChannel<IN, OUT>) ch;

				if (handler != null) {
					handler.apply(request);
				}

				Iterable<? extends Publisher<Void>> handlers = routeChannel(request);

				//404
				if (handlers == null) {
					if (request.checkHeader()) {
						request.delegate().writeAndFlush(
								new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND));
					}

					return Streams.empty();
				}
				return Streams.concat(handlers);
			}
		});
	}

	@Override
	protected Iterable<? extends Publisher<Void>> routeChannel(HttpChannel<IN, OUT> ch) {
		Iterable<? extends Publisher<Void>> handlers = super.routeChannel(ch);
		if (!handlers.iterator().hasNext()) {
			return null;
		} else {
			return handlers;
		}
	}

	@Override
	protected void onWebsocket(HttpChannel<IN, OUT> next) {
		ChannelPipeline pipeline = ((SocketChannel) next.delegate()).pipeline();
		pipeline.addLast(
				pipeline.remove(NettyHttpServerHandler.class)
				.withWebsocketSupport(next.uri(), null)
		);

	}

	@Override
	protected final Promise<Void> doShutdown() {
		return server.shutdown();
	}

	protected void bindChannel(ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>> handler, SocketChannel
			nativeChannel) {

		NettyChannelStream<IN, OUT> netChannel = new NettyChannelStream<IN, OUT>(
				getDefaultEnvironment(),
				getDefaultCodec(),
				getDefaultPrefetchSize(),
				getDefaultDispatcher(),
				nativeChannel
		);


		ChannelPipeline pipeline = nativeChannel.pipeline();

		if (log.isDebugEnabled()) {
			pipeline.addLast(new LoggingHandler(NettyHttpServer.class));
		}

		pipeline
				.addLast(new HttpServerCodec());

		if (hasWebsocketEndpoints()) {
			pipeline.addLast(new HttpObjectAggregator(65536));
		}

		pipeline
				.addLast(new NettyHttpServerHandler<IN, OUT>(handler, netChannel));

	}
}
