/** @file nes/nes.h
    NES specific functions.
*/
#ifndef _NES_H
#define _NES_H

#include <types.h>
#include <stdint.h>
#include <gbdk/version.h>
#include <nes/hardware.h>
#include <nes/rgb_to_nes_macro.h>

#define NINTENDO_NES

// Here NINTENDO means Game Boy & related clones
#ifdef NINTENDO
#undef NINTENDO
#endif

#ifdef SEGA
#undef SEGA
#endif

#ifdef MSX
#undef MSX
#endif

#define SYSTEM_BITS_NTSC        0x00
#define SYSTEM_BITS_PAL         0x40
#define SYSTEM_BITS_DENDY       0x80
extern const uint8_t _SYSTEM;

#define SYSTEM_60HZ    0x00
#define SYSTEM_50HZ    0x01

#define TIMER_VBLANK_PARITY_MODE_SYSTEM_60HZ    0x78
#define TIMER_VBLANK_PARITY_MODE_SYSTEM_50HZ    0x5D

#define RGB(r,g,b)        RGB_TO_NES(((r) | ((g) << 2) | ((b) << 4)))
#define RGB8(r,g,b)       RGB_TO_NES((((r) >> 6) | (((g) >> 6) << 2) | (((b) >> 6) << 4)))
#define RGBHTML(RGB24bit) RGB_TO_NES((((RGB24bit) >> 22) | ((((RGB24bit) & 0xFFFF) >> 14) << 2) | ((((RGB24bit) & 0xFF) >> 6) << 4)))

/** Common colors based on the EGA default palette.
 *
 * Manually entered from https://www.nesdev.org/wiki/PPU_palettes#RGBI
 *
 */
#define RGB_RED        0x16     // EGA12
#define RGB_DARKRED    0x06     // EGA4
#define RGB_GREEN      0x2A     // EGA10
#define RGB_DARKGREEN  0x1A     // EGA2
#define RGB_BLUE       0x12     // EGA9
#define RGB_DARKBLUE   0x02     // EGA1
#define RGB_YELLOW     0x28     // EGA14
#define RGB_DARKYELLOW 0x18     // EGA6
#define RGB_CYAN       0x2C     // EGA11
#define RGB_AQUA       0x1C     // EGA3
#define RGB_PINK       0x24     // EGA13
#define RGB_PURPLE     0x14     // EGA5
#define RGB_BLACK      0x0F     // EGA0
#define RGB_DARKGRAY   0x00     // EGA8
#define RGB_LIGHTGRAY  0x10     // EGA7
#define RGB_WHITE      0x30     // EGA15

typedef uint8_t palette_color_t;

void set_bkg_palette(uint8_t first_palette, uint8_t nb_palettes, const palette_color_t *rgb_data) NO_OVERLAY_LOCALS;

void set_sprite_palette(uint8_t first_palette, uint8_t nb_palettes, const palette_color_t *rgb_data) NO_OVERLAY_LOCALS;

void set_bkg_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rgb_data) NO_OVERLAY_LOCALS;

void set_sprite_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rgb_data) NO_OVERLAY_LOCALS;

/** Joypad bits.
    A logical OR of these is used in the wait_pad and joypad
    functions.  For example, to see if the B button is pressed
    try

    uint8_t keys;
    keys = joypad();
    if (keys & J_B) {
        ...
    }

    @see joypad
 */
#define J_UP         0x08U
#define J_DOWN       0x04U
#define J_LEFT       0x02U
#define J_RIGHT      0x01U
#define J_A          0x80U
#define J_B          0x40U
#define J_SELECT     0x20U
#define J_START      0x10U

/** Screen modes.
    Normally used by internal functions only.
    @see mode()
 */
#define M_DRAWING    0x01U
#define M_TEXT_OUT   0x02U
#define M_TEXT_INOUT 0x03U
/** Set this in addition to the others to disable scrolling

    If scrolling is disabled, the cursor returns to (0,0)
    @see mode()
*/
#define M_NO_SCROLL  0x04U
/** Set this to disable interpretation
    @see mode()
*/
#define M_NO_INTERP  0x08U

/** If this is set, sprite colours come from OBJ1PAL. Else
    they come from OBJ0PAL
    @see set_sprite_prop().
*/
#define S_PALETTE    0x10U
/** If set the sprite will be flipped horizontally.
    @see set_sprite_prop()
 */
#define S_FLIPX      0x40U
/** If set the sprite will be flipped vertically.
    @see set_sprite_prop()
 */
#define S_FLIPY      0x80U
/** If this bit is clear, then the sprite will be displayed
    on top of the background and window.
    @see set_sprite_prop()
*/
#define S_PRIORITY   0x20U
/** Defines how palette number is encoded in OAM.
    Required for the png2asset tool's metasprite output.
*/
#define S_PAL(n)     n

/* Interrupt flags */
/** Disable calling of interrupt service routines
 */
#define EMPTY_IFLAG  0x00U
/** VBlank Interrupt occurs at the start of the vertical blank.

    During this period the video ram may be freely accessed.
    @see set_interrupts(), @see add_VBL
 */
#define VBL_IFLAG    0x01U
/** LCD Interrupt when triggered by the STAT register.
    @see set_interrupts(), @see add_LCD
*/
#define LCD_IFLAG    0x02U
/** Timer Interrupt when the timer @ref TIMA_REG overflows.
    @see set_interrupts(), @see add_TIM
 */
#define TIM_IFLAG    0x04U

/* DMG Palettes */
#define DMG_BLACK     0x03
#define DMG_DARK_GRAY 0x02
#define DMG_LITE_GRAY 0x01
#define DMG_WHITE     0x00
/** Macro to create a DMG palette from 4 colors

    @param C0    Color for Index 0
    @param C1    Color for Index 1
    @param C2    Color for Index 2
    @param C3    Color for Index 3

    The resulting format is four greyscale colors
    packed into a single unsigned byte.

    Example:
    \code{.c}
    REG_BGP = DMG_PALETTE(DMG_BLACK, DMG_DARK_GRAY, DMG_LITE_GRAY, DMG_WHITE);
    \endcode

    @see OBP0_REG, OBP1_REG, BGP_REG
    @see DMG_BLACK, DMG_DARK_GRAY, DMG_LITE_GRAY, DMG_WHITE

 */
