/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/06/02, 14:25
!  AUTHOR(S): KOGA, Junichiro
!  File : TermPanel.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.ssh.terminal;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.io.OutputStream;

import javax.swing.JInternalFrame;
import javax.swing.JPanel;

import ciss.phase_viewer.ssh.SessionCreator;
import ciss.phase_viewer.ssh.hosts.HostInfo;
import ciss.phase_viewer.ssh.hosts.MyUserInfo;

import com.jcraft.jcterm.Emulator;
import com.jcraft.jcterm.EmulatorVT100;
import com.jcraft.jcterm.Splash;
import com.jcraft.jcterm.Term;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

/**
 * @author KOGA, Junichiro
 */

public class TermPanel extends JPanel implements KeyListener, Runnable, Term {

    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
            .getLogger(TermPanel.class.getName());

    private OutputStream out;

    private InputStream in;

    private BufferedImage img;

    private BufferedImage background;

    private Graphics2D cursor_graphics;

    private Graphics2D graphics;

    private Color bground = Color.white;

    private Color fground = Color.black;

    private Component term_area = null;

    private Font font;

    private Splash splash = null;

    private int term_width = 80;

    private int term_height = 24;

    private int orig_term_width = term_width;

    private int orig_term_height = term_height;

    private int x = 0;

    private int y = 0;

    private int descent = 0;

    private int wsav;

    private int hsav;

    private int char_width;

    private int char_height;

    private boolean xforwarding = true;

    private String xhost = "127.0.0.1";

    private int xport = 0;

    private Session session = null;

    private boolean antialiasing = true;

    private int line_space = -2;

    private HostInfo hostinfo;

    private MyUserInfo userinfo;

    private String user;

    private String hostname;

    private int origw;

    private int origh;

    private JInternalFrame parent;

    private String initCommand;

    private boolean initDone = false;

    public TermPanel() {
        this("", null);
    }

    private Terminal terminal;

    /** Creates a new instance of TermPanel */
    public TermPanel(String initCommand, Terminal terminal) {
        this.parent = parent;
        this.initCommand = initCommand;
        this.terminal = terminal;
        font = java.awt.Font.decode("Monospaced-14");
        img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        graphics = (Graphics2D) (img.getGraphics());
        graphics.setFont(font);
        {
            FontMetrics fo = graphics.getFontMetrics();
            descent = fo.getDescent();
            char_width = (int) (fo.charWidth((char) '@'));
            char_height = (int) (fo.getHeight()) + (line_space * 2);
            descent += line_space;
        }
        img.flush();
        graphics.dispose();

        background = new BufferedImage(char_width, char_height,
                BufferedImage.TYPE_INT_RGB);
        {
            Graphics2D foog = (Graphics2D) (background.getGraphics());
            foog.setColor(bground);
            foog.fillRect(0, 0, char_width, char_height);
            foog.dispose();
        }

        img = new BufferedImage(getTermWidth(), getTermHeight(),
                BufferedImage.TYPE_INT_RGB);
        graphics = (Graphics2D) (img.getGraphics());
        graphics.setFont(font);

        if (splash != null) {
            splash.draw(img, getTermWidth(), getTermHeight());
        } else {
            clear();
        }

        cursor_graphics = (Graphics2D) (img.getGraphics());
        cursor_graphics.setColor(fground);
        cursor_graphics.setXORMode(bground);

        setAntiAliasing(antialiasing);

        term_area = this;
        JPanel panel = this;

        panel.setPreferredSize(new Dimension(getTermWidth(), getTermHeight()));
        wsav = getTermWidth();
        hsav = getTermHeight();

        panel.setSize(getTermWidth(), getTermHeight());
        panel.setFocusable(true);
        panel.enableInputMethods(true);

        panel.setFocusTraversalKeysEnabled(false);

        origw = getSize().width;
        origh = getSize().height;
    }

    public void setFrame(java.awt.Component term_area) {
        this.term_area = term_area;
    }

    private Thread thread = null;

    public void connect(HostInfo hostinfo) {
        this.hostinfo = hostinfo;
        antialiasing = hostinfo.getBooleanProperty("antialiasing");
        setAntiAliasing(antialiasing);
        this.thread = new Thread(this);
        this.thread.start();
    }

    public void disconnect() {
        if (session != null) {
            // if ( session.isConnected() ) {
            // session.disconnect();
            // }
            if (thread != null) {
                thread = null;
            }
            try {
                session.disconnect();
            } catch (RuntimeException e) {
                // e.printStackTrace();
            }
            session = null;
        }
    }

