package nxm.sys.net;

import java.awt.Container;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;

import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.Command;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Midas;
import nxm.sys.lib.Table;
import nxm.sys.libg.GWidget;
import nxm.sys.libg.MBox;
import nxm.sys.libg.MPanel;
import nxm.sys.libg.Theme;
import nxm.sys.prim.panel;


/**
  A class to implement a Remote Display Protocol using an HTTP server.

  @author Jeff Schoen
  @since NeXtMidas 4.1.0 from ICE Option tree

*/
@ProvisionalUseOnly("contributed but not tested in test matrix")
public class HDisplay extends HSource {

  private Midas M;
  private panel panel;
  private GraphicsEnvironment ge;
  private GraphicsDevice gd;
  private Robot robot;
  private boolean headless;

  /** Constructor
   *
   *  @param name  name of HDisplay
   *  @param M     Midas instance
   */
  public HDisplay (String name, Midas M) {
    this.M = M;
    this.name = name+"/";
    headless = Theme.isHeadless();
    if (headless) {
    } else {
      ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
      gd = ge.getDefaultScreenDevice();
      try { robot = new Robot(); }
      catch (Exception e) { M.printStackTrace("Unable to instantiate Robot class for event generation ",e); }
    }
  }

  public void handleRequest (String uri, HPage hp) {
    String option = uri.substring(name.length()+1);
    if (panel==null && M!=null) panel = (panel)M.registry.get("PANEL");
    if (option.length()==0) option = (panel!=null)? "js/Pane0" : "js/Screen";
    int ia = option.indexOf('?');
    String args=null;
    if (ia>0) { args=option.substring(ia+1); option=option.substring(0,ia); }

    if (option.startsWith("sc/")) {
      scPage (hp,option.substring(3),args);
    }
    else if (option.startsWith("js/")) {
      jsPage (hp,option.substring(3),args);
    }
    else if (option.startsWith("ws/")) {
      wsPage (hp,option.substring(3),args);
    }
    else {
      homePage (hp,option);
    }
  }

  /** Screen Capture page
  *
  *  @param hp    HPage that parses the HTTP request
  *  @param name  name of page
  */
  public void homePage (HPage hp, String name) {
    hp.openToBody("HDisplay "+name);
    hp.writeln ("<br>");
    hp.writeln ("<center><h3> Display Capture </h3></center>");
    hp.writeln ("<br>");
    GraphicsDevice[] devs = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
    for (int i=0; i<devs.length; i++) {
      GraphicsDevice dev = devs[i];
      Rectangle rect = dev.getDefaultConfiguration().getBounds();
      int w=(int)rect.getWidth(), h=(int)rect.getHeight();
      hp.writeln ("<center><a href='sc/Screen'>Full Session Screen ("+w+"x"+h+")</a></center>");
    }
    panel.Pane[] panes = panel.getPanesArray();
    for (int i=0; i<panes.length; i++) {
      String key = (i==0)? "APP" : panes[i].name;
      String value = (i==0)? M.macro.args.name : panes[i].title;
      hp.writeln ("<center><a href='sc/Pane"+i+"'>"+key+" = "+value+"</a></center>");
    }
    hp.writeln ("<br>");
    hp.writeln ("<center><h3> Display Server </h3></center>");
    hp.writeln ("<br>");
    hp.writeln ("<center><a href='js/Screen'>Full Session Screen</a></center>");
    for (int i=0; i<panes.length; i++) {
      String key = (i==0)? "APP" : panes[i].name;
      String value = (i==0)? M.macro.args.name : panes[i].title;
      hp.writeln ("<center><a href='js/Pane"+i+"'>"+key+" = "+value+"</a></center>");
    }
    hp.closeFromBody();
  }

  /** Screen Capture page
   *
   *  @param hp    HPage that parses the HTTP request
   *  @param name  name of page
   *  @param args  not used
   */
  public void scPage (HPage hp, String name, String args) {
    Rectangle rect=null;
    if (headless) {
    } else {
      rect = gd.getDefaultConfiguration().getBounds();
    }
    if (name.startsWith("Pane")) {
      int ip = Convert.s2l(name.substring(4));
      panel.Pane[] panes = panel.getPanesArray();
      panel.Pane pane = panes[ip];
      MBox pos = panel.MW.pos;
      if (ip==0) rect.setBounds(pos.x,pos.y,pos.w,pos.h);
      else rect.setBounds(pos.x+pane.pos.x,pos.y+pane.pos.y,pane.pos.w,pane.pos.h);
    }
    java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
    try {
      BufferedImage image = robot.createScreenCapture(rect);
      ImageIO.write(image, "png", baos);
    } catch (Exception e) { }
    hp.setContentType("image/png");
    hp.setContentLength((long)baos.size());
    hp.open();
    if (!hp.getMethod().equals("HEAD")) hp.write(baos);
    hp.close();
  }

  /** JavaScript RFB Client page
  *
  *  @param hp    HPage that parses the HTTP request
  *  @param name  name of page
  *  @param args  not used
  */
  public void jsPage (HPage hp, String name, String args) {
    MBox pos = new MBox(0,0,100,100);
    if (name.startsWith("Pane")) {
      int ip = Convert.s2l(name.substring(4));
      panel.Pane[] panes = panel.getPanesArray();
      panel.Pane pane = panes[ip];
      pos = new MBox(panel.MW.pos);
      if (ip>0) pos.setBounds(pos.x+pane.pos.x,pos.y+pane.pos.y,pane.pos.w,pane.pos.h);
    }
    else {
      Rectangle rect = gd.getDefaultConfiguration().getBounds();
      pos.w=(int)rect.getWidth(); pos.h=(int)rect.getHeight();
    }
    String host = server.getHost();
    int port = server.getPort();
    hp.openUpToBody("HDisplay "+name);
    hp.writeln ("<script type='text/javascript' src='/nmroot/nxm/sys/net/hdisplay.js'></script>");
    hp.writeln ("<body onload=\"hdisplay('"+host+"',"+port+","+pos.x+","+pos.y+","+pos.w+","+pos.h+",'"+name+"');\">");
    hp.writeln ("<canvas id=canvas width='"+pos.w+"' height='"+pos.h+"'></canvas>");
    hp.closeFromBody();
  }

