/*
 * Copyright 2012 Jared Boone
 * Copyright 2015 Benjamin Vernoux <bvernoux@airspy.com>
 *
 * This file is part of AirSpy (based on HackRF project).
 *
 * 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, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */
#include <stdint.h>
#include <stddef.h>

#include "usb_standard_request.h"

#include "usb.h"
#include "usb_type.h"
#include "usb_queue.h"

/* Shall be defined in user file usb_descriptor.c/.h */
extern uint8_t usb_descriptor_MSDescriptor[];
extern uint8_t usb_descriptor_ExtProps[];

uint16_t  USB_InterfaceStatus = 0; //Always 0x0000 value reseverd for future use
uint16_t  USB_DeviceStatus = 0; //Bit 0 = SelfPowerred, Bit=1 Remote Wakeup
uint8_t   USB_CurrentInterface = 0; //Bit 0 = SelfPowerred, Bit=1 Remote Wakeup
uint16_t  USB_EpStatus;

static uint_fast8_t usb_endpoint_number(const uint_fast8_t endpoint_address)
{
  return (endpoint_address & 0xF);
}

const uint8_t* usb_endpoint_descriptor(const usb_endpoint_t* const endpoint)
{
  const usb_configuration_t* const configuration = endpoint->device->configuration;
  if( configuration )
  {
    const uint8_t* descriptor = configuration->descriptor;
    while( descriptor[0] != 0 )
    {
      if( descriptor[1] == USB_DESCRIPTOR_TYPE_ENDPOINT )
      {
        if( descriptor[2] == endpoint->address ) {
          return descriptor;
        }
      }
      descriptor += descriptor[0];
    }
  }

  return 0;
}

uint_fast16_t usb_endpoint_descriptor_max_packet_size(const uint8_t* const endpoint_descriptor)
{
  return (endpoint_descriptor[5] << 8) | endpoint_descriptor[4];
}

usb_transfer_type_t usb_endpoint_descriptor_transfer_type(const uint8_t* const endpoint_descriptor)
{
  return (endpoint_descriptor[3] & 0x3);
}

void (*usb_configuration_changed_cb)(usb_device_t* const) = NULL;

void usb_set_configuration_changed_cb(void (*callback)(usb_device_t* const))
{
  usb_configuration_changed_cb = callback;
}

bool usb_set_configuration(
  usb_device_t* const device,
  const uint_fast8_t configuration_number)
{
  const usb_configuration_t* new_configuration = 0;
  if( configuration_number != 0 )
  {
    // Locate requested configuration.
    if( device->configurations )
    {
      usb_configuration_t** configurations = *(device->configurations);
      uint32_t i = 0;
      const usb_speed_t usb_speed_current = usb_speed(device);
      while( configurations[i] )
      {
        if( (configurations[i]->speed == usb_speed_current) &&
            (configurations[i]->number == configuration_number) )
        {
          new_configuration = configurations[i];
          break;
        }
        i++;
      }
    }

    // Requested configuration not found: request error.
    if( new_configuration == 0 ) {
      return false;
    }
  }

  if( new_configuration != device->configuration ) {
    // Configuration changed.
    device->configuration = new_configuration;
  }

  if (usb_configuration_changed_cb)
    usb_configuration_changed_cb(device);

  return true;
}

