/*
quickdev16.c - Quickdev16 support for uCON64

Copyright (c) 2009        david@optixx.org
Copyright (c) 2015 - 2019 dbjh


This program 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 2 of the License, or
(at your option) any later version.

This program 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 program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "misc/archive.h"
#include "misc/misc.h"
#include "misc/term.h"
#include "misc/usb.h"
#include "ucon64_misc.h"
#include "console/snes.h"
#include "backup/quickdev16.h"


#ifdef  USE_USB
static st_ucon64_obj_t quickdev16_obj[] =
  {
    {UCON64_SNES, WF_DEFAULT | WF_STOP | WF_NO_SPLIT}
  };
#endif

const st_getopt2_t quickdev16_usage[] =
  {
    {
      NULL, 0, 0, 0,
      NULL, "Quickdev16"/* http://www.optixx.org */,
      NULL
    },
#ifdef  USE_USB
    {
      "xqd16", 0, 0, UCON64_XQD16,              // send only
      NULL, "send ROM to Quickdev16",
      &quickdev16_obj[0]
    },
#endif
    {NULL, 0, 0, 0, NULL, NULL, NULL}
  };


#ifdef  USE_USB

#if 0 // unused
#define USB_UPLOAD_INIT      0
#define USB_UPLOAD_ADDR      1
#define USB_DOWNLOAD_INIT    2
#define USB_DOWNLOAD_ADDR    3
#define USB_CRC              4
#define USB_CRC_ADDR         5
#endif

#define USB_BULK_UPLOAD_INIT 6
#define USB_BULK_UPLOAD_ADDR 7
#define USB_BULK_UPLOAD_NEXT 8
#define USB_BULK_UPLOAD_END  9
#define USB_MODE_SNES        10
#define USB_MODE_AVR         11

#if 0 // unused
#define USB_AVR_RESET        12
#endif

#define READ_BUFFER_SIZE 8192
#define SEND_BUFFER_SIZE 128
#define SNES_HIROM_SHIFT 16
#define SNES_LOROM_SHIFT 15

#define TARGET (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT)

static int
check_quit (void)
{
  return (!ucon64.frontend ? kbhit () : 0) && getch () == 'q';
}


int
quickdev16_write_rom (const char *filename)
{
  int vendor_id = 0x16c0, product_id = 0x05dd, size, bytesread, numbytes,
      bytessent = 0;
  char vendor[] = "optixx.org", product[] = "QUICKDEV16", *buffer;
  usb_dev_handle *handle = NULL;
  FILE *file;
  time_t starttime;
  uint32_t bank_size, address = 0;
  uint16_t bank_shift;

#if     defined __unix__ || defined __BEOS__ || defined __APPLE__
  init_conio ();
  if (register_func (deinit_conio) == -1)
    {
      fputs ("ERROR: Could not register function with register_func()\n", stderr);
      exit (1);
    }
#if     !defined __BEOS__ && !defined __CYGWIN__
  regain_privileges ();
#endif
#endif

  usb_init ();
  switch (usbport_open (&handle, vendor_id, vendor, product_id, product))
    {
    case USBOPEN_SUCCESS:
      printf ("Opened USB device \"%s\" with vendor ID 0x%04x and product ID 0x%04x\n",
              product, vendor_id, product_id);
      break;
    case USBOPEN_ERR_ACCESS:
      fprintf (stderr,
               "ERROR: Could not access USB device \"%s\" with vendor ID 0x%04x and\n"
               "       product ID 0x%04x\n", product, vendor_id, product_id);
      exit (1);
      break;
    case USBOPEN_ERR_NOTFOUND:
      fprintf (stderr,
               "ERROR: Could not find USB device \"%s\" with vendor ID 0x%04x and\n"
               "       product ID 0x%04x\n", product, vendor_id, product_id);
      exit (1);
      break;
    }

  if ((file = fopen (filename, "rb")) == NULL)
    {
      fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], filename);
      usb_close (handle);
      exit (1);
    }
  if ((buffer = (char *) malloc (READ_BUFFER_SIZE)) == NULL)
    {
      fprintf (stderr, ucon64_msg[FILE_BUFFER_ERROR], READ_BUFFER_SIZE);
      fclose (file);
      usb_close (handle);
      exit (1);
    }

  if (snes_get_snes_hirom ())
    {
      bank_shift = SNES_HIROM_SHIFT;
      bank_size = 1 << SNES_HIROM_SHIFT;
    }
  else
    {
      bank_shift = SNES_LOROM_SHIFT;
      bank_size = 1 << SNES_LOROM_SHIFT;
    }

  size = (int) ucon64.file_size - ucon64.nfo->backup_header_len;
  fseek (file, ucon64.nfo->backup_header_len, SEEK_SET);

  printf ("Send: %d Bytes\n", size);
  puts ("Press q to abort\n");
  starttime = time (NULL);

  usb_control_msg (handle, TARGET, USB_MODE_AVR, 0, 0, NULL, 0, 5000);
  // wait for the loader to depack
  wait2 (500);
  usb_control_msg (handle, TARGET, USB_BULK_UPLOAD_INIT, bank_shift,
                   size / bank_size, NULL, 0, 5000);

  while ((bytesread = fread (buffer, 1, READ_BUFFER_SIZE, file)) > 0)
    {
      int quit = 0, offset = 0;

      while (offset < bytesread && (quit = check_quit ()) == 0)
        {
          numbytes = usb_control_msg (handle, TARGET,
                                      address ? USB_BULK_UPLOAD_NEXT : USB_BULK_UPLOAD_ADDR,
                                      (address >> 16) & 0x00ff, address & 0xffff,
                                      buffer + offset,
                                      bytesread - offset >= SEND_BUFFER_SIZE ?
                                        SEND_BUFFER_SIZE : bytesread - offset, 5000);
          if (numbytes < 0)
            {
              fprintf (stderr, "\nERROR: USB error: %s\n", usb_strerror ());
              free (buffer);
              fclose (file);
              usb_close (handle);
              exit (1);
            }

          bytessent += numbytes;
          ucon64_gauge (starttime, bytessent, size);

          address += numbytes;
          offset += numbytes;
        }
      if (quit)
        {
          fputs ("\nTransfer aborted", stdout);
          break;
        }
    }

  usb_control_msg (handle, TARGET, USB_BULK_UPLOAD_END, 0, 0, NULL, 0, 5000);
  usb_control_msg (handle, TARGET, USB_MODE_SNES, 0, 0, NULL, 0, 5000);

  free (buffer);
  fclose (file);
  usb_close (handle);
#if     defined __unix__ || defined __APPLE__
  drop_privileges ();
#endif

  return 0;
}

#endif // USE_USB