    protected void parentResized(double scalew, double scaleh) {
        // term_height = (int) ((double) orig_term_height * scaleh);
        // term_width = (int) ((double) orig_term_width * scalew);
        this.setSize((int) scalew * origw, (int) scaleh * origh);
        // redraw(0, 0, getTermWidth(), getTermHeight());
    }

    private Emulator emulator = null;

    private int retries = 0;

    private int max = 4;

    public void run() {
        JSch jsch = null;
        while (thread != null) {
            try {
                if (session == null || !session.isConnected()) {
                    try {
                        retries++;
                        if (retries == max) {
                            logger.error("exceeded max retries");
                            break;
                        }
                        session = SessionCreator.getSession(hostinfo);
                        session.setTimeout(0);
                        logger.info("connected");
                    } catch (Exception e) {
                        logger.error("failed connection...");
                        e.printStackTrace();
                    }

                    if (session != null) {
                        logger.debug("connected? " + session.isConnected());
                    }
                    Channel channel = null;
                    if (session != null) {
                        channel = session.openChannel("shell");
                        xforwarding = hostinfo
                                .getBooleanProperty("xforwarding");
                        if (xforwarding) {
                            session.setX11Host(xhost);
                            session.setX11Port(xport + 6000);
                            channel.setXForwarding(true);
                        }

                        out = channel.getOutputStream();
                        in = channel.getInputStream();
                        channel.connect();

                        if (!initDone && initCommand != null
                                && initCommand.length() != 0) {
                            byte[] init = new String(initCommand + "\n")
                                    .getBytes();
                            out.write(init);
                            out.flush();
                        }
                        initDone = true;
                        requestFocus();

                        emulator = new EmulatorVT100(this, in);
                        emulator.reset();
                        emulator.start();
                    }

                }
            } catch (Exception exc) {
                logger.info("not connected.");
                exc.printStackTrace();
            }
        }

        thread = null;

        if (session != null) {
            session.disconnect();
            session = null;
        }

        if (splash != null) {
            splash.draw(img, getTermWidth(), getTermHeight());
        } else {
            clear();
        }

        redraw(0, 0, getTermWidth(), getTermHeight());
    }

    public void paint(Graphics g) {
        super.paint(g);
        if (img != null)
            g.drawImage(img, 0, 0, term_area);
    }

    // public void paintComponent(Graphics g) {
    // super.paintComponent(g);
    // if (img != null) {
    // g.drawImage(img, 0, 0, term_area);
    // }
    // }

    // public void paint(Graphics g){
    // super.paint(g);
    // }

    public void processKeyEvent(KeyEvent e) {
        int id = e.getID();
        if (id == KeyEvent.KEY_PRESSED) {
            keyPressed(e);
        } else if (id == KeyEvent.KEY_RELEASED) { /* keyReleased(e); */
        } else if (id == KeyEvent.KEY_TYPED) {
            keyTyped(e);/* keyTyped(e); */
        }
        e.consume();
    }

    private byte[] obuffer = new byte[3];

    public void keyPressed(KeyEvent e) {
        int keycode = e.getKeyCode();
        byte[] code = null;
        switch (keycode) {
        case KeyEvent.VK_CONTROL:
        case KeyEvent.VK_SHIFT:
        case KeyEvent.VK_ALT:
        case KeyEvent.VK_CAPS_LOCK:
            return;
        case KeyEvent.VK_ENTER:
            code = emulator.getCodeENTER();
            break;
        case KeyEvent.VK_UP:
            code = emulator.getCodeUP();
            break;
        case KeyEvent.VK_DOWN:
            code = emulator.getCodeDOWN();
            break;
        case KeyEvent.VK_RIGHT:
            code = emulator.getCodeRIGHT();
            break;
        case KeyEvent.VK_LEFT:
            code = emulator.getCodeLEFT();
            break;
        case KeyEvent.VK_F1:
            code = emulator.getCodeF1();
            break;
        case KeyEvent.VK_F2:
            code = emulator.getCodeF2();
            break;
        case KeyEvent.VK_F3:
            code = emulator.getCodeF3();
            break;
        case KeyEvent.VK_F4:
            code = emulator.getCodeF4();
            break;
        case KeyEvent.VK_F5:
            code = emulator.getCodeF5();
            break;
        case KeyEvent.VK_F6:
            code = emulator.getCodeF6();
            break;
        case KeyEvent.VK_F7:
            code = emulator.getCodeF7();
            break;
        case KeyEvent.VK_F8:
            code = emulator.getCodeF8();
            break;
        case KeyEvent.VK_F9:
            code = emulator.getCodeF9();
            break;
        case KeyEvent.VK_F10:
            code = emulator.getCodeF10();
            break;
        }
        if (code != null) {
            try {
                out.write(code, 0, code.length);
                out.flush();
            } catch (Exception ee) {
            }
            return;
        }

        char keychar = e.getKeyChar();
        if ((keychar & 0xff00) == 0) {
            obuffer[0] = (byte) (e.getKeyChar());
            try {
                out.write(obuffer, 0, 1);
                out.flush();
            } catch (Exception ee) {
            }
        }
    }

