/**
 * Driver for Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON IR/VFD Module
 *
 * In order to be able to use it, you have to get and install one of
 * the following kernel modules:
 *  - standalone iMON VFD driver from http://venky.ws/projects/imon/
 *  - the iMON module included with LIRC ver. 0.7.1 or newer
 *    from http://www.lirc.org/
 *
 * Copyright (c) 2004, Venky Raju <dev@venky.ws>, original author of
 * the LCDproc 0.4.5 iMON driver, the standalone and the LIRC kernel
 * modules for the iMON IR/VFD at http://venky.ws/projects/imon/
 * Inspired by:
 * 	TextMode driver (LCDproc authors?)
 *	Sasem driver	(Oliver Stabel)
 *
 * Copyright (c)  2005 Lucian Muresan <lucianm AT users.sourceforge.net>,
 *                     porting the LCDproc 0.4.5 code to LCDproc 0.5
 * Copyright (c)  2006 John Saunders, use graphics characters
 *
 * This source code is being released under the GPL.
 * Please see the file COPYING in this package for details.
 *
 */

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


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

#include "lcd.h"
#include "lcd_lib.h"
#include "shared/debug.h"
//#define DEBUG
#include "report.h"


#include "imon.h"

// iMon reserves the first 8 locations for the
// special bargraph characters
#define IMON_CHAR_1_BAR		0x00
#define IMON_CHAR_2_BARS	0x01
#define IMON_CHAR_3_BARS	0x02
#define IMON_CHAR_4_BARS	0x03
#define IMON_CHAR_5_BARS	0x04
#define IMON_CHAR_6_BARS	0x05
#define IMON_CHAR_7_BARS	0x06
#define IMON_CHAR_8_BARS	0x07

// Standard music control characters
#define IMON_CHAR_PLAY		0x10	// >  Play
#define IMON_CHAR_RPLAY		0x11	// <  Reverse Play
#define IMON_CHAR_PAUSE		0xA0	// || Pause
#define IMON_CHAR_RECORD	0x16	// O  Record

#define IMON_CHAR_TRI_UP	0x1E	// ^
#define IMON_CHAR_TRI_DOWN	0x1F	// V
#define IMON_CHAR_DLB_TRI_UP	0x14
#define IMON_CHAR_DBL_TRI_DOWN	0x15

#define IMON_CHAR_ARROW_UP	0x18
#define IMON_CHAR_ARROW_DOWN	0x19
#define IMON_CHAR_ARROW_RIGHT	0x1A
#define IMON_CHAR_ARROW_LEFT	0x1B
#define IMON_CHAR_ENTER		0x17

#define IMON_CHAR_HOUSE		0x7F

#define IMON_CHAR_HEART		0x9D

#define IMON_CHAR_BLOCK_FILLED	IMON_CHAR_8_BARS
#define IMON_CHAR_BLOCK_EMPTY	' '

#define DEFAULT_DEVICE	"/dev/usb/lcd"
#define DEFAULT_SIZE	"16x2"

#define VFD_DEFAULT_CELL_WIDTH	5
#define VFD_DEFAULT_CELL_HEIGHT	8


// 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 = "imon_";

// our private data
typedef struct {
	char info[255];
	int imon_fd;
	unsigned char *framebuf;
	int height;
	int width;
	int cellwidth;
	int cellheight;
} PrivateData;


/**
 * driver initialization
 */
/**
 * Initialize the driver.
 * \param drvthis  Pointer to driver structure.
 * \return  Information of success (1) or failure (< 0).
 */