#define DMG_PALETTE(C0, C1, C2, C3) ((uint8_t)((((C3) & 0x03) << 6) | (((C2) & 0x03) << 4) | (((C1) & 0x03) << 2) | ((C0) & 0x03)))

/* Limits */
/** Width of the visible screen in pixels.
 */
#define SCREENWIDTH  DEVICE_SCREEN_PX_WIDTH
/** Height of the visible screen in pixels.
 */
#define SCREENHEIGHT DEVICE_SCREEN_PX_HEIGHT

/** Interrupt handlers
 */
typedef void (*int_handler)(void) NONBANKED;

/** The remove functions will remove any interrupt handler.

   A handler of NULL will cause bad things
   to happen if the given interrupt is enabled.

   Removes the VBL interrupt handler. @see add_VBL()
*/
void remove_VBL(int_handler h) NO_OVERLAY_LOCALS;

/** Removes the LCD interrupt handler.
    @see add_LCD(), remove_VBL()
*/
void remove_LCD(int_handler h) NO_OVERLAY_LOCALS;

/** Removes the TIM interrupt handler.
    @see add_TIM(), remove_VBL()
*/
void remove_TIM(int_handler h) NO_OVERLAY_LOCALS;

/** Adds a Vertical Blanking interrupt handler.

    @param h  The handler to be called whenever a V-blank
    interrupt occurs.

    Only a single handler is currently supported for NES.

    __Do not__ use the function definition attributes
    @ref CRITICAL and @ref INTERRUPT when declaring
    ISR functions added via add_VBL() (or LCD, etc).
    Those attributes are only required when constructing
    a bare jump from the interrupt vector itself (such as
    with @ref ISR_VECTOR()).

    ISR handlers added using add_VBL()/etc are instead
    called via the GBDK ISR dispatcher which makes
    the extra function attributes unecessary.

    @note The default GBDK VBL is installed automatically.

    @note On the current NES implementation, this handler
    is actually faked, and called before vblank occurs, by
    @ref vsync(). Writes to PPU registers should be done to
    the shadow_ versions, so they are updated by the default
    VBL handler only when vblank actually occurs.

    @see ISR_VECTOR()
*/
void add_VBL(int_handler h) NO_OVERLAY_LOCALS;

/** Adds a LCD interrupt handler.

    Called when the scanline matches the _lcd_scanline variables.

    Only a single handler is currently supported for NES.

    The use-case is to indicate to the user when the
    video hardware is about to redraw a given LCD line.
    This can be useful for dynamically controlling the
    scrolling registers to perform special video effects.

    __Do not__ use the function definition attributes
    @ref CRITICAL and @ref INTERRUPT when declaring
    ISR functions added via add_VBL() (or LCD, etc).
    Those attributes are only required when constructing
    a bare jump from the interrupt vector itself (such as
    with @ref ISR_VECTOR()).

    ISR handlers added using add_VBL()/etc are instead
    called via the GBDK ISR dispatcher which makes
    the extra function attributes unecessary.

    @note On the current NES implementation, this handler
    is actually faked, and called by the default VBL handler
    after a manual delay loop. Only one such faked "interrupt"
    is possible per frame.
    This means the CPU cycles wasted in the delay loop increase
    with higher values of _lcd_scanline. In practice, it makes
    this functionality mostly suited for a top status bar.

    @see add_VBL, nowait_int_handler, ISR_VECTOR()
*/
void add_LCD(int_handler h) NO_OVERLAY_LOCALS;

/** Adds a timer interrupt handler.

    Can not be used together with @ref add_low_priority_TIM

    This interrupt handler is invoked at the end of the NMI handler 
    for gbdk-nes, after first processing the registers writes done 
    by the VBL and and LCD handlers. 
    It is therefore currently limited to 60Hz / 50Hz 
    (depending on system).

    @note
    Make sure to wrap TIM interrupt handlers with a nooverlay pragma. 
    For more details see @ref docs_nes_tim_overlay

    @see add_VBL
    @see set_interrupts() with TIM_IFLAG, ISR_VECTOR()
*/
void add_TIM(int_handler h) NO_OVERLAY_LOCALS;

/** The maximum number of times the LCD handler will be called per frame.
 */
#define MAX_LCD_ISR_CALLS 4

/** Set the current screen mode - one of M_* modes

    Normally used by internal functions only.

    @see M_DRAWING, M_TEXT_OUT, M_TEXT_INOUT, M_NO_SCROLL, M_NO_INTERP
*/
void mode(uint8_t m) NO_OVERLAY_LOCALS;

/** Returns the current mode

    @see M_DRAWING, M_TEXT_OUT, M_TEXT_INOUT, M_NO_SCROLL, M_NO_INTERP
*/
uint8_t get_mode(void) NO_OVERLAY_LOCALS;

/** Returns the system gbdk is running on.

*/
inline uint8_t get_system(void) {
    if(_SYSTEM == SYSTEM_BITS_NTSC)
        return SYSTEM_60HZ;
    else
        return SYSTEM_50HZ;
}

/** Global Time Counter in VBL periods (60Hz)

    Increments once per Frame

    Will wrap around every ~18 minutes (unsigned 16 bits = 65535 / 60 / 60 = 18.2)
*/
extern volatile uint16_t sys_time;

/** Tracks current active ROM bank

    The active bank number is not tracked by @ref _current_bank when
    @ref SWITCH_ROM_MBC5_8M is used.

    This variable is updated automatically when you call SWITCH_ROM_MBC1 or
    SWITCH_ROM_MBC5, SWITCH_ROM(), or call a BANKED function.

    @see SWITCH_ROM_MBC1(), SWITCH_ROM_MBC5(), SWITCH_ROM()
*/
extern volatile uint8_t _current_bank;
#define CURRENT_BANK _current_bank

