/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */

#ifndef SATCOM_SATURN_H
#define SATCOM_SATURN_H

#include "../sc_common.h"
#ifndef _SGL_H_
/* Don't include when using SGL, because some registers are redefined. */
#include "satregs/satregs.h"
#endif

/**
 *           --- Note about compile flags: ---
 *
 * When building a "Release" version of your program, you should 
 * define "SC_RELEASE" and rebuild your program.
 * Hence, transfer and debug-related functions will be removed in the resulting binary.
 *
 * When your program will run in Low RAM, you shoud define "SC_EXEC_IN_LRAM"
 * and rebuild your program.
 * When SC_EXEC_IN_LRAM is defined, temporary upload area for code to execute
 * is set from 0x06004000, and when SC_EXEC_IN_LRAM is not defined, this area
 * is set from the begining of Low RAM.
 *
 * When you want to use CD-ROM related functions, you shoud define "SC_USE_CDROM"
 * and rebuild your program.
 *
 *
 * It is recommended to define stuff by using gcc's -D command line switch.
 * Example: gcc -DSC_RELEASE <etc>
**/








/*----------------------------------------------------------------------------*/
/* Custom memory allocator routines. -----------------------------------------*/
//#include "bget/bget.h"
/**
 * Typical how to use :
 * call sc_malloc_init at the VERY begining of your program:
 * scm_alloc_init(SC_MALLOC_NOSGL_DEFAULT);
 * Then, simply use scm_malloc and scm_free instead of malloc and free.
 *
 * Note: if you want to use custom memory pools, you should use the
 * lines below instead of sc_malloc_init(SC_MALLOC_NOSGL_DEFAULT):
 *
 * scm_alloc_init(SCM_LRAM);
 * -> pool memory from Low RAM.
 * Note: As ramcart pooling is automatically performed in sc_init
 *       function, you don't need to perform initialisation for it.
 *
 * char dummy_buffer[1024];
 * scm_pool((void*)dummy_buffer, sizeof(dummy_buffer), SCM_HRAM);
 * -> pool memory from 1KB memory buffer.
 *    (in fact, it is a little less than 1KB, because of memory allocator's internal data)
 *
 * unsigned char* buff = (unsigned char*)scm_malloc(100, SCM_LRAM | SCM_RAMCART);
 * -> Alloc 100 bytes on LRAM or RAM cart.
 *
 * scm_free((void*)buff);
 * -> Frees the buffer allocated above.
 *
 * Also, scm_alloc_init and scm_pool returns pools memory total
 * size, so you can check if memory pool fits your program memory needs.
**/





/**
 * Saturn memory tags:
 * The top 8 bits are used by SatCom. Feel free to use
 * the low 8 bits for your personal usage.
**/
/* Base tags */
#define SCM_LRAM    0x0100
#define SCM_HRAM    0x0200
#define SCM_RAMCART 0x0400
#define SCM_VDP1RAM 0x0800

/* Unclassifiable tags */
#define SCM_OTHERS  0x8000

/* All tags. */
#define SCM_ANYTAG (BGET_ANY_TAG)





/**
 * Use an arbitrary memory space for custom malloc.
 * Call this function at the VERY BEGINING of your program !
 * Return memory pool total size.
**/
unsigned long sc_mpool(void *buffer, unsigned long len, unsigned short tag);


/**
 * Init memory space for custom malloc.
 * Call this function at the VERY BEGINNING of your program !
 * Return memory pool total size.
 *
 * The `init_flags' parameter designates the memory zone to pool.
 * As it is used as a bit field, multiple area can be set.
 *  SCM_LRAM    : Whole Low RAM
 *  SCM_HRAM    : High RAM from 0x060C0000
 *  SCM_RAMCART : RAM cart (start address and size depends on your RAM cart)
 *  SCM_VDP1RAM : VRAM last portion free to user under SGL
 *
 * Note: As ramcart pooling is automatically performed in sc_init
 *       function, you don't need to perform initialisation for it.
**/
/* Some typical `init_flags' values: */
#ifdef SC_EXEC_IN_LRAM
/* User's program run in Low RAM */
#   define SC_MALLOC_SGL_DEFAULT   (SCM_VDP1RAM)
#   define SC_MALLOC_NOSGL_DEFAULT (SCM_HRAM)
#else
/* User's program run in High RAM */
#   define SC_MALLOC_SGL_DEFAULT   (SCM_LRAM | SCM_VDP1RAM)
#   define SC_MALLOC_NOSGL_DEFAULT (SCM_LRAM | SCM_HRAM)
#endif
unsigned long scm_alloc_init(unsigned short init_flags);


