/*
 * SPI.cpp
 *
 *  Created on: 11.06.2018
 *      Author: Sven
 */

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "esp_log.h"
#include "sdkconfig.h"
#include "esp_heap_alloc_caps.h"
#include "../SPIExt.h"
#include <string.h>

namespace sts {
namespace driver {

	#define SPI_LIB_TEST 1	// <--------------- change it
	#define TRANSFER_BYTE_BY_BYTE  0

	spi_transaction_t SPI::trans_desc[3] = {0};

	SPI::SPI(int mosi, int miso, int clk, int cs, int clock_freq_hz)
	{
		currentByte = 0;
		PIN_MOSI = gpio_num_t(mosi);
		PIN_MISO = gpio_num_t(miso);
		PIN_SCLK = gpio_num_t(clk);
		PIN_CS   = gpio_num_t(cs);
		m_freq_hz = clock_freq_hz;

	#if SPI_LIB_TEST == 1
		m_spi_device = nullptr;
		memset(&m_spi_buscfg, 0, sizeof(spi_lobo_bus_config_t));
		memset(&m_spi_devcfg, 0, sizeof(spi_lobo_device_interface_config_t));
	#endif

	#if SPI_LIB_TEST == 0
		m_handle = nullptr;
		m_host = VSPI_HOST;
		memset(&m_bus_config, 0, sizeof(spi_bus_config_t));
		memset(&m_dev_config, 0, sizeof(spi_device_interface_config_t));
	#endif

		initialize();
	}


	SPI::~SPI()
	{
		ESP_ERROR_CHECK(::spi_bus_remove_device(m_handle));
		ESP_ERROR_CHECK(::spi_bus_free(m_host));
	}


	void SPI::initialize()
	{
		esp_err_t ret;

	#if SPI_LIB_TEST == 1
		m_spi_buscfg.miso_io_num 		= PIN_MISO;				// set SPI MISO pin
		m_spi_buscfg.mosi_io_num 		= PIN_MOSI;				// set SPI MOSI pin
		m_spi_buscfg.sclk_io_num 		= PIN_SCLK;				// set SPI CLK pin
		m_spi_buscfg.quadwp_io_num		= -1;
		m_spi_buscfg.quadhd_io_num		= -1;
		m_spi_buscfg.max_transfer_sz 	= 0;

		m_spi_devcfg.address_bits		= 0;
		m_spi_devcfg.command_bits		= 0;
		m_spi_devcfg.dummy_bits			= 0;
		m_spi_devcfg.clock_speed_hz		= m_freq_hz;            // clock out at 1 MHz
		m_spi_devcfg.mode				= 1;                    // SPI mode 1
		m_spi_devcfg.spics_io_num		= -1;                   // we will use external CS pin
		m_spi_devcfg.spics_ext_io_num 	= PIN_CS;           	// external CS pin
		m_spi_devcfg.flags				= 0;        			// ALWAYS SET  to HALF DUPLEX MODE!! for display spi

		//Initialize the SPI bus
		ret = spi_lobo_bus_add_device(spi_lobo_host_device_t(2), &m_spi_buscfg, &m_spi_devcfg, &m_spi_device);
		ESP_ERROR_CHECK(ret);
	#endif

	#if SPI_LIB_TEST == 0
		m_bus_config.miso_io_num		= PIN_MISO;
		m_bus_config.mosi_io_num		= PIN_MOSI;
		m_bus_config.sclk_io_num		= PIN_SCLK;
		m_bus_config.quadwp_io_num		= -1;				// not used
		m_bus_config.quadhd_io_num 		= -1;				// not used
		m_bus_config.max_transfer_sz	= 0;				// 0 means default

		m_dev_config.address_bits		= 0;
		m_dev_config.command_bits		= 0;
		m_dev_config.dummy_bits			= 0;
		m_dev_config.clock_speed_hz		= m_freq_hz;		// Clock out at 1 MHz  1*1000*1000
		m_dev_config.input_delay_ns		= 0;
		m_dev_config.mode				= 1;                // SPI mode 1
		if(PIN_CS == 5 || PIN_CS == 15)
			m_dev_config.spics_io_num = PIN_CS;         	// CS pin nativ
		else
			m_dev_config.spics_io_num = -1;         		// CS pin not used
		m_dev_config.queue_size			= 1;
		m_dev_config.duty_cycle_pos 	= 128;
		m_dev_config.cs_ena_posttrans	= 0;
		m_dev_config.cs_ena_pretrans 	= 0;				// with "0" i get errors like .... greater then one not possible in duplex
		m_dev_config.flags 				= 0;
	//	m_dev_config.pre_cb 			= SPI::TransferStarting;
	//	m_dev_config.post_cb 			= SPI::TransferEnd;

		//Initialize the SPI bus
		int dma_channel = 0;
		ret = spi_bus_initialize(m_host, &m_bus_config, dma_channel);
		ESP_ERROR_CHECK(ret);

		//Attach to the SPI bus
		ret = spi_bus_add_device(m_host, &m_dev_config, &m_handle);
		ESP_ERROR_CHECK(ret);
	#endif
	}


