/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <Max3421e.h>
#include <Usb.h>
#include <AndroidAccessory.h>

#define USB_ACCESSORY_VENDOR_ID         0x18D1
#define USB_ACCESSORY_PRODUCT_ID        0x2D00

#define USB_ACCESSORY_ADB_PRODUCT_ID    0x2D01
#define ACCESSORY_STRING_MANUFACTURER   0
#define ACCESSORY_STRING_MODEL          1
#define ACCESSORY_STRING_DESCRIPTION    2
#define ACCESSORY_STRING_VERSION        3
#define ACCESSORY_STRING_URI            4
#define ACCESSORY_STRING_SERIAL         5

#define ACCESSORY_GET_PROTOCOL          51
#define ACCESSORY_SEND_STRING           52
#define ACCESSORY_START                 53


AndroidAccessory::AndroidAccessory(const char *manufacturer,
                                   const char *model,
                                   const char *description,
                                   const char *version,
                                   const char *uri,
                                   const char *serial) : manufacturer(manufacturer),
                                                         model(model),
                                                         description(description),
                                                         version(version),
                                                         uri(uri),
                                                         serial(serial),
                                                         connected(false)
{

}

void AndroidAccessory::powerOn(void)
{
    max.powerOn();
    delay(200);
}

int AndroidAccessory::getProtocol(byte addr)
{
    uint16_t protocol = -1;
    usb.ctrlReq(addr, 0,
                USB_SETUP_DEVICE_TO_HOST |
                USB_SETUP_TYPE_VENDOR |
                USB_SETUP_RECIPIENT_DEVICE,
                ACCESSORY_GET_PROTOCOL, 0, 0, 0, 2, (char *)&protocol);
    return protocol;
}

void AndroidAccessory::sendString(byte addr, int index, const char *str)
{
    usb.ctrlReq(addr, 0,
                USB_SETUP_HOST_TO_DEVICE |
                USB_SETUP_TYPE_VENDOR |
                USB_SETUP_RECIPIENT_DEVICE,
                ACCESSORY_SEND_STRING, 0, 0, index,
                strlen(str) + 1, (char *)str);
}


bool AndroidAccessory::switchDevice(byte addr)
{
    int protocol = getProtocol(addr);

    if (protocol >= 1) {
        Serial.print("device supports protocol 1 or higher\n");
    } else {
        Serial.print("could not read device protocol version\n");
        return false;
    }

    sendString(addr, ACCESSORY_STRING_MANUFACTURER, manufacturer);
    sendString(addr, ACCESSORY_STRING_MODEL, model);
    sendString(addr, ACCESSORY_STRING_DESCRIPTION, description);
    sendString(addr, ACCESSORY_STRING_VERSION, version);
    sendString(addr, ACCESSORY_STRING_URI, uri);
    sendString(addr, ACCESSORY_STRING_SERIAL, serial);

    usb.ctrlReq(addr, 0,
                USB_SETUP_HOST_TO_DEVICE |
                USB_SETUP_TYPE_VENDOR |
                USB_SETUP_RECIPIENT_DEVICE,
                ACCESSORY_START, 0, 0, 0, 0, NULL);

    while (usb.getUsbTaskState() != USB_DETACHED_SUBSTATE_WAIT_FOR_DEVICE) {
        max.Task();
        usb.Task();
    }

    return true;
}

