/* usb.c
 *
 * Copyright (C) 2001 Jean-Jacques Sarton jj.sarton@t-online.de
 */
/*
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* This file is a wrapper for using of libusb on MacOS 10.
 *
 * The file contain wrapper function for the open(), read() and write()
 * calls as well as the initialization code for the usblib
 * Some portion of the code was borowed from the sane project
 *
 */

#ifdef MACOS

#include <stdio.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <usb.h>
#include "usbHlp.h"
#include "d4lib.h"
#include "mtink.h"
#include "cmd.h"


/*#define DEBUG 1*/

/* Storage for our informations */
#define MAX_DEVICES 100

static int libusb_timeout = 10000;

device_list_type devices[MAX_DEVICES];

static int usbClose(int dn)
{
   if (dn >= MAX_DEVICES || dn < 0)
   {
#ifdef DEBUG
      printf("usb_close: dn >= MAX_DEVICES || dn < 0\n");
#endif
      return -1;
   }
   if (!devices[dn].open)
   {
#ifdef DEBUG
      printf("usb_close: device %d already closed or never opened\n", dn);
#endif
      return -1;
   }
   else
   {
      usb_release_interface (devices[dn].libusb_handle, 
                             devices[dn].interface_nr);
      usb_close (devices[dn].libusb_handle);
   }
   devices[dn].open = 0;
   return 0;
}

/*************************************************
 *
 * usbOpen()
 *
 * open the usb user interface to printer referenced
 * by int idx
 *
 *************************************************/

static int usbOpen(int idx)
{
   int status = 0;
   struct usb_device *dev;
   struct usb_interface_descriptor *interface;
   int result, num;

   devices[idx].libusb_handle = usb_open (devices[idx].libusb_device);
   if (!devices[idx].libusb_handle)
   {
#ifdef DEBUG
      printf("usb_open: can't open device `%s': %s\n", devices[idx].devname, strerror (errno));
#endif
      if (errno == EPERM)
      {
#ifdef DEBUG
          printf("Make sure you run as root or set appropriate permissions\n");
#endif
          status = -1;
      }
      else if (errno == EBUSY)
      {
#ifdef DEBUG
          printf("Maybe the kernel printer driver claims the "
               "printer's interface?\n");
#endif
          status = -1;
      }
   }
   else
   {
      dev = usb_device (devices[idx].libusb_handle);
      /* Set the configuration */
      if (!dev->config)
      {
         printf("usb_open: device `%s' not configured?\n", devices[idx].devname);
         return -1;
      }
      if (dev->descriptor.bNumConfigurations > 1)
      {
          printf("usb_open: more than one "
               "configuration (%d), choosing first config (%d)\n",
               dev->descriptor.bNumConfigurations, 
               dev->config[0].bConfigurationValue);
      }
      result = usb_set_configuration (devices[idx].libusb_handle,
                                      dev->config[0].bConfigurationValue);
      if (result < 0)
      {
         status = -1;
#ifdef DEBUG
         printf("usb_open: libusb complained: %s\n", usb_strerror ());
#endif
         if (errno == EPERM)
         {
#ifdef DEBUG
            printf("Make sure you run as root or set appropriate "
                   "permissions\n");
#endif
            status = -1;
         }
         else if (errno == EBUSY)
         {
#ifdef DEBUG
            printf("Maybe the kernel printer driver claims the "
                   "printer's interface?\n");
#endif
            status = -1;
         }
         usb_close (devices[idx].libusb_handle);
         return status;
      }
      
      /* Claim the interface */
      result = usb_claim_interface (devices[idx].libusb_handle, 
                                    devices[idx].interface_nr);
      if (result < 0)
      {
         status = -1;

#ifdef DEBUG
         printf("usb_open libusb complained: %s\n", usb_strerror ());
#endif
         if (errno == EPERM)
         {
#ifdef DEBUG
            printf("Make sure you run as root or set appropriate "
                   "permissions\n");
#endif
            status = -1;
         }
         else if (errno == EBUSY)
         {
#ifdef DEBUG
            printf("Maybe the kernel printer driver claims the "
                   "printer's interface?\n");
#endif
            status = -1;
         }
         usb_close (devices[idx].libusb_handle);
         return status;
      }
      interface = &dev->config[0].interface->altsetting[0];

      /* Now we look for usable endpoints */
      for (num = 0; num < interface->bNumEndpoints; num++)
      {
         struct usb_endpoint_descriptor *endpoint;
         int address, direction, transfer_type;

         endpoint = &interface->endpoint[num];
         address = endpoint->bEndpointAddress & USB_ENDPOINT_ADDRESS_MASK;
         direction = endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
         transfer_type = endpoint->bmAttributes & USB_ENDPOINT_TYPE_MASK;

         /* save the endpoints we need later */
         if (transfer_type == USB_ENDPOINT_TYPE_BULK)
         {
            if (direction)      /* in */
            {
               if (devices[idx].bulk_in_ep)
                   ;
               else
                  devices[idx].bulk_in_ep = endpoint->bEndpointAddress;
            }
            else
            {
               if (devices[idx].bulk_out_ep)
                  ;
               else
                  devices[idx].bulk_out_ep = endpoint->bEndpointAddress;
            }
         }
         /* ignore currently unsupported endpoints */
         else
         {
            continue;
         }
      }
   }
   if ( status == 0 )
      devices[idx].open = 1;
   return status;
}

