/*
 * upstream_statemachine.c
 *
 *  Created on: 20/08/2015
 *      Author: Robert Fisk
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */


#include "upstream_statemachine.h"
#include "upstream_spi.h"
#include "upstream_hid.h"
#include "usb_device.h"
#include "usbd_core.h"
#include "usbd_msc.h"
#include "usbd_hid.h"
#include "build_config.h"

#define POLL_DEVICE_CONNECTED_TIMEOUT_MS    1000


UpstreamStateTypeDef            UpstreamState           = STATE_TEST_INTERFACE;
InterfaceCommandClassTypeDef    ConfiguredDeviceClass   = COMMAND_CLASS_INTERFACE;
uint32_t                        PollDeviceConnectedTimer;


void Upstream_StateMachine_TestInterfaceReplyCallback(UpstreamPacketTypeDef* replyPacket);
void Upstream_StateMachine_NotifyDevice(UpstreamPacketTypeDef* freePacket);
void Upstream_StateMachine_NotifyDeviceReplyCallback(UpstreamPacketTypeDef* replyPacket);
void Upstream_StateMachine_PollDeviceConnectedReplyCallback(UpstreamPacketTypeDef* replyPacket);



void Upstream_InitStateMachine(void)
{
    UpstreamPacketTypeDef* freePacket;
    uint16_t i;
    uint8_t  testDataValue;

    Upstream_InitSPI();

    //Prepare SPI test packet
    freePacket = Upstream_GetFreePacketImmediately();
    if (freePacket == NULL)
    {
        UpstreamState = STATE_ERROR;
        return;
    }

    freePacket->Length16 = UPSTREAM_PACKET_LEN_16;
    freePacket->CommandClass = COMMAND_CLASS_INTERFACE;
    freePacket->Command = COMMAND_INTERFACE_ECHO;

    //Fill our test packet with some junk
    testDataValue = 0xFF;
    for (i = 0; i < MSC_MEDIA_PACKET; i++)
    {
        freePacket->Data[i] = testDataValue;
        testDataValue += 39;
    }

    if (Upstream_TransmitPacket(freePacket) == HAL_OK)
    {
        Upstream_ReceivePacket(Upstream_StateMachine_TestInterfaceReplyCallback);
    }
}


//Used by upstream_spi freakout macro, indicates we should stop everything.
void Upstream_StateMachine_SetErrorState(void)
{
    UpstreamState = STATE_ERROR;
    if ((ConfiguredDeviceClass > COMMAND_CLASS_INTERFACE) &&
        (ConfiguredDeviceClass < COMMAND_CLASS_ERROR))
    {
        USBD_DeInit(&hUsbDeviceFS);
    }
}


InterfaceCommandClassTypeDef Upstream_StateMachine_CheckActiveClass(void)
{
    if (UpstreamState == STATE_ERROR)
    {
        return COMMAND_CLASS_ERROR;
    }

    if ((UpstreamState == STATE_DEVICE_ACTIVE) ||
        (UpstreamState == STATE_SUSPENDED))
    {
        return ConfiguredDeviceClass;
    }

    //else:
    UPSTREAM_STATEMACHINE_FREAKOUT;
    return COMMAND_CLASS_INTERFACE;
}


uint32_t Upstream_StateMachine_GetSuspendState(void)
{
    if (UpstreamState == STATE_SUSPENDED)
    {
        return 1;
    }
    return 0;
}


void Upstream_StateMachine_TestInterfaceReplyCallback(UpstreamPacketTypeDef* replyPacket)
{
    uint16_t i;
    uint8_t  testDataValue;

    if (UpstreamState >= STATE_ERROR)
    {
        return;
    }

    if ((UpstreamState != STATE_TEST_INTERFACE) ||
        (replyPacket == NULL))
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    if (replyPacket->Length16 != UPSTREAM_PACKET_LEN_16)
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    testDataValue = 0xFF;
    for (i = 0; i < MSC_MEDIA_PACKET; i++)
    {
        if (replyPacket->Data[i] != testDataValue)
        {
            UPSTREAM_STATEMACHINE_FREAKOUT;
            return;
        }
        testDataValue += 39;
    }

    //SPI interface passed checks. Now we wait for a device to be attached to downstream.
    Upstream_StateMachine_NotifyDevice(replyPacket);
}


void Upstream_StateMachine_NotifyDevice(UpstreamPacketTypeDef* freePacket)
{
    UpstreamState = STATE_WAIT_DEVICE;
    freePacket->Length16 = UPSTREAM_PACKET_HEADER_LEN_16;
    freePacket->CommandClass = COMMAND_CLASS_INTERFACE;
    freePacket->Command = COMMAND_INTERFACE_NOTIFY_DEVICE;
    if (Upstream_TransmitPacket(freePacket) == HAL_OK)
    {
        Upstream_ReceivePacket(Upstream_StateMachine_NotifyDeviceReplyCallback);
    }
}