// Finds the first bulk IN and bulk OUT endpoints
bool AndroidAccessory::findEndpoints(byte addr, EP_RECORD *inEp, EP_RECORD *outEp)
{
    int len;
    byte err;
    uint8_t *p;

    err = usb.getConfDescr(addr, 0, 4, 0, (char *)descBuff);
    if (err) {
        Serial.print("Can't get config descriptor length\n");
        return false;
    }


    len = descBuff[2] | ((int)descBuff[3] << 8);
    if (len > sizeof(descBuff)) {
        Serial.print("config descriptor too large\n");
            /* might want to truncate here */
        return false;
    }

    err = usb.getConfDescr(addr, 0, len, 0, (char *)descBuff);
    if (err) {
        Serial.print("Can't get config descriptor\n");
        return false;
    }

    p = descBuff;
    inEp->epAddr = 0;
    outEp->epAddr = 0;
    while (p < (descBuff + len)){
        uint8_t descLen = p[0];
        uint8_t descType = p[1];
        USB_ENDPOINT_DESCRIPTOR *epDesc;
        EP_RECORD *ep;

        switch (descType) {
        case USB_DESCRIPTOR_CONFIGURATION:
            Serial.print("config desc\n");
            break;

        case USB_DESCRIPTOR_INTERFACE:
            Serial.print("interface desc\n");
            break;

        case USB_DESCRIPTOR_ENDPOINT:
            epDesc = (USB_ENDPOINT_DESCRIPTOR *)p;
            if (!inEp->epAddr && (epDesc->bEndpointAddress & 0x80))
                ep = inEp;
            else if (!outEp->epAddr)
                ep = outEp;
            else
                ep = NULL;

            if (ep) {
                ep->epAddr = epDesc->bEndpointAddress & 0x7f;
                ep->Attr = epDesc->bmAttributes;
                ep->MaxPktSize = epDesc->wMaxPacketSize;
                ep->sndToggle = bmSNDTOG0;
                ep->rcvToggle = bmRCVTOG0;
            }
            break;

        default:
            Serial.print("unkown desc type ");
            Serial.println( descType, HEX);
            break;
        }

        p += descLen;
    }

    if (!(inEp->epAddr && outEp->epAddr))
        Serial.println("can't find accessory endpoints");

    return inEp->epAddr && outEp->epAddr;
}

bool AndroidAccessory::configureAndroid(void)
{
    byte err;
    EP_RECORD inEp, outEp;

    if (!findEndpoints(1, &inEp, &outEp))
        return false;

    memset(&epRecord, 0x0, sizeof(epRecord));

    epRecord[inEp.epAddr] = inEp;
    if (outEp.epAddr != inEp.epAddr)
        epRecord[outEp.epAddr] = outEp;

    in = inEp.epAddr;
    out = outEp.epAddr;

    Serial.println(inEp.epAddr, HEX);
    Serial.println(outEp.epAddr, HEX);

    epRecord[0] = *(usb.getDevTableEntry(0,0));
    usb.setDevTableEntry(1, epRecord);

    err = usb.setConf( 1, 0, 1 );
    if (err) {
        Serial.print("Can't set config to 1\n");
        return false;
    }

    usb.setUsbTaskState( USB_STATE_RUNNING );

    return true;
}

bool AndroidAccessory::isConnected(void)
{
    USB_DEVICE_DESCRIPTOR *devDesc = (USB_DEVICE_DESCRIPTOR *) descBuff;
    byte err;

    max.Task();
    usb.Task();

    if (!connected &&
        usb.getUsbTaskState() >= USB_STATE_CONFIGURING &&
        usb.getUsbTaskState() != USB_STATE_RUNNING) {
        Serial.print("\nDevice addressed... ");
        Serial.print("Requesting device descriptor.\n");

        err = usb.getDevDescr(1, 0, 0x12, (char *) devDesc);
        if (err) {
            Serial.print("\nDevice descriptor cannot be retrieved. Trying again\n");
            return false;
        }

        if (isAccessoryDevice(devDesc)) {
            Serial.print("found android acessory device\n");

            connected = configureAndroid();
        } else {
            Serial.print("found possible device. swithcing to serial mode\n");
            switchDevice(1);
        }
    } else if (usb.getUsbTaskState() == USB_DETACHED_SUBSTATE_WAIT_FOR_DEVICE) {
        if (connected)
            Serial.println("disconnect\n");
        connected = false;
    }

    return connected;
}

int AndroidAccessory::read(void *buff, int len, unsigned int nakLimit)
{
    return usb.newInTransfer(1, in, len, (char *)buff, nakLimit);
}

int AndroidAccessory::write(void *buff, int len)
{
    usb.outTransfer(1, out, len, (char *)buff);
    return len;
}