//========================================================================================
//  SpritePatternViewer.java
//    en:Sprite pattern viewer
//    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;

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;  //TextLayout
import java.awt.geom.*;  //Rectangle2D
import java.awt.image.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;  //ChangeListener

public class SpritePatternViewer {

  public static final boolean SPV_ON = true;

  //定数
  static final int SPV_COLS = 16;
  static final int SPV_ROWS = 16;
  static final int SPV_LEFT_MARGIN = 20;
  static final int SPV_TOP_MARGIN = 20;
  static final int SPV_SPACE = 4;
  static final int SPV_WIDTH = SPV_LEFT_MARGIN + (32 + SPV_SPACE) * SPV_COLS;
  static final int SPV_HEIGHT = SPV_TOP_MARGIN + (32 + SPV_SPACE) * SPV_ROWS;
  static final int SPV_BACKGROUND_RGB = 0xff333333;  //背景色
  static final int SPV_FOREGROUND_RGB = 0xffffffff;  //文字色
  static final String SPV_FONT_NAME = "Dialog";
  static final int SPV_FONT_STYLE = Font.PLAIN;
  static final int SPV_FONT_SIZE = 12;

  //イメージ
  static BufferedImage spvImage;  //イメージ

  //ビットマップ
  static int[] spvBitmap;  //ビットマップ

  //キャンバス
  static JPanel spvCanvas;  //キャンバス
  static JScrollPane spvScrollPane;  //スクロールペイン

  //バンク
  static int spvBankNumber;  //バンク
  static JSpinner spvBankSpinner;  //バンクスピナー
  static SpinnerNumberModel spvBankModel;  //バンクスピナーのスピナーモデル

  //パレットブロック
  //  スプライトは16x16だがBGが8x8で使用する場合があるので8x8毎にパレットブロックを持たせる
  static final int[] spvLastBlockArray = new int[4 * 4096];  //パターン→前回のパレットブロック<<4
  static final int[] spvBlockArray = new int[4 * 4096];  //パターン→パレットブロック<<4
  static boolean spvAutoBlock;  //true=自動,false=固定
  static int spvFixedBlock;  //固定パレットブロック。0～15
  static JSpinner spvBlockSpinner;  //パレットブロックスピナー
  static SpinnerNumberModel spvBlockModel;  //パレットブロックスピナーのスピナーモデル

  //停止
  static boolean spvStopped;  //true=停止中

  //ウインドウ
  static JFrame spvFrame;

  //タイマー
  public static final int SPV_INTERVAL = 10;
  public static int spvTimer;

  //spvInit ()
  //  初期化
  public static void spvInit () {

    //バンク
    spvBankNumber = 0;

    //パレットブロック
    for (int n = 0; n < 4 * 4096; n++) {  //8x8パターン番号
      spvLastBlockArray[n] = 1 << 4;  //8x8パレットブロック1<<4
    }
    spvAutoBlock = true;
    spvFixedBlock = 1;

    //停止
    spvStopped = false;

    //ウインドウ
    spvFrame = null;

    //タイマー
    spvTimer = 0;

  }  //spvInit

  //spvStart ()
  //  開始
  public static void spvStart () {
    if (RestorableFrame.rfmGetOpened (Settings.SGS_SPV_FRAME_KEY)) {
      spvOpen ();
    }
  }  //spvStart

  //spvOpen ()
  //  開く
  public static void spvOpen () {
    if (spvFrame == null) {
      spvMakeFrame ();
    } else {
      spvUpdateFrame ();
    }
    XEiJ.dbgVisibleMask |= XEiJ.DBG_SPV_VISIBLE_MASK;
    XEiJ.pnlExitFullScreen (false);
    spvFrame.setVisible (true);
  }  //spvOpen

