/*
 * Copyright 2000-2015 JetBrains s.r.o.
 *
 * 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.jetbrains.io;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.NotNullProducer;
import com.intellij.util.net.NetUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BuiltInServer implements Disposable {
  // Some antiviral software detect viruses by the fact of accessing these ports so we should not touch them to appear innocent.
  private static final int[] FORBIDDEN_PORTS = {6953, 6969, 6970};

  private final EventLoopGroup eventLoopGroup;
  private final int port;
  private final ChannelRegistrar channelRegistrar;
  private final boolean isOwnerOfEventLoopGroup;

  private BuiltInServer(@NotNull EventLoopGroup eventLoopGroup,
                        int port,
                        @NotNull ChannelRegistrar channelRegistrar,
                        boolean isOwnerOfEventLoopGroup) {
    this.eventLoopGroup = eventLoopGroup;
    this.port = port;
    this.channelRegistrar = channelRegistrar;
    this.isOwnerOfEventLoopGroup = isOwnerOfEventLoopGroup;
  }

  @NotNull
  public EventLoopGroup getEventLoopGroup() {
    return eventLoopGroup;
  }

  public int getPort() {
    return port;
  }

  public boolean isRunning() {
    return !channelRegistrar.isEmpty();
  }

  @Override
  public void dispose() {
    channelRegistrar.close(isOwnerOfEventLoopGroup);
    Logger.getInstance(BuiltInServer.class).info("web server stopped");
  }

  @NotNull
  public static BuiltInServer start(int workerCount,
                                    int firstPort,
                                    int portsCount,
                                    boolean tryAnyPort,
                                    @Nullable NotNullProducer<ChannelHandler> handler) throws Exception {
    return start(new NioEventLoopGroup(workerCount, createExecutor()), true, firstPort, portsCount, tryAnyPort, handler);
  }

  private static ExecutorService createExecutor() {
    return Executors.newCachedThreadPool(ConcurrencyUtil.newNamedThreadFactory("Netty Builtin Server"));
  }

  @NotNull
  public static BuiltInServer start(@NotNull EventLoopGroup eventLoopGroup,
                                    boolean isEventLoopGroupOwner,
                                    int firstPort,
                                    int portsCount,
                                    boolean tryAnyPort,
                                    @Nullable NotNullProducer<ChannelHandler> handler) throws Exception {
    ChannelRegistrar channelRegistrar = new ChannelRegistrar();
    ServerBootstrap bootstrap = NettyUtil.nioServerBootstrap(eventLoopGroup);
    configureChildHandler(bootstrap, channelRegistrar, handler);
    int port = bind(firstPort, portsCount, tryAnyPort, bootstrap, channelRegistrar);
    return new BuiltInServer(eventLoopGroup, port, channelRegistrar, isEventLoopGroupOwner);
  }

  static void configureChildHandler(@NotNull ServerBootstrap bootstrap,
                                    @NotNull final ChannelRegistrar channelRegistrar,
                                    @Nullable final NotNullProducer<ChannelHandler> channelHandler) {
    final PortUnificationServerHandler portUnificationServerHandler = channelHandler == null ? new PortUnificationServerHandler() : null;
    bootstrap.childHandler(new ChannelInitializer() {
      @Override
      protected void initChannel(@NotNull Channel channel) throws Exception {
        channel.pipeline().addLast(channelRegistrar, channelHandler == null ? portUnificationServerHandler : channelHandler.produce());
      }
    });
  }

  private static int bind(int firstPort,
                          int portsCount,
                          boolean tryAnyPort,
                          @NotNull ServerBootstrap bootstrap,
                          @NotNull ChannelRegistrar channelRegistrar) throws Exception {
    InetAddress address = NetUtils.getLoopbackAddress();

    for (int i = 0; i < portsCount; i++) {
      int port = firstPort + i;

      if (ArrayUtil.indexOf(FORBIDDEN_PORTS, i) >= 0) {
        continue;
      }

      ChannelFuture future = bootstrap.bind(address, port).awaitUninterruptibly();
      if (future.isSuccess()) {
        channelRegistrar.add(future.channel());
        return port;
      }
      else if (!tryAnyPort && i == portsCount - 1) {
        ExceptionUtil.rethrowAll(future.cause());
      }
    }

    Logger.getInstance(BuiltInServer.class).info("We cannot bind to our default range, so, try to bind to any free port");
    ChannelFuture future = bootstrap.bind(address, 0).awaitUninterruptibly();
    if (future.isSuccess()) {
      channelRegistrar.add(future.channel());
      return ((InetSocketAddress)future.channel().localAddress()).getPort();
    }
    ExceptionUtil.rethrowAll(future.cause());

    return -1;  // unreachable
  }

  public static void replaceDefaultHandler(@NotNull ChannelHandlerContext context, @NotNull ChannelHandler channelHandler) {
    context.pipeline().replace(DelegatingHttpRequestHandler.class, "replacedDefaultHandler", channelHandler);
  }
}
@@ -1177,92 +1177,8 @@
 ess;
-%0Aimport java.util.concurrent.ExecutorService;%0Aimport java.util.concurrent.Executors;
 %0A%0Apu
@@ -2762,176 +2762,8 @@
 nt, 
-createExecutor()), true, firstPort, portsCount, tryAnyPort, handler);%0A  %7D%0A%0A  private static ExecutorService createExecutor() %7B%0A    return Executors.newCachedThreadPool(
 Conc
@@ -2820,16 +2820,67 @@
 erver%22))
+, true, firstPort, portsCount, tryAnyPort, handler)
 ;%0A  %7D%0A%0A 

use thread factory instead of cached thread pool executor to simplify things