static usb_request_status_t usb_send_descriptor(
  usb_endpoint_t* const endpoint,
  const uint8_t* const descriptor_data)
{
  const uint32_t setup_length = endpoint->setup.length;
  uint32_t descriptor_length = descriptor_data[0];
  if ( ( descriptor_data[1] == USB_DESCRIPTOR_TYPE_CONFIGURATION ) ||
        ( descriptor_data[1] == USB_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION ) )
  {
    descriptor_length = (descriptor_data[3] << 8) | descriptor_data[2];
  }
  // We cast the const away but this shouldn't be a problem as this is a write transfer
  usb_transfer_schedule_block(
    endpoint->in,
    (uint8_t* const) descriptor_data,
    (setup_length > descriptor_length) ? descriptor_length : setup_length);
  usb_transfer_schedule_ack(endpoint->out);
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_send_descriptor_string(usb_endpoint_t* const endpoint)
{
  if ( endpoint->setup.value_l == 0xee)
  {
    return usb_send_descriptor(endpoint, usb_descriptor_MSDescriptor);
  }
  else
  {
    uint_fast8_t index = endpoint->setup.value_l;
    for( uint_fast8_t i=0; endpoint->device->descriptor_strings[i] != 0; i++ )
    {
      if( i == index ) {
        return usb_send_descriptor(endpoint, endpoint->device->descriptor_strings[i]);
      }
    }
  }

  return USB_REQUEST_STATUS_STALL;
}

static usb_request_status_t usb_send_descriptor_config(
  usb_endpoint_t* const endpoint,
  usb_speed_t speed,
  const uint8_t config_num)
{
  usb_configuration_t** config = *(endpoint->device->configurations);
  unsigned int i = 0;
  for( ; *config != NULL; config++ )
  {
    if( (*config)->speed == speed)
    {
      if (i == config_num)
      {
        return usb_send_descriptor(endpoint, (*config)->descriptor);
      } else {
        i++;
      }
    }
  }
  return USB_REQUEST_STATUS_STALL;
}

static usb_request_status_t usb_standard_request_get_descriptor_setup(
  usb_endpoint_t* const endpoint)
{
  switch( endpoint->setup.value_h )
  {
    case USB_DESCRIPTOR_TYPE_DEVICE:
      return usb_send_descriptor(endpoint, endpoint->device->descriptor);

    case USB_DESCRIPTOR_TYPE_CONFIGURATION:
      // TODO: Duplicated code. Refactor.
      if( usb_speed(endpoint->device) == USB_SPEED_HIGH ) {
        return usb_send_descriptor_config(endpoint, USB_SPEED_HIGH, endpoint->setup.value_l);
      } else {
        return usb_send_descriptor_config(endpoint, USB_SPEED_FULL, endpoint->setup.value_l);
      }

    case USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER:
      return usb_send_descriptor(endpoint, endpoint->device->qualifier_descriptor);

    case USB_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION:
      // TODO: Duplicated code. Refactor.
      if( usb_speed(endpoint->device) == USB_SPEED_HIGH ) {
        return usb_send_descriptor_config(endpoint, USB_SPEED_FULL, endpoint->setup.value_l);
      } else {
        return usb_send_descriptor_config(endpoint, USB_SPEED_HIGH, endpoint->setup.value_l);
      }

    case USB_DESCRIPTOR_TYPE_STRING:
      return usb_send_descriptor_string(endpoint);

    case USB_DESCRIPTOR_TYPE_INTERFACE:
    case USB_DESCRIPTOR_TYPE_ENDPOINT:
    default:
      return USB_REQUEST_STATUS_STALL;
  }
}

static usb_request_status_t usb_standard_request_get_descriptor(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  switch( stage )
  {
    case USB_TRANSFER_STAGE_SETUP:
      return usb_standard_request_get_descriptor_setup(endpoint);

    case USB_TRANSFER_STAGE_DATA:
    case USB_TRANSFER_STAGE_STATUS:
      return USB_REQUEST_STATUS_OK;

    default:
      return USB_REQUEST_STATUS_STALL;
  }
}

/*********************************************************************/

static usb_request_status_t usb_standard_request_set_address_setup(
  usb_endpoint_t* const endpoint)
{
  usb_set_address_deferred(endpoint->device, endpoint->setup.value_l);
  usb_transfer_schedule_ack(endpoint->in);
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_standard_request_set_address(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  switch( stage )
  {
    case USB_TRANSFER_STAGE_SETUP:
      return usb_standard_request_set_address_setup(endpoint);

    case USB_TRANSFER_STAGE_DATA:
    case USB_TRANSFER_STAGE_STATUS:
      /* NOTE: Not necessary to set address here, as DEVICEADR.USBADRA bit
       * will cause controller to automatically perform set address
       * operation on IN ACK.
       */
      return USB_REQUEST_STATUS_OK;

    default:
      return USB_REQUEST_STATUS_STALL;
  }
}

/*********************************************************************/

static usb_request_status_t usb_standard_request_set_configuration_setup(
  usb_endpoint_t* const endpoint)
{
  const uint8_t usb_configuration = endpoint->setup.value_l;
  if( usb_set_configuration(endpoint->device, usb_configuration) )
  {
    if( usb_configuration == 0 )
    {
      // TODO: Should this be done immediately?
      usb_set_address_immediate(endpoint->device, 0);
    }
    usb_transfer_schedule_ack(endpoint->in);
    return USB_REQUEST_STATUS_OK;
  }
  else
  {
    return USB_REQUEST_STATUS_STALL;
  }
}

static usb_request_status_t usb_standard_request_set_configuration(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  switch( stage )
  {
    case USB_TRANSFER_STAGE_SETUP:
      return usb_standard_request_set_configuration_setup(endpoint);

    case USB_TRANSFER_STAGE_DATA:
    case USB_TRANSFER_STAGE_STATUS:
      return USB_REQUEST_STATUS_OK;

    default:
      return USB_REQUEST_STATUS_STALL;
  }
}

/*********************************************************************/

static usb_request_status_t usb_standard_request_get_configuration_setup(
  usb_endpoint_t* const endpoint)
{
  if( endpoint->setup.length == 1 )
  {
    endpoint->buffer[0] = 0;
    if( endpoint->device->configuration )
    {
      endpoint->buffer[0] = endpoint->device->configuration->number;
    }
    usb_transfer_schedule_block(endpoint->in, &endpoint->buffer, 1);
    usb_transfer_schedule_ack(endpoint->out);
    return USB_REQUEST_STATUS_OK;
  } else {
    return USB_REQUEST_STATUS_STALL;
  }
}

static usb_request_status_t usb_standard_request_get_configuration(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  switch( stage )
  {
    case USB_TRANSFER_STAGE_SETUP:
      return usb_standard_request_get_configuration_setup(endpoint);

    case USB_TRANSFER_STAGE_DATA:
    case USB_TRANSFER_STAGE_STATUS:
      return USB_REQUEST_STATUS_OK;

    default:
      return USB_REQUEST_STATUS_STALL;
  }
}
static usb_request_status_t usb_standard_request_get_status_endpoint(usb_endpoint_t* const endpoint)
{
  const uint_fast8_t endpoint_number = usb_endpoint_number(endpoint->setup.index);
  USB_EpStatus = (USB0_ENDPTCTRL(endpoint_number) & (USB0_ENDPTCTRL_RXS | USB0_ENDPTCTRL_TXS)) ? 1 : 0;
  usb_transfer_schedule_block(endpoint->in, &USB_EpStatus, 2);
  usb_transfer_schedule_ack(endpoint->out);
  return USB_REQUEST_STATUS_OK;
}
static usb_request_status_t usb_standard_request_get_status(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  if (stage == USB_TRANSFER_STAGE_SETUP)
  {
    switch (endpoint->setup.request_type & USB_SETUP_REQUEST_TYPE_RECIPIENT_mask)
    {
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_DEVICE:
        usb_transfer_schedule_block(endpoint->in, &USB_DeviceStatus, 2);
        usb_transfer_schedule_ack(endpoint->out);
        return USB_REQUEST_STATUS_OK;
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_INTERFACE:
        usb_transfer_schedule_block(endpoint->in, &USB_InterfaceStatus, 2);
        usb_transfer_schedule_ack(endpoint->out);
        return USB_REQUEST_STATUS_OK;
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_ENDPOINT:
        return usb_standard_request_get_status_endpoint(endpoint);
        break;
    }
  }
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_standard_request_get_interface(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  if (stage == USB_TRANSFER_STAGE_SETUP)
  {
    switch (endpoint->setup.request_type & USB_SETUP_REQUEST_TYPE_RECIPIENT_mask)
    {
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_DEVICE:
        break;

      case USB_SETUP_REQUEST_TYPE_RECIPIENT_INTERFACE:
        usb_transfer_schedule_block(endpoint->in, &USB_CurrentInterface, 1);
        usb_transfer_schedule_ack(endpoint->out);
        return USB_REQUEST_STATUS_OK;
        break;

      case USB_SETUP_REQUEST_TYPE_RECIPIENT_ENDPOINT:
        break;
    }
  }
  return USB_REQUEST_STATUS_OK;
}


static usb_request_status_t usb_standard_request_set_interface(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  if (stage == USB_TRANSFER_STAGE_SETUP)
  {
    switch (endpoint->setup.request_type & USB_SETUP_REQUEST_TYPE_RECIPIENT_mask)
    {
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_DEVICE:
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_INTERFACE:
        usb_transfer_schedule_ack(endpoint->in);
        return USB_REQUEST_STATUS_OK;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_ENDPOINT:
        break;
    }
  }
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_standard_request_clear_feature_endpoint(
  usb_endpoint_t* const endpoint)
{
  const uint_fast8_t endpoint_number = usb_endpoint_number(endpoint->setup.index);
  USB0_ENDPTCTRL(endpoint_number) &= ~(USB0_ENDPTCTRL_RXS | USB0_ENDPTCTRL_TXS);
  usb_transfer_schedule_ack(endpoint->in);
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_standard_request_clear_feature(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  if (stage == USB_TRANSFER_STAGE_SETUP)
  {
    switch (endpoint->setup.request_type & USB_SETUP_REQUEST_TYPE_RECIPIENT_mask)
    {
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_DEVICE:
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_INTERFACE:
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_ENDPOINT:
        return usb_standard_request_clear_feature_endpoint(endpoint);
        break;
    }
  }
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_standard_request_set_feature_endpoint(
  usb_endpoint_t* const endpoint)
{
  const uint_fast8_t endpoint_number = usb_endpoint_number(endpoint->setup.index);
  USB0_ENDPTCTRL(endpoint_number) |= (USB0_ENDPTCTRL_RXS | USB0_ENDPTCTRL_TXS);
  usb_transfer_schedule_ack(endpoint->in);
  return USB_REQUEST_STATUS_OK;
}

static usb_request_status_t usb_standard_request_set_feature(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  if (stage == USB_TRANSFER_STAGE_SETUP)
  {
    switch (endpoint->setup.request_type & USB_SETUP_REQUEST_TYPE_RECIPIENT_mask)
    {
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_DEVICE:
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_INTERFACE:
        break;
      case USB_SETUP_REQUEST_TYPE_RECIPIENT_ENDPOINT:
        return usb_standard_request_set_feature_endpoint(endpoint);
        break;
    }
  }
  return USB_REQUEST_STATUS_OK;
}

/*********************************************************************/

usb_request_status_t usb_standard_request(
  usb_endpoint_t* const endpoint,
  const usb_transfer_stage_t stage)
{
  switch( endpoint->setup.request )
  {
    case USB_STANDARD_REQUEST_GET_STATUS:
      return usb_standard_request_get_status(endpoint, stage);

    case USB_STANDARD_REQUEST_CLEAR_FEATURE:
      return usb_standard_request_clear_feature(endpoint, stage);

    case USB_STANDARD_REQUEST_SET_FEATURE:
      return usb_standard_request_set_feature(endpoint, stage);

  //USB_STANDARD_REQUEST_SET_DESCRIPTOR = 7,*/

    case USB_STANDARD_REQUEST_GET_INTERFACE:
      return usb_standard_request_get_interface(endpoint, stage);

    case USB_STANDARD_REQUEST_SET_INTERFACE:
      return usb_standard_request_set_interface(endpoint, stage);

    case USB_STANDARD_REQUEST_GET_DESCRIPTOR:
      return usb_standard_request_get_descriptor(endpoint, stage);

    case USB_STANDARD_REQUEST_SET_ADDRESS:
      return usb_standard_request_set_address(endpoint, stage);

    case USB_STANDARD_REQUEST_SET_CONFIGURATION:
      return usb_standard_request_set_configuration(endpoint, stage);

    case USB_STANDARD_REQUEST_GET_CONFIGURATION:
      return usb_standard_request_get_configuration(endpoint, stage);

    default:
      return USB_REQUEST_STATUS_STALL;
  }
}