/** Obtains the __bank number__ of VARNAME

    @param VARNAME Name of the variable which has a __bank_VARNAME companion symbol which is adjusted by bankpack

    Use this to obtain the bank number from a bank reference
    created with @ref BANKREF().

    @see BANKREF_EXTERN(), BANKREF()
*/
#ifndef BANK
#define BANK(VARNAME) ( (uint8_t) & __bank_ ## VARNAME )
#endif

/** Creates a reference for retrieving the bank number of a variable or function

    @param VARNAME Variable name to use, which may be an existing identifier

    @see BANK() for obtaining the bank number of the included data.

    More than one `BANKREF()` may be created per file, but each call should
    always use a unique VARNAME.

    Use @ref BANKREF_EXTERN() within another source file
    to make the variable and it's data accesible there.
*/
#define BANKREF(VARNAME) void __func_ ## VARNAME(void) __banked __naked { \
__asm \
    .local b___func_ ## VARNAME \
    ___bank_ ## VARNAME = b___func_ ## VARNAME \
    .globl ___bank_ ## VARNAME \
__endasm; \
}

/** Creates extern references for accessing a BANKREF() generated variable.

    @param VARNAME Name of the variable used with @ref BANKREF()

    This makes a @ref BANKREF() reference in another source
    file accessible in the current file for use with @ref BANK().

    @see BANKREF(), BANK()
*/
#define BANKREF_EXTERN(VARNAME) extern const void __bank_ ## VARNAME;

/** Dummy macro for no-bank-switching WIP prototype
    @param b   ROM bank to switch to
*/
#define SWITCH_ROM_DUMMY(b)

/** Macro for simple UNROM-like switching (write bank# to single 8-bit register)
    @param b   ROM bank to switch to
*/
#define SWITCH_ROM_UNROM(b) _switch_prg0(b)

/** Makes default mapper switch the active ROM bank
    @param b   ROM bank to switch to (max 255)

    @see SWITCH_ROM_UNROM
*/
#define SWITCH_ROM SWITCH_ROM_UNROM

/** No-op at the moment. Placeholder for future mappers / test compatibility.
    @param b   SRAM bank to switch to

*/
#define SWITCH_RAM(b) 0

/** No-op at the moment. Placeholder for future mappers / test compatibility.

*/
#define ENABLE_RAM

/** No-op at the moment. Placeholder for future mappers / test compatibility.

*/
#define DISABLE_RAM

/** Delays the given number of milliseconds.
    Uses no timers or interrupts, and can be called with
    interrupts disabled
 */
void delay(uint16_t d) NO_OVERLAY_LOCALS;

/** Reads and returns the current state of the joypad.
    Return value is an OR of J_*

    When testing for multiple different buttons, it's
    best to read the joypad state *once* into a variable
    and then test using that variable.

    @see J_START, J_SELECT, J_A, J_B, J_UP, J_DOWN, J_LEFT, J_RIGHT
*/
uint8_t joypad(void) NO_OVERLAY_LOCALS;

/** Waits until at least one of the buttons given in mask are pressed.

    Normally only used for checking one key, but it will
    support many, even J_LEFT at the same time as J_RIGHT. :)

    @see joypad
    @see J_START, J_SELECT, J_A, J_B, J_UP, J_DOWN, J_LEFT, J_RIGHT
*/
uint8_t waitpad(uint8_t mask) NO_OVERLAY_LOCALS;

/** Waits for the directional pad and all buttons to be released.

*/
void waitpadup(void) NO_OVERLAY_LOCALS;

/** Multiplayer joypad structure.

    Must be initialized with @ref joypad_init() first then it
    may be used to poll all avaliable joypads with @ref joypad_ex()
*/
typedef struct {
    uint8_t npads;
    union {
        struct {
            uint8_t joy0, joy1, joy2, joy3;
        };
        uint8_t joypads[4];
    };
} joypads_t;

/** Initializes joypads_t structure for polling multiple joypads
    @param npads    number of joypads requested (1, 2 or 4)
    @param joypads  pointer to joypads_t structure to be initialized

    Only required for @ref joypad_ex, not required for calls to regular @ref joypad()
    @returns number of joypads avaliable
    @see joypad_ex(), joypads_t
*/
uint8_t joypad_init(uint8_t npads, joypads_t * joypads) NO_OVERLAY_LOCALS;

/** Polls all avaliable joypads

    @see joypad_init(), joypads_t
*/
void joypad_ex(joypads_t * joypads) NO_OVERLAY_LOCALS;



/** Enables unmasked interrupts

    @note Use @ref CRITICAL {...} instead for creating a block of
          of code which should execute with interrupts  temporarily
          turned off.

    @see disable_interrupts, set_interrupts, CRITICAL
*/
inline void enable_interrupts(void) {
    __asm__("cli");
}

/** Disables interrupts

    @note Use @ref CRITICAL {...} instead for creating a block of
          of code which should execute with interrupts  temporarily
          turned off.

    This function may be called as many times as you like;
    however the first call to @ref enable_interrupts will re-enable
    them.

    @see enable_interrupts, set_interrupts, CRITICAL
*/
inline void disable_interrupts(void) {
    __asm__("sei");
}

/** Sets the interrupt mask to flags.
    @param flags	A logical OR of *_IFLAGS

    @see VBL_IFLAG, LCD_IFLAG, TIM_IFLAG
*/
void set_interrupts(uint8_t flags) NO_OVERLAY_LOCALS;

/** Performs a soft reset.

    For the Game Boy and related it does this by jumping to address 0x0150
    which is in crt0.s (the c-runtime that executes before main() is called).

    This performs various startup steps such as resetting the stack,
    clearing WRAM and OAM, resetting initialized variables and some
    display registers (scroll, window, LCDC), etc.

    This is not the same a hard power reset.
*/
inline void reset(void) {
    __asm__("jmp [0xFFFC]");
}

/** Waits for the vertical blank interrupt.

    This is often used in main loops to idle the CPU
    until it's time to start the next frame. It's also useful for
    syncing animation with the screen re-draw.

    Warning: If the VBL interrupt is disabled, this function will
    never return.
*/
void vsync(void) NO_OVERLAY_LOCALS;

/** Obsolete. This function has been replaced by vsync(), which has identical behavior.
*/
void wait_vbl_done(void) NO_OVERLAY_LOCALS;

