/* insert license */

#include "usb_callbacks.h"
#include "usb_lib.h"
#include "descriptors.h"
#include "usb_config.h"
#include "usb.h"
#include "usb_hardware.h"

ONE_DESCRIPTOR Device_Descriptor = {
    (uint8*)&usbVcomDescriptor_Device,
    sizeof(USB_Descriptor_Device)
};

ONE_DESCRIPTOR Config_Descriptor = {
    (uint8*)&usbVcomDescriptor_Config,
    0x43//sizeof(USB_Descriptor_Config)
};

ONE_DESCRIPTOR String_Descriptor[3] = {
    {(uint8*)&usbVcomDescriptor_LangID,       USB_DESCRIPTOR_STRING_LEN(1)},
    {(uint8*)&usbVcomDescriptor_iManufacturer,USB_DESCRIPTOR_STRING_LEN(8)},
    {(uint8*)&usbVcomDescriptor_iProduct,     USB_DESCRIPTOR_STRING_LEN(8)}
};

uint8 last_request = 0;

USB_Line_Coding line_coding = {
 bitrate:     115200,
 format:      0x00, /* stop bits-1 */
 paritytype:  0x00,
 datatype:    0x08
};

uint8 vcomBufferRx[VCOM_RX_BUFLEN];
volatile uint32 countTx    = 0;
volatile uint32 recvBufIn  = 0;
volatile uint32 recvBufOut = 0;
volatile uint32 maxNewBytes   = VCOM_RX_BUFLEN;
volatile uint32 newBytes = 0;
RESET_STATE reset_state = DTR_UNSET;
uint8       line_dtr_rts = 0;

void vcomDataTxCb(void) {
    /* do whatever after data has been sent to host */

    /* allows usbSendBytes to stop blocking */

    /* assumes tx transactions are atomic 64 bytes (nearly certain they are) */
    countTx = 0;
}

/* we could get arbitrarily complicated here for speed purposes
   however, the simple scheme here is to implement a receive fifo
   and always set the maximum to new bytes to the space remaining
   in the fifo. this number will be reincremented after calls
   to usbReceiveBytes */
void vcomDataRxCb(void) {
  /* do whatever after data has been received from host */

  /* setEPRxCount on the previous cycle should garuntee
     we havnt received more bytes than we can fit */
  newBytes = GetEPRxCount(VCOM_RX_ENDP);
  SetEPRxStatus(VCOM_RX_ENDP,EP_RX_NAK);

  /* todo, not checking very carefully for edge cases. USUALLY,
     if we emit the reset pulse and send 4 bytes, then newBytes
     should be 4. But its POSSIBLE that this would be violated
     in some cases */

  /* magic number, {0x31, 0x45, 0x41, 0x46} is "1EAF" */
  char chkBuf[4];
  char cmpBuf[4] = {0x31, 0x45, 0x41, 0x46};
  if (reset_state == DTR_NEGEDGE) {
    reset_state = DTR_LOW;

    if  (newBytes >= 4) {
      unsigned int target = (unsigned int)usbWaitReset | 0x1;

      PMAToUserBufferCopy(chkBuf,VCOM_RX_ADDR,4);

      int i;
      USB_Bool cmpMatch = TRUE;
      for (i=0; i<4; i++) {
          if (chkBuf[i] != cmpBuf[i]) {
              cmpMatch = FALSE;
          }
      }

      if (cmpMatch) {
          asm volatile("mov r0, %[stack_top]      \n\t"             // Reset the stack
                       "mov sp, r0                \n\t"
                       "mov r0, #1                \n\t"
                       "mov r1, %[target_addr]    \n\t"
                       "mov r2, %[cpsr]           \n\t"
                       "push {r2}                 \n\t"             // Fake xPSR
                       "push {r1}                 \n\t"             // Target address for PC
                       "push {r0}                 \n\t"             // Fake LR
                       "push {r0}                 \n\t"             // Fake R12
                       "push {r0}                 \n\t"             // Fake R3
                       "push {r0}                 \n\t"             // Fake R2
                       "push {r0}                 \n\t"             // Fake R1
                       "push {r0}                 \n\t"             // Fake R0
                       "mov lr, %[exc_return]     \n\t"
                       "bx lr"
                       :
                       : [stack_top] "r" (STACK_TOP),
                         [target_addr] "r" (target),
                         [exc_return] "r" (EXC_RETURN),
                         [cpsr] "r" (DEFAULT_CPSR)
                       : "r0", "r1", "r2");
          /* should never get here */
      }
    }
  }

  PMAToUserBufferCopy(&vcomBufferRx[0],VCOM_RX_ADDR,newBytes);
}

