//========================================================================================
//  FUK.java
//    en:FLOPPY Ukun
//    ja:ふろっぴU君
//  Copyright (C) 2025 TNB製作所
//========================================================================================

package xeij;

import java.awt.event.*;  //ActionEvent
import java.util.*;  //Arrays
import javax.swing.*;  //JMenu

import com.fazecast.jSerialComm.*;  //SerialPort

public class FUK {

  private static final int DMAC_CH0 = 0x00e84000;
  private static int TTT = 20000000 * 2;
  

  //設定
  public static final boolean FUK_ON = true;  //true=ふろっぴU君に対応する
  public static boolean fukOnRequest;  //true=リセット後接続する
  public static boolean fukOn;  //true=接続している

  //ポート
  public static final int FUK_VID = 0x04d8;  //ベンダーID
  public static final int FUK_PID = 0xe6b1;  //プロダクトID
  private static SerialPort fukPort1;  //ポート1
  private static SerialPort fukPort2;  //ポート2

  //メニュー
  public static JMenu fukMenu;  //ふろっぴU君メニュー
  public static JCheckBoxMenuItem fukConnectCheckBox;  //接続チェックボックス

  //fukInit ()
  //  初期化
  public static void fukInit () {
    fukOnRequest = Settings.sgsGetOnOff ("fuk");
    fukOn = false;
    fukPort1 = null;
    fukPort2 = null;
  }  //fukInit

  //fukTini ()
  //  後始末
  public static void fukTini () {
    if (fukOn) {
      fukDisconnect ();
       fukOn = false;
    }
    Settings.sgsPutOnOff ("fuk", fukOnRequest);
  }  //fukTini