/** Turns the display on.

    @see DISPLAY_ON
*/
void display_on(void) NO_OVERLAY_LOCALS;

/** Turns the display off immediately.
    @see DISPLAY_ON
*/
void display_off(void) NO_OVERLAY_LOCALS;

/** Copies data from shadow OAM to OAM
 */
void refresh_OAM(void) NO_OVERLAY_LOCALS;

/** Turns the display back on.
    @see display_off, DISPLAY_OFF
*/
#define DISPLAY_ON \
  display_on();

/** Turns the display off immediately.
    @see display_off, DISPLAY_ON
*/
#define DISPLAY_OFF \
  display_off();

/** Blanks leftmost column, so it is not garbaged when you use horizontal scroll
    @see SHOW_LEFT_COLUMN
*/
#define HIDE_LEFT_COLUMN \
    shadow_PPUMASK &= ~(PPUMASK_SHOW_BG_LC | PPUMASK_SHOW_SPR_LC); \

/** Shows leftmost column
    @see HIDE_LEFT_COLUMN
*/
#define SHOW_LEFT_COLUMN \
    shadow_PPUMASK |= (PPUMASK_SHOW_BG_LC | PPUMASK_SHOW_SPR_LC);

/** Does nothing for NES
    not implemented yet
 */
#define SET_BORDER_COLOR(C)

/** Turns on the background layer.
    Sets bit 0 of the LCDC register to 1.
*/
#define SHOW_BKG \
    shadow_PPUMASK |= PPUMASK_SHOW_BG;

/** Turns off the background layer.
    Sets bit 0 of the LCDC register to 0.
*/
#define HIDE_BKG \
    shadow_PPUMASK &= ~PPUMASK_SHOW_BG;

/** Turns on the sprites layer.
    Sets bit 1 of the LCDC register to 1.
*/
#define SHOW_SPRITES \
    shadow_PPUMASK |= PPUMASK_SHOW_SPR;

/** Turns off the sprites layer.
    Clears bit 1 of the LCDC register to 0.
*/
#define HIDE_SPRITES \
    shadow_PPUMASK &= ~PPUMASK_SHOW_SPR;

/** Sets sprite size to 8x16 pixels, two tiles one above the other.
    Sets bit 2 of the LCDC register to 1.
*/
#define SPRITES_8x16 \
  shadow_PPUCTRL |= PPUCTRL_SPR_8X16;

/** Sets sprite size to 8x8 pixels, one tile.
    Clears bit 2 of the LCDC register to 0.
*/
#define SPRITES_8x8 \
  shadow_PPUCTRL &= ~PPUCTRL_SPR_8X16;



/**
 * Set byte in vram at given memory location
 *
 * @param addr address to write to
 * @param v value
 */
void set_vram_byte(uint8_t * addr, uint8_t v) NO_OVERLAY_LOCALS;

/**
 * Get address of X,Y tile of background map
 */
uint8_t * get_bkg_xy_addr(uint8_t x, uint8_t y) NO_OVERLAY_LOCALS;

#define COMPAT_PALETTE(C0,C1,C2,C3) ((uint8_t)(((C3) << 6) | ((C2) << 4) | ((C1) << 2) | (C0)))

/** Sets palette for 2bpp color translation for GG/SMS, does nothing on GB
 */
inline void set_2bpp_palette(uint16_t palette) {
    palette;
}

extern uint16_t _current_1bpp_colors;
void set_1bpp_colors_ex(uint8_t fgcolor, uint8_t bgcolor, uint8_t mode) NO_OVERLAY_LOCALS;
inline void set_1bpp_colors(uint8_t fgcolor, uint8_t bgcolor) {
    set_1bpp_colors_ex(fgcolor, bgcolor, 0);
}

/** Sets VRAM Tile Pattern data for the Background

    Writes __nb_tiles__ tiles to VRAM starting at __first_tile__, tile data
    is sourced from __data__. Each Tile is 16 bytes in size (8x8 pixels, 2 bits-per-pixel).

    Note: Sprite Tiles 128-255 share the same memory region as Background Tiles 128-255.

    @see set_tile_data
*/
void set_bkg_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS;
#define set_bkg_2bpp_data set_bkg_data

/** Sets VRAM Tile Pattern data for the Background using 1bpp source data

    Similar to @ref set_bkg_data, except source data is 1 bit-per-pixel
    which gets expanded into 2 bits-per-pixel.

    For a given bit that represent a pixel:
    \li 0 will be expanded into color 0
    \li 1 will be expanded into color 1, 2 or 3 depending on color argument

    @see SHOW_BKG, HIDE_BKG, set_bkg_tiles
*/
void set_bkg_1bpp_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS;

/** Sets a rectangular region of Background Tile Map.

    Entries are copied from map at __tiles__ to the Background Tile Map starting at
    __x__, __y__ writing across for __w__ tiles and down for __h__ tiles.

    Use @ref set_bkg_submap() instead when:
    \li Source map is wider than 32 tiles.
    \li Writing a width that does not match the source map width __and__ more
    than one row high at a time.

    One byte per source tile map entry.

    Writes that exceed coordinate 31 on the x or y axis will wrap around to
    the Left and Top edges.

    @see SHOW_BKG
    @see set_bkg_data, set_bkg_submap, set_win_tiles, set_tiles
*/
void set_bkg_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles) NO_OVERLAY_LOCALS;
#define set_tile_map set_bkg_tiles

/** Sets a rectangular region of Background Tile Map Attributes.

    @param x          X Start position in Background Map tile coordinates. Range 0 - 15
    @param y          Y Start position in Background Map tile coordinates. Range 0 - 14
    @param w          Width of area to set in tiles. Range 1 - 16
    @param h          Height of area to set in tiles. Range 1 - 15
    @param attributes Pointer to source tile map attribute data

    Entries are copied from map at __tiles__ to the Background Tile Map starting at
    __x__, __y__ writing across for __w__ tiles and down for __h__ tiles.

    NES 16x16 Tile Attributes are tightly packed into 4 attributes per byte,
    with each 16x16 area of a 32x32 pixel block using the bits as follows:
    D1-D0: Top-left 16x16 pixels
    D3-D2: Top-right 16x16 pixels
    D5-D4: Bottom-left 16x16 pixels
    D7-D6: Bottom-right 16x16 pixels

    https://www.nesdev.org/wiki/PPU_attribute_tables

    @see SHOW_BKG
    @see set_bkg_data, set_bkg_submap_attributes, set_win_tiles, set_tiles
*/
void set_bkg_attributes_nes16x16(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes) NO_OVERLAY_LOCALS;

