/** \file server/drivers/lcdlinux.c
 *  LCDd \c LCD-Linux driver.
 *
 *  Visit http://lcd-linux.sourceforge.net/ for more information.
 *
 * Copyright (C) 2009  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#define LCD_LINUX_MAIN
#include <linux/lcd-linux.h>
#include <sys/ioctl.h>

#include "lcd.h"
#include "lcd_lib.h"
#include "lcdlinux.h"
#include "report.h"
#include "adv_bignum.h"

static int lcdlinux_fd;

/* Vars for the server core */
MODULE_EXPORT char *api_version = API_VERSION;
MODULE_EXPORT int stay_in_foreground = 0;
MODULE_EXPORT int supports_multiple = 0;
MODULE_EXPORT char *symbol_prefix = "lcdlinux_";


/**
 * Initialize the driver.
 * \param drvthis  Pointer to driver structure.
 * \retval 0       Success.
 * \retval <0      Error.
 */
MODULE_EXPORT int
lcdlinux_init (Driver *drvthis)
{
	PrivateData *p;
	const char *s;

	/* Allocate and store private data */
	p = (PrivateData *) calloc(1, sizeof(PrivateData));
	if (p == NULL)
		return -1;
	if (drvthis->store_private_ptr(drvthis, p))
		return -1;

	s = drvthis->config_get_string(drvthis->name, "Device", 0, "/dev/lcd");
	report(RPT_INFO,"%s: using device: %s", drvthis->name, s);
	lcdlinux_fd = open(s, O_WRONLY);
	if (lcdlinux_fd == -1) {
		report(RPT_ERR, "%s: open() failed", drvthis->name);
		return -1;
	}

	ioctl(lcdlinux_fd, LCDL_RAW_MODE, 1);

	/* initialize private data */
	if (ioctl(lcdlinux_fd, LCDL_GET_PARAM, &p->par) != 0) {
		report(RPT_ERR, "%s: unable to get LCD parameters", drvthis->name);
		return -1;
	}
	p->par.vs_rows = 0;
	p->par.vs_cols = 0;
	if (ioctl(lcdlinux_fd, LCDL_SET_PARAM, &p->par) != 0) {
		report(RPT_ERR, "%s: unable to set LCD parameters", drvthis->name);
		return -1;
	}
	
	p->width = p->par.cntr_cols;
	p->height = p->par.cntr_rows*p->par.num_cntr;
	p->cellwidth = LCD_DEFAULT_CELLWIDTH;
	p->cellheight = LCD_DEFAULT_CELLHEIGHT;

	report(RPT_DEBUG, "%s: init() done", drvthis->name);

	return 1;
}


/**
 * Close the driver (do necessary clean-up).
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
lcdlinux_close (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	if (p != NULL)
		free(p);
	drvthis->store_private_ptr(drvthis, NULL);
	ioctl(lcdlinux_fd, LCDL_RAW_MODE, 0);
	close(lcdlinux_fd);
}


/**
 * Return the display width in characters.
 * \param drvthis  Pointer to driver structure.
 * \return         Number of characters the display is wide.
 */
MODULE_EXPORT int
lcdlinux_width (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	return p->width;
}


/**
 * Return the display height in characters.
 * \param drvthis  Pointer to driver structure.
 * \return         Number of characters the display is high.
 */
MODULE_EXPORT int
lcdlinux_height (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	return p->height;
}


/**
 * Return the width of a character in pixels.
 * \param drvthis  Pointer to driver structure.
 * \return  Number of pixel columns a character cell is wide.
 */
MODULE_EXPORT int
lcdlinux_cellwidth(Driver *drvthis)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;

	return p->cellwidth;
}


/**
 * Return the height of a character in pixels.
 * \param drvthis  Pointer to driver structure.
 * \return  Number of pixel lines a character cell is high.
 */
MODULE_EXPORT int
lcdlinux_cellheight(Driver *drvthis)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;

	return p->cellheight;
}


/**
 * Clear the screen.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
lcdlinux_clear (Driver *drvthis)
{
	ioctl(lcdlinux_fd, LCDL_CLEAR_DISP);
}


/**
 * Flush data on screen to the display.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
lcdlinux_flush (Driver *drvthis)
{

}


/**
 * Print a string on the screen at position (x,y).
 * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column).
 * \param y        Vertical character position (row).
 * \param string   String that gets written.
 */
MODULE_EXPORT void
lcdlinux_string (Driver *drvthis, int x, int y, const char string[])
{
	int pos = ((--y)*lcdlinux_width(drvthis))+(--x);
	
	if ((x < 0) || (x >= lcdlinux_width(drvthis)))
                return;
	if ((y < 0) || (y >= lcdlinux_height(drvthis)))
                return;

	if (lseek(lcdlinux_fd, pos, SEEK_SET) == (off_t) - 1) {
		report(RPT_ERR, "%s: lseek() failed", drvthis->name);
		return;
	}

	write(lcdlinux_fd, string, strlen(string));
}


/**
 * Print a character on the screen at position (x,y).
 * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column).
 * \param y        Vertical character position (row).
 * \param c        Character that gets written.
 */