  //fukGetMenu ()
  //  メニューを返す
  public static JMenu fukGetMenu () {
    if (fukMenu != null) {
       return fukMenu;
    }
    //アクションリスナー
    ActionListener actionListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
        case "Connect":  //接続
          fukOnRequest = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }  //actionPerformed
    };  //actionListener
    //メニュー
    fukMenu = Multilingual.mlnText (
      ComponentFactory.createMenu (
        "FLOPPY Ukun",
        fukConnectCheckBox = Multilingual.mlnText (
          ComponentFactory.createCheckBoxMenuItem (fukOnRequest, "Connect", actionListener),
          "ja", "接続")
        ),
      "ja", "ふろっぴU君");
    return fukMenu;
  }  //fukGetMenu

  //fukReset ()
  //  リセット
  public static void fukReset () {
    TickerQueue.tkqRemove (fukPoolTicker);
    TickerQueue.tkqRemove (onRecvTimeOut);
    isReadCommandR = false;
    if (fukOnRequest) {
      if (fukPort1 != null) {  //接続している
        fukPort1.writeBytes (new byte[] { (byte) 0x40 }, 1);
        //
        fukQueueInit ();
        cmdCount = 0;
        lastCmd = 0;
        tickCounter = XEiJ.mpuClockTime;
        mode = EMode.Mode_None;
        initDriveOption();
        driveOption_actuaDrive = 0;
        driveOption_drive = 0;
        tickSenseDeviceStatus = XEiJ.mpuClockTime + 10000 * TTT;
        for (int i = 0; i < 8; i++) { senseDeviceStatus[i] = -1; }
        isForceReadyStatus = false;
        isNotifyForceReady = false;
        is_do_fdc_intr = false;
        return;
      }
      if (!fukConnect ()) {
        fukOnRequest = false;
      }
    } else {
        fukDisconnect ();
    }
    fukOn = fukOnRequest;
  }  //fukReset

  //fukConnect ()
  //  接続する
  public static boolean fukConnect () {
       //System.out.println ("#connet\n");
    if (fukPort1 != null) {  //接続している
      return true;
    }
    //ふろっぴU君のポートを探す
    SerialPort port1 = null;
    SerialPort port2 = null;
    for (SerialPort port : SerialPort.getCommPorts ()) {  //すべてのシリアル通信ポートについて
        if (port.getVendorID () == FUK_VID &&  //ベンダーIDが一致
            port.getProductID () == FUK_PID) {  //プロダクトIDが一致
          if (port1 == null) {  //1個目
            if (port.openPort (0, 65536, 65536)) {
                port1 = port;  //ポート1(仮)
            }
          } else if (port2 == null) {  //2個目
            if (port.openPort (0, 65536, 65536)) {
                port2 = port;  //ポート2(仮)
          }
          }
        }
    }
    if (!XEiJ.prgIsWindows && port2 == null) {  //見つからない
      String path1 = "/dev/serial/by-id/usb-TNB_Factory._FDC_emulator-if00";
      String path2 = "/dev/serial/by-id/usb-TNB_Factory._FDC_emulator-if02";
      try {
        port1 = SerialPort.getCommPort (path1);
        port2 = SerialPort.getCommPort (path2);
        port1.openPort (0, 65536, 65536);
        if (!port2.openPort (0, 65536, 65536)) {
          port2 = null;
        }
      } catch (SerialPortInvalidPortException e) {
        port2 = null;
      }
    }
    if (port2 == null) {  //見つからない
        System.out.println (Multilingual.mlnJapanese ?
                          "ふろっぴ U 君のポートが見つかりません" :
                          "FLOPPY UKun not found");
        return false;
    }
    //パラメータを設定する
    //  9600bpsに設定されているので480Mbpsに変更する
    port1.setComPortParameters (480000000, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
    port2.setComPortParameters (480000000, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
    int n1 = port1.bytesAvailable ();
    if (n1 > 0) {
      byte[] b1 = new byte[n1];
      port1.readBytes (b1, n1);
    }
    int n2 = port2.bytesAvailable ();
    if (n2 > 0) {
      byte[] b2 = new byte[n1];
      port2.readBytes (b2, n1);
    }
    //タイムアウトを設定する
    //  readBytesは50msを上限として少なくとも1バイト受信するまでブロックする
    //  writeBytesは送信バッファが一杯になるまでブロックしない
    port1.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 50, 0);
    port2.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 50, 0);
   //ポート1とポート2を判別する
    //  50ms以内に応答がないと失敗する
    byte[] b = new byte[] { 0x00, 0x00 };
    port1.writeBytes (b, 1, 0);  //ポート1(仮)へ0x00を送信する
    port1.readBytes (b, 1, 0);  //ポート1(仮)から1バイト受信する
    port2.writeBytes (b, 1, 1);  //ポート2(仮)へ0x00を送信する
    port2.readBytes (b, 1, 1);  //ポート2(仮)から1バイト受信する
    if (b[0] == 'f' && b[1] == 'F') {  //(仮)は合っている
      ;
    } else if (b[0] == 'F' && b[1] == 'f') {  //(仮)は逆
      //ポート1とポート2を入れ替える
      SerialPort port = port1;
      port1 = port2;
      port2 = port;
    } else {  //不明
      System.out.printf ("1='%c', 2='%c'\n", b[0], b[1]);
      port1.closePort ();
      port2.closePort ();
      System.out.println (Multilingual.mlnJapanese ?
                         "ふろっぴ U 君のポートを判別できません" :
                         "Unable to identify FLOPPY UKun");
      return false;
    }
    //タイムアウトを再設定する
    //  readBytesは指定された長さを受信するまでブロックする。以降はSerialPortEventのgetReceivedDataを使う
    //  writeBytesは送信バッファが一杯になるまでブロックしない
    port1.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
    port2.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
    //初期化する
    //  ポート1へ0x80,0xfeを送信する
    //  0xffのエスケープは用いない
    port1.writeBytes (new byte[] { (byte) 0x80 }, 1);
    port1.writeBytes (new byte[] { (byte) 0xfe }, 1);
    port1.writeBytes (new byte[] { (byte) 0x40 }, 1);
    //完了
    fukPort1 = port1;
    fukPort2 = port2;
    System.out.printf (Multilingual.mlnJapanese ?
                       "ふろっぴ U 君 (%s,%s) に接続しました\n" :
                       "FLOPPY UKun (%s,%s) connected\n",
                       fukPort1.getSystemPortName (),
                       fukPort2.getSystemPortName ());
    //受信キュー
    fukQueueInit ();
    //遅延送信
    fukPoolInit ();
    //シリアルポートデータリスナーを登録する
    fukPort1.addDataListener (fukDataListener1);
    fukPort2.addDataListener (fukDataListener2);
    cmdCount = 0;
    lastCmd = 0;
    tickCounter = XEiJ.mpuClockTime;
    mode = EMode.Mode_None;
    initDriveOption();
    driveOption_actuaDrive = 0;
    driveOption_drive = 0;
    tickSenseDeviceStatus = XEiJ.mpuClockTime + 10000;
    for (int i = 0; i < 8; i++) { senseDeviceStatus[i] = -1; }
    isForceReadyStatus = false;
    isNotifyForceReady = false;
    return true;
  }  //fukConnect

  //fukDisconnect ()
  //  切断する
  public static void fukDisconnect () {
       //System.out.println ("#disconnect\n");
    if (fukPort1 == null) {  //接続していない
        return;
    }
    //シリアルポートデータリスナーを解除する
    fukPort1.removeDataListener ();
    fukPort2.removeDataListener ();
    //ポートを閉じる
    System.out.printf (Multilingual.mlnJapanese ?
                       "ふろっぴ U 君 (%s,%s) を切り離しました\n" :
                       "FLOPPY UKun (%s,%s) disconnected\n",
                       fukPort1.getSystemPortName (),
                       fukPort2.getSystemPortName ());
    fukPort1.clearRTS ();
    fukPort2.clearRTS ();
    fukPort1.clearDTR ();
    fukPort2.clearDTR ();
    fukPort1.closePort ();
    fukPort2.closePort ();
    fukPort1 = null;
    fukPort2 = null;
  }  //fukDisconnect

  //fukPeek (a)
  //  ピーク
  public static int fukPeek (int a) {
    return fukRead ((byte)(a & 0x0F));
  }  //fukPeek
  
  //fukRead (a)
  //  リード
  public static int fukRead (byte cmd) {
    synchronized (mLock1) {
      check_ints1 ();
    }
    if (cmd == 0x01) {
      // Status(0x00E94001 レジスタ内容)
      int s = -1;
      long w = 2000;
      final long SW = 200;
      switch (mode) {
      case Mode_SenseDeivceStatus_WaitStatus90:
        s = 0x90;
        mode = EMode.Mode_SenseDeivceStatus_WaitStatusD0;
        break;
      case Mode_SenseDeivceStatus_WaitStatusD0:
        s = 0xD0;
        break;
      case Mode_SenseDeivceStatus_CheckStatus:
      case Mode_SenseDeivceStatus_ReturnStatus:
        s = 0xD0;
        break;
      case Mode_SenseDeivceStatus_WaitStatus80:
        s = 0x80;
        mode = EMode.Mode_None;
        break;
      case Mode_SenseInterruptStatus_StatusD0:
        switch (modeCounter) {
        case 0:
          s = 0xD0; 
          break;
        default: 
          w  = SW;
          mode = EMode.Mode_SenseInterruptStatus_NoWait;
          break;
        }
        break;
      case Mode_SenseInterruptStatus_NoWait:
        w = SW;
        break;
      case Mode_Seek_StatusD0:
        if (++modeCounter < 3) {
          s = 0x90;
        } else {
          mode = EMode.Mode_Seek_NoWait;
          w = SW;
        }
        break;
      case Mode_Seek_NoWait:
        w = SW;
        break;
      case Mode_Recalibrate_Status90:
        if (++modeCounter < 2) {
          s = 0x90;
        } else {
          mode = EMode.Mode_Recalibrate_NoWait;
          w = SW;
        }
        break;
      case Mode_Recalibrate_NoWait:
        w = SW;
        break;
      case Mode_ReadData_Command:
        s = 0x90;
        break;
      case Mode_ReadData_Receive:
        s = 0x10;
        break;
      case Mode_ReadData_SoftReceive:
        s = 0xF0;
        break;
      case Mode_ReadData_Response:
        s = 0xD0;
        break;
      case Mode_ReadData_After:
        switch (modeCounter) {
        case 1:
          s = 0xD0;
          break;
        default:
          s = 0x80;
          break;
        }
        break;
      case Mode_WriteData_Command:
        s = 0x90;
        break;
      case Mode_WriteData_Send:
        s = 0x10;
        break;
      case Mode_WriteData_Response:
        s = 0xD0;
        break;
      case Mode_ReadId_Command:
        if (++modeCounter < 2) {
          s = 0x90;
        }
        else {
          mode = EMode.Mode_ReadId_Wait;
          w = SW;
        }
        break;
      case Mode_ReadId_Wait:
        w = SW;
        break;
      case Mode_ReadId_Result:
        if (modeCounter >= 0) {
          if (--modeCounter > 0) {
            s = 0xD0;
          } else {
            mode = EMode.Mode_None;
          }
        } else {
          s = 0xD0;
        }
        break;
      default:
        break;
      }
      long ll = XEiJ.mpuClockTime - tickCounter;
      if (s >= 0) {
        //もし憶測値ありなら、それを返す。
        byte b = (byte)s;
        lastCmd = cmd;
        lastStatus = b;
        return b;
      }
      if (lastCmd == cmd && w != 0 && (XEiJ.mpuClockTime - tickCounter) / TTT < w) {
        //同じコマンドが一定の時間内に実行されていたら同じ値を返す。
        return lastStatus;
      }
      lastCmd = cmd;
      tickCounter = XEiJ.mpuClockTime;
    } else {
      lastCmd = cmd;
    }
    if (cmd == 0x03) {
      // Command(0x00E94003 レジスタ内容)
      if (mode == EMode.Mode_SenseDeivceStatus_ReturnStatus) {
        //データの戻りをキャッシュ値にする
        mode = EMode.Mode_None;
        byte r = (byte)senseDeviceStatus[modeCounter];
        return r;
      }
      else if (mode == EMode.Mode_ReadData_SoftReceive) {
        byte r = recvData[readDataCounter++];
        if (readDataCounter == recvDataSize - 7) {
          mode = EMode.Mode_ReadData_Response;
          readDataCounter = 0;
        }
        return r;
      } else if (mode == EMode.Mode_ReadData_Response) {
        // 読み込みモードのレスポンス中
        byte r = recvData[recvDataSize - 7 + readDataCounter];
        readDataCounter++;
        if (readDataCounter >= 7) {
          mode = EMode.Mode_ReadData_After;
          addQueue ((byte)'c');
        }
        return r;
      } else if (mode == EMode.Mode_ReadData_After) {
        modeCounter++;
        return 0x80;
      }
    } else if (cmd == 0x05) {
      // (0x00E94005 レジスタ内容)
      if (driveOption_drive == 0) {
        driveOption_drive = driveOption_actuaDrive;
        initDriveOption ();
      }
      if (driveOption_drive == 0) {
        //ドライブ指定がない
        return 0;
      }
      if (driveOption_result[driveOption_drive] >= 0) {
        //そのドライブは記憶している
        byte r = (byte)driveOption_result[driveOption_drive];
        return r;
      }
      //そのドライブは記憶していない
      if (driveOption_actuaDrive != driveOption_drive) {
        //想定のドライブと設定されているドライブが違う
        driveOption_actuaDrive = driveOption_drive;
        driveOption_isExec = true;
        synchronized (mLock1) {
          byte d = (byte)(driveOption_ctrl[driveOption_drive] | driveOption_drive);
          fukPoolAdd2 (0x05 | 0x80, d);
        }
      }
    }
    //リードコマンドを送信する
    int r = -1;
    synchronized (mLock1) {
      fukPoolAdd1 (cmd);  //リードコマンド
      fukPoolFlush ();
    }
    //読み出し中
    //受信キューからデータを読み出す
    int c = 0;
    while (true) {
      synchronized (mLock1) {
        r = fukQueueData ();
      }
      if (r >= 0) {
        break;
      }
      sleep(10);
    }
    if (r >= 0) {
      if (cmd == 0x01) {
        //Status(0x00E94001 レジスタ内容)
        if (mode == EMode.Mode_ReadId_Wait && r == 0xD0) {
          mode = EMode.Mode_ReadId_Result;
          modeCounter = -1;
        }
        if (mode == EMode.Mode_Seek_NoWait) {
          if (r == 0x90) {
            if (equalStatusCounter++ > 5) {
              equalStatusCounter = 0;
              r = 0x80;
            }
          } else {
            equalStatusCounter = 0;
          }
        }
         lastStatus = (byte)r;
      }
      if (cmd == 0x03) {
        //command(0x00E94003 レジスタ内容)
        if (mode == EMode.Mode_ReadId_Result && modeCounter < 0) {
          modeCounter = 7;
        } else if (mode == EMode.Mode_SenseDeivceStatus_CheckStatus) {
          //データの戻りを記憶する
          senseDeviceStatus[modeCounter] = (short)r;
          mode = EMode.Mode_None;
        } else if (mode == EMode.Mode_SenseInterruptStatus_StatusD0) {
          if (modeCounter++ == 0) {
            if ((r & 0x18) == 0x18) {
              r &= 0xEF;
            }
          }
        } else if (mode == EMode.Mode_WriteData_Response) {
          // 書き込みモードのレスポンス中
          if (++writeDataCounter >= 7) {
            mode = EMode.Mode_None;
            addQueue((byte)'c');
          }
        }
      }
      if (cmd == 0x05) {
        //(0x00E94005 レジスタ内容)
        driveOption_result[driveOption_actuaDrive] = r;
      }
      return (byte)(r);
    }
    return 0xFF;
  }  //fukRead

  /// コマンド別長さ
  private static final byte s_commandLength[] = {
    1,  //0x00 INVALID
    1,  //0x01 INVALID
    9,  //0x02 READ DIAGNOSTIC
    3,  //0x03 SPECIFY
    2,  //0x04 SENSE DEVICE STATUS
    9,  //0x05 WRITE DATA
    9,  //0x06 READ DATA
    2,  //0x07 RECALIBRATE
    1,  //0x08 SENSE INTERRUPT STATUS
    9,  //0x09 WRITE DELETED DATA
    2,  //0x0a READ ID
    1,  //0x0b INVALID
    9,  //0x0c READ DELETED DATA
    6,  //0x0d WRITE ID
    1,  //0x0e INVALID
    3,  //0x0f SEEK
    1,  //0x10 INVALID
    9,  //0x11 SCAN EQUAL
    1,  //0x12 INVALID
    1,  //0x13 INVALID
    1,  //0x14 RESET STANDBY
    1,  //0x15 SET STANDBY
    1,  //0x16 SOFTWARE RESET
    1,  //0x17 INVALID
    1,  //0x18 INVALID
    9,  //0x19 SCAN LOW OR EQUAL
    1,  //0x1a INVALID
    1,  //0x1b INVALID
    1,  //0x1c INVALID
    9,  //0x1d SCAN HIGH OR EQUAL
    1,  //0x1e INVALID
    1,  //0x1f INVALID
  };
  
  //fukWrite (a, d)
  //  ライト
  public static void fukWrite (byte cmd, byte prm) 
  {
    if (cmd == 0x23/*OPM*/) {
      //(0x00E90003 レジスタ内容)
      if ((prm & 0x40) != 0) {
        isForceReadyStatus = true;
        isNotifyForceReady = false;
      } else {
        if (isNotifyForceReady) {
          isNotifyForceReady = false;
          isForceReadyStatus = false;
          sendForceReady(false);
        }
      }
      return;
    }
    lastCmd = 0;
    if (cmd == 0x03) {
      // Command Register(0x00E94003 レジスタ内容)
      if (--cmdCount <= 0) {
        cmdCount = s_commandLength[prm & 0x1F];
        //
        if (activeCommandList[0] == 0x08 && mode == EMode.Mode_ReadData_After && prm == 0x08/*SENSE INTERRUPT STATUS*/) {
          mode = EMode.Mode_SenseInterruptStatus_StatusD0;
        } else if (mode == EMode.Mode_ReadData_After && prm == 0x08/*SENSE INTERRUPT STATUS*/) {
          activeCommandCount = 0;
          activeCommandList[activeCommandCount++] = prm;
          modeCounter++;
          // ここではコマンドを出さない。
          return;
        }
        //
        mode = EMode.Mode_None;
        modeCounter = 0;
        //
        if (prm == 0x04/*SENSE DEVICE STATUS_*/) {
          mode = EMode.Mode_SenseDeivceStatus_WaitStatus90;
        } else if (prm == 0x08/*SENSE INTERRUPT STATUS*/) {
          mode = EMode.Mode_SenseInterruptStatus_StatusD0;
        } else if (prm == 0x0F/*SEEK*/) {
          mode = EMode.Mode_Seek_StatusD0;
        } else if (prm == 0x07/*RECALIBRATE*/) {
          mode = EMode.Mode_Recalibrate_Status90;
          if (isForceReadyStatus && !isNotifyForceReady) {
            isNotifyForceReady = true;
            sendForceReady(true);
          }
        } else if (isReadTransCommand(prm)) {
          mode = EMode.Mode_ReadData_Command;
          readDataCounter = 9;
        } else if (isWriteTransCommand(prm)) {
          sendMTC();
          mode = EMode.Mode_WriteData_Command;
          writeDataCounter = 9;
        } else if (isWriteIdTransCommand(prm)) {
          sendMTC();
          mode = EMode.Mode_WriteData_Command;
          writeDataCounter = 6;
        } else if ((prm & 0xBF) == 0x0A/*READ ID*/) {
          mode = EMode.Mode_ReadId_Command;
        }
        activeCommandCount = 0;
      }
      activeCommandList[activeCommandCount++] = prm;
      //
      if (mode == EMode.Mode_SenseDeivceStatus_WaitStatus90) {
        // ここではコマンドを出さない。
        return;
      } else if (mode == EMode.Mode_SenseDeivceStatus_WaitStatusD0) {
        if (activeCommandCount == 2) {
          if ((XEiJ.mpuClockTime - tickSenseDeviceStatus) / TTT > 500) {
            tickSenseDeviceStatus = XEiJ.mpuClockTime;
            for (int i = 0; i < 8; i++) { senseDeviceStatus[i] = -1; }
          }
          if (senseDeviceStatus[prm & 7] < 0) {
            //キャッシュがない
            synchronized (mLock1) {
              fukPoolAdd2 (0x03 | 0x80, 0x04);
              fukPoolAdd2 (0x03 | 0x80, prm);
              fukPoolFlush ();
            }
            modeCounter = prm & 7;
            mode = EMode.Mode_SenseDeivceStatus_CheckStatus; //データの戻りを記憶する
          } else {
            //キャッシュあり
            modeCounter = prm;
            mode = EMode.Mode_SenseDeivceStatus_ReturnStatus; //データの戻りをキャッシュ値にする
          }
        }
        return;
      }
    }
    else if (cmd == 0x05) {
      if ((XEiJ.mpuClockTime - driveOption_tick) / TTT > 500) {
        initDriveOption();
      }
      if ((prm & 0x0F) != 0) {
        //指示だ
        driveOption_drive = (byte)(prm & 0x0F);
        if ((prm & 0x20) != 0) {
          tickSenseDeviceStatus = XEiJ.mpuClockTime;
          initDriveOption();
        }
        if (driveOption_ctrl[driveOption_drive] != (prm & 0xF0)) {
          //前回と違う指示だ
          driveOption_ctrl[driveOption_drive] = (prm & 0xF0);
          driveOption_isExec = true;
          driveOption_actuaDrive = driveOption_drive;
        } else {
          //同じ指示なら終わり
          return;
        }
      } else {
        //解除だ
        if (driveOption_isExec) {
          //コマンド実行している
          driveOption_drive = 0;
          driveOption_actuaDrive = 0;
          driveOption_isExec = false;
        } else {
          //してない＆ドライブ同じ
          driveOption_drive = 0;
          driveOption_actuaDrive = 0;
          return;
        }
      }
    }
    //
    if (cmd == 0x03) {
      //(0x00E94003 レジスタ内容)
      if (mode == EMode.Mode_ReadData_Command) {
        if (--readDataCounter == 0) {
          mode = EMode.Mode_ReadData_Receive;
          readDataCounter = 0;
        }
      } else if (mode == EMode.Mode_WriteData_Command) {
        if (--writeDataCounter == 0) {
          mode = EMode.Mode_WriteData_Send;
        }
      }
    }
    synchronized (mLock1) {
      fukPoolAdd2 (0x80 | cmd, prm);  //ライトコマンドとデータ
    }
  }  //fukWrite

  /**
   * 読み込み系コマンド？
   *  @param cmd コマンド
   *  @retval true 読み込み系
   *  @retval false 以外
   */
  private static boolean isReadTransCommand(byte cmd)
  {
    boolean f = false;
    if ((cmd & 0x1F) == 0x06) f = true;//READ DATA
    if ((cmd & 0x1F) == 0x0C) f = true;//READ DELETED DATA
    if ((cmd & 0xBF) == 0x02) f = true;//READ DIAGNOSTIC
    return f;
  }

  /**
   * 書き込み系コマンド？
   *  @param cmd コマンド
   *  @retval true 書き込み系
   *  @retval false 以外
   */
  private static boolean isWriteTransCommand (byte cmd) {
    boolean f = false;
    if ((cmd & 0x3F) == 0x05) f = true;//WRITE DATA
    if ((cmd & 0x3F) == 0x09) f = true;//WRITE DELETED DATA
    if ((cmd & 0x1F) == 0x11) f = true;//SCAN EQUAL
    if ((cmd & 0x1F) == 0x19) f = true;//SCAN LOW OR EQUAL
    if ((cmd & 0x1F) == 0x1D) f = true;//SCAN HIGH OR EQUAL
    return f;
  }

  /**
   * 書き込みID系コマンド？
   *  @param cmd コマンド
   *  @retval true 書き込み系
   *  @retval false 以外
   */
  private static boolean isWriteIdTransCommand (byte cmd) {
    boolean f = false;
    if ((cmd & 0xBF) == 0x0D) f = true;//WRITE ID
    return f;
  }

  /**
   * MTC値、送信
   */
  private static void sendMTC () {
    int mtc = HD63450.dmaReadWord(DMAC_CH0 + 0x0A);
    synchronized (mLock1) {
      fukPoolAdd2 (0x30 | 0x80, mtc & 0xFF);
      fukPoolAdd2 (0x31 | 0x80, mtc / 0x100);
    }
  }

  /**
   * ForceReady設定、送信
   */
  private static void sendForceReady (boolean isReady) {
    byte b = (byte)(isReady ? 0x40 : 0x00);
    synchronized (mLock1) {
      fukPoolAdd2 (0x23 | 0x80, b);
    }
  }

  private static void clearDmac () {
    HD63450.dmaWriteByte (DMAC_CH0 + 0x07 , 0x10);
    HD63450.dmaWriteWord (DMAC_CH0 + 0x0A , 0);//MTC
  }
  
  private static void check_ints1 () {
    while (true) {
      int r = fukQueueData ();
      if (r < 0) {
        break;
      }
      byte b = (byte)r;
      if (b == 'C') {
        if (is_do_fdc_intr) {
	      IOInterrupt.ioiFdcFall ();
        }
        IOInterrupt.ioiFdcRise ();
        is_do_fdc_intr = true;
      } else if (b == 'c') {
        IOInterrupt.ioiFdcFall ();
        is_do_fdc_intr = false;
      }
    }
  }

  private static void sleep (int ms) {
    try {
      Thread.sleep(0, ms * 1000);
    } catch (Exception e) {
    }
  }

  private static void onReceivedData () {
    //読み込み受信モードでスタンバイ出来た時
    int mtc = HD63450.dmaReadWord (DMAC_CH0 + 0x0A);
    clearDmac();
    if (recvDataSize >= 7) {
      //リザルトも含まれる
      if (mtc == 0) {
        mode = EMode.Mode_ReadData_SoftReceive;
      } else {
        mode = EMode.Mode_ReadData_Response;
      }
    } else {
      // RESULTもない時！！！
      recvDataSize = 7;
      recvData[0] = (byte)(0x40 | (activeCommandList[1] & 0x07));
      recvData[1] = 0;
      recvData[2] = 0;
      recvData[3] = activeCommandList[3];
      recvData[4] = activeCommandList[4];
      recvData[5] = activeCommandList[5];
      recvData[6] = activeCommandList[6];
      mode = EMode.Mode_ReadData_Response;
    }
    addQueue ((byte)'C');
    readDataCounter = 0;
  }

  //fukDataListener1
  //  ポート1データリスナー
  //  getReceivedData()について
  //    LISTENING_EVENT_DATA_AVAILABLEのときgetReceivedData()はnullを返す
  //    LISTENING_EVENT_DATA_RECEIVEDのときgetReceivedData()は受信バッファを先読みして返す
  //    受信バッファを先読みするだけで取り除かないのでこれだけでは2回目のイベントが発生しない
  public static final SerialPortDataListener fukDataListener1 = new SerialPortDataListener () {
    @Override public int getListeningEvents () {
      return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
    }  //getListeningEvents
    @Override public void serialEvent (SerialPortEvent spe) {
      if (spe.getEventType () != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
        return;
      }
      //データを受信する
      synchronized (mLock1) {
        int n = fukPort1.bytesAvailable ();
        byte[] b = new byte[n];
        fukPort1.readBytes (b, n);
        int w = fukQueueWrite;  //受信キューに書き込んだ長さ
        int r = fukQueueRead;  //受信キューから読み出した長さ
        //受信した長さが受信キューに書き込める長さを超えていてはならない
        if ((FUK_QUEUE_SIZE - (w - r)) < n) {
          System.out.printf ("%08x fukDataListener1.serialEvent() queue is full\n", XEiJ.regPC0);
          return;
        }
        //受信したデータを受信キューに移す
        int i = w & (FUK_QUEUE_SIZE - 1);  //受信キューに書き込む位置
        int k = Math.min (n, FUK_QUEUE_SIZE - i);  //1回目に書き込む長さ
        System.arraycopy (b, 0,  //from
                          fukQueueArray, i,  //to
                          k);  //length
        if (k < n) {  //受信キューの末尾と先頭を跨ぐとき
          System.arraycopy (b, k,  //from
                            fukQueueArray, 0,  //to
                            n - k);  //length
        }
        fukQueueWrite = w += n;
      }
    }  //serialEvent
  };  //fukDataListener1

  public static void addQueue (byte d) {
    synchronized (mLock1) {
      int n = 1;
      int w = fukQueueWrite;  //受信キューに書き込んだ長さ
      int r = fukQueueRead;  //受信キューから読み出した長さ
      //受信した長さが受信キューに書き込める長さを超えていてはならない
      if ((FUK_QUEUE_SIZE - (w - r)) < n) {
        System.out.printf ("%08x fukDataListener1.serialEvent() queue is full\n", XEiJ.regPC0);
        return;
      }
      byte[] b = new byte[1];
      b[0] = d;
      //受信したデータを受信キューに移す
      int i = w & (FUK_QUEUE_SIZE - 1);  //受信キューに書き込む位置
      int k = Math.min (n, FUK_QUEUE_SIZE - i);  //1回目に書き込む長さ
      System.arraycopy (b, 0,  //from
                        fukQueueArray, i,  //to
                        k);  //length
      if (k < n) {  //受信キューの末尾と先頭を跨ぐとき
        System.arraycopy (b, k,  //from
                          fukQueueArray, 0,  //to
                          n - k);  //length
      }
      fukQueueWrite = w += n;
    }
  }

  private static int mar = 0;
  private static int mtc = 0;
  private static boolean isReadCommandR = false;  // RコマンドR中？
  private static int rccnt = 0;
  private static int recvDataPos = 0;

  private static boolean checkReceiveData () {
    int n = fukPort2.bytesAvailable ();
    if (n == 0) {
      return false;
    }
    byte[] recv = new byte[n];
    fukPort2.readBytes (recv, n);
    int l = n;//recv.length;  //受信した長さ
    sleep (10);
    int i = 0;
    while (i < l) {
      byte b = recv[i++];
      if (isReadCommandR) {
        if (rccnt == 0) {
          recvDataSize = b;
        } else if (rccnt == 1) {
          recvDataSize |= (b << 8);
          recvDataPos = 0;
        } else {
          if (recvDataPos < mtc) {
            MainMemory.mmrWb ((int)(mar + recvDataPos), b);
          }
          recvData[recvDataPos++] = b;
          if (recvDataPos == recvDataSize) {
            //全データ＋RESULTを受信
            TickerQueue.tkqRemove (onRecvTimeOut);
            isReadCommandR = false;
            hasAllReadData = true;
            onReceivedData ();
          }
        }
        rccnt++;
        continue;
      }
      if (b == 'C') {
        ;
      } else if (b == 'c') {
        ;
      } else if (b == 'D') {
        ioiFdd(true);
        tickSenseDeviceStatus = XEiJ.mpuClockTime;
      } else if (b == 'd') {
        ioiFdd(false);
      } else if (b == 'R') {
        if (mode == EMode.Mode_ReadData_Receive) {
          mar = HD63450.dmaReadLong (DMAC_CH0 + 0x0C);
          mtc = HD63450.dmaReadWord (DMAC_CH0 + 0x0A);
          isReadCommandR = true;
          recvDataSize = 0;
          rccnt = 0;
          //tickCmdR = XEiJ.mpuClockTime;
          TickerQueue.tkqAdd (onRecvTimeOut, XEiJ.mpuClockTime + 50000L * TTT);
        }
      } else if (b == 'W') {
        if (mode == EMode.Mode_WriteData_Send) {
          mar = HD63450.dmaReadLong (DMAC_CH0 + 0x0C); // MTR
          writeDataCounter = HD63450.dmaReadWord (DMAC_CH0 + 0x0A);//MTC
          int j = 0;
          writeData[j++] = 0x4D;  //転送開始識別コマンド
          for (int k = 0; k < writeDataCounter; k++) {
            writeData[j++] = MainMemory.mmrRbs (mar++);
          }
          fukPort2.writeBytes (writeData, j);
          sleep (10);
        }
      } else if (b == 'w') {
        if (mode == EMode.Mode_WriteData_Send) {
          //書き込み送信モードでスタンバイ出来た時
          clearDmac ();
          mode = EMode.Mode_WriteData_Response;
          writeDataCounter = 0;
          addQueue ((byte)'C');
        }
      } else if (b == 't') {
        clearDmac();
        mode = EMode.Mode_None;
      } else {
      }
    }
    return true;
  }

  //onRecvTimeOut
  //  受信タイムアウト処理
  public static final TickerQueue.Ticker onRecvTimeOut = new TickerQueue.Ticker () {
    @Override protected void tick () {
      //データが足りない
      if (mode == EMode.Mode_ReadData_Receive) {
        while (true) {
          sleep (500);
          int n = fukPort2.bytesAvailable ();
          if (n == 0) {
            break;
          }
          byte[] recv = new byte[n];
          fukPort2.readBytes (recv, n);
        }
        recvDataSize = 0;
        hasAllReadData = false;
        isReadCommandR = false;
        onReceivedData();
        System.out.printf ("FUK; timeout cmd r mode\n");
      }
    }  //tick
  };

  //fukDataListener2
  //  ポート2データリスナー
  public static final SerialPortDataListener fukDataListener2 = new SerialPortDataListener () {
    @Override public int getListeningEvents () {
      return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
    }  //getListeningEvents
    @Override public void serialEvent (SerialPortEvent spe) {
      if (spe.getEventType () != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
        return;
      }
      //データを受信する
      while (checkReceiveData ()) {
        sleep (1);
      }
    }  //serialEvent
  };  //fukDataListener2

  //受信キュー
  //  ポート1から受信したデータは直ちに受信キューに移される
  //  受信キューは溢れてはならない
  public static final int FUK_QUEUE_SIZE = 1048576;  //キューの長さ。1MB
  public static final byte[] fukQueueArray = new byte[FUK_QUEUE_SIZE];  //キューの配列
  public static volatile int fukQueueWrite;  //書き込んだ長さ
  public static volatile int fukQueueRead;  //読み出した長さ

  //fukQueueInit ()
  //  初期化
  public static void fukQueueInit () {
    fukQueueWrite = 0;
    fukQueueRead = 0;
  }  //fukQueueInit

  //d = fukQueueData ()
  //  受信キューからデータを読み出す
  //  d  読み出したデータ。-1=受信キューが空
  public static int fukQueueData () {
    int w = fukQueueWrite;
    int r = fukQueueRead;
    if ((w - r) == 0) {  //受信キューが空
        return -1;
    }
    int d = 0xff & fukQueueArray[r++ & (FUK_QUEUE_SIZE - 1)];
    fukQueueRead = r;
    return d;
  }  //fukQueueData

  //遅延送信
  //  送信データを送信プールに溜めて一定時間送信がないか受信を行うときにまとめて送信する
  //  送信と受信の間隔が足りなくなる場合は個別に間隔保証を行うこと
  //  ティッカーはコアと同じスレッドで命令の後に呼び出されるので追加や削除で衝突する心配はない
  public static final int FUK_POOL_SIZE = 0x1000;  //送信プールのサイズ。511以下
  public static final long FUK_POOL_SPAN = XEiJ.TMR_FREQ / 500;  //一定時間。2ms
  public static final byte[] fukPoolBuffer = new byte[FUK_POOL_SIZE];  //送信プール
  public static volatile int fukPoolLength;  //送信プールにあるデータの長さ

  //fukPoolInit ()
  //  初期化
  public static void fukPoolInit () {
    fukPoolLength = 0;
  }  //fukPoolInit

  //fukPoolAdd1 (d)
  //  送信プールにデータを1個加える
  public static void fukPoolAdd1 (int d1) {
    if (FUK_POOL_SIZE - 1 < fukPoolLength) {
      fukPoolFlush ();
    }
    fukPoolBuffer[fukPoolLength++] = (byte) d1;
    TickerQueue.tkqAdd (fukPoolTicker, XEiJ.mpuClockTime + FUK_POOL_SPAN);
  }  //fukPoolAdd1

  //fukPoolAdd2 (d1, d2)
  //  送信プールにデータを2個加える
  public static void fukPoolAdd2 (int d1, int d2) {
    if (FUK_POOL_SIZE - 2 < fukPoolLength) {
      fukPoolFlush ();
    }
    fukPoolBuffer[fukPoolLength++] = (byte) d1;
    fukPoolBuffer[fukPoolLength++] = (byte) d2;
    TickerQueue.tkqAdd (fukPoolTicker, XEiJ.mpuClockTime + FUK_POOL_SPAN);
  }  //fukPoolAdd2

  //fukPoolTicker
  //  最後の送信から一定時間後に送信プールから送信するティッカー
  public static final TickerQueue.Ticker fukPoolTicker = new TickerQueue.Ticker () {
    @Override protected void tick () {
      //送信プールから送信する
      synchronized (mLock1) {
        fukPoolFlush ();
      }
    }  //tick
  };  //fukPoolTicker

  //fukPoolFlush ()
  //  送信プールから送信する
  public static void fukPoolFlush () {
    if (0 < fukPoolLength) {
      fukPort1.writeBytes (fukPoolBuffer, fukPoolLength);
      fukPoolLength = 0;
    }
  }  //fukPoolFlush

  private static Object mLock1 = new Object();

  /**
   * ドライブオプション関係初期化
   */
  private static void initDriveOption() {
    driveOption_tick = XEiJ.mpuClockTime;
    for (int i = 0; i < 16; i++) {
      driveOption_ctrl[i] = -1;
      driveOption_result[i] = -1;
    }
    driveOption_isExec = true;
  }

  public static final long FUK_FDD_SPAN = XEiJ.TMR_FREQ / 1000;  //一定時間。1ms

  private static void ioiFdd (boolean f){
    if (f) {
      TickerQueue.tkqAdd (ioiFddRise, XEiJ.mpuClockTime + FUK_FDD_SPAN);
    } else {
      TickerQueue.tkqAdd (ioiFddFall, XEiJ.mpuClockTime + FUK_FDD_SPAN);
    }
  }

  private static final TickerQueue.Ticker ioiFddRise = new TickerQueue.Ticker () {
    @Override protected void tick () {
      IOInterrupt.ioiFddRise ();
    }
  };

  private static final TickerQueue.Ticker ioiFddFall = new TickerQueue.Ticker () {
    @Override protected void tick () {
      IOInterrupt.ioiFddFall ();
    }
  };


  private static byte recvData[] = new byte[0x10000];    ///< 受信データ
  private static volatile int recvDataSize;      ///< 受信データ数
  private static volatile long  tickQueue;      ///< キュー送信用TICK
  private static int        cmdCount;        ///< コマンドカウント
  private static byte      lastCmd;        ///< 最後に処理したコマンド
  private static long      tickCounter;    ///< 0x01 コマンドの高速アクセス軽減用TICK
  private static byte      lastStatus;      ///< 最後に返したStatus値
  private static byte      activeCommandList[] = new byte[16];///< アクティブコマンド
  private static int      activeCommandCount;  ///< アクティブコマンド長さ
  private static boolean      hasAllReadData;    ///< 受信成功？
  private static boolean      isForceReadyStatus;  ///< 強制READY状態？
  private static boolean      isNotifyForceReady;  ///< 強制READY通知？
  private static boolean is_do_fdc_intr = false;
  
  private static long    driveOption_tick;          ///< タイム
  private static byte    driveOption_actuaDrive;        ///< 指定されているドライブ
  private static byte    driveOption_drive;          ///< 下位4bit 指定しているDRIVE 0～0x0F
  private static int      driveOption_ctrl[] = new int[16];  ///< オプションの上位4bit
  private static int      driveOption_result[] = new int[16];  ///< ドライバオプション結果値
  private static boolean    driveOption_isExec;          // 実施したか？！

  private static long    tickSenseDeviceStatus;///< センスデバイスのカウンタ
  private static int      senseDeviceStatus[] = new int[8] ;  ///< 現在のセンスデバイスのステータス
  private static int equalStatusCounter = 0;  

  private static byte writeData[] = new byte[0x10000];


  private static enum EMode {
    Mode_None,              ///< 処理なし

    Mode_SenseDeivceStatus_WaitStatus90,///< Statusには90を返す。
    Mode_SenseDeivceStatus_WaitStatusD0,///< StatusにはDOを返す,
    Mode_SenseDeivceStatus_CheckStatus, ///< データの戻りを記憶する
    Mode_SenseDeivceStatus_ReturnStatus,///< データの戻りをキャッシュ値にする
    Mode_SenseDeivceStatus_WaitStatus80,///< Statusには80を返す。
  
    Mode_SenseInterruptStatus_StatusD0,  ///< StatusにはD0を返す。
    Mode_SenseInterruptStatus_NoWait,  ///< NOWAIT
  
    Mode_Seek_StatusD0,          ///< StatusにはD0を返す。
    Mode_Seek_NoWait,          ///< NOWAIT

    Mode_Recalibrate_Status90,      ///< Statusには90を返す。
    Mode_Recalibrate_NoWait,      ///< NOWAIT

    Mode_ReadData_Command,        ///< コマンド処理中。Statusには0x90を返す。
    Mode_ReadData_Receive,        ///< 受信処理中。Statusには0x10を返す。
    Mode_ReadData_SoftReceive,      ///< 受信処理中。Statusには0xF0を返す。
    Mode_ReadData_Response,        ///< レスポンス処理中。Statusには0xD0を返す。
    Mode_ReadData_After,        ///< レスポンス終わり。0x80を返す。

    Mode_WriteData_Command,        ///< コマンド処理中。Statusには0x90を返す。
    Mode_WriteData_Send,        ///< 送信処理中。Statusには0xB0を返す。
    Mode_WriteData_Response,      ///< レスポンスより中。Statusには0xD0を返す。

    Mode_ReadId_Command,        ///< コマンド処理中。Statusには0x90を返す。
    Mode_ReadId_Wait,          ///< 待ち。Statusには0x70を返す。
    Mode_ReadId_Result,          // リザルト処理中。Statusには0xD0を返す。

  };
  private static volatile EMode  mode = EMode.Mode_None;          ///< 現在のモード
  private static int        modeCounter;      ///< 汎用カウンタ
  private static volatile int  readDataCounter;    ///< 読み込み汎用カウンタ
  private static volatile int  writeDataCounter;
  
}  //class FUK