/** Sets a rectangular region of Background Tile Map Attributes.

    Entries are copied from map at __tiles__ to the Background Tile Map starting at
    __x__, __y__ writing across for __w__ tiles and down for __h__ tiles.

    Use @ref set_bkg_submap_attributes() instead when:
    \li Source map is wider than 32 tiles.
    \li Writing a width that does not match the source map width __and__ more
    than one row high at a time.

    One byte per source tile map attribute entry.

    Writes that exceed coordinate 31 on the x or y axis will wrap around to
    the Left and Top edges.

    Please note that this is just a wrapper function for set_bkg_attributes_nes16x16()
    and divides the coordinates and dimensions by 2 to achieve this.
    It is intended to make code more portable by using the same coordinate system
    that systems with the much more common 8x8 attribute resolution would use.

    @see SHOW_BKG
    @see set_bkg_data, set_bkg_submap_attributes, set_win_tiles, set_tiles
*/
inline void set_bkg_attributes(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes)
{
    set_bkg_attributes_nes16x16(x >> 1, y >> 1, (w + 1) >> 1, (h + 1) >> 1, attributes);
}
/** Sets a rectangular area of the Background Tile Map using a sub-region
    from a source tile map. Useful for scrolling implementations of maps
    larger than 32 x 30 tiles / 16x15 attributes.

    @param x      X Start position in both the Source Attribute Map and hardware Background Map attribute coordinates. Range 0 - 255
    @param y      Y Start position in both the Source Attribute Map and hardware Background Map attribute coordinates. Range 0 - 255
    @param w      Width of area to set in Attributes. Range 1 - 127
    @param h      Height of area to set in Attributes. Range 1 - 127
    @param map    Pointer to source tile map data
    @param map_w  Width of source tile map in tiles. Range 1 - 127

    Entries are copied from __map__ to the Background Attribute Map starting at
    __x__, __y__ writing across for __w__ tiles and down for __h__ attributes,
    using __map_w__ as the rowstride for the source attribute map.

    The __x__ and __y__ parameters are in Source Attribute Map Attribute
    coordinates. The location tiles will be written to on the
    hardware Background Map is derived from those, but only uses
    the lower 5 bits of each axis, for range of 0-15 (they are
    bit-masked: `x & 0xF` and `y & 0xF`). As a result the two
    coordinate systems are aligned together.

    In order to transfer tile map data in a way where the
    coordinate systems are not aligned, an offset from the
    Source Attribute Map pointer can be passed in:
    `(map_ptr + x + (y * map_width))`.

    For example, if you want the tile id at `1,2` from the source map to
    show up at `0,0` on the hardware Background Map (instead of at `1,2`)
    then modify the pointer address that is passed in:
    `map_ptr + 1 + (2 * map_width)`

    Use this instead of @ref set_bkg_tiles when the source map is wider than
    32 tiles or when writing a width that does not match the source map width.

    One byte per source attribute map entry.

    Writes that exceed coordinate 15/14 on the x / y axis will wrap around to
    the Left and Top edges.

    See @ref set_bkg_tiles for setting CGB attribute maps with @ref VBK_REG.

    @see SHOW_BKG
    @see set_bkg_data, set_bkg_tiles, set_win_submap, set_tiles
*/
void set_bkg_submap_attributes_nes16x16(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w) NO_OVERLAY_LOCALS;

/** Sets a rectangular area of the Background Tile Map attributes using
    a sub-region from a source tile map. Useful for scrolling implementations
    of maps larger than 32 x 30 tiles.

    Please note that this is just a wrapper function for set_bkg_submap_attributes_nes16x16()
    and divides the coordinates and dimensions by 2 to achieve this.
    It is intended to make code more portable by using the same coordinate system
    that systems with the much more common 8x8 attribute resolution would use.

    @see SHOW_BKG
    @see set_bkg_data, set_bkg_tiles, set_win_submap, set_tiles
*/
inline void set_bkg_submap_attributes(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes, uint8_t map_w)
{
    set_bkg_submap_attributes_nes16x16(x >> 1, y >> 1, (w + 1) >> 1, (h + 1) >> 1, attributes, (map_w + 1) >> 1);
}


/** Sets a rectangular region of Background Tile Map.
    The offset value in __base_tile__ is added to
    the tile ID for each map entry.

    @param x      X Start position in Background Map tile coordinates. Range 0 - 31
    @param y      Y Start position in Background Map tile coordinates. Range 0 - 31
    @param w      Width of area to set in tiles. Range 1 - 32
    @param h      Height of area to set in tiles. Range 1 - 32
    @param tiles  Pointer to source tile map data
    @param base_tile Offset each tile ID entry of the source map by this value. Range 1 - 255

    This is identical to @ref set_bkg_tiles() except that it
    adds the __base_tile__ parameter for when a tile map's tiles don't
    start at index zero. (For example, the tiles used by the map
    range from 100 -> 120 in VRAM instead of 0 -> 20).

    @see set_bkg_tiles for more details
*/
inline void set_bkg_based_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles, uint8_t base_tile);


