/*
 * Copyright 2015 Julien Viet
 *
 * 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 io.termd.core.http;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.termd.core.io.BinaryDecoder;
import io.termd.core.io.BinaryEncoder;
import io.termd.core.io.TelnetCharset;
import io.termd.core.tty.TtyEvent;
import io.termd.core.tty.TtyEventDecoder;
import io.termd.core.tty.TtyConnection;
import io.termd.core.tty.TtyOutputMode;
import io.termd.core.util.Vector;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * A connection to an http client, independant of the protocol, it could be straight Websockets or
 * SockJS, etc...
 *
 * The incoming protocol is based on json messages:
 *
 * {
 *   "action": "read",
 *   "data": "what the user typed"
 * }
 *
 * or
 *
 * {
 *   "action": "resize",
 *   "cols": 30,
 *   "rows: 50
 * }
 *
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 * @author <a href="mailto:matejonnet@gmail.com">Matej Lazar</a>
 */
public abstract class HttpTtyConnection implements TtyConnection {

  private Vector size = new Vector(80, 24); // For now hardcoded
  private Consumer<Vector> sizeHandler;
  private final TtyEventDecoder eventDecoder;
  private final BinaryDecoder decoder;
  private final Consumer<int[]> stdout;
  private Consumer<Void> closeHandler;
  private Consumer<String> termHandler;

  public HttpTtyConnection() {
    eventDecoder = new TtyEventDecoder(3, 26, 4);
    decoder = new BinaryDecoder(512, TelnetCharset.INSTANCE, eventDecoder);
    stdout = new TtyOutputMode(new BinaryEncoder(StandardCharsets.US_ASCII, this::write));
  }

  @Override
  public String terminalType() {
    return "vt100";
  }

  protected abstract void write(byte[] buffer);

  public void writeToDecoder(String msg) {
    ObjectMapper mapper = new ObjectMapper();
    Map<String, Object> obj;
    String action;
    try {
      obj = mapper.readValue(msg, Map.class);
      action = (String) obj.get("action");
    } catch (IOException e) {
      // Handle this better!!!
      throw new RuntimeException("Cannot deserialize object from json", e);
    }
    if (action != null) {
      switch (action) {
        case "read":
          String data = (String) obj.get("data");
          decoder.write(data.getBytes()); //write back echo
          break;
        case "resize":
          int cols = (int) obj.get("cols");
          int rows = (int) obj.get("rows");
          size = new Vector(cols, rows);
          if (sizeHandler != null) {
            sizeHandler.accept(size);
          }
          break;
      }
    }
  }

  public Consumer<String> getTerminalTypeHandler() {
    return termHandler;
  }

  public void setTerminalTypeHandler(Consumer<String> handler) {
    termHandler = handler;
  }

  @Override
  public Vector size() {
    return size;
  }

  public Consumer<Vector> getSizeHandler() {
    return sizeHandler;
  }

  public void setSizeHandler(Consumer<Vector> handler) {
    this.sizeHandler = handler;
  }

  @Override
  public BiConsumer<TtyEvent, Integer> getEventHandler() {
    return eventDecoder.getEventHandler();
  }

  @Override
  public void setEventHandler(BiConsumer<TtyEvent, Integer> handler) {
    eventDecoder.setEventHandler(handler);
  }

  public Consumer<int[]> getStdinHandler() {
    return eventDecoder.getReadHandler();
  }

  public void setStdinHandler(Consumer<int[]> handler) {
    eventDecoder.setReadHandler(handler);
  }

  public Consumer<int[]> stdoutHandler() {
    return stdout;
  }

  @Override
  public void setCloseHandler(Consumer<Void> closeHandler) {
    this.closeHandler = closeHandler;
  }

  @Override
  public Consumer<Void> getCloseHandler() {
    return closeHandler;
  }
}

@@ -1692,62 +1692,86 @@
 ate 
-Vector size = new Vector(80, 24); // For now hardcoded
+static final Vector DEFAULT_SIZE = new Vector(80, 24);%0A%0A  private Vector size;
 %0A  p
@@ -2039,32 +2039,130 @@
 nection() %7B%0A    
+this(DEFAULT_SIZE);%0A  %7D%0A%0A  public HttpTtyConnection(Vector size) %7B%0A    this.size = size;%0A    this.
 eventDecoder = n
@@ -2191,24 +2191,29 @@
 26, 4);%0A    
+this.
 decoder = ne
@@ -2276,16 +2276,21 @@
 r);%0A    
+this.
 stdout =
@@ -2771,104 +2771,29 @@
  // 
-Handle this better!!!%0A      throw new RuntimeException(%22Cannot deserialize object from json%22, e)
+Log this%0A      return
 ;%0A  
@@ -3017,16 +3017,34 @@
 esize%22:%0A
+          try %7B%0A  
         
@@ -3073,18 +3073,39 @@
 .get
-(%22cols%22
+OrDefault(%22cols%22, size.x()
 );%0A
+  
     
@@ -3138,59 +3138,215 @@
 .get
-(%22rows%22);%0A          size = new Vector(cols, rows);%0A
+OrDefault(%22rows%22, size.y());%0A            if (cols %3E 0 && rows %3E 0) %7B%0A              Vector newSize = new Vector(cols, rows);%0A              if (!newSize.equals(size())) %7B%0A                size = newSize;%0A      
     
@@ -3386,24 +3386,30 @@
             
+     
+ 
 sizeHandler.
@@ -3422,16 +3422,150 @@
 (size);%0A
+                %7D%0A              %7D%0A            %7D%0A          %7D catch (Exception e) %7B%0A            // Invalid size%0A            // Log this%0A
         

Allow to specify an initial size in the HttpTtyConnection and improve resize decoding reliability