#include "defines.h"
#include <stdbool.h>
#include <stdint.h>
#include <avr/io.h>
#include <util/delay.h>
#include "hd44780.h"
#define GLUE(a, b)     a##b
#define SET_(what, p, m) GLUE(what, p) |= (1 << (m))
#define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m))
#define GET_( p, m) GLUE(PIN, p) & (1 << (m))
#define SET(what, x) SET_(what, x)
#define CLR(what, x) CLR_(what, x)
#define GET( x) GET_(x)
#define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \
                        ~((1 << (m)) | (1 << ((m) + 1)) | \
                          (1 << ((m) + 2)) | (1 << ((m) + 3)))) | \
                            ((v) << (m))
#define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \
                        (1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m)
#define ASSIGN(what, x, v) ASSIGN_(what, x, v)
#define READ(what, x) READ_(what, x)
#define HD44780_BUSYFLAG 0x80
static inline uint8_t
hd44780_pulse_e(bool readback) __attribute__((always_inline));
static inline uint8_t
hd44780_pulse_e(bool readback)
{
  uint8_t x;
  SET(PORT, HD44780_E);
  
#if F_CPU > 4000000UL
  _delay_us(0.5);
#else
  
  if (readback)
    __asm__ volatile("nop");
#  if F_CPU > 1000000UL
  __asm__ volatile("nop");
#    if F_CPU > 2000000UL
  __asm__ volatile("nop");
  __asm__ volatile("nop");
#    endif 
#  endif 
#endif
  if (readback)
    x = READ(PIN, HD44780_D4);
  else
    x = 0;
  CLR(PORT, HD44780_E);
  return x;
}
static void
hd44780_outnibble(uint8_t n, uint8_t rs)
{
  CLR(PORT, HD44780_RW);
  if (rs)
    SET(PORT, HD44780_RS);
  else
    CLR(PORT, HD44780_RS);
  ASSIGN(PORT, HD44780_D4, n);
  (void)hd44780_pulse_e(false);
}
void
hd44780_outbyte(uint8_t b, uint8_t rs)
{
  hd44780_outnibble(b >> 4, rs);
  hd44780_outnibble(b & 0xf, rs);
}
static uint8_t
hd44780_innibble(uint8_t rs)
{
  uint8_t x;
  SET(PORT, HD44780_RW);
  ASSIGN(DDR, HD44780_D4, 0x00);
  if (rs)
    SET(PORT, HD44780_RS);
  else
    CLR(PORT, HD44780_RS);
  x = hd44780_pulse_e(true);
  ASSIGN(DDR, HD44780_D4, 0x0F);
  CLR(PORT, HD44780_RW);
  return x;
}
uint8_t
hd44780_inbyte(uint8_t rs)
{
  uint8_t x;
  x = hd44780_innibble(rs) << 4;
  x |= hd44780_innibble(rs);
  return x;
}
void
hd44780_wait_ready(bool longwait)
{
#if USE_BUSY_BIT
  while (hd44780_incmd() & HD44780_BUSYFLAG) ;
#else
  if (longwait)
    _delay_ms(1.52);
  else
    _delay_us(37);
#endif
}
void
hd44780_init(void)
{
  SET(DDR, HD44780_RS);
  SET(DDR, HD44780_RW);
  SET(DDR, HD44780_E);
  ASSIGN(DDR, HD44780_D4, 0x0F);
  _delay_ms(15);        
  hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
  _delay_ms(4.1);
  hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
  _delay_ms(0.1);
  hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
  _delay_us(37);
  hd44780_outnibble(HD44780_FNSET(0, 1, 0) >> 4, 0);
  hd44780_wait_ready(false);
  hd44780_outcmd(HD44780_FNSET(0, 1, 0));
  hd44780_wait_ready(false);
  hd44780_outcmd(HD44780_DISPCTL(0, 0, 0));
  hd44780_wait_ready(false);
}
void
hd44780_powerdown(void)
{
  ASSIGN(PORT, HD44780_D4, 0);
  CLR(PORT, HD44780_RS);
  CLR(PORT, HD44780_RW);
  CLR(PORT, HD44780_E);
}