MODULE_EXPORT void
lcdlinux_chr (Driver *drvthis, int x, int y, char c)
{
	int pos = ((--y)*lcdlinux_width(drvthis))+(--x);
	
	if ((x < 0) || (x >= lcdlinux_width(drvthis)))
                return;
	if ((y < 0) || (y >= lcdlinux_height(drvthis)))
                return;

	if (lseek(lcdlinux_fd, pos, SEEK_SET) == (off_t) - 1) {
		report(RPT_ERR, "%s: lseek() failed", drvthis->name);
		return;
	}

	write(lcdlinux_fd, &c, 1);
}


/**
 * Get total number of custom characters available.
 * \param drvthis  Pointer to driver structure.
 * \return  Number of custom characters (always NUM_CCs).
 */
MODULE_EXPORT int
lcdlinux_get_free_chars(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	return p->par.cgram_chars;
}


/**
 * Define a custom character and write it to the LCD.
 * \param drvthis  Pointer to driver structure.
 * \param n        Custom character to define [0 - (NUM_CCs-1)].
 * \param dat      Array of 8(=cellheight) bytes, each representing a pixel row
 *                 starting from the top to bottom.
 *                 The bits in each byte represent the pixels where the LSB
 *                 (least significant bit) is the rightmost pixel in each pixel row.
 */
MODULE_EXPORT void
lcdlinux_set_char(Driver *drvthis, int n, unsigned char *dat)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;
	unsigned char buf[p->cellheight+1];

	if ((n < 0) || (n >= p->par.cgram_chars))
		return;
	if (! dat)
		return;

	buf[0] = n;
	memcpy(buf+1, dat, p->cellheight);
	ioctl(lcdlinux_fd, LCDL_SET_CGRAM_CHAR, buf);
}


/**
 * Draw a vertical bar bottom-up.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column) of the starting point.
 * \param y        Vertical character position (row) of the starting point.
 * \param len      Number of characters that the bar is high at 100%
 * \param promille Current height level of the bar in promille.
 * \param options  Options (currently unused).
 */
MODULE_EXPORT void
lcdlinux_vbar(Driver *drvthis, int x, int y, int len, int promille, int options)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;

	/* x and y are the start position of the bar.
	 * The bar by default grows in the 'up' direction
	 * (other direction not yet implemented).
	 * len is the number of characters that the bar is long at 100%
	 * promille is the number of promilles (0..1000) that the bar should be filled.
	 */

	unsigned char vBar[p->cellheight];
	int i;

	memset(vBar, 0, sizeof(vBar));

	for (i = 1; i < p->cellheight; i++) {
		vBar[p->cellheight - i] = 0xFF;
		lcdlinux_set_char(drvthis, i, vBar);
	}

	lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, 0);
}


/**
 * Draw a horizontal bar to the right.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column) of the starting point.
 * \param y        Vertical character position (row) of the starting point.
 * \param len      Number of characters that the bar is long at 100%
 * \param promille Current length level of the bar in promille.
 * \param options  Options (currently unused).
 */
MODULE_EXPORT void
lcdlinux_hbar(Driver *drvthis, int x, int y, int len, int promille, int options)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;

	/* x and y are the start position of the bar.
	 * The bar by default grows in the 'right' direction
	 * (other direction not yet implemented).
	 * len is the number of characters that the bar is long at 100%
	 * promille is the number of promilles (0..1000) that the bar should be filled.
	 */

	unsigned char hBar[p->cellheight];
	int i;

	for (i = 1; i <= p->cellwidth; i++) {
		memset(hBar, 0xFF & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar));
		lcdlinux_set_char(drvthis, i, hBar);
	}

	lib_hbar_static(drvthis, x, y, len, promille, options, p->cellwidth, 0);
}


/**
 * Place an icon on the screen.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column).
 * \param y        Vertical character position (row).
 * \param icon     synbolic value representing the icon.
 * \retval 0       Icon has been successfully defined/written.
 * \retval <0      Server core shall define/write the icon.
 */
