#ifndef __BLACKSASI_H__
#define __BLACKSASI_H__
#include "sdcard.h"
#include "gpio.h"
#include "log.h"
#include "config.h"
#include "scsi.h"
// Log File
#define VERSION "0.xx-SNAPSHOT-BLACKSASI-2022-09-28-F4"

#define LOG_FILENAME "LOG.txt"

#define SPI_CLOCK SD_SCK_MHZ(50)

#define DEBUG            1      // 0:No debug information output
                                // 1: Debug information output to USB Serial
                                // 2: Debug information output to LOG.txt (slow)

#define SCSI_SELECT      0      // 0 for STANDARD
                                // 1 for SHARP X1turbo
                                // 2 for NEC PC98
#define READ_SPEED_OPTIMIZE  1 // Faster reads
#define WRITE_SPEED_OPTIMIZE 1 // Speeding up writes
#define USE_DB2ID_TABLE      1 // Use table to get ID from SEL-DB

// SCSI config
#define NUM_SCSIID  7          // Maximum number of supported SCSI-IDs (The minimum is 0)
#define NUM_SCSILUN 2          // Maximum number of LUNs supported     (The minimum is 0)
#define READ_PARITY_CHECK 0    // Perform read parity check (unverified)

// HDD format
#define MAX_BLOCKSIZE 2048     // Maximum BLOCK size


#if DEBUG == 1
#define serial Serial
#define LOG(XX)      serial.print(XX)
#define LOGHEX(XX)   serial.print(XX, HEX)
#define LOGDEC(XX)   serial.print(XX, DEC)
#define LOGBIN(XX)   serial.print(XX, BIN)
#define LOGN(XX)     serial.println(XX)
#define LOGHEXN(XX)  serial.println(XX, HEX)
#define LOGDECN(XX)  serial.println(XX, DEC)
#define LOGBIN_N(XX) serial.println(XX, BIN)
#elif DEBUG == 2
#define LOG(XX)      LOG_FILE.print(XX); LOG_FILE.sync();
#define LOGHEX(XX)   LOG_FILE.print(XX, HEX); LOG_FILE.sync();
#define LOGDEC(XX)   LOG_FILE.print(XX, DEC); LOG_FILE.sync();
#define LOGBIN(XX)   LOG_FILE.print(XX, BIN); LOG_FILE.sync();
#define LOGN(XX)     LOG_FILE.println(XX); LOG_FILE.sync();
#define LOGHEXN(XX)  LOG_FILE.println(XX, HEX); LOG_FILE.sync();
#define LOGDECN(XX)  LOG_FILE.println(XX, DEC); LOG_FILE.sync();
#define LOGBIN_N(XX) LOG_FILE.println(XX, BIN); LOG_FILE.sync();
#else
#define LOG(XX)       //serial.print(XX)
#define LOGHEX(XX)    //serial.print(XX, HEX)
#define LOGDEC(XX)    //serial.print(XX, DEC)
#define LOGBIN(XX)    //serial.print(XX, BIN)
#define LOGN(XX)      //serial.println(XX)
#define LOGHEXN(XX)   //serial.println(XX, HEX)
#define LOGDECN(XX)   //serial.println(XX, DEC)
#define LOGBIN_N(XX)  //serial.println(XX, BIN)
#endif

#define active   1
#define inactive 0
#define high 0
#define low 1

#define isHigh(XX) ((XX) == high)
#define isLow(XX) ((XX) != high)

// GPIO register port
#define PAREG GPIOA->regs
#define PBREG GPIOB->regs
#define PCREG GPIOC->regs
#define PDREG GPIOD->regs
#define PEREG GPIOE->regs

// Termination control (LOW is active)
#define TERMINATION_HIGH()      GPIOREG(BOARD_SCSI_TERM_HIGH)->BSRR = (1 << BOARD_SCSI_TERM_HIGH % 16) << 16 | (1 << BOARD_SCSI_TERM_LOW % 16);
#define TERMINATION_LOW()       GPIOREG(BOARD_SCSI_TERM_HIGH)->BSRR = (1 << BOARD_SCSI_TERM_LOW % 16) << 16 | (1 << BOARD_SCSI_TERM_HIGH % 16);
#define TERMINATION_OFF()       GPIOREG(BOARD_SCSI_TERM_HIGH)->BSRR = (1 << BOARD_SCSI_TERM_HIGH % 16) | (1 << BOARD_SCSI_TERM_LOW % 16);