    public void keyTyped(KeyEvent e) {
        char keychar = e.getKeyChar();
        if ((keychar & 0xff00) != 0) {
            char[] foo = new char[1];
            foo[0] = keychar;
            try {
                byte[] goo = new String(foo).getBytes("EUC-JP");
                out.write(goo, 0, goo.length);
                out.flush();
            } catch (Exception eee) {
            }
        }
    }

    public void keyReleased(KeyEvent event) {
    }

    public int getTermWidth() {
        return char_width * term_width;
    }

    public int getTermHeight() {
        return char_height * term_height;
    }

    public int getCharWidth() {
        return char_width;
    }

    public int getCharHeight() {
        return char_height;
    }

    public int getColumnCount() {
        return term_width;
    }

    public int getRowCount() {
        return term_height;
    }

    public void clear() {
        graphics.setColor(bground);
        graphics.fillRect(0, 0, char_width * term_width, char_height
                * term_height);
        graphics.setColor(fground);
    }

    public void setCursor(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void draw_cursor() {
        cursor_graphics.fillRect(x, y - char_height, char_width, char_height);
        Graphics g = getGraphics();
        g.setClip(x, y - char_height, char_width, char_height);
        g.drawImage(img, 0, 0, term_area);
    }

    public void redraw(int x, int y, int width, int height) {
        Graphics g = getGraphics();
        if (g == null)
            return;
        g.setClip(x, y, width, height);
        g.drawImage(img, 0, 0, term_area);
    }

    public void clear_area(int x1, int y1, int x2, int y2) {
        graphics.setColor(bground);
        graphics.fillRect(x1, y1, x2 - x1, y2 - y1);
        graphics.setColor(fground);
    }

    public void scroll_area(int x, int y, int w, int h, int dx, int dy) {
        getGraphics().copyArea(x, y, w, h, dx, dy);
        graphics.copyArea(x, y, w, h, dx, dy);
        // wsav += x;
        // hsav += y;
        // logger.debug("value of x,y,w,h,dx,dy,wsav,hsav: "+x+" "+y+" "+w+"
        // "+h+" "+dx+" "+dy+" "+wsav+" "+hsav);
        // this.setPreferredSize(new Dimension(wsav,hsav));
    }

    public void drawBytes(byte[] buf, int s, int len, int x, int y) {
        graphics.drawBytes(buf, s, len, x, y - descent);
    }

    public void drawString(String str, int x, int y) {
        graphics.drawString(str, x, y - descent);
    }

    public void beep() {
        Toolkit.getDefaultToolkit().beep();
    }

    public void setXHost(String xhost) {
        this.xhost = xhost;
    }

    public void setXPort(int xport) {
        this.xport = xport;
    }

    public void setXForwarding(boolean foo) {
        this.xforwarding = foo;
    }

    public void setLineSpace(int foo) {
        this.line_space = foo;
    }

    public void setAntiAliasing(boolean foo) {
        if (graphics == null)
            return;
        antialiasing = foo;
        java.lang.Object mode = foo ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON
                : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
        RenderingHints hints = new RenderingHints(
                RenderingHints.KEY_TEXT_ANTIALIASING, mode);
        graphics.setRenderingHints(hints);
    }

    public void quit() {
        thread = null;
        if (session != null) {
            session.disconnect();
            session = null;
        }
    }

    public void setFGround(Object f) {
        if (f instanceof String) {
            fground = java.awt.Color.getColor((String) f);
        }
        if (f instanceof java.awt.Color) {
            fground = (java.awt.Color) f;
        }
        graphics.setColor(fground);
    }

    public void setBGround(Object b) {
        if (b instanceof String) {
            bground = java.awt.Color.getColor((String) b);
        }
        if (b instanceof java.awt.Color) {
            bground = (java.awt.Color) b;
        }
        Graphics2D foog = (Graphics2D) (background.getGraphics());
        foog.setColor(bground);
        foog.fillRect(0, 0, char_width, char_height);
        foog.dispose();
    }

    public Object getFGround() {
        return fground;
    }

    public Object getBGround() {
        return bground;
    }

}
