/* Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.

This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").

Contact information
-------------------

Circuits At Home, LTD
Web      :  http://www.circuitsathome.com
e-mail   :  support@circuitsathome.com
 */

#include "hiduniversal.h"
#include <USB/usb.h> // Microchip USB lib.
#include "usb_host_internal.h"

HIDUniversal::HIDUniversal(USB *p) :
USBHID(p),
qNextPollTime(0),
pollInterval(0),
bPollEnable(false),
bHasReportId(false) {
        Initialize();

        if(pUsb)
                pUsb->RegisterDeviceClass(this);
}

uint16_t HIDUniversal::GetHidClassDescrLen(uint8_t type, uint8_t num) {
        for(uint8_t i = 0, n = 0; i < HID_MAX_HID_CLASS_DESCRIPTORS; i++) {
                if(descrInfo[i].bDescrType == type) {
                        if(n == num)
                                return descrInfo[i].wDescriptorLength;
                        n++;
                }
        }
        return 0;
}

void HIDUniversal::Initialize() {
        for(uint8_t i = 0; i < MAX_REPORT_PARSERS; i++) {
                rptParsers[i].rptId = 0;
                rptParsers[i].rptParser = NULL;
        }
        for(uint8_t i = 0; i < HID_MAX_HID_CLASS_DESCRIPTORS; i++) {
                descrInfo[i].bDescrType = 0;
                descrInfo[i].wDescriptorLength = 0;
        }
        for(uint8_t i = 0; i < maxHidInterfaces; i++) {
                hidInterfaces[i].bmInterface = 0;
                hidInterfaces[i].bmProtocol = 0;

                for(uint8_t j = 0; j < maxEpPerInterface; j++)
                        hidInterfaces[i].epIndex[j] = 0;
        }
/*  Now using pointers to runtime-allocated structures at EndpointXtract
        for(uint8_t i = 0; i < totalEndpoints; i++) {
                epInfo[i]->bEndpointAddress = 0;
                epInfo[i]->wMaxPacketSize = (i) ? 0 : 8;
                epInfo[i]->bmSndToggle = 0;
                epInfo[i]->bmRcvToggle = 0;
                epInfo[i]->bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
        }
*/
        bNumEP = 1;
        bNumIface = 0;
        bConfNum = 0;
        pollInterval = 0;

        ZeroMemory(constBuffLen, prevBuf);
}

bool HIDUniversal::SetReportParser(uint8_t id, HIDReportParser *prs) {
        for(uint8_t i = 0; i < MAX_REPORT_PARSERS; i++) {
                if(rptParsers[i].rptId == 0 && rptParsers[i].rptParser == NULL) {
                        rptParsers[i].rptId = id;
                        rptParsers[i].rptParser = prs;
                        return true;
                }
        }
        return false;
}

HIDReportParser* HIDUniversal::GetReportParser(uint8_t id) {
        if(!bHasReportId)
                return ((rptParsers[0].rptParser) ? rptParsers[0].rptParser : NULL);

        for(uint8_t i = 0; i < MAX_REPORT_PARSERS; i++) {
                if(rptParsers[i].rptId == id)
                        return rptParsers[i].rptParser;
        }
        return NULL;
}

