package com.bytezone.diskbrowser.utilities;

public abstract class CPU
{
  // registers
  private byte xReg;
  private byte yReg;
  private byte aReg;

  // status register
  protected boolean carry;
  protected boolean zero;
  protected boolean interrupt;
  protected boolean decimal;
  protected boolean breakFlag;
  protected boolean overflow;       // Clancy
  protected boolean negative;

  // stack
  private final byte[] stack = new byte[0x100];
  private int sp = stack.length;

  private boolean debug = false;

  protected void setDebug (boolean value)
  {
    debug = value;
  }

  protected void and (byte mask)              // AND
  {
    aReg &= mask;
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("AND");
  }

  protected void asl ()                       // ASL
  {
    carry = (aReg & 0x80) != 0;       // move bit 7 into the carry flag
    aReg = (byte) (aReg << 1);        // shift left
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("ASL");
  }

  // unfinished
  protected void bit (byte value)             // BIT
  {
    byte b = (byte) (aReg & value);
    zero = b == 0;
    overflow = (value & 0x40) != 0;
    negative = (value & 0x80) != 0;
    debug ("BIT");
  }

  protected void clc ()                       // CLC
  {
    carry = false;
    debug ("CLC");
  }

  protected void cli ()                       // CLI
  {
    interrupt = false;
    debug ("CLI");
  }

  protected void clv ()                       // CLV
  {
    overflow = false;
    debug ("CLV");
  }

  protected void cmp (byte value)             // CMP
  {
    int tmp = (aReg & 0xFF) - (value & 0xFF);
    zero = tmp == 0;
    negative = (tmp & 0x80) != 0;
    carry = (aReg & 0xFF) >= (value & 0xFF);
    debug ("CMP");
  }

  protected void cpx (byte value)             // CPX
  {
    int tmp = (xReg & 0xFF) - (value & 0xFF);
    zero = tmp == 0;
    negative = (tmp & 0x80) != 0;
    carry = (xReg & 0xFF) >= (value & 0xFF);
    debug ("CPX");
  }

  protected void cpy (byte value)             // CPY
  {
    int tmp = (yReg & 0xFF) - (value & 0xFF);
    zero = tmp == 0;
    negative = (tmp & 0x80) != 0;
    carry = (yReg & 0xFF) >= (value & 0xFF);
    debug ("CPY");
  }

  protected byte dec (byte value)             // DEC
  {
    value = (byte) ((value & 0xFF) - 1);
    zero = value == 0;
    negative = (value & 0x80) != 0;
    debug ("DEC");
    return value;
  }

  protected byte inc (byte value)             // INC
  {
    value = (byte) ((value & 0xFF) + 1);
    zero = value == 0;
    negative = (value & 0x80) != 0;
    debug ("INC");
    return value;
  }

  protected void inx ()                       // INX
  {
    xReg = (byte) ((xReg & 0xFF) + 1);
    xReg &= 0xFF;
    zero = xReg == 0;
    negative = (xReg & 0x80) != 0;
    debug ("INX");
  }

  protected void lda (byte value)             // LDA
  {
    aReg = value;
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("LDA");
  }

  protected void lda (byte[] buffer, int offset)        // LDA
  {
    aReg = buffer[offset];
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("LDA");
  }

  protected void ldx (byte value)             // LDX
  {
    xReg = value;
    zero = xReg == 0;
    negative = (xReg & 0x80) != 0;
    debug ("LDX");
  }

  protected void ldy (byte value)             // LDY
  {
    yReg = value;
    zero = yReg == 0;
    negative = (yReg & 0x80) != 0;
    debug ("LDY");
  }

  protected void lsr ()                       // LSR
  {
    negative = false;
    carry = (aReg & 0x01) != 0;
    aReg = (byte) ((aReg & 0xFF) >>> 1);
    zero = aReg == 0;
    debug ("LSR");
  }

  protected void ora (byte mask)              // ORA
  {
    aReg |= mask;
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("ORA");
  }