void Upstream_StateMachine_NotifyDeviceReplyCallback(UpstreamPacketTypeDef* replyPacket)
{
    InterfaceCommandClassTypeDef newActiveClass = COMMAND_CLASS_INTERFACE;
    USBD_ClassTypeDef*           newClassPointer;

    if (UpstreamState >= STATE_ERROR)
    {
        return;
    }

    if ((UpstreamState != STATE_WAIT_DEVICE) ||
        (replyPacket == NULL))
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    if (replyPacket->Length16 != (UPSTREAM_PACKET_HEADER_LEN_16 + 1))
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    switch (replyPacket->Data[0])
    {
#ifdef CONFIG_MASS_STORAGE_ENABLED
    case COMMAND_CLASS_MASS_STORAGE:
        newActiveClass = COMMAND_CLASS_MASS_STORAGE;
        newClassPointer = &USBD_MSC;
        break;
#endif
#ifdef CONFIG_MOUSE_ENABLED
    case COMMAND_CLASS_HID_MOUSE:
        newActiveClass = COMMAND_CLASS_HID_MOUSE;
        newClassPointer = &USBD_HID;
        USBD_HID_PreinitMouse();
        break;
#endif
#ifdef CONFIG_KEYBOARD_ENABLED
    case COMMAND_CLASS_HID_KEYBOARD:
        newActiveClass = COMMAND_CLASS_HID_KEYBOARD;
        newClassPointer = &USBD_HID;
        USBD_HID_PreinitKeyboard();
        break;
#endif
        //Add other supported classes here...
    }

    Upstream_ReleasePacket(replyPacket);

    if (newActiveClass == COMMAND_CLASS_INTERFACE)
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    //Downstream should never change the active device class without rebooting!
    if ((ConfiguredDeviceClass != COMMAND_CLASS_INTERFACE) &&
        (ConfiguredDeviceClass != newActiveClass))
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    UpstreamState = STATE_DEVICE_ACTIVE;
    ConfiguredDeviceClass = newActiveClass;
    USB_Device_Init();
    USBD_RegisterClass(&hUsbDeviceFS, newClassPointer);
    USBD_Start(&hUsbDeviceFS);

    //The USB device stack will now receive commands from our host.
    //All we need to do is monitor for downstream device disconnection.
}


void Upstream_StateMachine_DeviceDisconnected(void)
{
    if ((ConfiguredDeviceClass == COMMAND_CLASS_INTERFACE) ||
        (ConfiguredDeviceClass >= COMMAND_CLASS_ERROR))
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    USBD_DeInit(&hUsbDeviceFS);
    Upstream_GetFreePacket(Upstream_StateMachine_NotifyDevice);
}


//Suspend event activated by our host
void Upstream_StateMachine_Suspend(void)
{
    if (UpstreamState >= STATE_SUSPENDED)       //Ignore when already suspended, or in error state
    {
        return;
    }
    if (UpstreamState != STATE_DEVICE_ACTIVE)
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    PollDeviceConnectedTimer = 0;
    UpstreamState = STATE_SUSPENDED;
}


//Resume event activated by our host
void Upstream_StateMachine_CheckResume(void)
{
    if (UpstreamState == STATE_SUSPENDED)
    {
        UpstreamState = STATE_DEVICE_ACTIVE;
    }
}


//Activated by downstream report, causing us to wake up the host
void Upstream_StateMachine_Wakeup(void)
{
    USBD_ClassTypeDef* activeClass;

    if (UpstreamState != STATE_SUSPENDED)
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    //This is how I'd wakeup the host, IF IT ACTUALLY WORKED!
    //USBD_LL_WakeupHost(&hUsbDeviceFS);

    //This is really ugly! But wakeup seems to be broken on the STM32, so we do it the hard way.
    activeClass = USBD_DeInit(&hUsbDeviceFS);
    USB_Device_Init();
    USBD_RegisterClass(&hUsbDeviceFS, activeClass);
    USBD_Start(&hUsbDeviceFS);
}


//Called by Systick_Handler every 1ms, at high interrupt priority.
void Upstream_StateMachine_PollDeviceConnected(void)
{
    UpstreamPacketTypeDef* freePacket;

    if ((UpstreamState != STATE_SUSPENDED) ||
        (ConfiguredDeviceClass != COMMAND_CLASS_MASS_STORAGE))
    {
        return;
    }

    if (++PollDeviceConnectedTimer >= POLL_DEVICE_CONNECTED_TIMEOUT_MS)
    {
        PollDeviceConnectedTimer = 0;
        freePacket = Upstream_GetFreePacketImmediately();
        if (freePacket == NULL)
        {
            UpstreamState = STATE_ERROR;
            return;
        }

        freePacket->Length16 = UPSTREAM_PACKET_HEADER_LEN_16;
        freePacket->CommandClass = COMMAND_CLASS_MASS_STORAGE;
        freePacket->Command = COMMAND_MSC_POLL_DISCONNECT;
        if (Upstream_TransmitPacket(freePacket) == HAL_OK)
        {
            Upstream_ReceivePacket(Upstream_StateMachine_PollDeviceConnectedReplyCallback);
        }
    }
}


void Upstream_StateMachine_PollDeviceConnectedReplyCallback(UpstreamPacketTypeDef* replyPacket)
{
    if ((UpstreamState != STATE_SUSPENDED) ||
        (replyPacket == NULL))
    {
        UPSTREAM_STATEMACHINE_FREAKOUT;
        return;
    }

    //Downstream device is still connected, so nothing to do here
    Upstream_ReleasePacket(replyPacket);
}
