/*
 * ConnectBot: simple, powerful, open-source SSH client for Android
 * Copyright 2007 Kenny Root, Jeffrey Sharkey
 *
 * 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 org.connectbot.service;

import com.googlecode.android_scripting.Log;

import de.mud.terminal.vt320;

import org.apache.harmony.niochar.charset.additional.IBM437;
import org.connectbot.transport.AbsTransport;
import org.connectbot.util.EastAsianWidth;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;

/**
 * @author Kenny Root
 */
public class Relay implements Runnable {

  private static final int BUFFER_SIZE = 4096;

  private static boolean useJNI = true;

  private TerminalBridge bridge;

  private Charset currentCharset;
  private CharsetDecoder decoder;
  private boolean isLegacyEastAsian = false;

  private AbsTransport transport;

  private vt320 buffer;

  private ByteBuffer byteBuffer;
  private CharBuffer charBuffer;

  private byte[] byteArray;
  private char[] charArray;

  static {
    useJNI = EastAsianWidth.useJNI;
  }

  public Relay(TerminalBridge bridge, AbsTransport transport, vt320 buffer, String encoding) {
    setCharset(encoding);
    this.bridge = bridge;
    this.transport = transport;
    this.buffer = buffer;
  }

  public void setCharset(String encoding) {
    Log.d("changing charset to " + encoding);
    Charset charset;
    if (encoding.equals("CP437")) {
      charset = new IBM437("IBM437", new String[] { "IBM437", "CP437" });
    } else {
      charset = Charset.forName(encoding);
    }

    if (charset == currentCharset || charset == null) {
      return;
    }

    CharsetDecoder newCd = charset.newDecoder();
    newCd.onUnmappableCharacter(CodingErrorAction.REPLACE);
    newCd.onMalformedInput(CodingErrorAction.REPLACE);

    currentCharset = charset;
    synchronized (this) {
      decoder = newCd;
    }
  }

  public void run() {
    byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
    charBuffer = CharBuffer.allocate(BUFFER_SIZE);

    /* for both JNI and non-JNI method */
    byte[] wideAttribute = new byte[BUFFER_SIZE];

    /* non-JNI fallback method */
    float[] widths = null;

    if (!useJNI) {
      widths = new float[BUFFER_SIZE];
    }

    byteArray = byteBuffer.array();
    charArray = charBuffer.array();

    CoderResult result;

    int bytesRead = 0;
    byteBuffer.limit(0);
    int bytesToRead;
    int offset;
    int charWidth;

    try {
      while (true) {
        charWidth = bridge.charWidth;
        bytesToRead = byteBuffer.capacity() - byteBuffer.limit();
        offset = byteBuffer.arrayOffset() + byteBuffer.limit();
        bytesRead = transport.read(byteArray, offset, bytesToRead);

        if (bytesRead > 0) {
          byteBuffer.limit(byteBuffer.limit() + bytesRead);

          synchronized (this) {
            result = decoder.decode(byteBuffer, charBuffer, false);
          }

          if (result.isUnderflow() && byteBuffer.limit() == byteBuffer.capacity()) {
            byteBuffer.compact();
            byteBuffer.limit(byteBuffer.position());
            byteBuffer.position(0);
          }

          offset = charBuffer.position();

          if (!useJNI) {
            bridge.getPaint().getTextWidths(charArray, 0, offset, widths);
            for (int i = 0; i < offset; i++) {
              wideAttribute[i] = (byte) (((int) widths[i] != charWidth) ? 1 : 0);
            }
          } else {
            EastAsianWidth.measure(charArray, 0, charBuffer.position(), wideAttribute,
                isLegacyEastAsian);
          }
          buffer.putString(charArray, wideAttribute, 0, charBuffer.position());
          charBuffer.clear();
          bridge.redraw();
        }
      }
    } catch (IOException e) {
      Log.e("Problem while handling incoming data in relay thread", e);
    }
  }
}