/**
 * Custom malloc : use theses function instead of malloc/free.
**/
void* scm_malloc(unsigned long size, unsigned short tag);
void scm_free(void *buffer);




/*----------------------------------------------------------------------------*/
/* Log stuff. ----------------------------------------------------------------*/

/**
 * Set callbacks to user-defined log functions.
 * (Set the callbacks to NULL in order to remove them.)
 *
 * Fct_scl_print function is a function that should print message on screen
 *  at a given coordinates. Coordinates unit is characters count, not pixel.
 *
 * Fct_scl_print function is a function that should print message on screen
 *  and scroll the previous messages as it would be done on a console terminal.
 *  Hence, log message's coordinates don't need to be specified.
**/
typedef void (*Fct_scl_print)(int/*col*/, int/*row*/, char*/*string*/, .../*param*/);
typedef void (*Fct_scl_console)(char*/*string*/, .../*param*/);
void scl_set_callbacks(Fct_scl_print prnt, Fct_scl_console cons);

/**
 * Logging macros.
 * (SATCOM INTERNAL USE ONLY !)
**/
#ifndef SC_RELEASE
#   define DBG_PRINT(_COL_, _ROW_, _STR_, ...) {if(sc.dbg_prnt) {sc.dbg_prnt(_COL_, _ROW_, _STR_, __VA_ARGS__);}}
#   define DBG_CONSOLE(_STR_, ...)             {if(sc.dbg_cons) {sc.dbg_cons(_STR_, __VA_ARGS__);}              }
#else
#   define DBG_PRINT(_COL_, _ROW_, _STR_, ...) {;}
#   define DBG_CONSOLE(_STR_, ...)             {;}
#endif // !SC_RELEASE


/*----------------------------------------------------------------------------*/
/*- Base functions. ----------------------------------------------------------*/


/**
 * Execute this function at the program startup.
**/
void sc_init(void);



/**
 * Use this function before slSynch()
**/
#ifndef SC_RELEASE
    void _sc_check_internal(void);
#   define sc_check() _sc_check_internal()
#else
#   define sc_check() {;}
#endif // !SC_RELEASE


/*----------------------------------------------------------------------------*/
/*- Protocol-related functions. ----------------------------------------------*/
/**
 * Supported protocols definition.
**/
#define SC_PROT_NONE     0   /* Do nothing !                                 */
#define SC_PROT_LEGACY   1   /* Action Replay Legacy comms protocol.         */
#define SC_PROT_EXTENDED 2   /* Action Replay eXtended Comms protocol.       */
#define SC_PROT_SERIAL   3   /* "Communication connector" behind the Saturn. */

#define SC_PROTOCOLS_COUNT 4


/**
 * SatCom settings kept in backup memory.
 *  - SC_BUP_VER1    : Version ID (1 byte) kept at the end of the backup data.
 *  - PROT_BUPFNAME  : backup's file name (11 non-null chars).
 *  - PROT_BUPCOMMENT: backup's comment (10 non-null chars).
 *
 * When you loose compatibility with previous structure (example: modify
 * structure size, and/or modify elements other than `reserved*', you MUST
 * change the version ID, and change backup file name too.
 * Hence, it is recommended to write the version ID in the backup file name, as below:
 *  SC_BUP_VER1   ->  0xMN
 *  PROT_BUPFNAME ->  "SatCom VM.N"
**/
#define SC_BUP_VER1     0x42
#define PROT_BUPFNAME   "SatCom V4.2"
#define PROT_BUPCOMMENT "SETTINGS  "