void vcomManagementCb(void) {
    /* unused. This enpoint would callback if we had sent a linestate
       changed notification */
}

u8* vcomGetSetLineCoding(uint16 length) {
    if (length == 0) {
        pInformation->Ctrl_Info.Usb_wLength = sizeof(USB_Line_Coding);
    }
    return (uint8*)&line_coding;
}

void vcomSetLineState(void) {
}

void usbInit(void) {
    pInformation->Current_Configuration = 0;
    usbPowerOn();

    _SetISTR(0);
    wInterrupt_Mask = ISR_MSK;
    _SetCNTR(wInterrupt_Mask);

    usbEnbISR();
    bDeviceState = UNCONNECTED;
}

void usbReset(void) {
    pInformation->Current_Configuration = 0;

    /* current feature is current bmAttributes */
    pInformation->Current_Feature = (USB_CONFIG_ATTR_BUSPOWERED | USB_CONFIG_ATTR_SELF_POWERED);

    _SetBTABLE(USB_BTABLE_ADDRESS);

    /* setup control endpoint 0 */
    _SetEPType(ENDP0, EP_CONTROL);
    _SetEPTxStatus(ENDP0, EP_TX_STALL);
    _SetEPRxAddr(ENDP0,VCOM_CTRL_RX_ADDR);
    _SetEPTxAddr(ENDP0,VCOM_CTRL_TX_ADDR);
    Clear_Status_Out(ENDP0);

    SetEPRxCount(ENDP0, pProperty->MaxPacketSize);
    SetEPRxValid(ENDP0);

    /* setup management endpoint 1  */
    SetEPType     (VCOM_NOTIFICATION_ENDP,     EP_INTERRUPT);
    SetEPTxAddr   (VCOM_NOTIFICATION_ENDP,     VCOM_NOTIFICATION_ADDR);
    SetEPTxStatus (VCOM_NOTIFICATION_ENDP,     EP_TX_NAK);
    SetEPRxStatus (VCOM_NOTIFICATION_ENDP,     EP_RX_DIS);

    /* setup data endpoint OUT (rx) */
    /*   SetEPType     (VCOM_RX_ENDP, EP_BULK); */
    /*   SetEPRxAddr   (VCOM_RX_ENDP, VCOM_RX_ADDR); */
    /*   SetEPRxCount  (VCOM_RX_ENDP, VCOM_RX_EPSIZE); */
    /*   // SetEPTxStatus (VCOM_RX_ENDP, EP_TX_DIS); */
    /*   SetEPRxStatus (VCOM_RX_ENDP, EP_RX_VALID); */

    SetEPType     (3, EP_BULK);
    SetEPRxAddr   (3, 0x110);
    SetEPRxCount  (3,64);
    // SetEPTxStatus (VCOM_RX_ENDP, EP_TX_DIS);
    SetEPRxStatus (3, EP_RX_VALID);

    /* setup data endpoint IN (tx)  */
    SetEPType     (VCOM_TX_ENDP, EP_BULK);
    SetEPTxAddr   (VCOM_TX_ENDP, VCOM_TX_ADDR);
    SetEPTxStatus (VCOM_TX_ENDP, EP_TX_NAK);
    SetEPRxStatus (VCOM_TX_ENDP, EP_RX_DIS);

    bDeviceState = ATTACHED;
    SetDeviceAddress(0);

    /* reset the rx fifo */
    recvBufIn   = 0;
    recvBufOut  = 0;
    maxNewBytes = VCOM_RX_EPSIZE;
    countTx     = 0;
}