/** Sets a rectangular area of the Background Tile Map using a sub-region
    from a source tile map. Useful for scrolling implementations of maps
    larger than 32 x 32 tiles.

    @ param x      X Start position in Background Map tile coordinates. Range 0 - 31
    @ param y      Y Start position in Background Map tile coordinates. Range 0 - 31
    @ param w      Width of area to set in tiles. Range 1 - 255
    @ param h      Height of area to set in tiles. Range 1 - 255
    @ param map    Pointer to source tile map data
    @ param map_w  Width of source tile map in tiles. Range 1 - 255

    Entries are copied from __map__ to the Background Tile Map starting at
    __x__, __y__ writing across for __w__ tiles and down for __h__ tiles,
    using __map_w__ as the rowstride for the source tile map.

    Use this instead of @ref set_bkg_tiles when the source map is wider than
    32 tiles or when writing a width that does not match the source map width.

    One byte per source tile map entry.

    Writes that exceed coordinate 31 on the x or y axis will wrap around to
    the Left and Top edges.

    See @ref set_bkg_tiles for setting CGB attribute maps with @ref VBK_REG.

    @see SHOW_BKG
    @see set_bkg_data, set_bkg_tiles, set_win_submap, set_tiles
*/
void set_bkg_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w) NO_OVERLAY_LOCALS;
#define set_tile_submap set_bkg_submap


/** Sets a rectangular area of the Background Tile Map using a sub-region
    from a source tile map. The offset value in __base_tile__ is added to
    the tile ID for each map entry.

    @param x      X Start position in Background Map tile coordinates. Range 0 - 31
    @param y      Y Start position in Background Map tile coordinates. Range 0 - 31
    @param w      Width of area to set in tiles. Range 1 - 255
    @param h      Height of area to set in tiles. Range 1 - 255
    @param map    Pointer to source tile map data
    @param map_w  Width of source tile map in tiles. Range 1 - 255
    @param base_tile Offset each tile ID entry of the source map by this value. Range 1 - 255

    This is identical to @ref set_bkg_submap() except that it
    adds the __base_tile__ parameter for when a tile map's tiles don't
    start at index zero. (For example, the tiles used by the map
    range from 100 -> 120 in VRAM instead of 0 -> 20).

    @see set_bkg_submap for more details
*/
inline void set_bkg_based_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w, uint8_t base_tile);


/** Copies a rectangular region of Background Tile Map entries into a buffer.

    Entries are copied into __tiles__ from the Background Tile Map starting at
    __x__, __y__ reading across for __w__ tiles and down for __h__ tiles.

    One byte per tile.

    The buffer pointed to by __tiles__ should be at least __x__ x __y__ bytes in size.

    @see get_bkg_tile_xy, get_tiles
*/
void get_bkg_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t *tiles) NO_OVERLAY_LOCALS;


/**
 * Set single tile t on background layer at x,y
 * @param x X-coordinate
 * @param y Y-coordinate
 * @param t tile index
 * @return returns the address of tile, so you may use faster set_vram_byte() later
 */
uint8_t * set_bkg_tile_xy(uint8_t x, uint8_t y, uint8_t t) NO_OVERLAY_LOCALS;
#define set_tile_xy set_bkg_tile_xy

/**
    Set single attribute data a on background layer at x,y

    @param x X-coordinate
    @param y Y-coordinate
    @param a tile attributes
 */
void set_bkg_attribute_xy_nes16x16(uint8_t x, uint8_t y, uint8_t a) NO_OVERLAY_LOCALS;

/**
    Set single attribute data a on background layer at x,y

    Please note that this is just a wrapper function for set_bkg_submap_attributes_nes16x16()
    and divides the coordinates and dimensions by 2 to achieve this.
    It is intended to make code more portable by using the same coordinate system
    that systems with the much more common 8x8 attribute resolution would use.

    @param x X-coordinate
    @param y Y-coordinate
    @param a tile attributes
 */
inline void set_bkg_attribute_xy(uint8_t x, uint8_t y, uint8_t a)
{
    set_bkg_attribute_xy_nes16x16(x >> 1, y >> 1, a);
}
#define set_attribute_xy set_bkg_attribute_xy

/**
 * Get single tile t on background layer at x,y
 * @param x X-coordinate
 * @param y Y-coordinate
 * @return returns tile index
 */
uint8_t get_bkg_tile_xy(uint8_t x, uint8_t y) NO_OVERLAY_LOCALS;


/** Moves the Background Layer to the position specified in __x__ and __y__ in pixels.

    @param x   X axis screen coordinate for Left edge of the Background
    @param y   Y axis screen coordinate for Top edge of the Background

    0,0 is the top left corner of the GB screen. The Background Layer wraps around the screen,
    so when part of it goes off the screen it appears on the opposite side (factoring in the
    larger size of the Background Layer versus the screen size).

    The background layer is always under the Window Layer.

    @see SHOW_BKG, HIDE_BKG
*/
inline void move_bkg(scroll_x_t x, scroll_y_t y) {
    // store low 8 bits to shadow scroll registers
    bkg_scroll_x = (uint8_t)x;
    bkg_scroll_y = (uint8_t)(y >= 240 ? (y - 240) : y);
    // store 9th bit of x and y in shadow PPUCTRL register
#if DEVICE_SCREEN_BUFFER_WIDTH > 32 && DEVICE_SCREEN_BUFFER_HEIGHT > 30
    uint8_t msb_x = (uint8_t)((x >> 8) & 1);
    uint8_t msb_y = (uint8_t)(y >= 240 ? 1 : 0);
    shadow_PPUCTRL = (shadow_PPUCTRL & 0xFC) | (msb_y << 1) | msb_x;
#elif DEVICE_SCREEN_BUFFER_WIDTH > 32
    uint8_t msb_x = (uint8_t)((x >> 8) & 1);
    shadow_PPUCTRL = (shadow_PPUCTRL & 0xFC) | msb_x;
#elif DEVICE_SCREEN_BUFFER_HEIGHT > 30
    uint8_t msb_y = (uint8_t)(y >= 240 ? 1 : 0);
    shadow_PPUCTRL = (shadow_PPUCTRL & 0xFC) | (msb_y << 1);
#endif
}


/** Moves the Background relative to it's current position.

    @param x   Number of pixels to move the Background on the __X axis__
               \n Range: -128 - 127
    @param y   Number of pixels to move the Background on the __Y axis__
               \n Range: -128 - 127

    @see move_bkg
*/
inline void scroll_bkg(int8_t x, int8_t y) {
    move_bkg(bkg_scroll_x + x, bkg_scroll_y + y);
}


