package telnet;

import javax.microedition.lcdui.*;
import javax.microedition.rms.*;
import java.io.*;

/* This file is part of "Telnet Floyd".
 *
 * (c) Radek Polak 2003-2004. All Rights Reserved.
 *
 * Please visit project homepage at http://phoenix.inf.upol.cz/~polakr
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * --LICENSE NOTICE--
 *
 */

/**
 * Class that acts as terminal. It can basicly draw input from emulation (see
 * variable "buffer"), execute and store actions defined by user.
 */

public class MidletTerminal
    extends Canvas
    implements CommandListener {

  /** the VDU buffer */
  private vt320 buffer;

  /** first top and left character in buffer, that is displayed */
  private int top, left;

  /** display size in characters */
  public int rows, cols;

  private Image backingStore = null;

  private DrawFont font;

  public int fgcolor = 0x000000;
  public int bgcolor = 0xffffff;

  /** A list of colors used for representation of the display */
  private int color[] = {
      0x000000,
      0xff0000,
      0x00ff00,
      0xffff00, // yellow
      0x0000ff, // blue
      0x00ffff, // magenta
      0x00ffff, // cyan
      0xffffff, // white
      0xffffff, // bold color
      0xffffff, // inverted color
  };

  public final static int COLOR_INVERT = 9;

  public java.util.Hashtable bindings = new java.util.Hashtable();

  public MidletTerminal(vt320 buffer) {

    this.buffer = buffer;

    if (Telnet.useColors) {
      fgcolor = 0xffffff;
      bgcolor = 0x000000;
    }

    font = new DrawFont();
    backingStore = Image.createImage(this.getWidth(), this.getHeight());

    cols = this.getWidth() / font.width;
    rows = this.getHeight() / font.height;

    buffer.setScreenSize(cols, rows);

    top = 0;
    left = 0;

    loadBindings();
    displayHelp();
  }

  public void commandAction(Command command, Displayable displayable) {
    Telnet.setDisplay(new SelectFunctionDlg());
  }

  protected void keyPressed(int keycode) {

    keycode += 64;
    Object o = bindings.get(new Integer(keycode));
    if (o == null) {
      Telnet.setDisplay(new SelectFunctionDlg());
      return;
    }
    Action action = (Action) o;

    int arg0 = 0;
    int arg1 = 0;
    int arg2 = 0;
    try {
      arg0 = Integer.parseInt(action.arg0);
    }
    catch (Exception e) {}
    try {
      arg1 = Integer.parseInt(action.arg1);
    }
    catch (Exception e) {}
    try {
      arg2 = Integer.parseInt(action.arg2);
    }
    catch (Exception e) {}

    switch (action.funct.hashCode()) {
      case Action.INPUT_DIALOG_HASH:
        InputDialog inputDlg = new InputDialog();
        inputDlg.setString(action.arg0);
        Telnet.setDisplay(inputDlg);
        break;
      case Action.SHOW_HELP_HASH:
        displayHelp();
        break;
      case Action.SCROLL_LEFT_HASH:
        if (left >= arg0) // arg0 is horizontal step here
          left -= arg0;
        else
          left = 0;
        redraw();
        break;
      case Action.SCROLL_RIGH_HASH:
        if ( (left + arg0 + cols) <= buffer.width)
          left += arg0;
        else
          left = buffer.width - cols;
        redraw();
        break;
      case Action.SCROLL_UP_HASH:
        if (top >= arg0) // arg0 is vertical step here
          top -= arg0;
        else
          top = 0;
        redraw();
        break;
      case Action.SCROLL_DOWN_HASH:
        if ( (top + arg0 + rows) < buffer.height)
          top += arg0;
        else
          top = buffer.height - rows;
        redraw();
        break;
      case Action.TYPE_STRING_HASH:
        String str = action.arg0;
        for (int i = 0; i < str.length(); i++)
          Telnet.emulation.keyTyped(0, str.charAt(i), 0);
        break;
      case Action.ENTER_STRING_HASH:
        str = action.arg0;
        for (int i = 0; i < str.length(); i++)
          Telnet.emulation.keyTyped(0, str.charAt(i), 0);
        Telnet.emulation.keyTyped(0, '\n', 0);
        break;
      case Action.KEY_PRESSED_HASH:
        Telnet.emulation.keyPressed(arg0, (char) arg1, arg2);
        break;
      case Action.KEY_TYPED_HASH:
        if (action.arg2.length() != 0) // key indentified by 3 integer codes
          Telnet.emulation.keyTyped(arg0, (char) arg1, arg2);
        else {
          char keyChar = (char) (action.arg0.charAt(action.arg0.length() - 1) -
                                 'a' + 1);
          if (action.arg0.equals("tab"))
            Telnet.emulation.keyTyped(0, '\t', 0);
          else
            Telnet.emulation.keyTyped(0, keyChar, getModifiers(action.arg0)); // key identified by specific string (CTRL+x)
        }
        break;
      case Action.SET_SCREEN_SIZE_HASH:
        Telnet.emulation.setScreenSize(arg0, arg1);
        break;
      case Action.CONNECT_HASH:
        Telnet.host = action.arg0;
        Telnet.sshIO.login = action.arg1;
        Telnet.sshIO.password = action.arg2;
        Telnet.reader.start();
        break;
      case Action.TRAFFIC_HASH:
        Telnet.console.append(Telnet.traffic / 1024 + "kb" );
        Telnet.setDisplay(Telnet.console);
        break;
      case Action.BG_COLOR_HASH:
        bgcolor = arg2 | (arg1 << 8) | (arg0 << 16);
        redraw();
        break;
      case Action.FG_COLOR_HASH:
        fgcolor = arg2 | (arg1 << 8) | (arg0 << 16);
        redraw();
        break;
      case Action.VIEW_CONSOLE_HASH:
        Telnet.setDisplay(Telnet.console);
        break;
      case Action.SET_SCOLLBACK_SIZE_HASH:
        buffer.setBufferSize(arg0);
        break;
      case Action.SCOLLBACK_UP_HASH:
        buffer.setWindowBase(buffer.getWindowBase() - arg0);
        break;
      case Action.SCOLLBACK_DOWN_HASH:
        buffer.setWindowBase(buffer.getWindowBase() + arg0);
        break;
      case Action.SHOW_TERMINAL_SIZE_HASH:
        Telnet.emulation.putString(buffer.width + "x" + buffer.height);
        redraw();
        break;
      case Action.SET_SLEEP_TIME_HASH:
        Telnet.sleepTime = arg0;
        break;
      case Action.KEEP_CONNECTION_TIME_HASH:
        Telnet.keepAliveCycles = (1000 * arg0) / Telnet.sleepTime;
        break;
      case Action.EXIT_APP_HASH:
        Telnet.quitApp();
        break;
    }
  }

  /**
   * Create a color representation that is brighter than the standard
   * color but not what we would like to use for bold characters.
   * @param clr the standard color
   * @return the new brighter color
   */
  private int brighten(int color) {
    int r = (color & 0xff0000) >> 16;
    int g = (color & 0x00ff00) >> 8;
    int b = (color & 0x0000ff);

    r *= 12;
    r /= 10;
    if (r > 255) {
      r = 255;
    }
    g *= 12;
    g /= 10;
    if (g > 255) {
      g = 255;
    }
    b *= 12;
    b /= 10;
    if (b > 255) {
      b = 255;
    }
    return b | (g << 8) | (r << 16);
  }

  /**
   * Create a color representation that is darker than the standard
   * color but not what we would like to use for bold characters.
   * @param clr the standard color
   * @return the new darker color
   */
  private int darken(int color) {
    int r = (color & 0xff0000) >> 16;
    int g = (color & 0x00ff00) >> 8;
    int b = (color & 0x0000ff);

    r *= 8;
    r /= 10;
    g *= 8;
    g /= 10;
    b *= 8;
    b /= 10;
    return b | (g << 8) | (r << 16);
  }

  /** Required paint implementation */
  protected void paint(Graphics g) {
    g.drawImage(backingStore, 0, 0, 0);
  }

  public void redraw() {

    Graphics g = backingStore.getGraphics();

    for (int l = top; l < buffer.height && l < (top + rows); l++) {
      if (!buffer.update[0] && !buffer.update[l + 1]) {
        continue;
      }
      buffer.update[l + 1] = false;
      for (int c = left; c < buffer.width && c < (left + cols); c++) {
        int addr = 0;
        int currAttr = buffer.charAttributes[buffer.windowBase + l][c];

        int fg = darken(fgcolor);
        int bg = darken(bgcolor);

        if ( (currAttr & buffer.COLOR_FG) != 0) {
          fg = darken(color[ ( (currAttr & buffer.COLOR_FG) >> 4) - 1]);
        }
        if ( (currAttr & buffer.COLOR_BG) != 0) {
          bg = darken(darken(color[ ( (currAttr & buffer.COLOR_BG) >> 8) - 1]));

          // bold font handling was DELETED

        }
        if ( (currAttr & VDUBuffer.LOW) != 0) {
          fg = darken(fg);
        }
        if ( (currAttr & VDUBuffer.INVERT) != 0) {
          int swapc = bg;
          bg = fg;
          fg = swapc;
        }

        // determine the maximum of characters we can print in one go
        while ( (c + addr < buffer.width) &&
               ( (buffer.charArray[buffer.windowBase + l][c + addr] < ' ') ||
                (buffer.charAttributes[buffer.windowBase + l][c + addr] ==
                 currAttr))) {
          if (buffer.charArray[buffer.windowBase + l][c + addr] < ' ') {
            buffer.charArray[buffer.windowBase + l][c + addr] = ' ';
            buffer.charAttributes[buffer.windowBase + l][c + addr] = 0;
            continue;
          }
          addr++;
        }

        // clear the part of the screen we want to change (fill rectangle)
        if (Telnet.useColors)
          g.setColor(bg);
        else
          g.setColor(bgcolor);

        g.fillRect( (c - left) * font.width, (l - top) * font.height,
                   addr * font.width, font.height);

        if (Telnet.useColors)
          g.setColor(fg);
        else
          g.setColor(fgcolor);

          // draw the characters
        font.drawChars(g, buffer.charArray[buffer.windowBase + l], c, addr,
                       (c - left) * font.width,
                       (l - top) * font.height);

        c += addr - 1;
      }
    }

    // draw cursor
    if (buffer.showcursor && (
        buffer.screenBase + buffer.cursorY >= buffer.windowBase &&
        buffer.screenBase + buffer.cursorY < buffer.windowBase + buffer.height)
        ) {
      g.setColor(fgcolor);
      g.fillRect( (buffer.cursorX - left) * font.width,
                 (buffer.cursorY - top + buffer.screenBase - buffer.windowBase) *
                 font.height,
                 font.width, font.height);
    }

    repaint();
  }

  /**
   * Set a new terminal (VDU) buffer.
   * @param buffer new buffer
   */
  public void setVDUBuffer(vt320 buffer) {
    this.buffer = buffer;
    buffer.setDisplay(this);
  }

  public void loadBindings() {
    bindings = new java.util.Hashtable();
    RecordStore rec = null;
    try {
//      RecordStore.deleteRecordStore("binds"); // uncomment to delete all binds
      rec = RecordStore.openRecordStore("binds", false);
    }
    catch (Exception e) { // Set up default bindings
      bindings.put(new Integer(Canvas.KEY_NUM1 + 64),
                   new Action(Action.CONNECT, "158.194.80.13", "polakr",
                              "mypass"));
      bindings.put(new Integer(Canvas.KEY_NUM5 + 64),
                   new Action(Action.INPUT_DIALOG, "", "", ""));
      bindings.put(new Integer(Canvas.KEY_STAR + 64),
                   new Action(Action.KEY_PRESSED, "37", "65535", "8"));
      bindings.put(new Integer(Canvas.KEY_POUND + 64),
                   new Action(Action.KEY_PRESSED, "39", "65535", "8"));
      bindings.put(new Integer(Canvas.KEY_NUM8 + 64),
                   new Action(Action.KEY_PRESSED, "38", "65535", "8"));
      bindings.put(new Integer(Canvas.KEY_NUM0 + 64),
                   new Action(Action.KEY_PRESSED, "40", "65535", "8"));
      bindings.put(new Integer(Canvas.KEY_NUM9 + 64),
                   new Action(Action.VIEW_CONSOLE, "", "", ""));
      return;
    }
    try {
      byte[] data;
      RecordEnumeration enumerator = rec.enumerateRecords(null, null, false);
      if (enumerator.hasNextElement()) {
        data = enumerator.nextRecord();
        ByteArrayInputStream stream = new ByteArrayInputStream(data);
        DataInputStream in = new DataInputStream(stream);
        while (in.available() > 0)
          bindings.put(new Integer(in.readInt()),
                       new Action(in.readUTF(), in.readUTF(), in.readUTF(),
                                  in.readUTF()));
        in.close();
      }
      rec.closeRecordStore();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void saveBindings() {
    try {
      try
      {
        RecordStore.deleteRecordStore("binds");
      }
      catch( Exception e ) {}
      RecordStore rec = RecordStore.openRecordStore("binds", true);
      ByteArrayOutputStream stream = new ByteArrayOutputStream();
      DataOutputStream out = new DataOutputStream(stream);
      for (int i = 0; i < 128; i++) {
        Object o = bindings.get(new Integer(i));
        if (o != null) {
          Action a = (Action) o;
          out.writeInt( i );
          out.writeUTF(a.funct);
          out.writeUTF(a.arg0);
          out.writeUTF(a.arg1);
          out.writeUTF(a.arg2);
        }
      }
      out.close();
      byte[] data = stream.toByteArray();
      rec.addRecord(data, 0, data.length);
      rec.closeRecordStore();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void displayHelp() {
    String help = "";
    for (int i = 0; i < 128; i++) {
      Object o = bindings.get(new Integer(i));
      if (o != null) {
        Action action = (Action) o;
        String line = getKeyName(i - 64) + " - " +
            action.funct + " " +
            action.arg0 + " " +
            action.arg1 + " " +
            action.arg2;
        help += line + "\n\r";
        if (Telnet.socket != null )
          Telnet.console.append(line);
      }
    }
    if (Telnet.socket != null) {
      Telnet.setDisplay(Telnet.console);
    }
    else {
      buffer.deleteArea(0, 0, buffer.width, buffer.height);
      buffer.R = 0;
      buffer.C = 0;
      Telnet.emulation.putString(help);
      redraw();
    }
  }

  /**
   * Types key specified as string argument
   * @param code - string kode of key e.g. CTRL+x
   * @return true if code was parsed and key was typed
   */
  public int getModifiers(String code) {
    int modifiers = 0;
    if (code.indexOf("ctrl") != -1)
      modifiers |= vt320.KEY_CONTROL;
    if (code.indexOf("shift") != -1)
      modifiers |= vt320.KEY_SHIFT;
    if (code.indexOf("alt") != -1)
      modifiers |= vt320.KEY_ALT;
    return modifiers;
  }

}