// Enable SCSI buffers
#define SCSI_OUTPUT_ENABLE()    GPIOREG(BOARD_TRANS_OE)->BSRR = (1 << (BOARD_TRANS_OE % 16)) << 16;
#define SCSI_OUTPUT_DISABLE()   GPIOREG(BOARD_TRANS_OE)->BSRR = (1 << (BOARD_TRANS_OE % 16));

// SCSI Data Direction
#define SCSI_DATABUS_IN()       GPIOREG(BOARD_SCSI_DTD)->BSRR = (1 << (BOARD_SCSI_DTD % 16)) << 16; 
#define SCSI_DATABUS_OUT()      GPIOREG(BOARD_SCSI_DTD)->BSRR = (1 << (BOARD_SCSI_DTD % 16))

// SCSI IND Direction
#define SCSI_IND_IN()           GPIOREG(BOARD_SCSI_IND)->BSRR = (1 << (BOARD_SCSI_IND % 16));
#define SCSI_IND_OUT()          GPIOREG(BOARD_SCSI_IND)->BSRR = (1 << (BOARD_SCSI_IND % 16)) << 16;

//SCSI Data and IND Direction
#define SCSI_DATABUS_IND_IN()   GPIOREG(BOARD_SCSI_DTD)->BSRR = ((1 << (BOARD_SCSI_DTD % 16)) << 16) | (1 << (BOARD_SCSI_IND % 16));
#define SCSI_DATABUS_IND_OUT()  GPIOREG(BOARD_SCSI_DTD)->BSRR = ((1 << (BOARD_SCSI_IND % 16)) << 16) | (1 << (BOARD_SCSI_DTD % 16));

// SCSI TAD Direction

#define SCSI_TAD_IN()           GPIOREG(BOARD_SCSI_TAD)->BSRR = (1 << (BOARD_SCSI_TAD % 16));
#define SCSI_TAD_OUT()          GPIOREG(BOARD_SCSI_TAD)->BSRR = (1 << (BOARD_SCSI_TAD % 16)) << 16;

// Virtual pin (Arduio compatibility is slow, so make it MCU-dependent)
#define PA(BIT)       (BIT)
#define PB(BIT)       (BIT + 16)
#define PC(BIT)       (BIT + 32)
#define PD(BIT)       (BIT + 48)
#define PE(BIT)       (BIT + 64)

// Virtual pin decoding
#define GPIOREG(VPIN)     ((VPIN) >= 16 ? ((VPIN) >= 32 ? ((VPIN) >= 48 ? ((VPIN) >= 48 ? PEREG : PDREG) : PCREG) : PBREG) : PAREG)
#define BITMASK(VPIN)     (1 << ((VPIN % 16) & 15))


#define vATN       PB(14)     // SCSI:ATN
#define vBSY       PB(6)      // SCSI:BSY
#define vACK       PB(7)      // SCSI:ACK
#define vRST       PA(15)     // SCSI:RST
#define vMSG       PE(2)      // SCSI:MSG
#define vSEL       PE(3)      // SCSI:SEL
#define vCD        PE(4)      // SCSI:C/D
#define vREQ       PE(5)      // SCSI:REQ
#define vIO        PE(6)      // SCSI:I/O
#define vSD_CS     PB(1)      // SDCARD:CS
#define vDTD       PC(0)      // SCSI:DTD
#define vIND       PC(1)      // SCSI:IND
#define vTAD       PC(2)      // SCSI:TAD
#define vTRANS_OE  PB(12)     // SCSI:TRANS_OE
// SCSI output pin control: active LOW (direct pin drive)
#define SCSI_OUT(VPIN,ACTIVE) { GPIOREG(VPIN)->BSRR = BITMASK(VPIN) << ((ACTIVE) ? 16 : 0); }

