#include "display.h"
#include "main.h"
#include <stdbool.h>

// Demo includes
#include "logger.h"
#include "dwt.h"
#include "systick.h"

// Functional states
#ifndef OFF
#define OFF    0
#endif
#ifndef ON
#define ON     ( !OFF )
#endif

// Electrical states
#ifndef LOW
#define LOW    0
#endif
#ifndef HIGH
#define HIGH   ( !LOW )
#endif

#define DISPLAY_IR_CLEAR_DISPLAY   0b00000001
#define DISPLAY_IR_ENTRY_MODE_SET  0b00000100
#define DISPLAY_IR_DISPLAY_CONTROL 0b00001000
#define DISPLAY_IR_FUNCTION_SET    0b00100000
#define DISPLAY_IR_SET_DDRAM_ADDR  0b10000000

#define DISPLAY_IR_ENTRY_MODE_SET_INCREMENT 0b00000010
#define DISPLAY_IR_ENTRY_MODE_SET_DECREMENT 0b00000000
#define DISPLAY_IR_ENTRY_MODE_SET_SHIFT     0b00000001
#define DISPLAY_IR_ENTRY_MODE_SET_NO_SHIFT  0b00000000

#define DISPLAY_IR_DISPLAY_CONTROL_DISPLAY_ON  0b00000100
#define DISPLAY_IR_DISPLAY_CONTROL_DISPLAY_OFF 0b00000000
#define DISPLAY_IR_DISPLAY_CONTROL_CURSOR_ON   0b00000010
#define DISPLAY_IR_DISPLAY_CONTROL_CURSOR_OFF  0b00000000
#define DISPLAY_IR_DISPLAY_CONTROL_BLINK_ON    0b00000001
#define DISPLAY_IR_DISPLAY_CONTROL_BLINK_OFF   0b00000000

#define DISPLAY_IR_FUNCTION_SET_8BITS    0b00010000
#define DISPLAY_IR_FUNCTION_SET_4BITS    0b00000000
#define DISPLAY_IR_FUNCTION_SET_2LINES   0b00001000
#define DISPLAY_IR_FUNCTION_SET_1LINE    0b00000000
#define DISPLAY_IR_FUNCTION_SET_5x10DOTS 0b00000100
#define DISPLAY_IR_FUNCTION_SET_5x8DOTS  0b00000000

#define DISPLAY_20x4_LINE1_FIRST_CHARACTER_ADDRESS 0
#define DISPLAY_20x4_LINE2_FIRST_CHARACTER_ADDRESS 64
#define DISPLAY_20x4_LINE3_FIRST_CHARACTER_ADDRESS 20
#define DISPLAY_20x4_LINE4_FIRST_CHARACTER_ADDRESS 84

#define DISPLAY_RS_INSTRUCTION 0
#define DISPLAY_RS_DATA        1

#define DISPLAY_RW_WRITE 0
#define DISPLAY_RW_READ  1

#define DISPLAY_PIN_RS 8
#define DISPLAY_PIN_EN 9
#define DISPLAY_PIN_D4 4
#define DISPLAY_PIN_D5 5
#define DISPLAY_PIN_D6 6
#define DISPLAY_PIN_D7 7

#define DISPLAY_DEL_37US 37ul
#define DISPLAY_DEL_01US 01ul

// declaration of private functions
static void display_pin_write(uint8_t pin_name, int value);
static void display_data_bus_write(uint8_t data_byte);
static void display_code_write(bool type, uint8_t data_bus);

// public functions

void display_init(void)
{
    display_code_write(DISPLAY_RS_INSTRUCTION,
            DISPLAY_IR_FUNCTION_SET |
            DISPLAY_IR_FUNCTION_SET_4BITS |
            DISPLAY_IR_FUNCTION_SET_2LINES |
            DISPLAY_IR_FUNCTION_SET_5x8DOTS);

    display_code_write(DISPLAY_RS_INSTRUCTION,
            DISPLAY_IR_CLEAR_DISPLAY);

    display_code_write(DISPLAY_RS_INSTRUCTION,
            DISPLAY_IR_ENTRY_MODE_SET |
            DISPLAY_IR_ENTRY_MODE_SET_INCREMENT |
            DISPLAY_IR_ENTRY_MODE_SET_NO_SHIFT);

    display_code_write(DISPLAY_RS_INSTRUCTION,
            DISPLAY_IR_DISPLAY_CONTROL |
            DISPLAY_IR_DISPLAY_CONTROL_DISPLAY_ON |
            DISPLAY_IR_DISPLAY_CONTROL_CURSOR_ON |
            DISPLAY_IR_DISPLAY_CONTROL_BLINK_ON);

}