uint8_t HIDUniversal::Init(uint8_t parent, uint8_t port, bool lowspeed) {
        uint8_t rcode;
        UsbDevice *p = NULL;
	epInfo[0] = usbDeviceInfo.pEndpoint0;	/* Endpoint 0 structure is initialised by the uC lib. */
        uint8_t num_of_conf; // number of configurations
//reconfig:
        AddressPool &addrPool = pUsb->GetAddressPool();

        USBTRACE("HU Init\r\n");

        if(bAddress)
                return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE;

        // Get pointer to pseudo device with address 0 assigned
        p = addrPool.GetUsbDevicePtr(0);

        if(!p)
                return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;

        if(!p->epinfo) {
                USBTRACE("epinfo\r\n");
                return USB_ERROR_EPINFO_IS_NULL;
        }

        USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(pUsb->uddbuf);
        p->lowspeed = lowspeed;

        VID = udd->idVendor; // Can be used by classes that inherits this class to check the VID and PID of the connected device
        PID = udd->idProduct;	
	printf("\nidVendor: 0x%X\nidProduct: 0x%X\n", VID, PID);

        if (usbDeviceInfo.deviceAddress == 0) {
           // Allocate new address according to device class
           bAddress = addrPool.AllocAddress(parent, false, port);
         
           if(!bAddress)
                   return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL;
           // Assign new address to the device
           rcode = pUsb->setAddr(0, 0, bAddress);
           usbDeviceInfo.deviceAddress = bAddress;
         
           if(rcode) {
                   p->lowspeed = false;
                   addrPool.FreeAddress(bAddress);
                   bAddress = 0;
                   USBTRACE2("setAddr:", rcode);
                   if (!(VID == SHANWAN_PS3_VID && PID == SHANWAN_PS3_PID))
                   return rcode;
           }
           usbDeviceInfo.deviceAddress = bAddress;
           _USB_InitErrorCounters();

           USBTRACE2("Addr:", bAddress);
        }
        else
          bAddress = usbDeviceInfo.deviceAddress;

        p = addrPool.GetUsbDevicePtr(bAddress);

        if(!p)
                return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;

	p->epinfo = epInfo; /* Endpoint 0 info again set by uC USB lib. */
        p->lowspeed = lowspeed;

        num_of_conf = udd->bNumConfigurations;
        // Assign epInfo to epinfo pointer
        rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);

        if(rcode)
                goto FailSetDevTblEntry;

        USBTRACE2("NC:", num_of_conf);

        for(uint8_t i = 0; i < num_of_conf; i++) {
                //HexDumper<USBReadParser, uint16_t, uint16_t> HexDump;
                ConfigDescParser<USB_CLASS_HID, 0, 0,
                        CP_MASK_COMPARE_CLASS> confDescrParser(this);
		if (i == 0) {
        		// Extract Max Packet Size from the device descriptor
        		epInfo[0]->wMaxPacketSize = udd->bMaxPacketSize0;
			// Assign epInfo to epinfo pointer
                        rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);
		}
                //rcode = pUsb->getConfDescr(bAddress, 0, i, &HexDump);
                rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);

                if(rcode)
                        goto FailGetConfDescr;

                if(bNumEP > 1)
                        break;
        } // for

        if(bNumEP < 2)
                return USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED;

        // Device identified, so clear configuration descriptor buffer
        for (uint8_t i = 0; i < num_of_conf; i++)
          free(pUsb->ucdbuf[i]);
        pUsb->ucdep   = 9999;
        pUsb->ucdaddr = 9999;

        // Assign epInfo to epinfo pointer
        rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo);

        USBTRACE2("Cnf:", bConfNum);
 
        // Set Configuration Value
        rcode = pUsb->setConf(bAddress, 0, bConfNum);

        if(rcode)
                goto FailSetConfDescr;
