/*
 * 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.IOException;
import java.net.Socket;
import java.util.Hashtable;

class ChannelX11 extends Channel {

  private static final int LOCAL_WINDOW_SIZE_MAX = 0x20000;
  private static final int LOCAL_MAXIMUM_PACKET_SIZE = 0x4000;

  private static final int TIMEOUT = 10 * 1000;

  private static String host = "127.0.0.1";
  private static int port = 6000;

  private boolean init = true;

  static byte[] cookie = null;
  private static byte[] cookie_hex = null;

  private static Hashtable<Session, byte[]> faked_cookie_pool = new Hashtable<>();
  private static Hashtable<Session, byte[]> faked_cookie_hex_pool = new Hashtable<>();

  private static byte[] table = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61,
      0x62, 0x63, 0x64, 0x65, 0x66};

  private Socket socket = null;

  static int revtable(byte foo) {
    for (int i = 0; i < table.length; i++) {
      if (table[i] == foo)
        return i;
    }
    return 0;
  }

  static void setCookie(String foo) {
    cookie_hex = Util.str2byte(foo);
    cookie = new byte[16];
    for (int i = 0; i < 16; i++) {
      cookie[i] = (byte) (((revtable(cookie_hex[i * 2]) << 4) & 0xf0)
          | ((revtable(cookie_hex[i * 2 + 1])) & 0xf));
    }
  }

  static void setHost(String foo) {
    host = foo;
  }

  static void setPort(int foo) {
    port = foo;
  }

  static byte[] getFakedCookie(Session session) {
    synchronized (faked_cookie_hex_pool) {
      byte[] foo = faked_cookie_hex_pool.get(session);
      if (foo == null) {
        Random random = Session.random;
        foo = new byte[16];
        synchronized (random) {
          random.fill(foo, 0, 16);
        }
        /*
        System.err.print("faked_cookie: ");
        for (int i = 0; i < foo.length; i++) {
          System.err.print(Integer.toHexString(foo[i] & 0xff) + ":");
        }
        System.err.println("");
        */
        faked_cookie_pool.put(session, foo);
        byte[] bar = new byte[32];
        for (int i = 0; i < 16; i++) {
          bar[2 * i] = table[(foo[i] >>> 4) & 0xf];
          bar[2 * i + 1] = table[(foo[i]) & 0xf];
        }
        faked_cookie_hex_pool.put(session, bar);
        foo = bar;
      }
      return foo;
    }
  }

  static void removeFakedCookie(Session session) {
    synchronized (faked_cookie_hex_pool) {
      faked_cookie_hex_pool.remove(session);
      faked_cookie_pool.remove(session);
    }
  }

  ChannelX11() {
    super();

    lwsize_max = LOCAL_WINDOW_SIZE_MAX;
    lwsize = LOCAL_WINDOW_SIZE_MAX;
    lmpsize = LOCAL_MAXIMUM_PACKET_SIZE;

    type = Util.str2byte("x11");

    connected = true;
    /*
    try {
      socket = Util.createSocket(host, port, TIMEOUT);
      socket.setTcpNoDelay(true);
      io = new IO();
      io.setInputStream(socket.getInputStream());
      io.setOutputStream(socket.getOutputStream());
    } catch (Exception e) {
      // System.err.println(e);
    }
    */
  }

  @Override
  void run() {

    try {
      socket = Util.createSocket(host, port, TIMEOUT);
      socket.setTcpNoDelay(true);
      io = new IO();
      io.setInputStream(socket.getInputStream());
      io.setOutputStream(socket.getOutputStream());
      sendOpenConfirmation();
    } catch (Exception e) {
      sendOpenFailure(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED);
      close = true;
      disconnect();
      return;
    }

    thread = Thread.currentThread();
    Buffer buf = new Buffer(rmpsize);
    Packet packet = new Packet(buf);
    int i = 0;
    try {
      Session _session = getSession();
      while (thread != null && io != null && io.in != null) {
        i = io.in.read(buf.buffer, 14, buf.buffer.length - 14 - _session.getBufferMargin());
        if (i <= 0) {
          eof();
          break;
        }
        if (close)
          break;
        packet.reset();
        buf.putByte((byte) Session.SSH_MSG_CHANNEL_DATA);
        buf.putInt(recipient);
        buf.putInt(i);
        buf.skip(i);
        _session.write(packet, this, i);
      }
    } catch (Exception e) {
      // System.err.println(e);
    }
    disconnect();
  }

  private byte[] cache = new byte[0];

  private byte[] addCache(byte[] foo, int s, int l) {
    byte[] bar = new byte[cache.length + l];
    System.arraycopy(foo, s, bar, cache.length, l);
    if (cache.length > 0)
      System.arraycopy(cache, 0, bar, 0, cache.length);
    cache = bar;
    return cache;
  }

  @Override
  void write(byte[] foo, int s, int l) throws IOException {
    // if (eof_local) return;

    if (init) {

      Session _session = null;
      try {
        _session = getSession();
      } catch (JSchException e) {
        throw new IOException(e.toString(), e);
      }

      foo = addCache(foo, s, l);
      s = 0;
      l = foo.length;

      if (l < 9)
        return;

      int plen = (foo[s + 6] & 0xff) * 256 + (foo[s + 7] & 0xff);
      int dlen = (foo[s + 8] & 0xff) * 256 + (foo[s + 9] & 0xff);

      if ((foo[s] & 0xff) == 0x42) {
      } else if ((foo[s] & 0xff) == 0x6c) {
        plen = ((plen >>> 8) & 0xff) | ((plen << 8) & 0xff00);
        dlen = ((dlen >>> 8) & 0xff) | ((dlen << 8) & 0xff00);
      } else {
        // ??
      }

      if (l < 12 + plen + ((-plen) & 3) + dlen)
        return;

      byte[] bar = new byte[dlen];
      System.arraycopy(foo, s + 12 + plen + ((-plen) & 3), bar, 0, dlen);
      byte[] faked_cookie = null;

      synchronized (faked_cookie_pool) {
        faked_cookie = faked_cookie_pool.get(_session);
      }

      /*
      System.err.print("faked_cookie: ");
      for (int i = 0; i < faked_cookie.length; i++) {
        System.err.print(Integer.toHexString(faked_cookie[i] & 0xff) + ":");
      }
      System.err.println("");
      System.err.print("bar: ");
      for (int i = 0; i < bar.length; i++) {
        System.err.print(Integer.toHexString(bar[i] & 0xff) + ":");
      }
      System.err.println("");
      */

      if (equals(bar, faked_cookie)) {
        if (cookie != null)
          System.arraycopy(cookie, 0, foo, s + 12 + plen + ((-plen) & 3), dlen);
      } else {
        // System.err.println("wrong cookie");
        thread = null;
        eof();
        io.close();
        disconnect();
      }
      init = false;
      io.put(foo, s, l);
      cache = null;
      return;
    }
    io.put(foo, s, l);
  }

  private static boolean equals(byte[] foo, byte[] bar) {
    if (foo.length != bar.length)
      return false;
    for (int i = 0; i < foo.length; i++) {
      if (foo[i] != bar[i])
        return false;
    }
    return true;
  }
}