// SCSI input pin check (inactive=0,active=1)
#define SCSI_IN(VPIN) ((~GPIOREG(VPIN)->IDR >> ((VPIN % 16) & 15)) & 1)

// HDDiamge file
#define HDIMG_ID_POS  2                 // Position to embed ID number
#define HDIMG_LUN_POS 3                 // Position to embed LUN numbers
#define HDIMG_BLK_POS 5                 // Position to embed block size numbers
#define MAX_FILE_PATH 32                // Maximum file name length

// SCSI
#define SCSI_INFO_BUF_SIZE 36
#define SCSI_INFO_VENDOR_SIZE 9
#define SCSI_INFO_PRODUCT_SIZE 17
#define SCSI_INFO_VERSION_SIZE 5
typedef struct hddimg_struct
{
	FsFile      m_file;                 // File object
	uint64_t    m_fileSize;             // File size
	size_t      m_blocksize;            // SCSI BLOCK size
}HDDIMG;

// Declare functions
void onFalseInit(void);
void onBusReset(void);
void switchImage(void);
void initFileLog(void);
void finalizeFileLog(void);


// SCSI config
#define MAX_SCSIID  7          // Maximum number of supported SCSI-IDs (The minimum is 0)
#define MAX_SCSILUN 8          // Maximum number of LUNs supported     (The minimum is 0)
#define NUM_SCSIID  MAX_SCSIID // Number of enabled SCSI IDs
#define NUM_SCSILUN 1          // Number of enabled LUNs
#define READ_PARITY_CHECK 0    // Perform read parity check (unverified)
#define DEFAULT_SCSI_ID 1
#define DEFAULT_SCSI_LUN 0
#define SCSI_BUF_SIZE 512      // Size of the SCSI Buffer
#define HDD_BLOCK_SIZE 512
#define OPTICAL_BLOCK_SIZE 2048

// HDD format
#define MAX_BLOCKSIZE 4096     // Maximum BLOCK size

// LED ERRORS
#define ERROR_FALSE_INIT  3
#define ERROR_NO_SDCARD   5

enum SCSI_DEVICE_TYPE
{
  SCSI_DEVICE_HDD,
  SCSI_DEVICE_OPTICAL,
};

#define CDROM_RAW_SECTORSIZE    2352
#define CDROM_COMMON_SECTORSIZE 2048

#define MAX_SCSI_COMMAND  0xff
#define SCSI_COMMAND_HANDLER(x) static byte x(SCSI_DEVICE *dev, const byte *cdb)


#define ATN       PB14      // SCSI:ATN
#define BSY       PB6      // SCSI:BSY
#define ACK       PB7     // SCSI:ACK
#define RST       PA15     // SCSI:RST
#define MSG       PE2      // SCSI:MSG
#define SEL       PE3      // SCSI:SEL
#define CD        PE4      // SCSI:C/D
#define REQ       PE5      // SCSI:REQ
#define IO        PB1      // SCSI:I/O

#define LED1      PA4     // LED
#define LED2      PA5     // Driven LED
#define LED3      PA6     // Driven LED
// Image Set Selector
#define IMAGE_SELECT1   PC4
#define IMAGE_SELECT2   PC5


#define NOP(x) for(unsigned _nopcount = x; _nopcount; _nopcount--) { asm("NOP"); }

/* SCSI Timing delays */
// Due to limitations in timing granularity all of these are "very" rough estimates
#define SCSI_BUS_SETTLE() NOP(30);                            // spec 400ns ours ~420us
#define SCSI_DATA_RELEASE() NOP(30);                          // spec 400ns ours ~420us
#define SCSI_HOLD_TIME() asm("NOP"); asm("NOP"); asm("NOP");  // spec 45ns ours ~42ns
#define SCSI_DESKEW() // asm("NOP"); asm("NOP"); asm("NOP");     // spec 45ns ours ~42ns
#define SCSI_CABLE_SKEW() // asm("NOP");                         // spec 10ns ours ~14ns
#define SCSI_RESET_HOLD() asm("NOP"); asm("NOP");             // spec 25ns ours ~28ns
#define SCSI_DISCONNECTION_DELAY() NOP(15);                   // spec 200ns ours ~210ns