#if 0
        // These sneeky Chinese controllers try and switch IDs by detaching and then reattaching as something else
        if (VID == SHANWAN_PS3_VID && PID == SHANWAN_PS3_PID) {
          puts("Spooking the sneeky Shanwan...");
          // Disable detection of USB disconnect/connect
          U1IEbits.DETACHIE = 0; // Disable USB detach interrupt

          // Try to replicate the actions that spook it normally and cause an endless loop - surprisingly difficult...
          // Nope, I give up, I can't do it! Might need to read string descriptors or something...
          // Actually I seem to be able to trick it into fake Xbox360 mode sometimes, so try it with the Xbox 360 controller driver?
          rcode = SetIdle(hidInterfaces[0].bmInterface, 0, 0);
          if (rcode)
            printf ("SetIdle Spooked out an 0x%X\n", rcode);
          else
            puts ("Not spooked by SetIdle!");
          SinkParser<USBReadParser, uint16_t, uint16_t> sink;
          uint8_t buf[64];
          rcode = getHidDescr(0,9,buf);
          if (rcode)
            printf ("getHidDescr Spooked out an 0x%X\n", rcode);
          else
            puts ("Not spooked by getHidDescr!");
          rcode = GetReportDescr(0, &sink);
          if (rcode)
            printf ("GetReportDescr Spooked out an 0x%X\n", rcode);
          else
            puts ("Not spooked by GetReportDescr!");
          uint16_t read = 64;
          bPollEnable = true;
          for (uint8_t i = 0; i < 4; i++) {
            Poll();
          }
          bPollEnable = false;
          delay (2000);
          rcode = pUsb->setAddr(0, 0, bAddress);
          if (rcode)
            printf ("setAddr Spooked out an 0x%X\n", rcode);
          else
            puts ("Not spooked by setAddr!");
          U1IEbits.DETACHIE = 1; // Enable USB detach interrupt

          // Reconfigure
          Release();
          pUsb->GetAddressPool().FreeAddress(bAddress);
          usbDeviceInfo.deviceAddress = 0;

          // Get start of device descriptor
          pUsb->getDevDescr(0, 0, 8, (uint8_t*)pUsb->uddbuf);
	  _USB_InitErrorCounters();

          // Set the EP0 packet size.
          usbDeviceInfo.pEndpoint0->wMaxPacketSize = udd->bMaxPacketSize0;

	  // Get full device descriptor
	  pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)pUsb->uddbuf);
	  _USB_InitErrorCounters();
          goto reconfig;
        }
#endif
        for(uint8_t i = 0; i < bNumIface; i++) {
                if(hidInterfaces[i].epIndex[epInterruptInIndex] == 0)
                        continue;

                rcode = SetIdle(hidInterfaces[i].bmInterface, 0, 0);

                if(rcode && rcode != hrSTALL)
                        goto FailSetIdle;
        }

        USBTRACE("HU configured\r\n");

        OnInitSuccessful();

        bPollEnable = true;
        return 0;

FailSetDevTblEntry:
#ifdef DEBUG_USB_HOST
        NotifyFailSetDevTblEntry();
        goto Fail;
#endif

FailGetConfDescr:
#ifdef DEBUG_USB_HOST
        NotifyFailGetConfDescr();
        goto Fail;
#endif

FailSetConfDescr:
#ifdef DEBUG_USB_HOST
        NotifyFailSetConfDescr();
        goto Fail;
#endif


FailSetIdle:
#ifdef DEBUG_USB_HOST
        USBTRACE("SetIdle:");
#endif

#ifdef DEBUG_USB_HOST
Fail:
        NotifyFail(rcode);
#endif
        Release();
        return rcode;
}

HIDUniversal::HIDInterface* HIDUniversal::FindInterface(uint8_t iface, uint8_t alt, uint8_t proto) {
        for(uint8_t i = 0; i < bNumIface && i < maxHidInterfaces; i++)
                if(hidInterfaces[i].bmInterface == iface && hidInterfaces[i].bmAltSet == alt
                        && hidInterfaces[i].bmProtocol == proto)
                        return hidInterfaces + i;
        return NULL;
}

void HIDUniversal::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep, USB_ENDPOINT_INFO *newEndpointInfo) {
	USBTRACE("HIDUniversal Xtract\n");
        // If the first configuration satisfies, the others are not concidered.
        if(bNumEP > 1 && conf != bConfNum)
                return;

        //ErrorMessage<uint8_t>(PSTR("\r\nConf.Val"), conf);
        //ErrorMessage<uint8_t>(PSTR("Iface Num"), iface);
        //ErrorMessage<uint8_t>(PSTR("Alt.Set"), alt);

        bConfNum = conf;

        uint8_t index = 0;
	epInfo[bNumEP] = newEndpointInfo;
        HIDInterface *piface = FindInterface(iface, alt, proto);

        // Fill in interface structure in case of new interface
        if(!piface) {
                piface = hidInterfaces + bNumIface;
                piface->bmInterface = iface;
                piface->bmAltSet = alt;
                piface->bmProtocol = proto;
                bNumIface++;
        }

        if((pep->bmAttributes & bmUSB_TRANSFER_TYPE) == USB_TRANSFER_TYPE_INTERRUPT)
                index = (pep->bEndpointAddress & 0x80) == 0x80 ? epInterruptInIndex : epInterruptOutIndex;

        if(index) {
                // Fill in the endpoint info structure
                //epInfo[bNumEP]->bEndpointAddress = (pep->bEndpointAddress & 0x0F);
                //epInfo[bNumEP]->wMaxPacketSize = (uint8_t)pep->wMaxPacketSize;
                epInfo[bNumEP]->bmSndToggle = 0;
                epInfo[bNumEP]->bmRcvToggle = 0;
                epInfo[bNumEP]->bmNakPower = USB_NAK_NOWAIT;

                // Fill in the endpoint index list
                piface->epIndex[index] = bNumEP; //(pep->bEndpointAddress & 0x0F);

                if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints
                        pollInterval = pep->bInterval;

                bNumEP++;
        }
        //PrintEndpointDescriptor(pep);
}