/*************************************************
 *
 * usbRead()
 *
 * read from the usb channel idx into the buffer
 * whith lenght size
 *
 *************************************************/

static int usbRead(int dn, char * buffer, size_t size)
{
   static char rBuf[512];
   static char mBuf[512];
   int         pos       = 0;
   static int  len       = 0;
   ssize_t     read_size = 0;

   if (!size)
   {
#ifdef DEBUG
       printf("usbRead: size == 0\n");
#endif
       return -1;
   }

   if (dn >= MAX_DEVICES || dn < 0)
   {
#ifdef DEBUG
       printf("usbRead: dn >= MAX_DEVICES || dn < 0\n");
#endif
       return -1;
   }

   if (devices[dn].bulk_in_ep)
   {
      /* with libusb on linux we need a buffered read !!! */
      if ( len > 0 )
      {
         if ( len > size )
         {
            memcpy(buffer, rBuf, len);
            memmove(rBuf, rBuf+size, len-size);
            len -= size;
            read_size = size;
         }
         else
         {
            memcpy(buffer, rBuf, len);
            read_size = len;
            len = 0;
         }
      }
      if ( read_size <  size )
      {
         len = usb_bulk_read (devices[dn].libusb_handle,
                                    devices[dn].bulk_in_ep,
                                    rBuf,
                                    sizeof(rBuf),
                                    libusb_timeout);
         if ( len < 0 )
             return len;
      }

      /* part of info into buffer (read_size>0 rest into rBuf */
      if ( len > 0 )
      {
         if ( len <= size - read_size )
         {
            memcpy(buffer+read_size, rBuf, len);
            read_size += len;
            len = 0;
         }
         else
         {
            int consummed;
            memcpy(buffer+read_size, rBuf, size - read_size);
            consummed = size - read_size;
            memmove(rBuf,rBuf+consummed, len - consummed);
            len -= (size - read_size);
            read_size = size;
         }
      }
   }
   else
   {
#ifdef DEBUG
       printf("usbRead: can't read without a bulk-in "
             "endpoint\n");
#endif
       return -1;
   }
 

   if (read_size < 0)
   {
#ifdef DEBUG
       printf( "usbRead: read failed: %s\n", strerror (errno));
#endif
       usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
       return -1;
   }
   if (read_size == 0)
   {
       usleep(100000);
       return 0;
   }
   return read_size;
}

static int usbWrite (int dn, char * buffer, size_t size)
{
  ssize_t write_size = 0;
  if (!size)
  {
#ifdef DEBUG
      printf("usbWrite: size == 0\n");
#endif
      return -1;
  }

  if (dn >= MAX_DEVICES || dn < 0)
  {
#ifdef DEBUG
      printf("usbWrite: dn >= MAX_DEVICES || dn < 0\n");
#endif
      return -1;
  }

  if (devices[dn].bulk_out_ep)
  {
      write_size = usb_bulk_write (devices[dn].libusb_handle,
                                   devices[dn].bulk_out_ep,
                                   buffer,
                                   (int) size, libusb_timeout);
  }
  else
  {
#ifdef DEBUG
     printf("usbWrite: can't write without a bulk-out "
             "endpoint\n");
#endif
     return -1;
  }


  if (write_size < 0)
  {
#ifdef DEBUG
     printf("usbWrite: write failed: %s\n", strerror (errno));
#endif
     usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
     return -1;
  }
  return write_size;
}