MODULE_EXPORT int imon_init (Driver *drvthis)
{
	PrivateData *p = NULL;

	// Alocate, initialize and store private p
	p = (PrivateData *) calloc(1, sizeof(PrivateData));
	if (p == NULL) {
		debug(RPT_ERR, "%s: failed to allocate private data", drvthis->name);
		return -1;
	}

	if (drvthis->store_private_ptr(drvthis, p)) {
		debug(RPT_ERR, "%s: failed to store private data pointer", drvthis->name);
		return -1;
	}

	char buf[256];
	p->imon_fd = -1;
	p->width = 0;
	p->height = 0;
	p->cellwidth = VFD_DEFAULT_CELL_WIDTH;
	p->cellheight = VFD_DEFAULT_CELL_HEIGHT;


	/* Get settings from config file*/

	/* Get device */
	strncpy(buf, drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE), sizeof(buf));
	buf[sizeof(buf)-1] = '\0';
	report(RPT_INFO, "%s: using Device %s", drvthis->name, buf);

	/* Open device for writing */
	if ((p->imon_fd = open(buf, O_WRONLY)) < 0) {
		report(RPT_ERR, "%s: ERROR opening %s (%s).", drvthis->name, buf, strerror(errno));
		report(RPT_ERR, "%s: Did you load the iMON VFD kernel module?", drvthis->name);
		report(RPT_ERR, "%s: More info in lcdproc/docs/README.imon", drvthis->name);
		return -1;
	}

	/* Get size settings*/
	strncpy(buf, drvthis->config_get_string(drvthis->name, "Size", 0, DEFAULT_SIZE), sizeof(buf));
	buf[sizeof(buf)-1] = '\0';
	if ((sscanf(buf , "%dx%d", &p->width, &p->height) != 2)
	    || (p->width <= 0) || (p->width > LCD_MAX_WIDTH)
	    || (p->height <= 0) || (p->height > LCD_MAX_HEIGHT)) {
		report(RPT_WARNING, "%s: cannot read Size: %s; using default %s",
				drvthis->name, buf, DEFAULT_SIZE);
		sscanf(DEFAULT_SIZE , "%dx%d", &p->width, &p->height);
	}

	/* Make sure the frame buffer is there... */
	p->framebuf = (unsigned char *) malloc(p->width * p->height);
	if (p->framebuf == NULL) {
		report(RPT_ERR, "%s: unable to allocate framebuffer", drvthis->name);
		return -1;
	}
	memset(p->framebuf, ' ', p->width * p->height);

	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 imon_close (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	if (p != NULL) {
		if (p->imon_fd >= 0)
			close(p->imon_fd);

		if (p->framebuf != NULL)
			free(p->framebuf);
		p->framebuf = NULL;

		free(p);
	}
	drvthis->store_private_ptr(drvthis, NULL);
}


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

	strcpy(p->info, "Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON IR/VFD driver");
	return p->info;
}


/**
 * Clear the screen.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void imon_clear (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	memset(p->framebuf, ' ', p->width * p->height);
}


/**
 * Flush data on screen to the LCD.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void imon_flush (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	write(p->imon_fd, p->framebuf, p->width * p->height);
}


/**
 * 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 imon_string (Driver *drvthis, int x, int y, const char string[])
{
	int i;

	for (i = 0; string[i] != '\0'; i++)
		imon_chr(drvthis, x+i, y, string[i]);
}


/**
 * 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 imon_chr (Driver *drvthis, int x, int y, char c)
{
	PrivateData *p = drvthis->private_data;

	y--; x--;

	if ((x < 0) || (y < 0) || (x >= p->width) || (y >= p->height))
		return;

	p->framebuf[(y * p->width) + x] = c;
}


/**
 * 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.
 * \return  Information whether the icon is handled here or needs to be handled by the server core.
 */
