#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>
#include <libopencm3/stm32/spi.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/cdc.h>
#include <libopencm3/cm3/scb.h>
#include <stdlib.h>

usbd_device *usbd_dev;
volatile uint32_t cas=0;
volatile int patro=0;
volatile int dr_select=11;
char buf[64];
int data[3];
uint16_t send[8][12];
uint16_t send1=1;
uint16_t overeni[4]={1,2,3,4};


static const struct usb_device_descriptor dev = {
	.bLength = USB_DT_DEVICE_SIZE,
	.bDescriptorType = USB_DT_DEVICE,
	.bcdUSB = 0x0200,
	.bDeviceClass = USB_CLASS_CDC,
	.bDeviceSubClass = 0,
	.bDeviceProtocol = 0,
	.bMaxPacketSize0 = 64,
	.idVendor = 0x0483,
	.idProduct = 0x5740,
	.bcdDevice = 0x0200,
	.iManufacturer = 1,
	.iProduct = 2,
	.iSerialNumber = 3,
	.bNumConfigurations = 1,
};
static const struct usb_endpoint_descriptor comm_endp[] = {{
	.bLength = USB_DT_ENDPOINT_SIZE,
	.bDescriptorType = USB_DT_ENDPOINT,
	.bEndpointAddress = 0x83,
	.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
	.wMaxPacketSize = 16,
	.bInterval = 255,
} };
static const struct usb_endpoint_descriptor data_endp[] = {{
	.bLength = USB_DT_ENDPOINT_SIZE,
	.bDescriptorType = USB_DT_ENDPOINT,
	.bEndpointAddress = 0x01,
	.bmAttributes = USB_ENDPOINT_ATTR_BULK,
	.wMaxPacketSize = 64,
	.bInterval = 1,
}, {
	.bLength = USB_DT_ENDPOINT_SIZE,
	.bDescriptorType = USB_DT_ENDPOINT,
	.bEndpointAddress = 0x82,
	.bmAttributes = USB_ENDPOINT_ATTR_BULK,
	.wMaxPacketSize = 64,
	.bInterval = 1,
} };
static const struct {
	struct usb_cdc_header_descriptor header;
	struct usb_cdc_call_management_descriptor call_mgmt;
	struct usb_cdc_acm_descriptor acm;
	struct usb_cdc_union_descriptor cdc_union;
} __attribute__((packed)) cdcacm_functional_descriptors = {
	.header = {
		.bFunctionLength = sizeof(struct usb_cdc_header_descriptor),
		.bDescriptorType = CS_INTERFACE,
		.bDescriptorSubtype = USB_CDC_TYPE_HEADER,
		.bcdCDC = 0x0110,
	},
	.call_mgmt = {
		.bFunctionLength =
			sizeof(struct usb_cdc_call_management_descriptor),
		.bDescriptorType = CS_INTERFACE,
		.bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
		.bmCapabilities = 0,
		.bDataInterface = 1,
	},
	.acm = {
		.bFunctionLength = sizeof(struct usb_cdc_acm_descriptor),
		.bDescriptorType = CS_INTERFACE,
		.bDescriptorSubtype = USB_CDC_TYPE_ACM,
		.bmCapabilities = 0,
	},
	.cdc_union = {
		.bFunctionLength = sizeof(struct usb_cdc_union_descriptor),
		.bDescriptorType = CS_INTERFACE,
		.bDescriptorSubtype = USB_CDC_TYPE_UNION,
		.bControlInterface = 0,
		.bSubordinateInterface0 = 1,
	 }
};
static const struct usb_interface_descriptor comm_iface[] = {{
	.bLength = USB_DT_INTERFACE_SIZE,
	.bDescriptorType = USB_DT_INTERFACE,
	.bInterfaceNumber = 0,
	.bAlternateSetting = 0,
	.bNumEndpoints = 1,
	.bInterfaceClass = USB_CLASS_CDC,
	.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
	.bInterfaceProtocol = USB_CDC_PROTOCOL_AT,
	.iInterface = 0,

	.endpoint = comm_endp,

	.extra = &cdcacm_functional_descriptors,
	.extralen = sizeof(cdcacm_functional_descriptors)
} };
static const struct usb_interface_descriptor data_iface[] = {{
	.bLength = USB_DT_INTERFACE_SIZE,
	.bDescriptorType = USB_DT_INTERFACE,
	.bInterfaceNumber = 1,
	.bAlternateSetting = 0,
	.bNumEndpoints = 2,
	.bInterfaceClass = USB_CLASS_DATA,
	.bInterfaceSubClass = 0,
	.bInterfaceProtocol = 0,
	.iInterface = 0,

	.endpoint = data_endp,
} };
static const struct usb_interface ifaces[] = {{
	.num_altsetting = 1,
	.altsetting = comm_iface,
}, {
	.num_altsetting = 1,
	.altsetting = data_iface,
} };
static const struct usb_config_descriptor config = {
	.bLength = USB_DT_CONFIGURATION_SIZE,
	.bDescriptorType = USB_DT_CONFIGURATION,
	.wTotalLength = 0,
	.bNumInterfaces = 2,
	.bConfigurationValue = 1,
	.iConfiguration = 0,
	.bmAttributes = 0x80,
	.bMaxPower = 0x32,

	.interface = ifaces,
};
static const char *const usb_strings[] = {
	"Black Sphere Technologies",
	"CDC-ACM Demo",
	"DEMO",
};
/* Buffer to be used for control requests. */
    uint8_t usbd_control_buffer[128];
    static int cdcacm_control_request(usbd_device *usbd_devi,
	struct usb_setup_data *req, uint8_t **bufi, uint16_t *len,
	void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)){
	(void)complete;
	(void)buf;
	(void)usbd_devi;

	switch (req->bRequest) {
	case USB_CDC_REQ_SET_CONTROL_LINE_STATE: {
		/*
		 * This Linux cdc_acm driver requires this to be implemented
		 * even though it's optional in the CDC spec, and we don't
		 * advertise it in the ACM functional descriptor.
		 */
		return 1;
		}
	case USB_CDC_REQ_SET_LINE_CODING:
		if (*len < sizeof(struct usb_cdc_line_coding)) {
			return 0;
		}

		return 1;
	}
	return 0;
}
static void cdcacm_data_rx_cb(usbd_device *usbd_devi, uint8_t ep){
	(void)ep;
	usbd_ep_read_packet(usbd_devi, 0x01, buf, 64);
	if(buf[0]=='i')
    {
        usbd_ep_write_packet(usbd_devi,0x82,overeni,8);
    }
	char cislo[2];
	cislo[0]=buf[0];
	cislo[1]=buf[1];
	data[0]=strtol(cislo,NULL,16);
	cislo[0]=buf[2];
	cislo[1]=buf[3];
	data[1]=strtol(cislo,NULL,16);
	cislo[0]=buf[4];
	cislo[1]=buf[5];
	data[2]=strtol(cislo,NULL,16);

	gpio_toggle(GPIOC, GPIO5);
}
static void cdcacm_set_config(usbd_device *usbd_devi, uint16_t wValue){
	(void)wValue;

	usbd_ep_setup(usbd_devi, 0x01, USB_ENDPOINT_ATTR_BULK, 64,
			cdcacm_data_rx_cb);
	usbd_ep_setup(usbd_devi, 0x82, USB_ENDPOINT_ATTR_BULK, 64, NULL);
	usbd_ep_setup(usbd_devi, 0x83, USB_ENDPOINT_ATTR_INTERRUPT, 16, NULL);

	usbd_register_control_callback(
				usbd_devi,
				USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
				USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
				cdcacm_control_request);
}
static void clock_setup(void){
	rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_120MHZ]);
}
static void gpio_setup(void){
	/* Port A and C are on AHB1 */
	rcc_periph_clock_enable(RCC_GPIOA);
	rcc_periph_clock_enable(RCC_GPIOC);
	/* Set the digital test output on PC1 */
	gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO1);
	gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO1);
		/* Enable GPIOD clock. */
	rcc_periph_clock_enable(RCC_GPIOE);
	rcc_periph_clock_enable(RCC_GPIOB);
	rcc_periph_clock_enable(RCC_GPIOD);
	/* Set GPIO12 (in GPIO port D) to 'output push-pull'. */
	gpio_mode_setup(GPIOE, GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO3|GPIO8|GPIO9|GPIO10|GPIO11|GPIO12|GPIO13|GPIO14|GPIO15);
	gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO10|GPIO11|GPIO2|GPIO13);
	gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO12);
	gpio_set(GPIOE,GPIO3);//vypnuti nss
	gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO1);
	gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO1);
}
void sys_tick_handler(void){
    if((cas%256)==0)
    {
        switch(patro)
        {
            default: break;
            case 0:
                gpio_clear(GPIOE,GPIO15);
                gpio_set(GPIOE,GPIO8);
                gpio_set(GPIOB,GPIO10);
                break;
            case 1:
                gpio_clear(GPIOE,GPIO8);
                gpio_set(GPIOE,GPIO9);
                gpio_set(GPIOB,GPIO10);
                break;
            case 2:
                gpio_clear(GPIOE,GPIO9);
                gpio_set(GPIOE,GPIO10);
                gpio_set(GPIOB,GPIO10);
                break;
            case 3:
                gpio_clear(GPIOE,GPIO10);
                gpio_set(GPIOE,GPIO11);
                gpio_set(GPIOB,GPIO10);
                break;
            case 4:
                gpio_clear(GPIOE,GPIO11);
                gpio_set(GPIOE,GPIO12);
                gpio_set(GPIOB,GPIO10);
                break;
            case 5:
                gpio_clear(GPIOE,GPIO12);
                gpio_set(GPIOE,GPIO13);
                gpio_set(GPIOB,GPIO10);
                break;
            case 6:
                gpio_clear(GPIOE,GPIO13);
                gpio_set(GPIOE,GPIO14);
                gpio_set(GPIOB,GPIO10);
                break;
            case 7:
                gpio_clear(GPIOE,GPIO14);
                gpio_set(GPIOE,GPIO15);
                gpio_set(GPIOB,GPIO10);
                break;
        }
        if(cas!=0)
            {patro++;}
    }
    else
    {gpio_clear(GPIOB,GPIO10);}
    if((cas%256)<12)
    {
        spi_send(SPI1,send[patro][dr_select]);
        dr_select--;
        if((cas%256)==11)
            dr_select=11;
    }
    cas++;
    if(cas==2047)
    {
        cas=0;
        patro=0;
    }
}
static void systick_setup(void){
    systick_set_frequency(200000,120000000);
    systick_interrupt_enable();
    systick_counter_enable();
}
static void spi_setup(void){
    rcc_periph_clock_enable(RCC_SPI1);
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_GPIOB);

    gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO5|GPIO6|GPIO7);
    gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO10 | GPIO11|GPIO2|GPIO13);

    gpio_set_af(GPIOA,GPIO_AF5,GPIO5 | GPIO6 |GPIO7);

    gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100MHZ, GPIO5 | GPIO6 |GPIO7);

    spi_reset(SPI1);
    spi_set_master_mode(SPI1);// nastavit do modu master
    //spi_set_bidirectional_transmit_only_mode(SPI1);//nastavit do vysilaciho rezimu
    //1Mbps, positive clock polarity, leading edge trigger, 8-bit words,LSB first.

	spi_set_baudrate_prescaler(SPI1,SPI_CR1_BR_FPCLK_DIV_2); //
	spi_set_clock_polarity_0(SPI1);
	spi_set_clock_phase_0(SPI1);
	spi_set_unidirectional_mode(SPI1);
	spi_set_dff_16bit(SPI1);
	spi_set_full_duplex_mode(SPI1);
	spi_disable_software_slave_management(SPI1);
	//spi_send_lsb_first(SPI1);
	spi_send_msb_first(SPI1);
	spi_set_nss_high(SPI1);
	spi_enable_ss_output(SPI1);
	SPI_I2SCFGR(SPI1) = SPI_I2SCFGR_I2SCFG_MASTER_TRANSMIT | SPI_I2SCFGR_I2SSTD_MSB_JUSTIFIED;
	spi_enable(SPI1);
}
int main(void)
{
    clock_setup();
    gpio_setup();
	rcc_periph_clock_enable(RCC_GPIOA);
	rcc_periph_clock_enable(RCC_OTGFS);
	gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE,
			GPIO9 | GPIO11 | GPIO12);
	gpio_set_af(GPIOA, GPIO_AF10, GPIO9 | GPIO11 | GPIO12);
	usbd_dev = usbd_init(&otgfs_usb_driver, &dev, &config,
			usb_strings, 3,
			usbd_control_buffer, sizeof(usbd_control_buffer));
	usbd_register_set_config_callback(usbd_dev, cdcacm_set_config);
    spi_setup();
    systick_setup();
    int r=0,s=0;
    for(r=0;r<8;r++)
        {
            for(s=0;s<12;s++)
            {send[r][s]=0;}
        }
    int vcas=0;
    int rotace=11;
    while (1) {
		usbd_poll(usbd_dev);
        if(cas==2000)
        {
            vcas++;
            if(vcas==200)
            {
                vcas=0;
                //send[0][0]=rand()%0xFFFF;
                //send[5][5]=rand()%0xFFFF;
                /*s++;
                if(s==12)
                {
                    send[s][r]=0;
                    s=0;r++;
                    if(r==8)
                    {
                        r=0;
                    }
                }
                send[r][s-1]=0;*/
            }
        }
	}
}