static int decodePrinterType(unsigned char *buf, int len, int dn)
{
   char *s = buf;
   char *t = buf;
   int   i;

   if ( buf )
   {
      for (i=0; i < len; i++)
      {
         if ( strncmp(s, "DES:", 4) == 0 || strncmp(s, "MDL:", 4) == 0 )
         {
            s +=4;
            t = s;
            while(*t && *t != ';')
               t++;
            *t = '\0';
            /* remember the printer name */
            devices[dn].prtname = strdup(s);
            return 0;
         }
         else
         {
            s++;
         }
      }
   }
   return 0;
}



/**********************************************************
 * getId(int dn)
 *
 * get the printer identification string
 * normally the printer shall support the D4 protokoll
 * 
 *********************************************************/

static int getId(int dn)
{
   int wr;
   int rd;
   char buf[512];
   int state;
   char *iBuf = NULL;
 
   /* try non D4 read of printer description */
   wr = usbWrite(dn, "\033\1@EJL ID\r\n", 11);
   if ( wr != 11 )
   {
      return -1;
   }
   
   /* wait a little bit */
   usleep(100000);
   
   /* and read back info supplied by the printer */
   rd = usbRead(dn, buf, 511);
   if ( rd > 0 )
   {
      buf[rd] = 0;
      decodePrinterType(buf,rd, dn);
   }
   else if ( rd == 0 )
   {
      /* no info returned, disable D4 protocol */
      wr = usbWrite(dn, "\0\0\0\033\1@EJL 1284.4\n@EJL     \n", 27);

      /* wait a little bit */
      usleep(100000);
      /* and read back info supplied by the printer */
      rd = usbRead(dn, buf, 511);
      if ( rd > 0 )
      {
         buf[rd] = 0;
         decodePrinterType(buf,rd, dn );
      }
      state = callPrg(GET_ID, devices[dn].devname, PROT_D4, 0, 0, &iBuf, NULL );
   }

   return 0;
}

/**********************************************************
 * usbInit(void)
 *
 * initialize libusb and return the number of EPSON
 * printer founds
 *********************************************************/

int usbInit(void)
{
   struct usb_bus *busses;
   struct usb_bus *bus;
   int c, i, a;
   int dn = 0;
   char devname[1024];
   int found = 0;

   memset (devices, 0, sizeof (devices));

   usb_init();
   usb_set_debug(0);
   usb_find_busses();
   usb_find_devices();
   busses = usb_get_busses();

   for (bus = busses; bus; bus = bus->next)
   {
      struct usb_device *dev;
      for (dev = bus->devices; dev; dev = dev->next)
      {
         int interface;
         if (!dev->config)
         {
#ifdef DEBUG
            printf("init: device 0x%04x/0x%04x is not configured\n",
                   dev->descriptor.idVendor, dev->descriptor.idProduct);
#endif
            continue;
         }
         if (dev->descriptor.idVendor == 0 || dev->descriptor.idProduct == 0)
         {
            continue;
         }
         for (interface = 0; interface < dev->config[0].bNumInterfaces && !found; interface++)
         {
            switch (dev->descriptor.bDeviceClass)
            {
               case USB_CLASS_VENDOR_SPEC:
                  found = 1;
                  break;
               case USB_CLASS_PER_INTERFACE:
                  switch (dev->config[0].interface[interface].altsetting[0].bInterfaceClass)
                  {
                     case 7:  /* printer class */
                        if (  dev->descriptor.idVendor != 0x04b8) /* EPSON vendor ID */
                        {
                           break;
                        }
                        found = 1;
                        devices[dn].libusb_device = dev;
                        snprintf (devname, sizeof (devname), "libusb:%s:%s",
                                  dev->bus->dirname, dev->filename);
                        devices[dn].devname = strdup (devname);
                        if (!devices[dn].devname)
                        {
                           return 0; /* no more memory ! */
                        }
                        devices[dn].vendor  = dev->descriptor.idVendor;
                        devices[dn].product = dev->descriptor.idProduct;
                        devices[dn].open = 0;
                        devices[dn].interface_nr = interface;
                        if ( usbOpen(dn) == 0 )
                        {
                           getId(dn);
                           usbClose(dn);
                           if ( devices[dn].prtname == NULL )
                           {
                              return 0; /* no more memory or printer not recognized ! */
                           }
                           dn++;
                        }
                        if (dn >= MAX_DEVICES)
                           return dn;
                        break;
                  }
                  break;
            }
         }
         interface--;
         if (!found)
         {
             continue;
         }
      }
   }
   return dn;
}

