#ifndef SH4_GUEST_H
#define SH4_GUEST_H

#include <stdint.h>
#include <stdlib.h>
#include "jit/jit_guest.h"

/*
 * sh4 guest context
 */

/* sr bits  */

/* true / false condition or carry/borrow bit */
#define T_BIT 0
/* specifies a saturation operation for a MAC instruction */
#define S_BIT 1
/* interrupt mask level */
#define I_BIT 4
/* used by the DIV0S, DIV0U, and DIV1 instructions */
#define Q_BIT 8
#define M_BIT 9
/* an FPU instr causes a general FPU disable exception */
#define FD_BIT 15
/* interrupt requests are masked */
#define BL_BIT 28
/* general register bank specifier in privileged mode (set
   to 1 by a reset, exception, or interrupt) */
#define RB_BIT 29
/* processor mode (0 is user mode, 1 is privileged mode) */
#define MD_BIT 30

#define T_MASK (1u << T_BIT)
#define S_MASK (1u << S_BIT)
#define I_MASK 0xf0
#define Q_MASK (1u << Q_BIT)
#define M_MASK (1u << M_BIT)
#define FD_MASK (1u << FD_BIT)
#define BL_MASK (1u << BL_BIT)
#define RB_MASK (1u << RB_BIT)
#define MD_MASK (1u << MD_BIT)

#define SR_MASK                                                                \
  (MD_MASK | RB_MASK | BL_MASK | FD_MASK | M_MASK | Q_MASK | I_MASK | S_MASK | \
   T_MASK)

/* fpscr bits */

/* denormalization mode */
#define DN_BIT 18
/* precision mode */
#define PR_BIT 19
/* transfer size mode */
#define SZ_BIT 20
/* floating-point register bank */
#define FR_BIT 21

#define RM_MASK 0x3
#define FLAG_MASK 0x7c
#define ENABLE_MASK 0xf80
#define CAUSE_MASK 0x3f000
#define DN_MASK (1u << DN_BIT)
#define PR_MASK (1u << PR_BIT)
#define SZ_MASK (1u << SZ_BIT)
#define FR_MASK (1u << FR_BIT)

#define FPSCR_MASK                                                      \
  (RM_MASK | FLAG_MASK | ENABLE_MASK | CAUSE_MASK | DN_MASK | PR_MASK | \
   SZ_MASK | FR_MASK)

struct sh4_context {
  /* there are 24 32-bit general registers, r0_bank0-r7_bank0, r0_bank1-r7_bank1
     and r8-r15. r contains the active bank's r0-r7 as well as r8-r15. ralt
     contains the inactive bank's r0-r7 and is swapped in when the processor
     mode changes */
  uint32_t r[16], ralt[8];

  /* there are 32 32-bit floating point registers, fr0-fr15 and xf0-xf15. these
     registers are banked, and swapped with eachother when the bank bit of
     FPSCR changes. in addition, fr0–fr15 can be used as the eight registers
     dr0/2/4/6/8/10/12/14 (double-precision, or pair registers) or the four
     registers fv0/4/8/12 (vector registers). while xf0-xf15 can be used as
     the eight registers xd0/2/4/6/8/10/12/14 (pair registers) or register
     matrix XMTRX

     note, the sh4 does not support endian conversion for 64-bit data.
     therefore, if 64-bit floating point access is performed in little endian
     mode, the upper and lower 32 bits will be reversed. for example, dr2
     aliases fr2 and fr3, but fr3 is actually the low-order word

     in order to avoid swapping the words in every double-precision opcode, the
     mapping for each pair of single-precision registers is instead swapped by
     XOR'ing the actual index with 1. for example, fr2 becomes fr[3] and fr3
     becomes fr[2], enabling dr2 to perfectly alias fr[2]

     note note, this incorrectly causes fv registers to be swizzled. fv0 should
     be loaded as {fr0, fr1, fr2, fr3} but it's actually loaded as
     {fr1, fr0, fr3, fr2}. however, due to the way the FV registers are
     used (FIPR and FTRV) this doesn't actually affect the results */
  uint32_t fr[16], xf[16];

  /* sr_t and sr_s are the S and T bits from the status register, they are
     kept in their own unique context slots to avoid excessive shifting and
     masking to load / store them

     sr_m and sr_qm are a similar story. sr_m stores the M bit from the status
     register, while sr_qm stores a value that repsresents if Q == M, which is
     all that's needed to emulate div1. these two values can be used to derive
     the real QM bits when building the status register in sh4_implode_sr */
  uint32_t pc, pr, sr, sr_t, sr_s, sr_m, sr_qm;

  uint32_t fpscr;
  uint32_t dbr, gbr, vbr;
  uint32_t fpul, mach, macl;
  uint32_t sgr, spc, ssr;
  uint64_t pending_interrupts;

  /* processor sleep state */
  uint32_t sleep_mode;

  /* the main dispatch loop is ran until run_cycles is <= 0 */
  int32_t run_cycles;

  /* debug information */
  int32_t ran_instrs;

  uint8_t cache[0x2000];
};

static inline void sh4_swap_gpr_bank(struct sh4_context *ctx) {
  for (int s = 0; s < 8; s++) {
    uint32_t tmp = ctx->r[s];
    ctx->r[s] = ctx->ralt[s];
    ctx->ralt[s] = tmp;
  }
}

static inline void sh4_swap_fpr_bank(struct sh4_context *ctx) {
  for (int s = 0; s < 16; s++) {
    uint32_t tmp = ctx->fr[s];
    ctx->fr[s] = ctx->xf[s];
    ctx->xf[s] = tmp;
  }
}

static inline void sh4_implode_sr(struct sh4_context *ctx) {
  uint32_t sr_q = (ctx->sr_qm >> 31) == ctx->sr_m;
  ctx->sr &= ~(M_MASK | Q_MASK | S_MASK | T_MASK);
  ctx->sr |= (ctx->sr_m << M_BIT) | (sr_q << Q_BIT) | (ctx->sr_s << S_BIT) |
             (ctx->sr_t << T_BIT);
}

static inline void sh4_explode_sr(struct sh4_context *ctx) {
  uint32_t sr_q = (ctx->sr & Q_MASK) >> Q_BIT;
  ctx->sr_t = (ctx->sr & T_MASK) >> T_BIT;
  ctx->sr_s = (ctx->sr & S_MASK) >> S_BIT;
  ctx->sr_m = (ctx->sr & M_MASK) >> M_BIT;
  ctx->sr_qm = (sr_q == ctx->sr_m) << 31;
}

/*
 * sh4 guest runtime interface
 */
typedef void (*sh4_invalid_instr_cb)(void *);
typedef void (*sh4_ltlb_cb)(void *);
typedef void (*sh4_pref_cb)(void *, uint32_t);
typedef void (*sh4_sleep_cb)(void *);
typedef void (*sh4_sr_updated_cb)(void *, uint32_t);
typedef void (*sh4_fpscr_updated_cb)(void *, uint32_t);

struct sh4_guest {
  struct jit_guest;

  sh4_invalid_instr_cb invalid_instr;
  sh4_ltlb_cb ltlb;
  sh4_pref_cb pref;
  sh4_sleep_cb sleep;
  sh4_sr_updated_cb sr_updated;
  sh4_fpscr_updated_cb fpscr_updated;
};

#endif
