#ifndef LCDINT_H
#define LCDINT_H

#include <types.h>


/* Set this such that TMR0PRESCALE * TMR0FACTOR >= Fosc/MHz.
 * TMR0PRESCALE should be 1, 2, 4, 8, 16 or 32 and TMR0FACTOR should
 * be 1, 2, 3, 4 or 5.
 * NOTE on delays: if t_datasheet is a required waiting time on the
 * HD44780U datasheet, the program will wait
 * (TMR0PRESCALE * TMR0FACTOR * t_datasheet * 270kHz / (Fosc/4))
 * >= t_datasheet * 108% if the above inequality is satisfied. */
#define TMR0PRESCALE  16
#define TMR0FACTOR     3

/* Size of the LCD display.
 * LCD_WIDTH must be between 0 and 40, LCD_HEIGHT must be 1 or 2. */
#define LCD_WIDTH     16
#define LCD_HEIGHT     2


/* Initialize the LCD library and start up the display */
void lcd_init();


/* Status */
#if (!LCDINT_H_NO_EXTERN)
extern uint8_t lcd_status;
#endif
/* INITBIT is set if the LCD display has been properly initialized */
#define LCD_STATUS_INITBIT          0b10000000u  /* Display has been initialized */
/* If INITBIT is NOT set, status is one of the following: */
#define LCD_STATUS_NO_INIT          0b00000000u  /* Completely not initialized */
#define LCD_STATUS_INIT0            0b00000100u  /* Waiting for the first time */
#define LCD_STATUS_INIT1            0b00000101u  /* Waiting for the second time */
#define LCD_STATUS_INIT2            0b00000110u  /* Waiting for the third time */
/* IDLEBIT is set if the LCD library is idle, meaning that no further
 * commands should be sent to the LCD.  IDLEBIT can only be set if
 * INITBIT is also set. */
#define LCD_STATUS_IDLEBIT          0b01000000u  /* We are finished! */
/* If IDLEBIT is set, the  lower 6 bits of status count how long the LCD
 * has been idle, in 50ms increments.  For example, if status is
 * 0b10000101, the LCD has been idle for 250ms.  See also the
 * lcd_status_idle_ms() macro below. */
#define LCD_STATUS_IDLE_MIN         0b11000000u
#define LCD_STATUS_IDLE_MAX         0b11111111u
/* Clean bits, these only apply when INITBIT is set and IDLEBIT is NOT set.
 * They indicate which kind of commands still have to be sent.
 * 0 means that the particular command needs to be sent to LCD display,
 * 1 means it is updated.  If all CLEAN bits have been set, status
 * becomes 0b11000000 (idle). */
#define LCD_STATUS_CLEAN_FUNC       0b00100000
#define LCD_STATUS_CLEAN_DISPLAY    0b00010000
#define LCD_STATUS_CLEAN_ENTRY_MODE 0b00001000
#define LCD_STATUS_CLEAN_SHIFT      0b00000100  /* Currently unused, i.e. always set */
#define LCD_STATUS_CLEAN_CGRAM      0b00000010  /* CGRAM is okay */
#define LCD_STATUS_CLEAN_DDRAM      0b00000001  /* DDRAM is okay */
#define LCD_STATUS_ALL_CLEAN        0b10111111

/* Backlight control */
#define lcd_backlight_off()               LATC &= 0b11111110;
#define lcd_backlight_on()                LATC |= 0b00000001;

/* Enable/disable interrupts.  Note that the user program should
 * provide the interrupt handler. */
#define lcd_enable_low_pri_interrupt()    INTCON |=  0b00100000; INTCON2 &= 0b11111011;
#define lcd_enable_high_pri_interrupt()   INTCON |=  0b00100000; INTCON2 |= 0b00000100;
#define lcd_disable_interrupt()           INTCON &=  0b11011111;
/* Check whether the interrupt has occured.
 * Return a non-zero value if lcd_handle() can (and should) be called. */
#define lcd_poll()                        (INTCON & 0b00000100)
/* Handle the interrupt.  Call this ONLY if lcd_poll() is non-zero. */
void lcd_handle();

#define lcd_is_initialized()      (lcd_status & LCD_STATUS_INITBIT)
#define lcd_is_idle()             (lcd_status & LCD_STATUS_IDLEBIT)
/* Has the LCD library been idle since at least t ms?  This has a
 * precision of 50ms. */
#define lcd_is_idle_since_ms(t)   (lcd_status >= (uint8_t)(LCD_STATUS_IDLE_MIN + ((unsigned long)(t) > 3100ul ? 63 : ((unsigned long)(t)+49ul)/50ul)))


/* Virtual LCD screen */
#if (!LCDINT_H_NO_EXTERN)
extern char lcd_ddram[LCD_WIDTH * LCD_HEIGHT];  /* Display RAM how it should be, special value of 9 means updated */
extern uint8_t lcd_cgram[64];                   /* Chargen RAM how it should be, special value of 255 means updated */
extern int8_t lcd_x, lcd_y;                     /* User write position in DDRAM */
#endif
#define ddram_clean ((char)9)
#define cgram_clean ((uint8_t)255)

/* Call this after updating the virtual LCD screen "by hand" */
void lcd_update_cgram();
void lcd_update_ddram();

/* Write a string to the LCD.
 * The following special characters are supported:
 * \0: null terminator, end of string
 * \1 -> \7: chargen characters 1->7
 * \10: move cursor left
 * \11: move cursor right
 * \12: change line
 * \13: move cursor to top left position
 * \14: clear display and move cursor to top left position
 * \15: move cursor to the left position of current line
 * \16: reserved for future use
 * \17: chargen character 0
 * All other characters are written literally
 */
void lcd_write(const char* s);

/* As lcd_write, but write only 1 character and escape characters
 * are not supported, except \0 -> \7 standing for chargen characters
 * 0 -> 7. */
void lcd_putc(char c);

/* Set cursor position */
#define lcd_set_pos(x, y)   {lcd_x = (x); lcd_y = (y);}


/* Set display configuration:
 * Bit 0: blinking cursor; bit 1: line cursor; bit 2: display on */
void lcd_config_display(uint8_t cfg);



/* A block in which no interrupts can occur */
#define BEGIN_LOCK     saved_INTCON = INTCON; INTCON &= 0b01111111;
#define END_LOCK       if (saved_INTCON & 0b10000000) INTCON |= 0b10000000;

#endif