MODULE_EXPORT int imon_icon (Driver *drvthis, int x, int y, int icon)
{
	switch (icon) {
		case ICON_BLOCK_FILLED:
			imon_chr(drvthis, x, y, IMON_CHAR_BLOCK_FILLED);
			break;
		case ICON_HEART_OPEN:
			imon_chr(drvthis, x, y, IMON_CHAR_BLOCK_EMPTY);
			break;
		case ICON_HEART_FILLED:
			imon_chr(drvthis, x, y, IMON_CHAR_HEART);
			break;
		case ICON_ARROW_UP:
			imon_chr(drvthis, x, y, IMON_CHAR_ARROW_UP);
			break;
		case ICON_ARROW_DOWN:
			imon_chr(drvthis, x, y, IMON_CHAR_ARROW_DOWN);
			break;
		case ICON_ARROW_LEFT:
			imon_chr(drvthis, x, y, IMON_CHAR_ARROW_LEFT);
			break;
		case ICON_ARROW_RIGHT:
			imon_chr(drvthis, x, y, IMON_CHAR_ARROW_RIGHT);
			break;
		case ICON_STOP:
			imon_chr(drvthis, x, y, IMON_CHAR_BLOCK_FILLED);
			imon_chr(drvthis, x+1, y, ' ');
			break;
		case ICON_PAUSE:
			imon_chr(drvthis, x, y, IMON_CHAR_PAUSE);
			imon_chr(drvthis, x+1, y, ' ');
			break;
		case ICON_PLAY:
			imon_chr(drvthis, x, y, IMON_CHAR_PLAY);
			imon_chr(drvthis, x+1, y, ' ');
			break;
		case ICON_PLAYR:
			imon_chr(drvthis, x, y, IMON_CHAR_RPLAY);
			imon_chr(drvthis, x+1, y, ' ');
			break;
		case ICON_FF:
			imon_chr(drvthis, x, y, IMON_CHAR_PLAY);
			imon_chr(drvthis, x+1, y, IMON_CHAR_PLAY);
			break;
		case ICON_FR:
			imon_chr(drvthis, x, y, IMON_CHAR_RPLAY);
			imon_chr(drvthis, x+1, y, IMON_CHAR_RPLAY);
			break;
		case ICON_NEXT:
			imon_chr(drvthis, x, y, IMON_CHAR_PLAY);
			imon_chr(drvthis, x+1, y, '|');
			break;
		case ICON_PREV:
			imon_chr(drvthis, x, y, '|');
			imon_chr(drvthis, x+1, y, IMON_CHAR_RPLAY);
			break;
		case ICON_REC:
			imon_chr(drvthis, x, y, IMON_CHAR_RECORD);
			imon_chr(drvthis, x+1, y, ' ');
			break;
		default:
		      /* let the server core do the rest */
		      return -1;
	}
	return 0;
}


/**
 * 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 imon_vbar (Driver *drvthis, int x, int y, int len, int promille, int options)
{
	PrivateData *p = drvthis->private_data;

	// Special characters start at 0 not 1, so pass -1 as first char.
	// This can be safely done as heartbeat icon is not 0
	lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, -1);
}


/**
 * 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 imon_hbar (Driver *drvthis, int x, int y, int len, int promille, int options)
{
	PrivateData *p = drvthis->private_data;
	int pixels = ((long) 2 * len * p->cellwidth) * promille / 2000;
	int pos;

	if ((x <= 0) || (y <= 0) || (y > p->height))
		return;

	for (pos = 0; pos < len; pos++) {

		if (x + pos > p->width)
			return;

		if (pixels >= p->cellwidth * 3/4) {
			/* write a "full" block to the screen... */
			imon_chr(drvthis, x+pos, y, IMON_CHAR_BLOCK_FILLED);
		}
		else if (pixels >= p->cellwidth * 2/4) {
			/* write a partial block... */
			imon_chr(drvthis, x+pos, y, IMON_CHAR_PLAY);
			break;
		}
		else if (pixels >= p->cellwidth * 1/4) {
			/* write a partial block... */
			imon_chr(drvthis, x+pos, y, '>');
			break;
		}
		else {
			; // write nothing (not even a space)
		}

		pixels -= p->cellwidth;
	}
}


/**
 * Return the display width in characters.
 * \param drvthis  Pointer to driver structure.
 * \return  Number of characters the display is wide.
 */
MODULE_EXPORT int imon_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  imon_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 imon_cellwidth (Driver *drvthis)
{
	PrivateData *p = 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  imon_cellheight (Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	return p->cellheight;
}

// EOF


syntax highlighted by Code2HTML, v. 0.9.1