//========================================================================================
//  ByteQueue.java
//    en:Byte queue
//    ja:バイトキュー
//  Copyright (C) 2003-2025 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

public class ByteQueue {

  private static final class Block {
    private volatile Block p = null;  //前のブロック。先頭のブロックの方向のリンク。null=先頭のブロック
    private volatile Block n = null;  //後のブロック。末尾のブロックの方向のリンク。null=末尾のブロック
    
    private static final int s = 1 << 16;  //配列のバイト数。2の累乗
    private static final byte[] a = new byte[s];  //配列
    private volatile long w = 0;  //書き込んだバイト数
    private volatile long r = w;  //読み出したバイト数
    //  a[(int)w&(s-1)]  書き込む位置
    //  a[(int)r&(s-1)]  読み出す位置
    //  r+s-w            書き込めるバイト数
    //  w-r              読み出せるバイト数
    //  w==r+s           満
    //  r==w             空
    //  0<=w-r<=s
    //  r<=w<=r+s

    //length = clear ()
    //  空にする。読み飛ばしたバイト数を返す
    private int clear () {
      return skip (used ());
    }  //clear

    //data = read ()
    //  1バイト読み出して0x00～0xffの範囲で返す。-1=空
    private int read () {
      return r != w ? a[(int) (r++) & (s - 1)] & 0xff : -1;
    }  //read

    //length = read (array, offset, length)
    //  配列へ読み出す。読み出せたバイト数を返す
    private int read (byte[] array, int offset, int length) {
      length = Math.min (length, (int) (w - r));  //読み出せるバイト数
      int offset0 = offset;
      for (;;) {
        int k = Math.min (length, s - ((int) r & (s - 1)));  //読み出す位置から末尾までのバイト数
        if (k == 0) {
          return offset - offset0;
        }
        System.arraycopy (a, (int) r & (s - 1),
                          array, offset,
                          k);
        r += k;
        offset += k;
        length -= k;
      }
    }  //read

    //length = skip (length)
    //  読み飛ばす。読み飛ばせた長さを返す
    private int skip (int length) {
      length = Math.min (length, (int) (w - r));  //読み飛ばせるバイト数
      r += length;
      return length;
    }  //skip

    //length = unused ()
    //  書き込めるバイト数を返す。0=満
    private int unused () {
      return (int) (r + s - w);
    }  //unused

    //length = used ()
    //  読み出せるバイト数を返す。0=空
    private int used () {
      return (int) (w - r);
    }  //used

    //write (data)
    //  1バイト書き込む。書き込めたバイト数を返す
    private int write (int data) {
      if (w == r + s) {
        return 0;
      }
      a[(int) (w++) & (s - 1)] = (byte) data;
      return 1;
    }  //write

    //length = write (array, offset, length)
    //  配列から書き込む。書き込めたバイト数を返す
    private int write (byte[] array, int offset, int length) {
      length = Math.min (length, (int) (r + s - w));  //書き込めるバイト数
      int offset0 = offset;
      for (;;) {
        int k = Math.min (length, s - ((int) w & (s - 1)));  //書き込む位置から末尾までのバイト数
        if (k == 0) {
          return offset - offset0;
        }
        System.arraycopy (array, offset,
                          a, (int) w & (s - 1),
                          k);
        w += k;
        offset += k;
        length -= k;
      }
    }  //write

  }  //class Block

  private volatile Block h = new Block ();  //先頭のブロック。次に読み出すブロック。h.p==null
  private volatile Block t = h;  //末尾のブロック。次に書き込むブロック。t.n==null

  private static final int s = 0x7fffffff;  //上限のバイト数
  private volatile long w = 0;  //書き込んだバイト数
  private volatile long r = w;  //読み出したバイト数
  //  r+s-w            書き込めるバイト数
  //  w-r              読み出せるバイト数
  //  w==r+s           満
  //  r==w             空
  //  0<=w-r<=s
  //  r<=w<=r+s

  //length = clear ()
  //  空にする。読み飛ばしたバイト数を返す
  protected int clear () {
    return skip (used ());
  }  //clear

  //data = read ()
  //  1バイト読み出して0x00～0xffの範囲で返す。-1=空
  protected int read () {
    int data = h.read ();
    if (data < 0) {
      if (h == t) {
        return data;
      }
      Block n = h.n;
      n.p = null;
      h.n = null;
      h = n;
      data = h.read ();
    }
    r++;
    return data;
  }  //read

  //length = read (array, offset, length)
  //  配列へ読み出す。読み出せたバイト数を返す
  protected int read (byte[] array, int offset, int length) {
    if (length == 0) {
      return 0;
    }
    //length = Math.min (length, (int) (w - r));  //読み出せるバイト数
    long r0 = r;
    int k = h.read (array, offset, length);
    r += k;
    offset += k;
    length -= k;
    while (length != 0) {
      if (h == t) {
        return (int) (r - r0);
      }
      Block n = h.n;
      n.p = null;
      h.n = null;
      h = n;
      k = h.read (array, offset, length);
      r += k;
      offset += k;
      length -= k;
    }
    return (int) (r - r0);
  }  //read

