#include <cstdint>
#include <cstdio>

#include "onewire_task.h"
#include "main.h"
#include "task.h"
#include "tim.h"

static constexpr uint16_t delay_ms = 5000U;
#ifdef DEBUG
static constexpr uint16_t DS18B20_480µs_DELAY	= 700U;
static constexpr uint16_t DS18B20_400µs_DELAY	= 620U;
static constexpr uint16_t DS18B20_80µs_DELAY	= 80U;
static constexpr uint16_t DS18B20_60µs_DELAY	= 60U;
static constexpr uint16_t DS18B20_50µs_DELAY	= 46U;
static constexpr uint16_t DS18B20_45µs_DELAY	= 45U;
static constexpr uint16_t DS18B20_15µs_DELAY	= 15U;
static constexpr uint16_t DS18B20_2µs_DELAY		= 2U;
static constexpr uint16_t DS18B20_1µs_DELAY		= 0U;
#else
static constexpr uint16_t DS18B20_480µs_DELAY	= 700U;
static constexpr uint16_t DS18B20_400µs_DELAY	= 620U;
static constexpr uint16_t DS18B20_80µs_DELAY	= 80U;
static constexpr uint16_t DS18B20_60µs_DELAY	= 60U;
static constexpr uint16_t DS18B20_50µs_DELAY	= 50U;
static constexpr uint16_t DS18B20_45µs_DELAY	= 45U;
static constexpr uint16_t DS18B20_15µs_DELAY	= 15U;
static constexpr uint16_t DS18B20_2µs_DELAY		= 2U;
static constexpr uint16_t DS18B20_1µs_DELAY		= 0U;
#endif

static constexpr uint8_t DS18B20_SKIP_ROM_CMD		= 0xCC;
static constexpr uint8_t DS18B20_CONVERT_CMD		= 0x44;
static constexpr uint8_t DS18B20_READ_SCRPAD_CMD	= 0xBE;

static constexpr GPIO_PinState DS18B20_HI_LEVEL = GPIO_PIN_SET;
static constexpr GPIO_PinState DS18B20_LO_LEVEL = GPIO_PIN_RESET;

enum class DeviceOnBus { PRESENT, NOT_PRESENT };
enum class Conversion { READY, NOT_READY };

QueueHandle_t onewireQueue;
static onewire_msg_t owm;

void DS18B20_Delay(uint16_t us);
DeviceOnBus DS18B20_Start(void);
void DS18B20_Write(uint8_t data);
uint8_t DS18B20_Read(void);
Conversion DS18B20_Conversion(void);

void onewireTaskStart(void* argument)
{
	UNUSED(argument);

	while (1)
	{
		DeviceOnBus presence = DS18B20_Start();
		if (presence == DeviceOnBus::PRESENT)
		{
			//printf("DS18B20-like device is detected.\n");
			DS18B20_Write(DS18B20_SKIP_ROM_CMD);
			DS18B20_Write(DS18B20_CONVERT_CMD);

			if (DS18B20_Conversion() == Conversion::READY)
			{
				printf("Conversion is finished.\n");

				presence = DS18B20_Start();
				if (presence == DeviceOnBus::PRESENT)
				{
					DS18B20_Write(DS18B20_SKIP_ROM_CMD);
					DS18B20_Write(DS18B20_READ_SCRPAD_CMD);

        			uint16_t temp_lo = DS18B20_Read();
        			uint16_t temp_hi = DS18B20_Read();

        			uint16_t Temp = (temp_hi << 8U) | temp_lo;
        			float T = (float)Temp / 16.0;
        			printf("T = %f\n", T);
				}
			}
		}

		//DS18B20_Write(0xFA);

		/*BaseType_t res = xQueueReceive(beeperQueue, &bm, portMAX_DELAY);
		if (res == pdPASS)
		{
			for (auto i = 0U; i < bm.beeper_repeat_cnt; i++)
			{
				HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, BUZZER_ON);
				vTaskDelay(bm.beeper_on_time);
				HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, BUZZER_OFF);
				vTaskDelay(bm.beeper_off_time);
			}
		}*/
		vTaskDelay(delay_ms);
	}
}

//------------------------------------------------------------------------------

Conversion DS18B20_Conversion(void)
{
	/*GPIO_InitTypeDef GPIO_InitStruct  = GPIO_InitTypeDef();

    GPIO_InitStruct.Pin = ONEWIRE_TEMP_BUS_Pin;

	vPortEnterCritical();

	GPIO_PinState pinState = DS18B20_LO_LEVEL;
	auto cnt = 15000U;															// Total awaiting time will be greater than max conversion time with 12bit resolution
	do
	{
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);			// set the pin as output
		HAL_GPIO_WritePin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin, DS18B20_LO_LEVEL);  // pull the data pin LOW

		DS18B20_Delay(DS18B20_1µs_DELAY);										// wait for 1 us

		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);			// set the pin as output

		DS18B20_Delay(DS18B20_15µs_DELAY);										// wait for 15 us

		pinState = HAL_GPIO_ReadPin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin);

		DS18B20_Delay(DS18B20_45µs_DELAY);										// wait for 45 us
	}
	while(--cnt && pinState ==  DS18B20_LO_LEVEL);

	vPortExitCritical();

	if (cnt) return Conversion::READY;
	else return Conversion::NOT_READY;*/

	vTaskDelay(pdMS_TO_TICKS(800U));

	return Conversion::READY;
}

