/*
 * Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The names of the authors may not be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jcraft.jsch;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Vector;

class Util {

  private static final byte[] b64 =
      Util.str2byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");

  private static byte val(byte foo) {
    if (foo == '=')
      return 0;
    for (int j = 0; j < b64.length; j++) {
      if (foo == b64[j])
        return (byte) j;
    }
    return 0;
  }

  static byte[] fromBase64(byte[] buf, int start, int length) throws JSchException {
    try {
      byte[] foo = new byte[length];
      int j = 0;
      for (int i = start; i < start + length; i += 4) {
        foo[j] = (byte) ((val(buf[i]) << 2) | ((val(buf[i + 1]) & 0x30) >>> 4));
        if (buf[i + 2] == (byte) '=') {
          j++;
          break;
        }
        foo[j + 1] = (byte) (((val(buf[i + 1]) & 0x0f) << 4) | ((val(buf[i + 2]) & 0x3c) >>> 2));
        if (buf[i + 3] == (byte) '=') {
          j += 2;
          break;
        }
        foo[j + 2] = (byte) (((val(buf[i + 2]) & 0x03) << 6) | (val(buf[i + 3]) & 0x3f));
        j += 3;
      }
      byte[] bar = new byte[j];
      System.arraycopy(foo, 0, bar, 0, j);
      return bar;
    } catch (ArrayIndexOutOfBoundsException e) {
      throw new JSchException("fromBase64: invalid base64 data", e);
    }
  }

  static byte[] toBase64(byte[] buf, int start, int length, boolean include_pad) {

    byte[] tmp = new byte[length * 2];
    int i, j, k;

    int foo = (length / 3) * 3 + start;
    i = 0;
    for (j = start; j < foo; j += 3) {
      k = (buf[j] >>> 2) & 0x3f;
      tmp[i++] = b64[k];
      k = (buf[j] & 0x03) << 4 | (buf[j + 1] >>> 4) & 0x0f;
      tmp[i++] = b64[k];
      k = (buf[j + 1] & 0x0f) << 2 | (buf[j + 2] >>> 6) & 0x03;
      tmp[i++] = b64[k];
      k = buf[j + 2] & 0x3f;
      tmp[i++] = b64[k];
    }

    foo = (start + length) - foo;
    if (foo == 1) {
      k = (buf[j] >>> 2) & 0x3f;
      tmp[i++] = b64[k];
      k = ((buf[j] & 0x03) << 4) & 0x3f;
      tmp[i++] = b64[k];
      if (include_pad) {
        tmp[i++] = (byte) '=';
        tmp[i++] = (byte) '=';
      }
    } else if (foo == 2) {
      k = (buf[j] >>> 2) & 0x3f;
      tmp[i++] = b64[k];
      k = (buf[j] & 0x03) << 4 | (buf[j + 1] >>> 4) & 0x0f;
      tmp[i++] = b64[k];
      k = ((buf[j + 1] & 0x0f) << 2) & 0x3f;
      tmp[i++] = b64[k];
      if (include_pad) {
        tmp[i++] = (byte) '=';
      }
    }
    byte[] bar = new byte[i];
    System.arraycopy(tmp, 0, bar, 0, i);
    return bar;

    // return sun.misc.BASE64Encoder().encode(buf);
  }

  static String[] split(String foo, String split) {
    if (foo == null)
      return null;
    byte[] buf = Util.str2byte(foo);
    Vector<String> bar = new Vector<>();
    int start = 0;
    int index;
    while (true) {
      index = foo.indexOf(split, start);
      if (index >= 0) {
        bar.addElement(Util.byte2str(buf, start, index - start));
        start = index + 1;
        continue;
      }
      bar.addElement(Util.byte2str(buf, start, buf.length - start));
      break;
    }
    String[] result = new String[bar.size()];
    for (int i = 0; i < result.length; i++) {
      result[i] = bar.elementAt(i);
    }
    return result;
  }

  static boolean glob(byte[] pattern, byte[] name) {
    return glob0(pattern, 0, name, 0);
  }

  private static boolean glob0(byte[] pattern, int pattern_index, byte[] name, int name_index) {
    if (name.length > 0 && name[0] == '.') {
      if (pattern.length > 0 && pattern[0] == '.') {
        if (pattern.length == 2 && pattern[1] == '*')
          return true;
        return glob(pattern, pattern_index + 1, name, name_index + 1);
      }
      return false;
    }
    return glob(pattern, pattern_index, name, name_index);
  }

  private static boolean glob(byte[] pattern, int pattern_index, byte[] name, int name_index) {
    // System.err.println("glob: " + new String(pattern) + ", " + pattern_index + " " + new String(name) + "," + name_index);

    int patternlen = pattern.length;
    if (patternlen == 0)
      return false;

    int namelen = name.length;
    int i = pattern_index;
    int j = name_index;

    while (i < patternlen && j < namelen) {
      if (pattern[i] == '\\') {
        if (i + 1 == patternlen)
          return false;
        i++;
        if (pattern[i] != name[j])
          return false;
        i += skipUTF8Char(pattern[i]);
        j += skipUTF8Char(name[j]);
        continue;
      }

      if (pattern[i] == '*') {
        while (i < patternlen) {
          if (pattern[i] == '*') {
            i++;
            continue;
          }
          break;
        }
        if (patternlen == i)
          return true;

        byte foo = pattern[i];
        if (foo == '?') {
          while (j < namelen) {
            if (glob(pattern, i, name, j)) {
              return true;
            }
            j += skipUTF8Char(name[j]);
          }
          return false;
        } else if (foo == '\\') {
          if (i + 1 == patternlen)
            return false;
          i++;
          foo = pattern[i];
          while (j < namelen) {
            if (foo == name[j]) {
              if (glob(pattern, i + skipUTF8Char(foo), name, j + skipUTF8Char(name[j]))) {
                return true;
              }
            }
            j += skipUTF8Char(name[j]);
          }
          return false;
        }

        while (j < namelen) {
          if (foo == name[j]) {
            if (glob(pattern, i, name, j)) {
              return true;
            }
          }
          j += skipUTF8Char(name[j]);
        }
        return false;
      }

      if (pattern[i] == '?') {
        i++;
        j += skipUTF8Char(name[j]);
        continue;
      }

      if (pattern[i] != name[j])
        return false;

      i += skipUTF8Char(pattern[i]);
      j += skipUTF8Char(name[j]);

      if (!(j < namelen)) { // name is end
        if (!(i < patternlen)) { // pattern is end
          return true;
        }
        if (pattern[i] == '*') {
          break;
        }
      }
      continue;
    }

    if (i == patternlen && j == namelen)
      return true;

    if (!(j < namelen) && // name is end
        pattern[i] == '*') {
      boolean ok = true;
      while (i < patternlen) {
        if (pattern[i++] != '*') {
          ok = false;
          break;
        }
      }
      return ok;
    }

    return false;
  }

  static String quote(String path) {
    byte[] _path = str2byte(path);
    int count = 0;
    for (int i = 0; i < _path.length; i++) {
      byte b = _path[i];
      if (b == '\\' || b == '?' || b == '*')
        count++;
    }
    if (count == 0)
      return path;
    byte[] _path2 = new byte[_path.length + count];
    for (int i = 0, j = 0; i < _path.length; i++) {
      byte b = _path[i];
      if (b == '\\' || b == '?' || b == '*') {
        _path2[j++] = '\\';
      }
      _path2[j++] = b;
    }
    return byte2str(_path2);
  }

  static String unquote(String path) {
    byte[] foo = str2byte(path);
    byte[] bar = unquote(foo);
    if (foo.length == bar.length)
      return path;
    return byte2str(bar);
  }

  static byte[] unquote(byte[] path) {
    int pathlen = path.length;
    int i = 0;
    while (i < pathlen) {
      if (path[i] == '\\') {
        if (i + 1 == pathlen)
          break;
        System.arraycopy(path, i + 1, path, i, path.length - (i + 1));
        pathlen--;
        i++;
        continue;
      }
      i++;
    }
    if (pathlen == path.length)
      return path;
    byte[] foo = new byte[pathlen];
    System.arraycopy(path, 0, foo, 0, pathlen);
    return foo;
  }

  private static String[] chars =
      {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

  static String getFingerPrint(HASH hash, byte[] data, boolean include_prefix, boolean force_hex) {
    try {
      hash.init();
      hash.update(data, 0, data.length);
      byte[] foo = hash.digest();
      StringBuilder sb = new StringBuilder();
      if (include_prefix) {
        sb.append(hash.name());
        sb.append(":");
      }
      if (force_hex || hash.name().equals("MD5")) {
        int bar;
        for (int i = 0; i < foo.length; i++) {
          bar = foo[i] & 0xff;
          sb.append(chars[(bar >>> 4) & 0xf]);
          sb.append(chars[(bar) & 0xf]);
          if (i + 1 < foo.length)
            sb.append(":");
        }
      } else {
        byte[] b64str = toBase64(foo, 0, foo.length, false);
        sb.append(byte2str(b64str, 0, b64str.length));
      }
      return sb.toString();
    } catch (Exception e) {
      return "???";
    }
  }

  static boolean array_equals(byte[] foo, byte bar[]) {
    int i = foo.length;
    if (i != bar.length)
      return false;
    for (int j = 0; j < i; j++) {
      if (foo[j] != bar[j])
        return false;
    }
    // try {
    //   while (true) {
    //     i--;
    //     if (foo[i] != bar[i])
    //       return false;
    //   }
    // } catch (Exception e) {
    // }
    return true;
  }

  static Socket createSocket(String host, int port, int timeout) throws JSchException {
    Socket socket = new Socket();
    try {
      socket.connect(new InetSocketAddress(host, port), timeout);
      return socket;
    } catch (Exception e) {
      try {
        socket.close();
      } catch (Exception ignore) {
      }

      String message =
          e instanceof SocketTimeoutException ? "timeout: socket is not established" : e.toString();
      throw new JSchException(message, e);
    }
  }

  static byte[] str2byte(String str, Charset encoding) {
    if (str == null)
      return null;
    return str.getBytes(encoding);
  }

  static byte[] str2byte(String str) {
    return str2byte(str, StandardCharsets.UTF_8);
  }

  static String byte2str(byte[] str, Charset encoding) {
    return byte2str(str, 0, str.length, encoding);
  }

  static String byte2str(byte[] str, int s, int l, Charset encoding) {
    return new String(str, s, l, encoding);
  }

  static String byte2str(byte[] str) {
    return byte2str(str, 0, str.length, StandardCharsets.UTF_8);
  }

  static String byte2str(byte[] str, int s, int l) {
    return byte2str(str, s, l, StandardCharsets.UTF_8);
  }

  static String toHex(byte[] str) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < str.length; i++) {
      String foo = Integer.toHexString(str[i] & 0xff);
      sb.append("0x" + (foo.length() == 1 ? "0" : "") + foo);
      if (i + 1 < str.length)
        sb.append(":");
    }
    return sb.toString();
  }

  static final byte[] empty = str2byte("");

  /*
  static byte[] char2byte(char[] foo) {
    int len = 0;
    for (int i = 0; i < foo.length; i++) {
      if ((foo[i] & 0xff00) == 0) len++;
      else len += 2;
    }
    byte[] bar = new byte[len];
    for (int i = 0, j = 0; i < foo.length; i++) {
      if ((foo[i] & 0xff00) == 0) {
        bar[j++] = (byte)foo[i];
      } else {
        bar[j++] = (byte)(foo[i] >>> 8);
        bar[j++] = (byte)foo[i];
      }
    }
    return bar;
  }
  */
  static void bzero(byte[] foo) {
    if (foo == null)
      return;
    for (int i = 0; i < foo.length; i++)
      foo[i] = 0;
  }

  static String diffString(String str, String[] not_available) {
    String[] stra = Util.split(str, ",");
    String result = null;
    loop: for (int i = 0; i < stra.length; i++) {
      for (int j = 0; j < not_available.length; j++) {
        if (stra[i].equals(not_available[j])) {
          continue loop;
        }
      }
      if (result == null) {
        result = stra[i];
      } else {
        result = result + "," + stra[i];
      }
    }
    return result;
  }

  static String checkTilde(String str) {
    try {
      if (str.startsWith("~")) {
        str = str.replace("~", System.getProperty("user.home"));
      }
    } catch (SecurityException e) {
    }
    return str;
  }

  private static int skipUTF8Char(byte b) {
    if ((byte) (b & 0x80) == 0)
      return 1;
    if ((byte) (b & 0xe0) == (byte) 0xc0)
      return 2;
    if ((byte) (b & 0xf0) == (byte) 0xe0)
      return 3;
    return 1;
  }

  static byte[] fromFile(String _file) throws IOException {
    _file = checkTilde(_file);
    File file = new File(_file);
    try (InputStream fis = new FileInputStream(_file)) {
      byte[] result = new byte[(int) (file.length())];
      int len = 0;
      while (true) {
        int i = fis.read(result, len, result.length - len);
        if (i <= 0)
          break;
        len += i;
      }
      return result;
    }
  }

  static boolean arraysequals(byte[] a, byte[] b) {
    if (a.length != b.length)
      return false;
    int res = 0;
    for (int i = 0; i < a.length; i++) {
      res |= a[i] ^ b[i];
    }
    return res == 0;
  }

  static String getSystemEnv(String name) {
    try {
      return System.getenv(name);
    } catch (SecurityException e) {
      return null;
    }
  }

  static String getSystemProperty(String key) {
    try {
      return System.getProperty(key);
    } catch (SecurityException e) {
      return null;
    }
  }

  static String getSystemProperty(String key, String def) {
    try {
      return System.getProperty(key, def);
    } catch (SecurityException e) {
      return def;
    }
  }
}