/** Sets VRAM Tile Pattern data for Sprites

    Writes __nb_tiles__ tiles to VRAM starting at __first_tile__, tile data
    is sourced from __data__. Each Tile is 16 bytes in size (8x8 pixels, 2 bits-per-pixel).

    Note: Sprite Tiles 128-255 share the same memory region as Background Tiles 128-255.

    GBC only: @ref VBK_REG determines which bank of tile patterns are written to.
    \li VBK_REG=0 indicates the first bank
    \li VBK_REG=1 indicates the second
*/
void set_sprite_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS;
#define set_sprite_2bpp_data set_sprite_data

/** Sets VRAM Tile Pattern data for Sprites using 1bpp source data

    Similar to @ref set_sprite_data, except source data is 1 bit-per-pixel
    which gets expanded into 2 bits-per-pixel.

    For a given bit that represent a pixel:
    \li 0 will be expanded into color 0
    \li 1 will be expanded into color 3

    @see SHOW_SPRITES, HIDE_SPRITES, set_sprite_tile
*/
void set_sprite_1bpp_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS;

/** Sprite Attributes structure
    @param x     X Coordinate of the sprite on screen
    @param y     Y Coordinate of the sprite on screen - 1
    @param tile  Sprite tile number (see @ref set_sprite_tile)
    @param prop  OAM Property Flags (see @ref set_sprite_prop)
*/
typedef struct OAM_item_t {
    uint8_t y;     //< Y coordinate of the sprite on screen - 1
    uint8_t tile;  //< Sprite tile number
    uint8_t prop;  //< OAM Property Flags
    uint8_t x;     //< X coordinate of the sprite on screen
} OAM_item_t;


/** Shadow OAM array in WRAM, that is DMA-transferred into the real OAM each VBlank
*/
extern volatile struct OAM_item_t shadow_OAM[];

/** MSB of shadow_OAM address is used by OAM DMA copying routine
*/
extern uint8_t _shadow_OAM_base;

#define DISABLE_OAM_DMA \
    _shadow_OAM_base = 0

/** Disable OAM DMA copy each VBlank
*/
#define DISABLE_VBL_TRANSFER DISABLE_OAM_DMA

#define ENABLE_OAM_DMA \
    _shadow_OAM_base = (uint8_t)((uint16_t)&shadow_OAM >> 8)

/** Enable OAM DMA copy each VBlank and set it to transfer default shadow_OAM array
*/
#define ENABLE_VBL_TRANSFER ENABLE_OAM_DMA

/** Amount of hardware sprites in OAM
*/
#define MAX_HARDWARE_SPRITES 64

/** True if sprite hardware can flip sprites by X (horizontally)
*/
#define HARDWARE_SPRITE_CAN_FLIP_X 1

/** True if sprite hardware can flip sprites by Y (vertically)
*/
#define HARDWARE_SPRITE_CAN_FLIP_Y 1

/** Enable OAM DMA copy each VBlank and set it to transfer any 256-byte aligned array
*/
inline void SET_SHADOW_OAM_ADDRESS(void * address) {
    _shadow_OAM_base = (uint8_t)((uint16_t)address >> 8);
}

/** Sets sprite number __nb__in the OAM to display tile number __tile__.

    @param nb    Sprite number, range 0 - 63
    @param tile  Selects a tile (0 - 255) from PPU memory at 0000h - 0FFFh / 1000h - 1FFFh

    In 8x16 mode:
    \li The sprite will also display the next tile (__tile__ + 1)
        directly below (y + 8) the first tile.
    \li The lower bit of the tile number is ignored:
        the upper 8x8 tile is (__tile__ & 0xFE), and
        the lower 8x8 tile is (__tile__ | 0x01).
    \li See: @ref SPRITES_8x16
*/
void set_sprite_tile(uint8_t nb, uint8_t tile) NO_OVERLAY_LOCALS;


/** Returns the tile number of sprite number __nb__ in the OAM.

@param nb    Sprite number, range 0 - 63

@see set_sprite_tile for more details
*/
uint8_t get_sprite_tile(uint8_t nb) NO_OVERLAY_LOCALS;


/** Sets the OAM Property Flags of sprite number __nb__ to those defined in __prop__.

    @param nb    Sprite number, range 0 - 39
    @param prop  Property setting (see bitfield description)

    The bits in __prop__ represent:
    \li Bit 7 - Vertical flip. Dictates which way up the sprite is drawn
              vertically.
              \n 0: normal
              \n 1: upside down
    \li Bit 6 - Horizontal flip. Dictates which way up the sprite is
              drawn horizontally.
              \n 0: normal
              \n 1: back to front
    \li Bit 5 - Priority flag. When this is set, the sprites appear behind the
              background and window layer.
              \n 0: infront
              \n 1: behind
    \li Bit 4 - Unimplemented
    \li Bit 3 - Unimplemented
    \li Bit 2 - Unimplemented
    \li Bit 1 - See bit 0.
    \li Bit 0 - Bits 0-1 indicate which color palette the sprite should use. Note: only palettes 4 to 7 will be available for NES sprites.

    It's recommended to use GBDK constants (eg: S_FLIPY) to configure sprite properties as these are crossplatform.

    \code{.c}
    // Load palette data into the first palette
    set_sprite_palette(4, 1, exampleSprite_palettes)

    // Set the OAM value for the sprite
    // These flags tell the sprite to use the first sprite palette (palette 4) and to flip the sprite both vertically and horizontally.
    set_sprite_prop(0, S_FLIPY | S_FLIPX);
    \endcode

    @see S_PALETTE, S_FLIPX, S_FLIPY, S_PRIORITY
*/
void set_sprite_prop(uint8_t nb, uint8_t prop) NO_OVERLAY_LOCALS;


/** Returns the OAM Property Flags of sprite number __nb__.

    @param nb    Sprite number, range 0 - 39
    @see set_sprite_prop for property bitfield settings
*/
uint8_t get_sprite_prop(uint8_t nb) NO_OVERLAY_LOCALS;