/* SCSI phases
+=============-===============-==================================-============+
|    Signal   |  Phase name   |       Direction of transfer      |  Comment   |
|-------------|               |                                  |            |
| MSG|C/D|I/O |               |                                  |            |
|----+---+----+---------------+----------------------------------+------------|
|  0 | 0 | 0  |  DATA OUT     |       Initiator to target     \  |  Data      |
|  0 | 0 | 1  |  DATA IN      |       Initiator from target   /  |  phase     |
|  0 | 1 | 0  |  COMMAND      |       Initiator to target        |            |
|  0 | 1 | 1  |  STATUS       |       Initiator from target      |            |
|  1 | 0 | 0  |  *            |                                  |            |
|  1 | 0 | 1  |  *            |                                  |            |
|  1 | 1 | 0  |  MESSAGE OUT  |       Initiator to target     \  |  Message   |
|  1 | 1 | 1  |  MESSAGE IN   |       Initiator from target   /  |  phase     |
|-----------------------------------------------------------------------------|
| Key:  0 = False,  1 = True,  * = Reserved for future standardization        |
+=============================================================================+ 
*/
// SCSI phase change as single write to port B
#define SCSIPHASEMASK(MSGACTIVE, CDACTIVE, IOACTIVE) ((BITMASK(vMSG)<<((MSGACTIVE)?16:0)) | (BITMASK(vCD)<<((CDACTIVE)?16:0)) | (BITMASK(vIO)<<((IOACTIVE)?16:0)))

#define SCSI_PHASE_DATAOUT SCSIPHASEMASK(inactive, inactive, inactive)
#define SCSI_PHASE_DATAIN SCSIPHASEMASK(inactive, inactive, active)
#define SCSI_PHASE_COMMAND SCSIPHASEMASK(inactive, active, inactive)
#define SCSI_PHASE_STATUS SCSIPHASEMASK(inactive, active, active)
#define SCSI_PHASE_MESSAGEOUT SCSIPHASEMASK(active, active, inactive)
#define SCSI_PHASE_MESSAGEIN SCSIPHASEMASK(active, active, active)

#define SCSI_PHASE_CHANGE(MASK) { PBREG->BSRR = (MASK); }
//BLACKSASI clean this up and use defines
// Data pins
//                                                         5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
static const uint32_t scsiDbOutputRegOr_PDREG         = 0b00000000000000000101010101010101;
static const uint32_t scsiDbInputOutputAnd_PDREG      = 0b00000000000000000000000000000000;
static const uint32_t scsiDbInputOutputPullAnd_PDREG  = 0b00000000000000000101010101010101;
// Control pins
//                                                         5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
static const uint32_t scsiDbOutputRegOr_PEREG         = 0b00000000000000000000000000000001;
static const uint32_t scsiDbInputOutputAnd_PEREG      = 0b00000000000000000011111111110000;
static const uint32_t scsiDbInputOutputPullAnd_PEREG  = 0b00000000000000000001010101010001;

// Put DB and DP in output mode and control buffers
#define SCSI_DB_OUTPUT() { PDREG->MODER = (PDREG->MODER & scsiDbInputOutputAnd_PDREG) | scsiDbOutputRegOr_PDREG; PEREG->MODER = (PEREG->MODER & scsiDbInputOutputAnd_PEREG) | scsiDbOutputRegOr_PEREG; SCSI_DATABUS_OUT() ;}

// Put DB and DP in input mode and control buffers
#define SCSI_DB_INPUT()  { PDREG->MODER = (PDREG->MODER & scsiDbInputOutputAnd_PDREG); PEREG->MODER = (PEREG->MODER & scsiDbInputOutputAnd_PEREG); SCSI_DATABUS_IN();}
#define SCSI_SET_PULL()  { PDREG->PUPDR |= scsiDbInputOutputPullAnd_PDREG; PEREG->PUPDR |= scsiDbInputOutputPullAnd_PEREG; }