  //spvMakeFrame ()
  //  作る
  //  ここでは開かない
  static void spvMakeFrame () {

    //イメージ
    spvImage = new BufferedImage (SPV_WIDTH, SPV_HEIGHT, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = spvImage.createGraphics ();
    g2.setColor (new Color (SPV_BACKGROUND_RGB));
    g2.fillRect (0, 0, SPV_WIDTH, SPV_HEIGHT);
    g2.setColor (new Color (SPV_FOREGROUND_RGB));
    Font font = new Font (SPV_FONT_NAME, SPV_FONT_STYLE, SPV_FONT_SIZE);
    g2.setFont (font);
    FontRenderContext frc = g2.getFontRenderContext ();
    for (int i = 0; i < 16; i++) {
      String s = String.format ("x%X", i);
      TextLayout tl = new TextLayout (s, font, frc);
      Rectangle2D r = tl.getBounds ();
      int rx = (int) Math.round (r.getX ());
      int ry = (int) Math.round (r.getY ());
      int rw = (int) Math.round (r.getWidth ());
      int rh = (int) Math.round (r.getHeight ());
      int bx = SPV_LEFT_MARGIN + (32 + SPV_SPACE) * i + 32 / 2;  //下辺の中央を合わせる位置
      int by = SPV_TOP_MARGIN - SPV_SPACE;
      g2.drawString (s, bx - rx - rw / 2, by - ry - rh);
    }
    for (int i = 0; i < 16; i++) {
      String s = String.format ("%Xx", i);
      TextLayout tl = new TextLayout (s, font, frc);
      Rectangle2D r = tl.getBounds ();
      int rx = (int) Math.round (r.getX ());
      int ry = (int) Math.round (r.getY ());
      int rw = (int) Math.round (r.getWidth ());
      int rh = (int) Math.round (r.getHeight ());
      int bx = SPV_LEFT_MARGIN - SPV_SPACE;  //右辺の中央を合わせる位置
      int by = SPV_TOP_MARGIN + (32 + SPV_SPACE) * i + 32 / 2;
      g2.drawString (s, bx - rx - rw, by - ry - rh / 2);
    }

    //ビットマップ
    spvBitmap = ((DataBufferInt) spvImage.getRaster ().getDataBuffer ()).getData ();

    //キャンバス
    spvCanvas = ComponentFactory.setFixedSize (
      new JPanel () {
        @Override protected void paintComponent (Graphics g) {
          super.paintComponent (g);
          g.drawImage (spvImage, 0, 0, null);
        }
        @Override protected void paintBorder (Graphics g) {
        }
        @Override protected void paintChildren (Graphics g) {
        }
      },
      SPV_WIDTH, SPV_HEIGHT);
    spvCanvas.setBackground (new Color (SPV_BACKGROUND_RGB));
    spvCanvas.setOpaque (true);
    spvScrollPane = ComponentFactory.setPreferredSize (
      new JScrollPane (spvCanvas),
      SPV_WIDTH + 20, SPV_HEIGHT + 20);

    //アクションリスナー
    ActionListener listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
        case "Stop":
          spvStopped = ((JCheckBox) source).isSelected ();
          break;
        case "Auto ":
          spvAutoBlock = true;
          break;
        case "Fixed ":
          spvAutoBlock = false;
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };

    //ウインドウ
    ButtonGroup paletGroup = new ButtonGroup ();
    spvFrame = Multilingual.mlnTitle (
      ComponentFactory.createRestorableSubFrame (
        Settings.SGS_SPV_FRAME_KEY,
        "Sprite Pattern Viewer",
        null,
        ComponentFactory.createBorderPanel (
          0, 0,
          //CENTER
          spvScrollPane,
          //NORTH
          ComponentFactory.createHorizontalBox (
            Box.createHorizontalGlue (),
            Multilingual.mlnText (
              ComponentFactory.createLabel ("Bank "),
              "ja", "バンク "),
            spvBankSpinner = ComponentFactory.createNumberSpinner (
              spvBankModel = new SpinnerNumberModel (spvBankNumber, 0, 15, 1),
              2,
              new ChangeListener () {
                @Override public void stateChanged (ChangeEvent ce) {
                  spvBankNumber = spvBankModel.getNumber ().intValue ();
                }
              }
              ),
            Box.createHorizontalStrut (20),
            Multilingual.mlnText (
              ComponentFactory.createLabel ("Palet "),
              "ja", "パレット "),
            Multilingual.mlnText (
              ComponentFactory.createRadioButton (paletGroup, spvAutoBlock, "Auto ", listener),
              "ja", "自動 "),
            Multilingual.mlnText (
              ComponentFactory.createRadioButton (paletGroup, !spvAutoBlock, "Fixed ", listener),
              "ja", "固定 "),
            spvBlockSpinner = ComponentFactory.createNumberSpinner (
              spvBlockModel = new SpinnerNumberModel (spvFixedBlock, 0, 15, 1),
              2,
              new ChangeListener () {
                @Override public void stateChanged (ChangeEvent ce) {
                  spvFixedBlock = spvBlockModel.getNumber ().intValue ();
                }
              }
              ),
            Box.createHorizontalStrut (20),
            Multilingual.mlnText (
              ComponentFactory.createCheckBox (spvStopped, "Stop", listener),
              "ja", "停止"),
            Box.createHorizontalGlue ()
            )  //HorizontalBox
          )  //BorderPanel
        ),  //SubFrame
      "ja", "スプライトパターンビュア");

    //ウインドウリスナー
    ComponentFactory.addListener (
      spvFrame,
      new WindowAdapter () {
        @Override public void windowClosing (WindowEvent we) {
          XEiJ.dbgVisibleMask &= ~XEiJ.DBG_SPV_VISIBLE_MASK;
        }
      });

  }  //spvMakeFrame