	/*
	 * Read and Write operation with custom user transaction
	*/
	void SPI::transfer(spi_lobo_transaction_t& transaction) const
	{
		esp_err_t res;
		res = spi_lobo_transfer_data(m_spi_device, &transaction); // Receive using direct mode

		if (res != ESP_OK) {
			ESP_LOGE("SPI", "transfer:spi_device_transmit: %d", res);
		}
	}

	/*
	 * Read and Write operation with custom user transaction
	*/
	void SPI::transfer(spi_transaction_t& transaction) const
	{
		esp_err_t res;
		res = spi_device_transmit(m_handle, &transaction); // Receive using direct mode

		if (res != ESP_OK) {
			ESP_LOGE("SPI", "transfer:spi_device_transmit: %d", res);
		}
	}


	/*
	 * Read and Write operation up to 4 Bytes?
	*/
	void SPI::transfer(uint8_t* data, size_t len, int address, int command) const
	{
		//	TransferData* tranferData = new TransferData();
		//	tranferData->cs_enabled = len > 1 ? true : (bool)address;
		//	tranferData->cs_pin = PIN_CS;
#if SPI_LIB_TEST == 1
		static spi_lobo_transaction_t trans_desc;
		memset(&trans_desc, 0, sizeof(spi_lobo_transaction_t));

		trans_desc.address 	= 0;
		trans_desc.command	= 0;
#elif SPI_LIB_TEST == 0
		static spi_transaction_t trans_desc;
		memset(&trans_desc, 0, sizeof(spi_transaction_t));
		trans_desc.addr 	= 0;
		trans_desc.cmd		= 0;
#endif

		trans_desc.flags    = 0;
		trans_desc.length   = (8 * len);
		trans_desc.rxlength = (8 * len);
		trans_desc.tx_buffer = data;
		trans_desc.rx_buffer = data;
		//trans_desc.user		 = tranferData;

#if TRANSFER_BYTE_BY_BYTE == 0
		transfer(trans_desc);
#else
		spi_lobo_device_select(m_spi_device, 0);
		for(int i=0;i<len;i++)
		{
			// Wait for SPI bus ready
			while (m_spi_device->host->hw->cmd.usr);

			m_spi_device->host->hw->data_buf[0] = data[i];

			SPI::_spi_transfer_start(m_spi_device, 8, 8);

			data[i] = m_spi_device->host->hw->data_buf[0];
		}
		spi_lobo_device_deselect(m_spi_device);
#endif
	}


