#include "config.h"
#include "scsi_defs.h"

#if SUPPORT_TAPE
/*
 * READ6 / 10 Command processing.
 */
void onTapeReadCommand(uint32_t adds, uint32_t len)
{
  LOG("-R ");
  LOGHEX4N(len);

  if(!m_sel) {
    m_sts |= STATUS_CHECK;
    m_phase = PHASE_STATUSIN;
    return;
  }
  
  switch(m_cmd[1] & 0x3) {
    case 0:
      if(len == 0) return;
      break;
    case 1: // Fixed
      break;
    case 2: // SILI
      break;
    case 3: // Illegal Request
      m_sts = 0x02;
      m_sel->m_sense.m_key = ILLEGAL_REQUEST;
      m_sel->m_sense.m_code = INVALID_FIELD_IN_CDB;       /* "Invalid field in CDB" */
      m_sel->m_sense.m_key_specific[0] = ERROR_IN_OPCODE;  /* "Error in Byte 1" */
      m_sel->m_sense.m_key_specific[1] = 0x00;
      m_sel->m_sense.m_key_specific[2] = 0x01;
      m_phase = PHASE_STATUSIN;
      return;
  }

  LED_ON();
  writeDataPhaseTape(len);
  LED_OFF();

  m_phase = PHASE_STATUSIN;
}

/*
 * WRITE6 / 10 Command processing.
 */
void onTapeWriteCommand(uint32_t adds, uint32_t len)
{
  LOG("-W ");
  LOGHEX4N(len);
  
  if(!m_sel) {
    m_sts |= STATUS_CHECK;
    m_phase = PHASE_STATUSIN;
    return;
  }
  
  LED_ON();
  m_sel->m_file.write(&len, 4);
  readDataPhaseTape(len);
  m_sel->m_file.write(&len, 4);
  LED_OFF();

  m_phase = PHASE_STATUSIN;
}

void TapeRead6CommandHandler() {
  LOG("[Read6]");
  m_sts |= onTapeReadCommand((((uint32_t)m_cmd[1] & 0x1F) << 16) | ((uint32_t)m_cmd[2] << 8) | m_cmd[3], (m_cmd[4] == 0) ? 0x100 : m_cmd[4]);
  m_phase = PHASE_STATUSIN;
}

void TapeWrite6CommandHandler() {
  LOG("[Write6]");
  m_sts |= onTapeWriteCommand((((uint32_t)m_cmd[1] & 0x1F) << 16) | ((uint32_t)m_cmd[2] << 8) | m_cmd[3], (m_cmd[4] == 0) ? 0x100 : m_cmd[4]);
  m_phase = PHASE_STATUSIN;
}

void TapeSeek6CommandHandler() {
  LOG("[Seek6]");
  m_phase = PHASE_STATUSIN;
}

void TapeRead10CommandHandler() {
  LOG("[Read10]");
  m_sts |= onTapeReadCommand(((uint32_t)m_cmd[2] << 24) | ((uint32_t)m_cmd[3] << 16) | ((uint32_t)m_cmd[4] << 8) | m_cmd[5], ((uint32_t)m_cmd[7] << 8) | m_cmd[8]);
  m_phase = PHASE_STATUSIN;
}

void TapeWrite10CommandHandler() {
  LOG("[Write10]");
  m_sts |= onTapeWriteCommand(((uint32_t)m_cmd[2] << 24) | ((uint32_t)m_cmd[3] << 16) | ((uint32_t)m_cmd[4] << 8) | m_cmd[5], ((uint32_t)m_cmd[7] << 8) | m_cmd[8]);
  m_phase = PHASE_STATUSIN;
}

void TapeSeek10CommandHandler() {
  LOG("[Seek10]");
  m_phase = PHASE_STATUSIN;
}