//----------------------------------------------------------------------
// SatCom data in backup memory.
// It consists in 16 bytes containing :
//  1 byte    : this structure size, in bytes (=16)
//  1 byte x 2: Main/sub transfer device/protocol set by user.
//  1 byte    : ARP address type (0:Auto Detect, 1:EMS, 2:Datel).
//  1 byte    : Saturn debug flag (0:normal, 1:no screen init, 2:no starfield) (used in SCFirwmWare).
//  1 byte    : this structure version (=SC_BUP_VER1)
//  other     : reserved for future usage.
// 
//  Note: "Sub" protocol can be used when connecting 2 devices to your Saturn.
//        Example: main=ARP device, sub=communication port.
//        i.e., it may be useful in the case of debuging Saturn communication port.
// 
//----------------------------------------------------------------------
#if defined( __GNUC__ )
#pragma pack(1)
#else
#pragma pack(push,1)
#endif

typedef struct _sc_bup_data_t
{
    /*-----------------------------------------*/
    /* This structure size.                    */
    unsigned char struct_size;
    /* Reserved for future usage.              */
    unsigned char reserved1;
    /* Link/protocol type used.                */
    unsigned char type[2];

    /*-----------------------------------------*/
    /* ARP type, in the case an ARP is used.   */
    unsigned char arp_address;
    /* Debug flag, for SCFirmWare.             */
    unsigned char debug;
    /* Reserved for future usage.              */
    unsigned char reserved2[2];

    /*-----------------------------------------*/
    /* Reserved for future usage.              */
    unsigned char reserved3[4];

    /*-----------------------------------------*/
    /* Reserved for future usage.              */
    unsigned char reserved4[3];
    /* This structure version.                 */
    unsigned char struct_version;
} sc_bup_data_t;

#if defined( __GNUC__ )
#pragma pack()
#else
#pragma pack(pop)
#endif


/**
 * Get/Set protocol.
 * You can manually set the communication protocol you are using.
 * Warning : A non-NULL parameter will cause theses functions to read/write
 *           to the backup memory, so you shouldn't do this on every frame !
**/
void sc_getprot(sc_bup_data_t* dat_out);
void sc_setprot(sc_bup_data_t* dat_in);
/* Return a string containing the protocol's name. */
char* sc_get_protname(unsigned char p);





/*----------------------------------------------------------------------------*/
/*- Bonus : Data execute function. -------------------------------------------*/

/**
 * Parameters: tmp_address(4), length(4), exec_address(4)
**/
void sc_execute(unsigned long tmp_address, unsigned long length, unsigned long exec_address);


/*----------------------------------------------------------------------------*/
/*- Bonus : Wait for VBlank function. ----------------------------------------*/
void sc_wait_vblank(void);


/*----------------------------------------------------------------------------*/
/*- Bonus : Action replay Flash memory write functions. ----------------------*/

/**
 * Get Flash vendor ID when vendorid == 1.
 * Get Flash device ID when vendorid == 0.
**/
unsigned short sc_flash_getid(unsigned char vendorid);
/**
 * Write page data (256 bytes) to the Flash.
 * Note: Erase flash when source == NULL.
**/
void sc_flash_write(volatile unsigned short* source, volatile unsigned short* dest);



/*----------------------------------------------------------------------------*/
/*- Bonus : "stopwatch-like" timer functions. --------------------------------*/


/**
 * Start/Reset the timer.
**/
void sc_tmr_start(void);
/**
 * sc_tmr_lap     : Get timer value in CPU tick unit.
 * sc_tmr_lap_usec: Get timer value in micro-second unit.
 * Note1: 1 tick = 0.3 usec (approx.), so you can safely directly use the usec value.
 *        (using tick value is recommended in the case you need to add dozens of lap values)
 * Note2: tick value overflows at around 16msec (16000usec), so don't measure long time interval !!
**/
unsigned short sc_tmr_lap(void);
unsigned short sc_tmr_lap_usec(void);

/**
 * Convert tick value to usec value.
**/
unsigned long sc_tmr_tick2usec(unsigned long tick);
/**
 * Wait for a given amount of time (in usec).
 * Note: You can safely sleep up to 42 seconds (= 42000000 usec).
 *       After this value, sleep will be incorrect due to overflow errors.
**/
void sc_usleep(unsigned long usec);