  //spvUpdateFrame ()
  //  更新する
  static void spvUpdateFrame () {
    if (spvFrame == null) {  //未初期化
      return;
    }
    if (spvStopped) {  //停止中
      return;
    }

    if (spvAutoBlock) {  //自動
      //パターンに割り当てられたパレットブロックを集める
      //  一旦すべて消す
      Arrays.fill (spvBlockArray, -1);
      //  スプライトから集める
      for (int sn = 0; sn < SpriteScreen.sprNumberOfSprites; sn++) {  //スプライト番号
        if (256 <= sn && sn < 256 + 8) {  //欠番
          continue;
        }
        if (SpriteScreen.sprPrw[sn] != 0) {  //表示されている
          int n = 4 * SpriteScreen.sprNumPort[sn];  //8x8パターン番号
          int p = SpriteScreen.sprColPort[sn];  //パレットブロック<<4
          for (int cr = 0; cr < 4; cr++) {  //16x16内8x8桁行
            if (spvBlockArray[n] < 0) {
              spvBlockArray[n] =
                spvLastBlockArray[n] = p;
            }
            n++;
          }
        }
      }
      //  バックグラウンドから集める
      int tm = 0;  //表示されているテキストページのマスク
      if ((SpriteScreen.sprReg4BgCtrlPort & 1) != 0) {  //BG0が表示されている
        tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 1) & 3);  //BG0に割り当てられているテキストページ
      }
      if (((SpriteScreen.sprReg8ResoPort & 3) == 0 ||  //256x256または
           SpriteScreen.spr512bg1) &&  //512x512でBG1を表示かつ
          (SpriteScreen.sprReg4BgCtrlPort & 8) != 0) {  //BG1が表示されている
        tm |= 1 << ((SpriteScreen.sprReg4BgCtrlPort >> 4) & 3);  //BG1に割り当てられているテキストページ
      }
      if ((SpriteScreen.sprReg8ResoPort & 3) == 0) {  //256x256
        for (int tp = 0; tp < 4; tp++) {  //テキストページ
          if ((tm & (1 << tp)) != 0) {  //表示されている
            int o = 4096 * tp;  //テキスト配列インデックス開始位置
            for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
              int n = SpriteScreen.sprTNum[o + i] >> 3;  //8x8パターン番号
              int p = SpriteScreen.sprTColPort[o + i];  //パレットブロック<<4
              if (spvBlockArray[n] < 0) {
                spvBlockArray[n] =
                  spvLastBlockArray[n] = p;
              }
            }
          }
        }
      } else {  //512x512
        for (int tp = 0; tp < 4; tp++) {  //テキストページ
          if ((tm & (1 << tp)) != 0) {  //表示されている
            int o = 4096 * tp;  //テキスト配列インデックス開始位置
            for (int i = 0; i < 4096; i++) {  //テキスト配列インデックス
              int n = SpriteScreen.sprTNum[o + i] >> 1;  //8x8パターン番号
              int p = SpriteScreen.sprTColPort[o + i];  //パレットブロック<<4
              for (int cr = 0; cr < 4; cr++) {  //16x16内8x8桁行
                if (spvBlockArray[n] < 0) {
                  spvBlockArray[n] =
                    spvLastBlockArray[n] = p;
                }
                n++;
              }
            }
          }
        }
      }
      //  集められなかったものは前回と同じにする
      for (int n = 0; n < 4 * 4096; n++) {  //8x8パターン番号
        if (spvBlockArray[n] < 0) {
          spvBlockArray[n] = spvLastBlockArray[n];
        }
      }
    } else {  //固定
      Arrays.fill (spvBlockArray, spvFixedBlock << 4);
    }

    //パターンを表示する
    int a = 32 * SPV_COLS * SPV_ROWS * spvBankNumber;  //パターン配列インデックス
    for (int rr = 0; rr < SPV_ROWS; rr++) {  //16x16行
      int yy = SPV_TOP_MARGIN + (32 + SPV_SPACE) * rr;  //16x16パターン左上Y座標
      for (int cc = 0; cc < SPV_COLS; cc++) {  //16x16桁
        int xx = SPV_LEFT_MARGIN + (32 + SPV_SPACE) * cc;  //16x16パターン左上X座標
        int ii = xx + SPV_WIDTH * yy;  //16x16パターン左上ビットマップインデックス
        int jj = ii - 2;
        for (int vv = 0; vv < 16; vv++) {
          spvBitmap[jj] =
            spvBitmap[jj + 1] =
              spvBitmap[jj + SPV_WIDTH] =
                spvBitmap[jj + SPV_WIDTH + 1] = SPV_BACKGROUND_RGB;
          jj += SPV_WIDTH * 2;
        }
        int n = 4 * (SPV_COLS * SPV_ROWS * spvBankNumber + cc + SPV_COLS * rr);  //8x8パターン番号
        for (int c = 0; c < 2; c++) {  //16x16内8x8桁
          int x = xx + 16 * c;  //8x8パターン左上X座標
          for (int r = 0; r < 2; r++) {  //16x16内8x8行
            int y = yy + 16 * r;  //8x8パターン左上Y座標
            int i = x + SPV_WIDTH * y;  //8x8パターン左上ビットマップインデックス
            int p = spvBlockArray[n];  //8x8パレットブロック<<4
            int j = ii - 2 + SPV_WIDTH * 2 * (p >> 4);
            spvBitmap[j] =
              spvBitmap[j + 1] =
                spvBitmap[j + SPV_WIDTH] =
                  spvBitmap[j + SPV_WIDTH + 1] = SPV_FOREGROUND_RGB;
            for (int v = 0; v < 8; v++) {  //8x8内1x1行
              int d = SpriteScreen.sprPatPort[a++];  //8x1データ
              for (int u = 0; u < 8; u++) {  //8x8内1x1桁
                spvBitmap[i] =
                  spvBitmap[i + 1] =
                    spvBitmap[i + SPV_WIDTH] =
                      spvBitmap[i + SPV_WIDTH + 1] = VideoController.vcnPal32TS[p + ((d >> ((~u & 7) << 2)) & 15)];
                i += 2;
              }
              i += 2 * SPV_WIDTH - 2 * 8;
            }
            n++;
          }
        }
      }
    }
    spvCanvas.repaint ();

  }  //spvUpdateFrame

}  //class SpritePatternViewer