void TapeModeSense6CommandHandler() {
  uint8_t len;
  int page, pagemax, pagemin;

  LOGN("[ModeSense6]");
  /* Check whether medium is present */
  if(!m_sel) {
    m_sts |= STATUS_CHECK;
    m_phase = PHASE_STATUSIN;
    return;
  }

  memset(m_responsebuffer, 0, sizeof(m_responsebuffer));

  len = 1;
  /* Default medium type */
  m_responsebuffer[len++] = 0xf0;
  /* Write protected */
  m_responsebuffer[len++] = 0x80;

  /* Add block descriptor if DBD is not set */
  if (m_cmd[1] & 0x08) {
    m_responsebuffer[len++] = 0;             /* No block descriptor */
  } else {
    uint32_t capacity = (m_sel->m_fileSize / m_sel->m_blocksize) - 1;
    m_responsebuffer[len++] = 8;             /* Block descriptor length */
    m_responsebuffer[len++] = (capacity >> 24) & 0xff;
    m_responsebuffer[len++] = (capacity >> 16) & 0xff;
    m_responsebuffer[len++] = (capacity >> 8) & 0xff;
    m_responsebuffer[len++] = capacity & 0xff;
    m_responsebuffer[len++] = (m_sel->m_blocksize >> 24) & 0xff;
    m_responsebuffer[len++] = (m_sel->m_blocksize >> 16) & 0xff;
    m_responsebuffer[len++] = (m_sel->m_blocksize >> 8) & 0xff;
    m_responsebuffer[len++] = (m_sel->m_blocksize) & 0xff;
  }

  /* Check for requested mode page */
  page = m_cmd[2] & 0x3F;
  pagemax = (page != 0x3f) ? page : 0x3e;
  pagemin = (page != 0x3f) ? page : 0x00;
  for(page = pagemax; page >= pagemin; page--) {
    switch (page) {
      default:
        if(pagemin == pagemax) {
          /* Requested mode page is not supported */
          /* Prepare sense data */
          m_sts |= STATUS_CHECK;
          m_sel->m_sense.m_key = ILLEGAL_REQUEST;
          m_sel->m_sense.m_code = INVALID_FIELD_IN_CDB;       /* "Invalid field in CDB" */
          m_sel->m_sense.m_key_specific[0] = ERROR_IN_OPCODE;  /* "Error in Byte 2" */
          m_sel->m_sense.m_key_specific[1] = 0x00;
          m_sel->m_sense.m_key_specific[2] = 0x02;
          m_phase = PHASE_STATUSIN;
          return;
        }
    }
  }

  /* Report size of requested data */
  m_responsebuffer[0] = len;

  /* Truncate data if necessary */
  if (m_cmd[4] < len) {
    len = m_cmd[4];
  }

  // Send it
  writeDataPhase(len, m_responsebuffer);
  m_phase = PHASE_STATUSIN;
}

void TapeLoadUnloadCommandHandler() {
  if(m_cmd[4] & 1) {
    LOGN("[Load]");
  } else {
    LOGN("[Unload]");
  }
  if(!m_sel) {
    m_sts |= STATUS_CHECK;
    m_phase = PHASE_STATUSIN;
    return;
  }
  
  m_phase = PHASE_STATUSIN;
}

void TapePreventRemovalCommandHandler() {
  if(m_cmd[4] & 1) {
    LOGN("[Prevent Removal]");
  } else {
    LOGN("[Allow Removal]");
  }
  if(!m_sel) {
    m_sts |= STATUS_CHECK;
    m_phase = PHASE_STATUSIN;
    return;
  }
  
  m_phase = PHASE_STATUSIN;
}