  //length = skip (length)
  //  読み飛ばす。読み飛ばせた長さを返す
  protected int skip (int length) {
    if (length == 0) {
      return 0;
    }
    //length = Math.min (length, (int) (w - r));  //読み出せるバイト数
    long r0 = r;
    int k = h.skip (length);
    r += k;
    length -= k;
    while (length != 0) {
      if (h == t) {
        return (int) (r - r0);
      }
      Block n = h.n;
      n.p = null;
      h.n = null;
      h = n;
      k = h.skip (length);
      r += k;
      length -= k;
    }
    return (int) (r - r0);
  }  //skip

  //length = unused ()
  //  書き込めるバイト数を返す。0=満
  protected int unused () {
    return (int) (r + s - w);
  }  //unused

  //length = used ()
  //  読み出せるバイト数を返す。0=空
  protected int used () {
    return (int) (w - r);
  }  //used

  //length = write (data)
  //  1バイト書き込む。書き込めたバイト数を返す
  protected int write (int data) {
    if (w == r + s) {
      return 0;
    }
    if (t.write (data) == 0) {
      Block n = new Block ();
      n.p = t;
      t.n = n;
      t = n;
      t.write (data);
    }
    w++;
    Thread wt = waitThread;  //待機中のスレッドが
    if (wt != null) {  //あれば
      wt.interrupt ();  //割り込む
    }
    return 1;
  }  //write

  //length = write (array, offset, length)
  //  配列から書き込む。書き込めたバイト数を返す
  protected int write (byte[] array, int offset, int length) {
    if (length == 0) {
      return 0;
    }
    length = Math.min (length, (int) (r + s - w));  //書き込めるバイト数
    long w0 = w;
    int k = t.write (array, offset, length);
    w += k;
    offset += k;
    length -= k;
    while (length != 0) {
      Block n = new Block ();
      n.p = t;
      t.n = n;
      t = n;
      k = t.write (array, offset, length);
      w += k;
      offset += k;
      length -= k;
    }
    Thread wt = waitThread;  //待機中のスレッドが
    if (wt != null) {  //あれば
      wt.interrupt ();  //割り込む
    }
    return (int) (w - w0);
  }  //write


  private volatile Thread waitThread = null;  //waitAndReadで待機中のスレッド。null=ない
  private volatile int waitNumber;  //waitAndReadの呼び出し番号。waitAndReadが書き込む
  private volatile int cancelNumber;  //waitAndReadのキャンセル番号。cancelが書き込む

  //cancel ()
  //  waitAndReadをキャンセルする
  protected void cancel () {
    Thread wt = waitThread;  //待機中のスレッドが
    if (wt != null) {  //あれば
      cancelNumber = waitNumber;  //キャンセルして
      wt.interrupt ();  //割り込む
    }
  }  //cancel

  //data = waitAndRead ()
  //  1バイト読み出して0x00～0xffの範囲で返す
  //  1バイト読み出せるまでブロックする
  //  キャンセルされたとき-1を返す
  //  readと同じスレッドで呼び出すこと
  protected int waitAndRead () {
    int data = -1;
    waitThread = Thread.currentThread ();  //待機中のスレッドは現在のスレッド
    waitNumber++;
    while (waitNumber != cancelNumber) {  //キャンセルされていなければ繰り返す
      data = read ();  //データを読み出す。なければ-1
      if (0 <= data || waitNumber == cancelNumber) {  //データがあるかキャンセルされたら
        break;  //終わり
      }
      try {
        //while (true) {  //割り込まれなくても3億年経つと起きてしまうが条件が揃わなければ二度寝するだけなので以下略
        Thread.sleep (Long.MAX_VALUE);  //割り込みを待つ
        //}
      } catch (InterruptedException ie) {
      }
    }
    waitThread = null;  //待機中のスレッドはない
    return data;  //データを返す
  }  //waitAndRead

  //length = waitAndRead (array, offset, length)
  //  配列へ読み出す。読み出せたバイト数を返す
  //  少なくとも1バイト読み出せるまでブロックする
  //  キャンセルされたとき-1を返す
  //  readと同じスレッドで呼び出すこと
  protected int waitAndRead (byte[] array, int offset, int length) {
    if (length == 0) {
      return 0;
    }
    int data = waitAndRead ();  //1バイト読み出す
    if (data < 0) {  //キャンセルされた
      return -1;
    }
    array[offset] = (byte) data;  //1バイト目
    return 1 + read (array, offset + 1, Math.min (length - 1, used ()));  //2バイト目以降
  }  //waitAndRead


}  //class ByteQueue