//------------------------------------------------------------------------------

uint8_t DS18B20_Read(void)
{
	GPIO_InitTypeDef GPIO_InitStruct  = GPIO_InitTypeDef();
	uint8_t value=0;

    GPIO_InitStruct.Pin = ONEWIRE_TEMP_BUS_Pin;
    //GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    //HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);				// set the pin as input

	vPortEnterCritical();

	for (auto i = 0U; i < 8U; i++)
	{
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);			// set the pin as output
		HAL_GPIO_WritePin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin, DS18B20_LO_LEVEL);  // pull the data pin LOW

		DS18B20_Delay(DS18B20_1µs_DELAY);										// wait for 2 us

		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);			// set the pin as input

		DS18B20_Delay(DS18B20_15µs_DELAY);										// wait for 15us

		if (HAL_GPIO_ReadPin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin) == DS18B20_HI_LEVEL)  // if the pin is HIGH
			value |= 1U << i;  // read = 1

		DS18B20_Delay(DS18B20_45µs_DELAY);										// wait for 45 us
	}

	vPortExitCritical();

	return value;
}

//------------------------------------------------------------------------------

void DS18B20_Write(uint8_t data)
{
	GPIO_InitTypeDef GPIO_InitStruct  = GPIO_InitTypeDef();

    GPIO_InitStruct.Pin = ONEWIRE_TEMP_BUS_Pin;
    //GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	//GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    //HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);				// set the pin as output

	vPortEnterCritical();

	for (auto i = 0U; i < 8U; i++)
	{
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);			// set the pin as output
		HAL_GPIO_WritePin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin, DS18B20_LO_LEVEL);  // pull the pin LOW

		if ((data & (1U << i)) != 0U)											// if the bit is high
		{
			// write 1
			//GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
			//HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);		// set the pin as output
			//HAL_GPIO_WritePin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin, DS18B20_LO_LEVEL);  // pull the pin LOW
			DS18B20_Delay(DS18B20_1µs_DELAY);									// wait for 1 us

			GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
			HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);		// set the pin as input
			DS18B20_Delay(DS18B20_60µs_DELAY);									// wait for 60 us
		}
		else  // if the bit is low
		{
			// write 0
			//GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
			//HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);		// set the pin as output
			//HAL_GPIO_WritePin (ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin, DS18B20_LO_LEVEL);  // pull the pin LOW
			DS18B20_Delay(DS18B20_1µs_DELAY);									// wait for 1 us

			DS18B20_Delay(DS18B20_60µs_DELAY);									// wait for 60 us

			GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
			HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);		// set the pin as input
		}

		DS18B20_Delay(10);
	}

	vPortExitCritical();
}

//------------------------------------------------------------------------------

DeviceOnBus DS18B20_Start(void)
{
	DeviceOnBus Response = DeviceOnBus::NOT_PRESENT;
	GPIO_InitTypeDef GPIO_InitStruct  = GPIO_InitTypeDef();

	vPortEnterCritical();

    GPIO_InitStruct.Pin = ONEWIRE_TEMP_BUS_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);				// set the pin as output

	HAL_GPIO_WritePin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin, DS18B20_LO_LEVEL);  // pull the pin low
	DS18B20_Delay(DS18B20_480µs_DELAY);											// delay according to datasheet

    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    HAL_GPIO_Init(ONEWIRE_TEMP_BUS_GPIO_Port, &GPIO_InitStruct);				// set the pin as input

	DS18B20_Delay(DS18B20_80µs_DELAY);											// delay according to datasheet

	if (HAL_GPIO_ReadPin(ONEWIRE_TEMP_BUS_GPIO_Port, ONEWIRE_TEMP_BUS_Pin) == DS18B20_LO_LEVEL)
		Response = DeviceOnBus::PRESENT;										// if the pin is low i.e the presence pulse is detected

	DS18B20_Delay(DS18B20_400µs_DELAY);

	vPortExitCritical();

	return Response;
}

//------------------------------------------------------------------------------

void DS18B20_Delay(uint16_t us)
{
	__HAL_TIM_ENABLE(&htim6);
    __HAL_TIM_SET_COUNTER(&htim6, 0);
    while(__HAL_TIM_GET_COUNTER(&htim6) < us);
	__HAL_TIM_DISABLE(&htim6);

	/*for (auto i = 0U; i < us * 45; i++)
	{
		asm volatile ("nop"::);
	}*/
}