/*
 * Copyright 2000-2009 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 com.intellij.util.net;

import com.intellij.Patches;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.SystemInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;

/**
 * @author yole
 */
public class NetUtils {
  private static final Logger LOG = Logger.getInstance(NetUtils.class);

  private NetUtils() {
  }

  public static boolean canConnectToSocket(String host, int port) {
    return canConnectToSocket(host, port, false);
  }

  public static boolean canConnectToSocketOpenedByJavaProcess(String host, int port) {
    return canConnectToSocket(host, port, Patches.SUN_BUG_ID_7179799);
  }

  private static boolean canConnectToSocket(String host, int port, boolean alwaysTryToConnectDirectly) {
    if (isLocalhost(host)) {
      if (!canBindToLocalSocket(host, port)) {
        return true;
      }
      return alwaysTryToConnectDirectly && canConnectToRemoteSocket(host, port);
    }
    else {
      return canConnectToRemoteSocket(host, port);
    }
  }

  public static InetAddress getLoopbackAddress() {
    try {
      //  todo use JDK 7 InetAddress.getLoopbackAddress()
      return InetAddress.getByName(null);
    }
    catch (UnknownHostException e) {
      throw new RuntimeException(e);
    }
  }

  public static boolean isLocalhost(@NotNull String host) {
    return host.equalsIgnoreCase("localhost") || host.equals("127.0.0.1");
  }

  private static boolean canBindToLocalSocket(String host, int port) {
    try {
      ServerSocket socket = new ServerSocket();
      try {
        //it looks like this flag should be set but it leads to incorrect results for NodeJS under Windows
        //socket.setReuseAddress(true);
        socket.bind(new InetSocketAddress(host, port));
      }
      finally {
        try {
          socket.close();
        }
        catch (IOException ignored) {
        }
      }
      return true;
    }
    catch (IOException e) {
      LOG.debug(e);
      return false;
    }
  }

  public static boolean canConnectToRemoteSocket(String host, int port) {
    try {
      Socket socket = new Socket(host, port);
      socket.close();
      return true;
    }
    catch (IOException ignored) {
      return false;
    }
  }

  public static int findAvailableSocketPort() throws IOException {
    final ServerSocket serverSocket = new ServerSocket(0);
    try {
      int port = serverSocket.getLocalPort();
      // workaround for linux : calling close() immediately after opening socket
      // may result that socket is not closed
      synchronized (serverSocket) {
        try {
          serverSocket.wait(1);
        }
        catch (InterruptedException e) {
          LOG.error(e);
        }
      }
      return port;
    }
    finally {
      serverSocket.close();
    }
  }

  public static int[] findAvailableSocketPorts(int capacity) throws IOException {
    final int[] ports = new int[capacity];
    final ServerSocket[] sockets = new ServerSocket[capacity];

    for (int i = 0; i < capacity; i++) {
      final ServerSocket serverSocket = new ServerSocket(0);
      sockets[i] = serverSocket;
      ports[i] = serverSocket.getLocalPort();
    }
    //workaround for linux : calling close() immediately after opening socket
    //may result that socket is not closed
    synchronized(sockets) {
      try {
        sockets.wait(1);
      }
      catch (InterruptedException e) {
        LOG.error(e);
      }
    }

    for (ServerSocket socket : sockets) {
      socket.close();
    }
    return ports;
  }

  public static String getLocalHostString() {
    // HACK for Windows with ipv6
    String localHostString = "localhost";
    try {
      final InetAddress localHost = InetAddress.getByName(localHostString);
      if (localHost.getAddress().length != 4 && SystemInfo.isWindows){
        localHostString = "127.0.0.1";
      }
    }
    catch (UnknownHostException ignored) {
    }
    return localHostString;
  }

  /**
   * @param indicator Progress indicator.
   * @param inputStream source stream
   * @param outputStream destination stream
   * @param expectedContentSize expected content size, used in progress indicator. can be -1.
   * @return bytes copied
   * @throws IOException if IO error occur
   * @throws com.intellij.openapi.progress.ProcessCanceledException if process was canceled.
   */
  public static int copyStreamContent(@Nullable ProgressIndicator indicator,
                                      InputStream inputStream,
                                      OutputStream outputStream,
                                      int expectedContentSize) throws IOException, ProcessCanceledException {
    if (indicator != null) {
      indicator.checkCanceled();
      if (expectedContentSize < 0) indicator.setIndeterminate(true);
    }

    final byte[] buffer = new byte[4 * 1024];
    int count;
    int total = 0;
    while ((count = inputStream.read(buffer)) > 0) {
      outputStream.write(buffer, 0, count);
      total += count;

      if (indicator != null) {
        indicator.checkCanceled();

        if (expectedContentSize > 0) {
          indicator.setFraction((double)total / expectedContentSize);
        }
      }
    }

    if (indicator != null) {
      indicator.checkCanceled();
    }

    return total;
  }

}

@@ -4150,16 +4150,17 @@
 hronized
+ 
 (sockets
@@ -4594,16 +4594,17 @@
      if 
+(
 (localHo
@@ -4655,16 +4655,88 @@
 Windows)
+ %7C%7C%0A          (localHost.getAddress().length == 4 && SystemInfo.isMac)) 
 %7B%0A      
@@ -4892,16 +4892,26 @@
 dicator 
+          
 Progress
@@ -4946,16 +4946,24 @@
 tStream 
+        
 source s
@@ -4993,16 +4993,23 @@
 tStream 
+       
 destinat
@@ -5164,16 +5164,59 @@
 ception 
+                                           
 if IO er
@@ -6269,11 +6269,10 @@
 al;%0A  %7D%0A
-%0A
 %7D%0A

Fix localhost problem on Mac OSX Lion (see Serge's comment in PY-7070).