/*
static const uint32_t scsiDbOutputRegOr = 0x55150011;
static const uint32_t scsiDbInputOutputAnd = 0x00C0FFCC;
// Put DB and DP in output mode
#define SCSI_DB_OUTPUT() { PBREG->MODER = (PBREG->MODER & scsiDbInputOutputAnd) | scsiDbOutputRegOr; }

// Put DB and DP in input mode
#define SCSI_DB_INPUT()  { PBREG->MODER = (PBREG->MODER & scsiDbInputOutputAnd); }
*/

#if XCVR == 1

#define TR_TARGET        PC2   // Target Transceiver Control Pin
#define TR_DBP           PC0   // Data Pins Transceiver Control Pin
#define TR_INITIATOR     PC1   // Initiator Transciever Control Pin

#define vTR_TARGET       PC(2) // Target Transceiver Control Pin
#define vTR_DBP          PC(0) // Data Pins Transceiver Control Pin
#define vTR_INITIATOR    PC(1) // Initiator Transciever Control Pin

#define TR_INPUT 0
#define TR_OUTPUT 1

// Transceiver control definitions
#define TRANSCEIVER_IO_SET(VPIN,TR_INPUT) { GPIOREG(VPIN)->BSRR = BITMASK(VPIN) << ((TR_INPUT) ? 16 : 0); }


//                                                      5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
static const uint32_t SCSI_TARGET_PORTD_AND        = 0b11111111111111111100111111111111;
//                                                      5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
static const uint32_t SCSI_TARGET_PORTD_OR         = 0b00000000000000000001000000000000;

//                                                      5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
static const uint32_t SCSI_TARGET_PORTE_AND        = 0b11111111111111111100000110000011;
//                                                      5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
static const uint32_t SCSI_TARGET_PORTE_OR         = 0b00000000000000000001010100010000;

// Turn on the output only for BSY
#define SCSI_BSY_ACTIVE()      { PEREG->MODER = (PEREG->MODER & SCSI_TARGET_PORTE_AND) | SCSI_TARGET_PORTE_OR; PDREG->MODER = (PDREG->MODER & SCSI_TARGET_PORTD_AND) | SCSI_TARGET_PORTD_OR; SCSI_OUT(vBSY, active) }

// BSY,REQ,MSG,CD,IO Turn off output, BSY is the last input
#define SCSI_TARGET_INACTIVE() { PEREG->MODER = (PEREG->MODER & SCSI_TARGET_PORTE_AND); PDREG->MODER = (PDREG->MODER & SCSI_TARGET_PORTD_AND); TRANSCEIVER_IO_SET(vTR_TARGET,TR_INPUT); }

#define SCSI_TARGET_ACTIVE()   { PEREG->MODER = (PEREG->MODER & SCSI_TARGET_PORTE_AND) | SCSI_TARGET_PORTE_OR; PDREG->MODER = (PDREG->MODER & SCSI_TARGET_PORTD_AND) | SCSI_TARGET_PORTD_OR; }

#else

// Turn on the output only for BSY
#define SCSI_BSY_ACTIVE()      { pinMode(BSY, OUTPUT_OPEN_DRAIN); SCSI_OUT(vBSY,  active) }

// BSY,REQ,MSG,CD,IO Turn off output, BSY is the last input
#define SCSI_TARGET_INACTIVE() { SCSI_OUT(vREQ,inactive); SCSI_PHASE_CHANGE(SCSI_PHASE_DATAOUT); SCSI_OUT(vBSY,inactive); pinMode(BSY, INPUT); }

// BSY,REQ,MSG,CD,IO Turn on the output (no change required for OD)
#define SCSI_TARGET_ACTIVE()   { }

#endif

// HDDimage file
#define HDIMG_ID_POS  2                 // Position to embed ID number
#define HDIMG_LUN_POS 3                 // Position to embed LUN numbers
#define HDIMG_BLK_POS 5                 // Position to embed block size numbers
#define MAX_FILE_PATH 64                // Maximum file name length

/*
 *  Data byte to BSRR register setting value and parity table
*/
/**
 * BSRR register generator
 * Totally configurable for which pin is each data bit, which pin is PTY, and which pin is REQ.
 * The only requirement is that data and parity pins are in the same GPIO block.
 * REQ can be specified as -1 to ignore, as it doesn't have to be in the same GPIO block.
 * This is dramatically slower than the original static array, but is easier to configure
 */