void display_char_position_write(uint8_t x, uint8_t y)
{
    switch(x) 
    {
        case 0:
            display_code_write(DISPLAY_RS_INSTRUCTION,
                    DISPLAY_IR_SET_DDRAM_ADDR |
                    (DISPLAY_20x4_LINE1_FIRST_CHARACTER_ADDRESS + x));
        break;

        case 1:
        display_code_write(DISPLAY_RS_INSTRUCTION,
                DISPLAY_IR_SET_DDRAM_ADDR |
                (DISPLAY_20x4_LINE2_FIRST_CHARACTER_ADDRESS + x));
        break;

        case 2:
        display_code_write(DISPLAY_RS_INSTRUCTION,
                DISPLAY_IR_SET_DDRAM_ADDR |
                (DISPLAY_20x4_LINE3_FIRST_CHARACTER_ADDRESS + x));
        break;

        case 3:
        display_code_write(DISPLAY_RS_INSTRUCTION,
                DISPLAY_IR_SET_DDRAM_ADDR |
                (DISPLAY_20x4_LINE4_FIRST_CHARACTER_ADDRESS + x));
        break;
        default: break;
    }
}

void display_string_write(const char * str)
{
    while (*str) {
        display_code_write(DISPLAY_RS_DATA, *str++);
    }
}

void display_cursor_off(void)
{
    display_code_write(DISPLAY_RS_INSTRUCTION,
            DISPLAY_IR_DISPLAY_CONTROL |
            DISPLAY_IR_DISPLAY_CONTROL_DISPLAY_ON |
            DISPLAY_IR_DISPLAY_CONTROL_CURSOR_OFF |
            DISPLAY_IR_DISPLAY_CONTROL_BLINK_OFF);
}

void display_cursor_on(void)
{
    display_code_write(DISPLAY_RS_INSTRUCTION,
            DISPLAY_IR_DISPLAY_CONTROL |
            DISPLAY_IR_DISPLAY_CONTROL_DISPLAY_ON |
            DISPLAY_IR_DISPLAY_CONTROL_CURSOR_ON |
            DISPLAY_IR_DISPLAY_CONTROL_BLINK_ON);
}

// private functions

static void display_code_write( bool type, uint8_t data_bus )
{
    if (type == DISPLAY_RS_INSTRUCTION) {
        display_pin_write( DISPLAY_PIN_RS, DISPLAY_RS_INSTRUCTION );
    }
    else {
        display_pin_write(DISPLAY_PIN_RS, DISPLAY_RS_DATA);
    }

    display_data_bus_write(data_bus);
}

static void display_pin_write(uint8_t pin_name, int value)
{
    switch(pin_name)
    {
        case DISPLAY_PIN_D4:
            HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, value);
        break;
        case DISPLAY_PIN_D5:
            HAL_GPIO_WritePin(D5_GPIO_Port, D5_Pin, value);
        break;
        case DISPLAY_PIN_D6:
            HAL_GPIO_WritePin(D6_GPIO_Port, D6_Pin, value);
        break;
        case DISPLAY_PIN_D7:
            HAL_GPIO_WritePin(D7_GPIO_Port, D7_Pin, value);
        break;
        case DISPLAY_PIN_RS:
            HAL_GPIO_WritePin(D8_GPIO_Port, D8_Pin, value);
        break;
        case DISPLAY_PIN_EN:
            HAL_GPIO_WritePin(D9_GPIO_Port, D9_Pin, value);
        break;
        default: break;
    }
}

static void display_data_bus_write( uint8_t data_bus )
{
    display_pin_write( DISPLAY_PIN_EN, OFF );
    display_pin_write( DISPLAY_PIN_D7, data_bus & 0b10000000 );
    display_pin_write( DISPLAY_PIN_D6, data_bus & 0b01000000 );
    display_pin_write( DISPLAY_PIN_D5, data_bus & 0b00100000 );
    display_pin_write( DISPLAY_PIN_D4, data_bus & 0b00010000 );

	display_pin_write( DISPLAY_PIN_EN, ON );
	display_pin_write( DISPLAY_PIN_EN, OFF );
	display_pin_write( DISPLAY_PIN_D7, data_bus & 0b00001000 );
	display_pin_write( DISPLAY_PIN_D6, data_bus & 0b00000100 );
	display_pin_write( DISPLAY_PIN_D5, data_bus & 0b00000010 );
	display_pin_write( DISPLAY_PIN_D4, data_bus & 0b00000001 );

    display_pin_write( DISPLAY_PIN_EN, ON );
    display_pin_write( DISPLAY_PIN_EN, OFF );
    HAL_Delay(1);
}