uint8_t HIDUniversal::Release() {
//        pUsb->GetAddressPool().FreeAddress(bAddress);
        _USB_FreeConfigMemory();

        bNumEP = 1;
        bAddress = 0;
        qNextPollTime = 0;
        bPollEnable = false;
        return 0;
}

bool HIDUniversal::BuffersIdentical(uint8_t len, uint8_t *buf1, uint8_t *buf2) {
        for(uint8_t i = 0; i < len; i++)
                if(buf1[i] != buf2[i])
                        return false;
        return true;
}

void HIDUniversal::ZeroMemory(uint8_t len, uint8_t *buf) {
        for(uint8_t i = 0; i < len; i++)
                buf[i] = 0;
}

void HIDUniversal::SaveBuffer(uint8_t len, uint8_t *src, uint8_t *dest) {
        for(uint8_t i = 0; i < len; i++)
                dest[i] = src[i];
}

uint8_t HIDUniversal::Poll() {
        uint8_t rcode = 0;

        if(!bPollEnable)
                return 0;

        if((int32_t)((uint32_t)millis() - (uint32_t)qNextPollTime) >= (uint32_t)0) {
                qNextPollTime = (uint32_t)millis() + pollInterval;

                uint8_t buf[constBuffLen];

                for(uint8_t i = 0; i < bNumIface; i++) {
                        uint8_t index = hidInterfaces[i].epIndex[epInterruptInIndex];
                        uint16_t read = (uint16_t)epInfo[index]->wMaxPacketSize;

                        ZeroMemory(constBuffLen, buf);

                        uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[index]->bEndpointAddress, &read, buf);

                        if(rcode) {
                                if(rcode != hrNAK)
//                                        printf("(hiduniversal.h) Poll: ep %d, %db, ret 0x%X\n", epInfo[index]->bEndpointAddress, read, rcode);
                                          USBTRACE2("(hiduniversal.h) Poll:", rcode);
                                return rcode;
                        }

                        if (epInfo[index]->dataCount == 0) // No data received
                                return 0;

                        if(read > constBuffLen)
                                read = constBuffLen;

                        bool identical = BuffersIdentical(read, buf, prevBuf);

                        SaveBuffer(read, buf, prevBuf);

                        if(identical)
                                return 0;
#if 0
                        Notify(PSTR("\r\nBuf: "), 0x80);

                        for(uint8_t i = 0; i < read; i++) {
                                D_PrintHex<uint8_t > (buf[i], 0x80);
                                Notify(PSTR(" "), 0x80);
                        }

                        Notify(PSTR("\r\n"), 0x80);
#endif
                        ParseHIDData(this, bHasReportId, (uint8_t)read, buf);

                        HIDReportParser *prs = GetReportParser(((bHasReportId) ? *buf : 0));

                        if(prs)
                                prs->Parse(this, bHasReportId, (uint8_t)read, buf);
                }
        }
        return rcode;
}

// Send a report to interrupt out endpoint. This is NOT SetReport() request!
uint8_t HIDUniversal::SndRpt(uint16_t nbytes, uint8_t *dataptr) {
        return pUsb->outTransfer(bAddress, epInfo[epInterruptOutIndex]->bEndpointAddress, nbytes, dataptr);
}