  /** WebSockets RFB Client page - called from hdisplay.js
  *
  *  @param hp    HPage that parses the HTTP request
  *  @param name  name of page
  *  @param args  not used
  */
  public void wsPage (HPage hp, String name, String args) {
    Rectangle rect=null;
    if (headless) {
      rect = new Rectangle (0,0,1280,1024);
    } else {
      rect = gd.getDefaultConfiguration().getBounds();
    }
    if (name.startsWith("Pane")) {
      int ip = Convert.s2l(name.substring(4));
      panel.Pane[] panes = panel.getPanesArray();
      panel.Pane pane = panes[ip];
      MBox pos = panel.MW.pos;
      if (ip==0) rect.setBounds(pos.x,pos.y,pos.w,pos.h);
      else rect.setBounds(pos.x+pane.pos.x,pos.y+pane.pos.y,pane.pos.w,pane.pos.h);
    }
    hp.openWS("vnc-client");
    if (rgbs!=null) for (int i=0; i<rgbs.length; i++) rgbs[i]=0;
    byte[] b = new byte[1040];
    for (;panel.state==Command.PROCESS;) {
      int len = hp.recvWS(b,0,b.length);
      if (len<0) break; // Done
      if (len==3 && b[0]=='R' && b[1]=='U') { // Requested Update
        updateRFB(hp,rect,b);
        for(int i=0; i<8; i++) b[i]=0;
        hp.sendWS(b,0,8); // EndOfUpdate
        continue;
      }
      b[2]='{'; b[len]='}';
      Table t = new Table(b,2,len-1);
      int x = t.getL("X",0);
      int y = t.getL("Y",0);
      if (x>0||y>0) robot.mouseMove(x+(int)rect.getX(),y+(int)rect.getY());
      int bu = t.getL("B",0);
      int ab = Math.abs(bu);
      int bm = (ab==3)? InputEvent.BUTTON3_DOWN_MASK : (ab==2)? InputEvent.BUTTON2_DOWN_MASK : InputEvent.BUTTON1_DOWN_MASK ;
      if (bu>0) robot.mousePress(bm);
      if (bu<0) robot.mouseRelease(bm);
      int kp = t.getL("K",0);
      int km = keymap(Math.abs(kp));
      if (kp>0) robot.keyPress(km);
      if (kp<0) robot.keyRelease(km);
    }
    hp.closeWS();
  }

  int[] rgb = null;
  int[] rgbs = null;

  private void updateRFB (HPage hp, Rectangle r, byte[] b) {
    byte bs; int x,y,w=(int)r.getWidth(),h=(int)r.getHeight();
    int nt=h*w, nr=0, ns=0;
    if (rgb==null || nt>rgb.length) { rgb = new int[nt]; rgbs = new int[nt]; }
//double t1 = Time.current();
    BufferedImage image = getImage1(r);
    image.getRGB(0,0,w,h,rgb,0,w);
    // loop through 16x16 tiles
    for (y=0; y<h; y+=16) { if (y+16>h) y=h-16;
    for (x=0; x<w; x+=16) { if (x+16>w) x=w-16;
      int[] rgba = Convert.castL(b,false);
      boolean same=true;
      int i=y*w+x, j=2, p;
      for (int iy=0; iy<16; iy++,i+=(w-16)) {
      for (int ix=0; ix<16; ix++,i++,j++) {
        p=rgb[i];
        rgba[j]=p;
        if (p!=rgbs[i]) { same=false; rgbs[i]=p; }
      }}
      rgba[0] = (y<<16)|x;	// (x,y)
      rgba[1] = 0x00100010;	// (w,h)
      Convert.uncast(rgba,b,false);
      if (!same) {
        for (int k=8; k<8+1024; k+=4) { bs=b[k]; b[k]=b[k+2]; b[k+2]=bs; }
        hp.sendWS(b,0,8+256*4); ns++;
      }
      nr++;
    }}
//double t2 = Time.current()-t1;
//System.out.println("NR="+nr+" NS="+ns+" Elapse="+t2);
  }

  private BufferedImage getImage1 (Rectangle rect) {
    BufferedImage image = robot.createScreenCapture(rect);
    return image;
  }

  private BufferedImage getImage2 (Rectangle rrect) {
    Container p = panel.MW.panel;
    Rectangle rect = p.getBounds();
    int x=p.getX(), y=p.getY(), w=p.getWidth(), h=p.getHeight();
    try {
      BufferedImage image = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
      Graphics gc = image.getGraphics();
      p.paint(gc);
      for (int i=0; i<p.getComponentCount(); i++) {
        Object o = p.getComponent(i);
        if (o instanceof MPanel) {
          MPanel mp = (MPanel)o;
          Rectangle r=mp.getBounds();
          if (mp.MW instanceof GWidget && rect.contains(r.getX(),r.getY())) {
            gc.translate((int)r.getX()-x,(int)r.getY()-y);
            mp.paint(gc);
          }
        }
      }
      return image;
    }
    catch (Exception e) { System.out.println("Unable to export image: "+e); }
      return null;
  }

  private int keymap (int key) {
    switch (key) {
    case 13: key=10; break;
    case 46: key=127; break;
    case 188: key=0x2c; break;
    case 190: key=0x2e; break;
    default:
    }
    return key;
  }

}