/*----------------------------------------------------------------------------*/
/*- Bonus : Built-in backup memory load/save functions. ----------------------*/
/**
 * Note1: You don't need to init anything, because it is done in sc_init.
 * Note2: Backup devices other than builtin memory are not supported !
**/

/**
 * Read data containing the given name.
 * In the case the backup doesn't exist, don't set anything in data.
 * Note: `name' must be a string containing 11 non-null characters.
 * Return 0 on success, other value on failure.
**/
long sc_bup_read(char* name, void* data);
/**
 * Write data (datalen bytes) to {name, comment} backup.
 * In the case the backup failed, doesn't write anything.
 * Note1: `name' must be a string containing 11 non-null characters.
 * Note2: `comment' must be a string containing 10 non-null characters.
 * Return 0 on success, other value on failure.
**/
long sc_bup_write(void* data, unsigned long datalen, char* name, char* comment);






/*----------------------------------------------------------------------------*/
/*- Crappy Debugger stuff. ---------------------------------------------------*/
#include "debugger/scd_crappy_dbg.h"


/*----------------------------------------------------------------------------*/
/*- Debugger functions. ------------------------------------------------------*/



#include <stdarg.h>
#include <stdio.h>

#ifndef SC_RELEASE

/**
 * Send formatted string to debugger in PC.
 * Use this function in the same way as printf.
**/
    void _scd_logout_internal(unsigned char type, unsigned char id, char *string, ...);
#   define scd_logout(string, ...)     _scd_logout_internal(SCD_TYPE_MSG, 0, string, __VA_ARGS__)
#   define scd_msgout(id, string, ...) _scd_logout_internal(SCD_TYPE_MSG, id, string, __VA_ARGS__)

/**
 * Send prompt_str message to PC, then wait until receiving answer from PC.
 * Note1: returned string is a global variable, so you may need to copy
 *        it somewhere in the case you want to heap up calls to scd_prompt.
**/
    char* _scd_prompt_internal(char* prompt_str);
#   define scd_prompt(pstr) _scd_prompt_internal(pstr)

/**
 * Wait PC to receive all the debug messages.
 * If your program crashes, debug messages that are not sent
 * yet will be lost, so call this function in the case your
 * program may crash soon.
**/
    void _scd_logflush_internal(void);
#   define scd_logflush() _scd_logflush_internal()
//#   define scd_logflush() {;}



#else

#   define scd_logout(string, ...)     {;}
#   define scd_msgout(id, string, ...) {;}
#   define scd_prompt(pstr)            ""
#   define scd_logflush()              {;}

#endif // !SC_RELEASE

/*----------------------------------------------------------------------------*/
/*- File I/O functions. ------------------------------------------------------*/
/**
 *
 *
 *
 * Note1: About debug/release versions:
 * - When SC_RELEASE is defined, data is alaways read from CD-ROM.
 *   As an obvious consequence, writing or appending data in release mode won't perform anything.
 *
 * - And when SC_RELEASE is not defined, data is can be read from PC through
 *   Debugger, or from CD-ROM. PC / CD-ROM can be selected when opening the file.
 *
 * Note2: Please use the following folder/file naming rules :
 *   1. Please name from the root directory.
 *   2. Please use backslash ('\\') folder delimiter.
 *   3. Please use UPPER CASE maximum 8 characters directory names.
 *   4. Please use UPPER CASE 8.3 file names.
 *  -> Typical file name: "FOLDER1\\FOLDER2\\FILE.EXT"
 *  * Theses rules applies both in PC mode and CD-ROM mode.
**/


/**
 * File structure definition.
**/
#define SC_FILENAME_LEN 128
typedef struct _sc_file_t
{
    /* Set to 1 when open. */
    unsigned char is_open;

    /* When set to 1, read/write/append file from/to PC. */
    unsigned char pc_file;

    /* File name. */
    char filename[SC_FILENAME_LEN];

} sc_file_t;