void TapeReadCapacityCommandHandler() {
  LOGN("[ReadCapacity]");
  if(!m_sel) {
    m_sts |= STATUS_CHECK; // Image file absent
    m_phase = PHASE_STATUSIN;
    return;
  }

  uint32_t bl = m_sel->m_blocksize;
  uint32_t bc = m_sel->m_fileSize / bl;
  uint8_t buf[8] = {
    (uint8_t)(((uint32_t)(bc >> 24))&0xff), (uint8_t)(((uint32_t)(bc >> 16))&0xff), (uint8_t)(((uint32_t)(bc >> 8))&0xff), (uint8_t)(((uint32_t)(bc))&0xff),
    (uint8_t)(((uint32_t)(bl >> 24))&0xff), (uint8_t)(((uint32_t)(bl >> 16))&0xff), (uint8_t)(((uint32_t)(bl >> 8))&0xff), (uint8_t)(((uint32_t)(bl))&0xff)
  };
  writeDataPhase(8, buf);
  m_phase = PHASE_STATUSIN;
}

void TapeEraseCommandHandler() {
  LOGN("[Erase]");
  if(!m_sel) {
    m_sts |= STATUS_CHECK; // Image file absent
    m_phase = PHASE_STATUSIN;
    return;
  }
  // Truncate the current .tap file (maybe archive it by renaming instead of truncating?)
}

void TapeReadBlockLimitsCommandHandler() {
  uint16_t len = 0;
  LOGN("[ReadBlockLimits]");
  if(!m_sel) {
    m_sts |= STATUS_CHECK; // Image file absent
    m_phase = PHASE_STATUSIN;
    return;
  }
  m_responsebuffer[len++] = 0x00;
  m_responsebuffer[len++] = 0xFF; // Maximum Block Length
  m_responsebuffer[len++] = 0xFF;
  m_responsebuffer[len++] = 0xFF;
  m_responsebuffer[len++] = 0x00; // Minimum Block Length
  m_responsebuffer[len++] = 0x00;

  writeDataPhase(len, buf);
  m_phase = PHASE_STATUSIN;
}

void TapeRewindUnitCommandHandler() {
  LOGN("[Rewind]");
  if(!m_sel) {
    m_sts |= STATUS_CHECK; // Image file absent
    m_phase = PHASE_STATUSIN;
    return;
  }
  m_sel->m_filemarks = 0;
  m_sel->m_file.seek(0);
  m_phase = PHASE_STATUSIN;
}

void TapeReadPositionCommandHandler() {
  uint16_t len = 0;
  LOGN("[ReadPosition]");
  if(!m_sel) {
    m_sts |= STATUS_CHECK; // Image file absent
    m_phase = PHASE_STATUSIN;
    return;
  }

  m_responsebuffer[len++] = (m_sel->m_filemarks == 0) ? (1 << 7) : 0;
  m_responsebuffer[len++] = 0x00; // Partition(0)
  m_responsebuffer[len++] = 0x00; // Reserved
  m_responsebuffer[len++] = 0x00; // Reserved
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff); // First Block Location
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff00) >> 8;
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff0000) >> 16;
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff000000) >> 24;
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff); // Last Block Location
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff00) >> 8;
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff0000) >> 16;
  m_responsebuffer[len++] = (m_sel->m_filemarks & 0xff000000) >> 24;
  m_responsebuffer[len++] = 0x00; // Reserved
  m_responsebuffer[len++] = 0x00; // Blocks in buffer (0)
  m_responsebuffer[len++] = 0x00;
  m_responsebuffer[len++] = 0x00;
  m_responsebuffer[len++] = 0x00; // Bytes in buffer (0)
  m_responsebuffer[len++] = 0x00;
  m_responsebuffer[len++] = 0x00;
  m_responsebuffer[len++] = 0x00;

  writeDataPhase(len, buf);
  m_phase = PHASE_STATUSIN;
}