static uint32_t genBSRR(uint32_t data) {
  uint8_t masks[] = {0UL, 1UL, 2UL, 3UL, 4UL, 5UL, 6UL, 7UL};
  // Positions array indicates which bit position each data bit goes in
  // positions[0] is for data bit 0, position[1] for data bit 1, etc
  // DB0, DB1, DB2, DB4, DB5, DB6, DB7 in order
  uint8_t positions[] = {8UL, 9UL, 10UL, 2UL, 12UL, 13UL, 14UL, 15UL};
  uint8_t dbpPosition = 0UL;
  int reqPosition = 6;
  uint8_t bitsAsserted = 0;

  uint32_t output = 0x00000000;
  for (int i = 0; i < 8; i++) {
    if (data & (0x1 << masks[i])) {
      // There's a one in this bit position, BSRR reset
      output |= 0x1 << (positions[i] + 16);
      bitsAsserted++;
    } else {
      // There's a 0 in this bit position, BSRR set high
      output |= (0x1 << positions[i]);
    }
  }

  // Set the parity bit
  if (bitsAsserted % 2 == 0) {
    // Even number of bits asserted, Parity asserted (0, low, BSRR reset)
    output |= 0x01 << (dbpPosition + 16);
  } else {
    // Odd number of bits asserted, Parity deasserted (1, high, BSRR set)
    output |= 0x01 << dbpPosition;
  }

  // BSRR set REQ if specified
  // Only set > 0 if it's in the same GPIO block as DB and DBP
  if (reqPosition >= 0) {
    output |= 0x01 << reqPosition;
  }

  return output;
}

// BSRR register control value that simultaneously performs DB set, DP set, and REQ = H (inactrive)
//uint32_t db_bsrr[256];
// Parity bit acquisition
#define PARITY(DB) (db_bsrr[DB]&1)

// #define GET_CDB6_LBA(x) ((x[2] & 01f) << 16) | (x[3] << 8) | x[4]
#define READ_DATA_BUS() (byte)((~(uint32_t)GPIOB->regs->IDR)>>8)



struct SCSI_INQUIRY_DATA
{
  union
  {
  struct {
    // bitfields are in REVERSE order for ARM
    // byte 0
    byte peripheral_device_type:5;
    byte peripheral_qualifier:3;
    // byte 1
    byte reserved_byte2:7;
    byte rmb:1;
    // byte 2
    byte ansi_version:3;
    byte always_zero_byte3:5;
    // byte 3
    byte response_format:4;
    byte reserved_byte4:2;
    byte tiop:1;
    byte always_zero_byte4:1;
    // byte 4
    byte additional_length;
    // byte 5-6
    byte reserved_byte5;
    byte reserved_byte6;
    // byte 7
    byte sync:1;
    byte always_zero_byte7_more:4;
    byte always_zero_byte7:3;
    // byte 8-15
    char vendor[8];
    // byte 16-31
    char product[16];
    // byte 32-35
    char revision[4];
    // byte 36
    byte release;
    // 37-46
    char revision_date[10];
  };
  // raw bytes
  byte raw[64];
  };
};

// HDD image
typedef __attribute__((aligned(4))) struct _SCSI_DEVICE
{
	FsFile        *m_file;                  // File object
	uint64_t      m_fileSize;               // File size
	uint16_t      m_blocksize;              // SCSI BLOCK size
  uint16_t      m_rawblocksize;           // OPTICAL raw sector size
  uint8_t       m_type;                   // SCSI device type
  uint32_t      m_blockcount;             // blockcount
  bool          m_raw;                    // Raw disk
  SCSI_INQUIRY_DATA *inquiry_block;       // SCSI information
  uint8_t       m_senseKey;               // Sense key
  uint16_t      m_additional_sense_code;  // ASC/ASCQ 
  bool          m_mode2;                  // MODE2 CDROM
  uint8_t       m_sector_offset;          // optical sector offset for missing sync header
} SCSI_DEVICE;


#endif // __BLACKSASI_H__