/**
 * Number of files that can be opened simultaneously.
 * (The higher you set, the bigger the memory is used.)
 *
 * Note: The required global data memory size (in bytes) is:
 *       168 * SC_FILE_OPENMAX + 3168
**/
#define SC_FILE_OPENMAX 10
/**
 * Maximum number of subfolders + file name.
 * Example: "FOLDER1/TEST/FILE.BIN" -> at least 3 is required.
 * (The higher you set, the bigger the memory is used.)
 *
 * Note: The required global data memory size (in bytes) is:
 *       sizeof(GfsDirName) (=12 bytes) * SC_DIRMAX
 *       so, setting SC_DIRMAX to 20 is enough, and takes only 240 bytes.
**/
#define SC_DIRMAX 20
/**
 * Saturn CD-ROM sector's available data size.
 * (without ecc stuff, etc)
 * You shouldn't to modify this.
**/
#define SC_SECTOR_SIZE 2048


/**
 * Return a file handle to the specified file.
 * When too much files are open simultaneously, NULL is returned.
 *  In this case, you may need to set SC_FILE_OPENMAX to an higher value.
 *
 * Note1: Even if the specified file doesn't exist, an handle will be
 *        returned, because no "file exist check" is performed.
 * 
 * Note2: Set `pc_file' parameter to `SC_FILE_PC' in the case you want to
 *        access file in your PC. However, in release mode, `pc_file'
 *        parameter is ignored, and file will be accessed only from CD-ROM.
**/
#define SC_FILE_CDROM 0
#define SC_FILE_PC    1
sc_file_t* sc_fopen(unsigned char pc_file, char* filename);

/**
 * Free the specified file handle.
 * Please use this function when you don't need to access your file any more.
**/
void sc_fclose(sc_file_t* fh);


/**
 * Return specified file's size.
 * In the case the file doesn't exists, return 0;
**/
unsigned long sc_fsize(sc_file_t* fh);


/**
 * Read `size' bytes from specified file's `offset'th byte to `ptr'.
 *
 * Return the number of bytes actually read.
 * In the case the file doesn't exists, or read operation failed, return 0; *
 *
 * Note1: To increase performances when reading from CD-ROM, it is recommended
 *        to use offset values that are multiple of 2048 bytes.
 *        If it is not the case, 2 open->read->close requests are performed (instead of 1).
 *        Moreover, up to 2047 "useless" bytes may be read from CD when offset is not a multiple of 2048 bytes.
 *
 * Note2: To increase performances when reading from CD-ROM, it is recommended
 *        to use files in root folder only.
 *
 * Note3: If `size' is higher than file's size (=fsz), fsz bytes are actually read, and fsz is returned.
 *        So you can use the following code :
 *        char buffer[1234];
 *        unsigned long file_len = sc_fread(fh, 0, sizeof(buffer), (void*)buffer);
**/
unsigned long sc_fread(sc_file_t* fh, unsigned long offset, unsigned long size, void* ptr);



typedef void (*Fct_sc_fread_callback)(unsigned long /*data_transfer*/, unsigned long /*data_total*/, void* /*callback_data*/);
/**
 * Same stuff as `sc_fread', but allow to use callback between transfer.
 *
 * Return value:  see `sc_fread'.
**/
unsigned long sc_callback_fread(sc_file_t* fh, unsigned long offset, unsigned long size, void* ptr, 
                                unsigned long callback_count, 
                                Fct_sc_fread_callback callback_fct, void* callback_data);


/**
 * Write `size' bytes from `ptr' to specified file's `offset'th byte.
 *
 * Return the number of bytes actually written.
 * In the case the file doesn't exists, or write operation failed, return 0;
 *
 * Note: When trying to write to a file in the CD-ROM, 0 is returned.
**/
unsigned long sc_fwrite(void* ptr, unsigned long size, unsigned long offset, sc_file_t* fh);
/**
 * Append `size' bytes from `ptr' to specified file.
 *
 * Return the number of bytes actually appended.
 * In the case the file doesn't exists, or append operation failed, return 0;
 *
 * Note: When trying to append to a file in the CD-ROM, 0 is returned.
**/
unsigned long sc_fappend(void* ptr, unsigned long size, sc_file_t* fh);



#ifdef SC_USE_CDROM
/**
 * Extra function for initializing the CD-ROM when testing with PAR.
**/
int sc_cdrom_unlock(void);
#endif // SC_USE_CDROM



#endif //SATCOM_SATURN_H