/** Moves sprite number __nb__ to the __x__, __y__ position on the screen.

    @param nb  Sprite number, range 0 - 63
    @param x   X Position. Specifies the sprites horizontal position on the screen (minus 8).
    @param y   Y Position. Specifies the sprites vertical position on the screen (minus 16).
               \n An offscreen value (Y>=240) hides the sprite.

    Moving the sprite to 0,0 (or similar off-screen location) will hide it.
*/
void move_sprite(uint8_t nb, uint8_t x, uint8_t y) NO_OVERLAY_LOCALS;


/** Moves sprite number __nb__ relative to its current position.

    @param nb  Sprite number, range 0 - 63
    @param x   Number of pixels to move the sprite on the __X axis__
               \n Range: -128 - 127
    @param y   Number of pixels to move the sprite on the __Y axis__
               \n Range: -128 - 127

    @see move_sprite for more details about the X and Y position
 */
void scroll_sprite(uint8_t nb, int8_t x, int8_t y) NO_OVERLAY_LOCALS;


/** Hides sprite number __nb__ by moving it to Y position 240.

    @param nb  Sprite number, range 0 - 63
 */
void hide_sprite(uint8_t nb) NO_OVERLAY_LOCALS;



/** Copies arbitrary data to an address in VRAM
    without taking into account the state of LCDC bits 3 or 4.

    Copies __len__ bytes from a buffer at __data__ to VRAM starting at __vram_addr__.

    @see set_bkg_data, set_win_data, set_bkg_tiles, set_win_tiles, set_tile_data, set_tiles
*/
void set_data(uint8_t *vram_addr, const uint8_t *data, uint16_t len) NO_OVERLAY_LOCALS;


/** Sets a rectangular region of Tile Map entries at a given VRAM Address.

    @param x         X Start position in Map tile coordinates. Range 0 - 31
    @param y         Y Start position in Map tile coordinates. Range 0 - 31
    @param w         Width of area to set in tiles. Range 1 - 32
    @param h         Height of area to set in tiles.   Range 1 - 32
    @param vram_addr Pointer to destination VRAM Address
    @param tiles     Pointer to source Tile Map data

    Entries are copied from __tiles__ to Tile Map at address vram_addr starting at
    __x__, __y__ writing across for __w__ tiles and down for __h__ tiles.

    One byte per source tile map entry.

    There are two 32x30 Tile Maps in VRAM at addresses 2000h-23FFh and 2400h-27FFh.

    @see set_bkg_tiles
*/
void set_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t *vram_addr, const uint8_t *tiles) NO_OVERLAY_LOCALS;

/** Sets VRAM Tile Pattern data starting from given base address
    without taking into account the state of PPUMASK.

    @see set_bkg_data, set_data
*/
inline void set_tile_data(uint16_t first_tile, uint8_t nb_tiles, const uint8_t *data) {
    if (first_tile < 256) {
        set_bkg_data(first_tile, nb_tiles, data);
        if(first_tile + nb_tiles > 256)
            set_sprite_data(first_tile - 256, nb_tiles, data);
    } else {
        set_sprite_data(first_tile - 256, nb_tiles, data);
    }
}

/** Sets VRAM Tile Pattern data for the Background in the native format

    @param first_tile  Index of the first tile to write
    @param nb_tiles    Number of tiles to write
    @param data        Pointer to source tile data

    Writes __nb_tiles__ tiles to VRAM starting at __first_tile__, tile data
    is sourced from __data__.

    @see set_tile_data
*/
void set_bkg_native_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS;

/** Sets VRAM Tile Pattern data for Sprites in the native format

    @param first_tile  Index of the first tile to write
    @param nb_tiles    Number of tiles to write
    @param data        Pointer to source tile data

    Writes __nb_tiles__ tiles to VRAM starting at __first_tile__, tile data
    is sourced from __data__.
*/
void set_sprite_native_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS;

/** Sets VRAM Tile Pattern data in the native format

    @param first_tile  Index of the first tile to write (0 - 511)
    @param nb_tiles    Number of tiles to write
    @param data        Pointer to source Tile Pattern data.

    When `first_tile` is larger than 256 on the GB/AP, it
    will write to sprite data instead of background data.

    The bit depth of the source Tile Pattern data depends
    on which console is being used:
    \li NES: loads 2bpp tiles data
 */
inline void set_native_tile_data(uint16_t first_tile, uint8_t nb_tiles, const uint8_t *data) {
    if (first_tile < 256) {
        set_bkg_native_data(first_tile, nb_tiles, data);
        if(first_tile + nb_tiles > 256)
            set_sprite_native_data(first_tile - 256, nb_tiles, data);
    } else {
        set_sprite_native_data(first_tile - 256, nb_tiles, data);
    }
}

/** Initializes the entire Background Tile Map with Tile Number __c__
    @param c   Tile number to fill with

    Note: This function avoids writes during modes 2 & 3
*/
void init_bkg(uint8_t c) NO_OVERLAY_LOCALS;

/** Fills the VRAM memory region __s__ of size __n__ with Tile Number __c__
    @param s   Start address in VRAM
    @param c   Tile number to fill with
    @param n   Size of memory region (in bytes) to fill

    Note: This function avoids writes during modes 2 & 3
*/
void vmemset (void *s, uint8_t c, size_t n) NO_OVERLAY_LOCALS;

/** Fills a rectangular region of Tile Map entries for the Background layer with tile.

    @param x      X Start position in Background Map tile coordinates. Range 0 - 31
    @param y      Y Start position in Background Map tile coordinates. Range 0 - 31
    @param w      Width of area to set in tiles. Range 1 - 32
    @param h      Height of area to set in tiles. Range 1 - 32
    @param tile   Fill value
*/
void fill_bkg_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t tile) NO_OVERLAY_LOCALS;
#define fill_rect fill_bkg_rect

/** "Flushes" the updates to the shadow attributes so they are written
    to the transfer buffer, and then written to PPU memory on next vblank.

    This function must be called to see visible changes to attributes
    on the NES target. But it will automatically be called by @ref vsync(),
    so the use-cases for calling it manually are rare in practice.
*/
void flush_shadow_attributes(void) NO_OVERLAY_LOCALS;

uint8_t _switch_prg0(uint8_t bank) NO_OVERLAY_LOCALS;

#endif /* _NES_H */