void ConfigureTapeHandlers(VirtualDevice_t *vdev) {
  for(int c = 0; c < 256; c++)
    vdev->m_handler[c] = &UnknownCommandHandler;

  vdev->m_handler[CMD_ERASE]                         = &TapeEraseCommandHandler;
  vdev->m_handler[CMD_TEST_UNIT_READY]               = &TestUnitCommandHandler;
  vdev->m_handler[CMD_REZERO_UNIT]                   = &TapeRewindUnitCommandHandler;
  vdev->m_handler[CMD_REQUEST_SENSE]                 = &RequestSenseCommandHandler;
  vdev->m_handler[CMD_READ_BLOCK_LIMITS]             = &TapeReadBlockLimitsCommandHandler;
  vdev->m_handler[CMD_READ6]                         = &TapeRead6CommandHandler;
  vdev->m_handler[CMD_WRITE6]                        = &TapeWrite6CommandHandler;
  vdev->m_handler[CMD_SEEK6]                         = &TapeSeek6CommandHandler;
  vdev->m_handler[CMD_INQUIRY]                       = &InquiryCommandHandler;
  vdev->m_handler[CMD_MODE_SELECT6]                  = &ModeSelect6CommandHandler;
  vdev->m_handler[CMD_MODE_SENSE6]                   = &TapeModeSense6CommandHandler;
  vdev->m_handler[CMD_START_STOP_UNIT]               = &TapeLoadUnloadCommandHandler;
  vdev->m_handler[CMD_PREVENT_REMOVAL]               = &TapePreventRemovalCommandHandler;
  vdev->m_handler[CMD_READ_CAPACITY10]               = &TapeReadCapacityCommandHandler;
  vdev->m_handler[CMD_READ10]                        = &TapeRead10CommandHandler;
  vdev->m_handler[CMD_WRITE10]                       = &TapeWrite10CommandHandler;
  vdev->m_handler[CMD_SEEK10]                        = &TapeSeek10CommandHandler;
  vdev->m_handler[CMD_READPOSITION10]                = &TapeReadPositionCommandHandler;
  vdev->m_handler[CMD_MODE_SELECT10]                 = &ModeSelect10CommandHandler;
  vdev->m_handler[CMD_MODE_SENSE10]                  = &TapeModeSense10CommandHandler;
}

// If config file exists, read the first three lines and copy the contents.
// File must be well formed or you will get junk in the SCSI Vendor fields.
void ConfigureTape(VirtualDevice_t *vdev, const char *image_name) {
  for(int i = 0; SCSI_INQUIRY_RESPONSE[i][0] != 0xff; i++) {
    if(SCSI_INQUIRY_RESPONSE[i][0] == DEV_TAPE) {
      memcpy(vdev->m_inquiryresponse, SCSI_INQUIRY_RESPONSE[i], SCSI_INQUIRY_RESPONSE_SIZE);
      break;
    }
  }

  if(image_name) {
    char configname[MAX_FILE_PATH+1];
    memcpy(configname, image_name, MAX_FILE_PATH+1);
    char *psuffix = strstr(configname, ".img");
    if(psuffix) {
      strcpy(psuffix, ".cfg");
    } else {
      sprintf(configname, "mt%d%d.cfg", vdev->m_id, vdev->m_lun);
    }
  
    FsFile config_file = sd.open(configname, O_RDONLY);
    if (config_file.isOpen()) {
      char vendor[9];
      memset(vendor, 0, sizeof(vendor));
      config_file.readBytes(vendor, sizeof(vendor));
      LOGN("SCSI VENDOR: ");
      LOGN(vendor);
      memcpy(&(vdev->m_inquiryresponse[8]), vendor, 8);
    
      char product[17];
      memset(product, 0, sizeof(product));
      config_file.readBytes(product, sizeof(product));
      LOGN("SCSI PRODUCT: ");
      LOGN(product);
      memcpy(&(vdev->m_inquiryresponse[16]), product, 16);
    
      char version[5];
      memset(version, 0, sizeof(version));
      config_file.readBytes(version, sizeof(version));
      LOGN("SCSI VERSION: ");
      LOGN(version);
      memcpy(&(vdev->m_inquiryresponse[32]), version, 4);
      config_file.close();
    }
  }

  vdev->m_type = DEV_TAPE;
  ConfigureTapeHandlers(vdev);
}
#endif