void usbStatusIn(void) {
    /* adjust the usart line coding
       if we wish to couple the CDC line coding
       with the real usart port */
}

void usbStatusOut(void) {
}

RESULT usbDataSetup(uint8 request) {
    uint8 *(*CopyRoutine)(uint16);
    CopyRoutine = NULL;

    if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) {
        switch (request) {
        case (GET_LINE_CODING):
            CopyRoutine = vcomGetSetLineCoding;
            last_request = GET_LINE_CODING;
            break;
        case (SET_LINE_CODING):
            CopyRoutine = vcomGetSetLineCoding;
            last_request = SET_LINE_CODING;
            break;
        default: break;
        }
    }

    if (CopyRoutine == NULL) {
        return USB_UNSUPPORT;
    }

    pInformation->Ctrl_Info.CopyData = CopyRoutine;
    pInformation->Ctrl_Info.Usb_wOffset = 0;
    (*CopyRoutine)(0);
    return USB_SUCCESS;
}

RESULT usbNoDataSetup(u8 request) {
    uint8 new_signal;

    /* we support set com feature but dont handle it */
    if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) {

        switch (request) {
        case (SET_COMM_FEATURE):
            return USB_SUCCESS;
        case (SET_CONTROL_LINE_STATE):
            /* to reset the board, pull both dtr and rts low
               then pulse dtr by itself */
            new_signal = pInformation->USBwValues.bw.bb0 & (CONTROL_LINE_DTR | CONTROL_LINE_RTS);
            line_dtr_rts = new_signal & 0x03;

            switch (reset_state) {
                /* no default, covered enum */
            case DTR_UNSET:
                if ((new_signal & CONTROL_LINE_DTR) == 0 ) {
                    reset_state = DTR_LOW;
                } else {
                    reset_state = DTR_HIGH;
                }
                break;

            case DTR_HIGH:
                if ((new_signal & CONTROL_LINE_DTR) == 0 ) {
                    reset_state = DTR_NEGEDGE;
                } else {
                    reset_state = DTR_HIGH;
                }
                break;

            case DTR_NEGEDGE:
                if ((new_signal & CONTROL_LINE_DTR) == 0 ) {
                    reset_state = DTR_LOW;
                } else {
                    reset_state = DTR_HIGH;
                }
                break;

            case DTR_LOW:
                if ((new_signal & CONTROL_LINE_DTR) == 0 ) {
                    reset_state = DTR_LOW;
                } else {
                    reset_state = DTR_HIGH;
                }
                break;
            }

            return USB_SUCCESS;
        }
    }
    return USB_UNSUPPORT;
}

RESULT usbGetInterfaceSetting(uint8 interface, uint8 alt_setting) {
    if (alt_setting > 0) {
        return USB_UNSUPPORT;
    } else if (interface > 1) {
        return USB_UNSUPPORT;
    }

    return USB_SUCCESS;
}


u8* usbGetDeviceDescriptor(u16 length) {
    return Standard_GetDescriptorData(length, &Device_Descriptor);
}

u8* usbGetConfigDescriptor(u16 length) {
    return Standard_GetDescriptorData(length, &Config_Descriptor);
}

u8* usbGetStringDescriptor(u16 length) {
    uint8 wValue0 = pInformation->USBwValue0;

    if (wValue0 > 2) {
        return NULL;
    }
    return Standard_GetDescriptorData(length, &String_Descriptor[wValue0]);
}

/* internal callbacks to respond to standard requests */
void usbSetConfiguration(void) {
    if (pInformation->Current_Configuration != 0) {
        bDeviceState = CONFIGURED;
    }
}

void usbSetDeviceAddress(void) {
    bDeviceState = ADDRESSED;
}