	void IRAM_ATTR SPI::_spi_transfer_start(spi_lobo_device_handle_t spi_dev, int wrbits, int rdbits)
	{
		// Load send buffer
		spi_dev->host->hw->user.usr_mosi_highpart = 0;
		spi_dev->host->hw->mosi_dlen.usr_mosi_dbitlen = wrbits-1;
		spi_dev->host->hw->user.usr_mosi = 1;
		if (rdbits) {
			spi_dev->host->hw->miso_dlen.usr_miso_dbitlen = rdbits;
			spi_dev->host->hw->user.usr_miso = 1;
		}
		else {
			spi_dev->host->hw->miso_dlen.usr_miso_dbitlen = 0;
			spi_dev->host->hw->user.usr_miso = 0;
		}
		// Start transfer
		spi_dev->host->hw->cmd.usr = 1;
		// Wait for SPI bus ready
		while (spi_dev->host->hw->cmd.usr);
	}


	/*
	 * Read and Write operation
	*/
	uint8_t SPI::transferByte(uint8_t value, Transfer mode) const
	{
		transfer(&value, 1);	// select slave and transfer

	//	if(mode == Transfer::CONTINUE)
	//	{
	//		//begin(PIN_CS);
	//		//transfer(&value, 1, 0);	// select slave and transfer
	//	}
	////	else
	////	{
	////		transfer(&value, 1, 1);	// on last tranfer we deselect slave
	////		end(PIN_CS);
	////	}
	//
	//	memset(&trans_desc[currentByte], 0, sizeof(spi_transaction_t));
	//	trans_desc[currentByte].addr 	 	= 0;
	//	trans_desc[currentByte].cmd			= 0;
	//	trans_desc[currentByte].flags     	= SPI_TRANS_USE_RXDATA;
	//	trans_desc[currentByte].length    	= 8;
	//	trans_desc[currentByte].rxlength  	= 8;
	//	trans_desc[currentByte].tx_buffer 	= &value;
	//	trans_desc[currentByte].rx_buffer 	= 0;
	//
	//	esp_err_t ret;
	//	ret = spi_device_queue_trans(m_handle, &trans_desc[currentByte], portMAX_DELAY);
	//	ESP_ERROR_CHECK(ret);
	//
	//	currentByte = (currentByte + 1) % 3;
	//	if(currentByte == 0)
	//	{
	//		//begin(PIN_CS);
	//
	//		spi_transaction_t *rtrans;
	//		//Wait for all 3 transactions to be done and get back the results.
	//		for (int x=0; x<3; x++) {
	//			ret=spi_device_get_trans_result(m_handle, &rtrans, portMAX_DELAY);
	//			assert(ret==ESP_OK);
	//		}
	//
	//		//end(PIN_CS);
	//	}

		return value;
	}


	bool SPI::isCSSelected() const
	{
		// by default CS is low when selected
		return gpio_get_level(PIN_CS) == false;
	}


	/*
	 * Manuel CS select
	*/
	void SPI::begin(int cs_pin) const
	{
		// Set CS to low so a connected chip will be "selected" by default
		gpio_set_level(gpio_num_t(cs_pin), false);
	}


	/*
	 * Manuel CS deselect
	*/
	void SPI::end(int cs_pin) const
	{
		// Set CS to high so a connected chip will be "deselected" by default
		gpio_set_level(gpio_num_t(cs_pin), true);
	}


	void SPI::TransferStarting(spi_transaction_t *trans)
	{
		sts::driver::TransferData* td = (sts::driver::TransferData*)trans->user;
		if(td != nullptr)
		{
			gpio_set_level(gpio_num_t(td->cs_pin), false);
		}
	}


	void SPI::TransferEnd(spi_transaction_t *trans)
	{
		sts::driver::TransferData* td = (sts::driver::TransferData*)trans->user;
		if(td != nullptr)
		{
			gpio_set_level(gpio_num_t(td->cs_pin), td->cs_enabled);
			delete td;
		}
	}


} /* namespace driver */
} /* namespace sts */