MODULE_EXPORT int
lcdlinux_icon(Driver *drvthis, int x, int y, int icon)
{
	static unsigned char heart_open[] = 
		{ b__XXXXX,
		  b__X_X_X,
		  b_______,
		  b_______,
		  b_______,
		  b__X___X,
		  b__XX_XX,
		  b__XXXXX };
	static unsigned char heart_filled[] = 
		{ b__XXXXX,
		  b__X_X_X,
		  b___X_X_,
		  b___XXX_,
		  b___XXX_,
		  b__X_X_X,
		  b__XX_XX,
		  b__XXXXX };
	static unsigned char arrow_up[] = 
		{ b____X__,
		  b___XXX_,
		  b__X_X_X,
		  b____X__,
		  b____X__,
		  b____X__,
		  b____X__,
		  b_______ };
	static unsigned char arrow_down[] = 
		{ b____X__,
		  b____X__,
		  b____X__,
		  b____X__,
		  b__X_X_X,
		  b___XXX_,
		  b____X__,
		  b_______ };
	/*
	static unsigned char arrow_left[] = 
		{ b_______,
		  b____X__,
		  b___X___,
		  b__XXXXX,
		  b___X___,
		  b____X__,
		  b_______,
		  b_______ };
	static unsigned char arrow_right[] = 
		{ b_______,
		  b____X__,
		  b_____X_,
		  b__XXXXX,
		  b_____X_,
		  b____X__,
		  b_______,
		  b_______ };
	*/
	static unsigned char checkbox_off[] = 
		{ b_______,
		  b_______,
		  b__XXXXX,
		  b__X___X,
		  b__X___X,
		  b__X___X,
		  b__XXXXX,
		  b_______ };
	static unsigned char checkbox_on[] = 
		{ b____X__,
		  b____X__,
		  b__XXX_X,
		  b__X_XX_,
		  b__X_X_X,
		  b__X___X,
		  b__XXXXX,
		  b_______ };
	static unsigned char checkbox_gray[] = 
		{ b_______,
		  b_______,
		  b__XXXXX,
		  b__X_X_X,
		  b__XX_XX,
		  b__X_X_X,
		  b__XXXXX,
		  b_______ };
	/*
	static unsigned char selector_left[] = 
		{ b___X___,
		  b___XX__,
		  b___XXX_,
		  b___XXXX,
		  b___XXX_,
		  b___XX__,
		  b___X___,
		  b_______ };
	static unsigned char selector_right[] = 
		{ b_____X_,
		  b____XX_,
		  b___XXX_,
		  b__XXXX_,
		  b___XXX_,
		  b____XX_,
		  b_____X_,
		  b_______ };
	static unsigned char ellipsis[] = 
		{ b_______,
		  b_______,
		  b_______,
		  b_______,
		  b_______,
		  b_______,
		  b__X_X_X,
		  b_______ };
	*/	  
	static unsigned char block_filled[] = 
		{ b__XXXXX,
		  b__XXXXX,
		  b__XXXXX,
		  b__XXXXX,
		  b__XXXXX,
		  b__XXXXX,
		  b__XXXXX,
		  b__XXXXX };

	/* Yes I know, this is a VERY BAD implementation */
	switch (icon) {
		case ICON_BLOCK_FILLED:
			lcdlinux_set_char(drvthis, 6, block_filled);
			lcdlinux_chr(drvthis, x, y, 6);
			break;
		case ICON_HEART_FILLED:
			lcdlinux_set_char(drvthis, 0, heart_filled);
			lcdlinux_chr(drvthis, x, y, 0);
			break;
		case ICON_HEART_OPEN:
			lcdlinux_set_char(drvthis, 0, heart_open);
			lcdlinux_chr(drvthis, x, y, 0);
			break;
		case ICON_ARROW_UP:
			lcdlinux_set_char(drvthis, 1, arrow_up);
			lcdlinux_chr(drvthis, x, y, 1);
			break;
		case ICON_ARROW_DOWN:
			lcdlinux_set_char(drvthis, 2, arrow_down);
			lcdlinux_chr(drvthis, x, y, 2);
			break;
		case ICON_ARROW_LEFT:
			lcdlinux_chr(drvthis, x, y, 0x7F);
			break;
		case ICON_ARROW_RIGHT:
			lcdlinux_chr(drvthis, x, y, 0x7E);
			break;
		case ICON_CHECKBOX_OFF:
			lcdlinux_set_char(drvthis, 3, checkbox_off);
			lcdlinux_chr(drvthis, x, y, 3);
			break;
		case ICON_CHECKBOX_ON:
			lcdlinux_set_char(drvthis, 4, checkbox_on);
			lcdlinux_chr(drvthis, x, y, 4);
			break;
		case ICON_CHECKBOX_GRAY:
			lcdlinux_set_char(drvthis, 5, checkbox_gray);
			lcdlinux_chr(drvthis, x, y, 5);
			break;
		default:
			return -1; /* Let the core do other icons */
	}
	return 0;
}


/**
 * Change the display contrast.
 * Dumb text terminals do not support this, so we ignore it.
 * \param drvthis  Pointer to driver structure.
 * \param promille New contrast value in promille.
 */
MODULE_EXPORT void
lcdlinux_set_contrast (Driver *drvthis, int promille)
{
	//PrivateData *p = drvthis->private_data;

	debug(RPT_DEBUG, "Contrast: %d", promille);
}


/**
 * Turn the display backlight on or off.
 * Dumb text terminals do not support this, so we ignore it.
 * \param drvthis  Pointer to driver structure.
 * \param on       New backlight status.
 */
MODULE_EXPORT void
lcdlinux_backlight (Driver *drvthis, int on)
{
	//PrivateData *p = drvthis->private_data;

	debug(RPT_DEBUG, "Backlight %s", (on) ? "ON" : "OFF");
}


/**
 * Provide some information about this driver.
 * \param drvthis  Pointer to driver structure.
 * \return         Constant string with information.
 */
MODULE_EXPORT const char *
lcdlinux_get_info (Driver *drvthis)
{
	//PrivateData *p = drvthis->private_data;
        static char *info_string = "LCD-Linux driver";

	return info_string;
}