  protected void php ()                       // PHP
  {
    byte flags = 0;
    if (negative)
      flags |= 0x80;
    if (overflow)
      flags |= 0x40;
    if (breakFlag)
      flags |= 0x10;
    if (decimal)
      flags |= 0x08;
    if (interrupt)
      flags |= 0x04;
    if (zero)
      flags |= 0x02;
    if (carry)
      flags |= 0x01;
    stack[--sp] = flags;
    debug ("PHP");
  }

  protected void plp ()                       // PLP
  {
    byte flags = stack[sp++];
    negative = (flags & 0x80) != 0;
    overflow = (flags & 0x40) != 0;
    breakFlag = (flags & 0x10) != 0;
    decimal = (flags & 0x08) != 0;
    interrupt = (flags & 0x04) != 0;
    zero = (flags & 0x02) != 0;
    carry = (flags & 0x01) != 0;
    debug ("PLP");
  }

  protected void pha ()                       // PHA
  {
    stack[--sp] = aReg;
    debug ("PHA");
  }

  protected void pla ()                       // PLA
  {
    aReg = stack[sp++];
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("PLA");
  }

  protected void rol ()                       // ROL
  {
    boolean tempCarry = carry;
    carry = (aReg & 0x80) != 0;       // move bit 7 into the carry flag
    aReg = (byte) (aReg << 1);        // shift left
    if (tempCarry)
      aReg |= 0x01;                   // move old carry into bit 0
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("ROL");
  }

  protected byte rol (byte value)             // ROL
  {
    boolean tempCarry = carry;
    carry = (value & 0x80) != 0;      // move bit 7 into the carry flag
    value = (byte) (value << 1);
    if (tempCarry)
      value |= 0x01;                  // move old carry into bit 0
    zero = value == 0;
    negative = (value & 0x80) != 0;
    debug ("ROL");

    return value;
  }

  protected byte ror (byte value)             // ROR
  {
    boolean tempCarry = carry;
    carry = (value & 0x01) != 0;      // move bit 0 into the carry flag
    value = (byte) ((value & 0xFF) >>> 1);
    if (tempCarry)
      value |= 0x80;                  // move old carry into bit 7
    zero = value == 0;
    negative = (value & 0x80) != 0;
    debug ("ROR");

    return value;
  }

  protected byte sta ()                       // STA
  {
    debug ("STA");
    return aReg;
  }

  protected void sta (byte[] buffer, int offset)        // STA
  {
    buffer[offset] = aReg;
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("STA");
  }

  protected byte stx ()                       // STX
  {
    debug ("STX");
    return xReg;
  }

  protected byte sty ()                       // STY
  {
    debug ("STY");
    return yReg;
  }

  protected void txa ()                       // TXA
  {
    aReg = xReg;
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("TXA");
  }

  protected void tya ()                       // TYA
  {
    aReg = yReg;
    zero = aReg == 0;
    negative = (aReg & 0x80) != 0;
    debug ("TYA");
  }

  protected void tax ()                       // TAX
  {
    xReg = aReg;
    zero = xReg == 0;
    negative = (xReg & 0x80) != 0;
    debug ("TAX");
  }

  protected void tay ()                       // TAY
  {
    yReg = aReg;
    zero = yReg == 0;
    negative = (yReg & 0x80) != 0;
    debug ("TAY");
  }

  protected void sei ()                       // SEI
  {
    interrupt = true;
    debug ("SEI");
  }

  protected String debugString ()
  {
    return "";
  }

  protected void debug (String cmd)
  {
    if (debug)
    {
      String flags = String.format ("%s %s - %s %s %s %s %s", negative ? "1" : ".",
          overflow ? "1" : ".", breakFlag ? "1" : ".", decimal ? "1" : ".",
          interrupt ? "1" : ".", zero ? "1" : ".", carry ? "1" : ".");
      System.out.printf ("%3s  A: %02X  X: %02X  Y: %02X  %s  %s%n", cmd, aReg, xReg,
          yReg, flags, debugString ());
    }
  }

  protected int indirectY (int base, byte offset, byte page)
  {
    if (debug)
      System.out.printf ("base: %,6d, page: %02X, offset: %02X, yReg: %02X%n", base, page,
          offset, yReg);

    return ((page & 0xFF) * 256 + (offset & 0xFF)) - base + (yReg & 0xFF);
  }
}