/**********************************************************
 *
 * Wrapper for the open, close, read and write functions
 *
 * if the destination is the libusb the file descriptor
 * is be ored with 0x1000
 *********************************************************/

/**********************************************************
 * mOpen(char *name. int flags)
 *
 * open vial usbOpen() if the name look like
 *    /<path>/usb:<number>:<printer name>
 * or do a normal open()
 * 
 *********************************************************/

int mOpen(const char *name, int flags)
{
   int fd = -1;
   int i;
   int len = strlen(USB_PRT_NAME_PREFIX);
   char *s;
   if (strncmp(name, USB_PRT_NAME_PREFIX, len) == 0 )
   {
      /* get number */
      s = strstr(name,USB_PRT_NAME_PREFIX);
      if ( s )
      {
         s += strlen(USB_PRT_NAME_PREFIX);
         if ( isdigit(*s) )
         {
            i = atoi(s);
            if ( i >= 0 && i < MAX_DEVICES && devices[i].devname )
            {
               fd = usbOpen(i);
               if ( fd > -1 )
                  fd = i|0x1000;
            }
         }
      }
   }
   else
   {
      fd = open(name,flags);
   }
   return fd;
}

/**********************************************************
 * mWrite(int fd, char *buf, size_t count()
 *
 * write to given channel the informations contained into
 * buf
 * 
 *********************************************************/

ssize_t mWrite(int fd, char *buf, size_t count)
{
   if ( fd > -1 && (fd | 0x1000) )
      return usbWrite(fd & 0xfff, buf, count);
   else if ( fd > -1 )
      return write(fd, buf, count);
   else if ( fd > -1 )
      return -1;
}


/**********************************************************
 * mRead(int fd, char *buf, size_t count()
 *
 * read from given channel the informations into buf
 * 
 *********************************************************/

ssize_t mRead(int fd, void *buf, size_t count)
{
   if ( fd > -1 && (fd | 0x1000) )
      return usbRead(fd & 0xfff, buf, count);
   else if ( fd > -1 )
      return read(fd, buf, count);
   else
      return -1;
}


/**********************************************************
 * mClose(int fd)
 *
 * close the channel
 * 
 *********************************************************/

int mClose(int fd)
{
   if ( fd > -1 && (fd | 0x1000) )
      return usbClose(fd & 0xfff);
   else if ( fd > -1 )
      return close(fd);
   else
      return -1;
}

/***************************************************
 * 
 * bldFile()
 *
 * create speudo device file into the /vat/mtink
 * directory
 *
 **************************************************/
 
 int createFiles()
 {
    char buf[512];
    int dn = 0;
    while(devices[dn].prtname != NULL )
    {
       if ( snprintf(buf,sizeof(buf),USB_PRT_NAME_PREFIX"%02d:%s",dn,devices[dn].prtname) != sizeof(buf) )
       {
          mkdir(USB_PRT_DIR);
          creat(buf, 0644);
       }
       else
       {
          return -1;
       }
       dn++;
    }
    return 0;
 }
 
/****************************************************
 * unlinkFiles()
 *
 * delete all file previously created by createFiles()
 *
 ***************************************************/
 int unlinkFiles()
 {
    char buf[512];
    int dn = 0;
    while(devices[dn].prtname != NULL )
    {
       if ( snprintf(buf,sizeof(buf),USB_PRT_NAME_PREFIX"%02d:%s",dn,devices[dn].prtname) != sizeof(buf) )
       {
          unlink(buf);
       }
       else
       {
          return -1;
       }
       dn++;
    }
    return 0;
}

/*************************************************
 *
 * show available models
 *
 *************************************************/
 void showModels()
 {
    int dn = 0;
    while(devices[dn].prtname != NULL )
    {
       printf("%3d: %s\n",dn+1,devices[dn].prtname);
       dn++;
    }
}

/*************************************************
 *
 * show available models
 *
 *************************************************/
 char *getPrinterName(int dn)
 {
    if(devices[dn].prtname != NULL )
    {
       return devices[dn].prtname;
    }
    return NULL;
}
#endif
