/** \file server/drivers/imonlcd.c
 * Driver for SoundGraph iMON OEM LCD Modules
 *
 * Requires the lirc_imon module included with lirc >= 0.8.4a
 */

/*-
 * Copyright (c) 2004, Venky Raju <dev (at) venky (dot) ws>
 *               2007, Dean Harding <dean (at) codeka dotcom>
 *               2007, Christian Leuschen <christian (dot) leuschen (at) gmx (dot) de>
 *               2009, Jonathan Kyler <jkyler (at) users (dot) sourceforge (dot) net>
 *               2009, Eric Pooch < epooch (at) cox (dot) net>
 *
 * This file is released under the GNU General Public License. Refer to the
 * COPYING file distributed with this package.
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif
#include <math.h>

#include "lcd.h"
#include "lcd_lib.h"
#include "shared/report.h"

/* Defines a 6x8 font based on ISO 8859-15 */
#define LCD_DEFAULT_CELL_WIDTH	6
#define LCD_DEFAULT_CELL_HEIGHT   8
#include "imonlcd_font.h"

#include "imonlcd.h"

#define IMONLCD_PACKET_DATA_SIZE 7

#define DEFAULT_DEVICE       "/dev/lcd0"
#define DEFAULT_SIZE         "96x16"	/**< This is the size in "pixels" (colXrow) */
#define DEFAULT_CONTRAST     200
#define DEFAULT_BACKLIGHT    1	/**< turn backlight on */
#define DEFAULT_DISCMODE     0	/**< spin the "slim" disc */
#define DEFAULT_ON_EXIT      1	/**< show the big clock */
#define DEFAULT_PROTOCOL     0	/**< protocol for 15c2:ffdc device */


#define ON_EXIT_SHOWMSG      0	/**< Do nothing - just leave the "shutdown"
				 * message there */
#define ON_EXIT_SHOWCLOCK    1	/**< Show the big clock */
#define ON_EXIT_BLANKSCREEN  2	/**< Blank the device completely */

#define PROTOCOL_FFDC        0	/**< protocol ID for 15c2:ffdc device */
#define PROTOCOL_0038        1	/**< protocol ID for 15c2:0038 device */

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

/** Private data for the \c imonlcd driver */
typedef struct imonlcd_private_data {
	char info[255];
	int imon_fd;

	/* send buffer for one command or display data */
	unsigned char tx_buf[8];

	/* framebuffer and backingstore for current contents */
	unsigned char *framebuf;
	unsigned char *backingstore;

	int bytesperline;

	int width, height;
	int cellwidth, cellheight;

	int on_exit;
	int contrast;		/* 0 = lowest contrast, 1000 = highest */
	int backlightOn;	/* stores the backlight state */

	/*
	 * 0 = two disc-segments spinning as default, 1 = their complement
	 * spinning
	 */
	int discMode;

	/*
	 * 0 = protocol for 15c2:ffdc device, 1 = protocol for 15c2:0038
	 * device
	 */
	int protocol;

	/* store commands appropriate for the version of the iMON LCD */
	uint64_t command_display;
	uint64_t command_shutdown;
	uint64_t command_display_on;
	uint64_t command_clear_alarm;

	/* last "state" of the CD icon so that we can animate it */
	int last_cd_state;

	/* save the last output state so we don't needlessly reset the icons */
	int last_output_state;
	int last_output_bar_state;
} PrivateData;

/*
 * Just for convenience and to have the commands at one place.
 */
#define COMMANDS_SET_ICONS	(uint64_t) 0x0100000000000000LL
#define COMMANDS_SET_CONTRAST	(uint64_t) 0x0300000000000000LL
#define COMMANDS_DISPLAY	(uint64_t) 0x0000000000000000LL  // must be or'd with the applicable COMMANDS_DISPLAY_BYTE
#define COMMANDS_SHUTDOWN	(uint64_t) 0x0000000000000008LL  // must be or'd with the applicable COMMANDS_DISPLAY_BYTE
#define COMMANDS_DISPLAY_ON	(uint64_t) 0x0000000000000040LL  // must be or'd with the applicable COMMANDS_DISPLAY_BYTE
#define COMMANDS_CLEAR_ALARM	(uint64_t) 0x0000000000000000LL  // must be or'd with the applicable COMMANDS_ALARM_BYTE
#define COMMANDS_SET_LINES0	(uint64_t) 0x1000000000000000LL
#define COMMANDS_SET_LINES1	(uint64_t) 0x1100000000000000LL
#define COMMANDS_SET_LINES2	(uint64_t) 0x1200000000000000LL
#define COMMANDS_INIT		(uint64_t) 0x0200000000000000LL  //not exactly sure what this does, but it's needed
#define COMMANDS_LOW_CONTRAST	(uint64_t) (COMMANDS_SET_CONTRAST + (uint64_t) 0x00FFFFFF00580A00LL)
#define COMMAND_MASK		(uint64_t) 0xFF00000000000000LL

/* Allow for variations in Soundgraphs numerous protocols */
/* 15c2:0038 SoundGraph iMON */
#define COMMANDS_DISPLAY_BYTE_0038	(uint64_t) 0x8800000000000000LL
#define COMMANDS_ALARM_BYTE_0038	(uint64_t) 0x8a00000000000000LL

/* 15c2:ffdc SoundGraph iMON */
#define COMMANDS_DISPLAY_BYTE_FFDC	(uint64_t) 0x5000000000000000LL
#define COMMANDS_ALARM_BYTE_FFDC	(uint64_t) 0x5100000000000000LL


/*
 * These are used with the imon_output function to determine which icons to
 * turn on/off. Because we only get a 32-bit integer to play, some of the
 * icons are grouped into "sets" from which you can only select to turn one
 * on at a time.
 */
#define IMON_OUTPUT_CD_MASK		0x00000001
#define IMON_OUTPUT_TOPROW_MASK		0x0000000E
#define IMON_OUTPUT_SPEAKER_MASK	0x00000030
#define IMON_OUTPUT_SPDIF_MASK		0x00000040
#define IMON_OUTPUT_SRC_MASK		0x00000080
#define IMON_OUTPUT_FIT_MASK		0x00000100
#define IMON_OUTPUT_TV_MASK		0x00000200
#define IMON_OUTPUT_HDTV_MASK		0x00000400
#define IMON_OUTPUT_SCR1_MASK		0x00000800
#define IMON_OUTPUT_SCR2_MASK		0x00001000
#define IMON_OUTPUT_BRICONS_MASK	0x0000E000
#define IMON_OUTPUT_BMICONS_MASK	0x00070000
#define IMON_OUTPUT_BLICONS_MASK	0x00380000
#define IMON_OUTPUT_VOL_MASK		0x00400000
#define IMON_OUTPUT_TIME_MASK		0x00800000
#define IMON_OUTPUT_ALARM_MASK		0x01000000
#define IMON_OUTPUT_REC_MASK		0x02000000
#define IMON_OUTPUT_REP_MASK		0x04000000
#define IMON_OUTPUT_SFL_MASK		0x08000000

#define IMON_OUTPUT_PBARS_MASK		0x10000000
#define IMON_OUTPUT_DISK_IN_MASK	0x20000000

#define IMON_ICON_ALL		(uint64_t) 0x00FFFFFFFFFFFFFFLL

/* Byte 6 */
#define IMON_ICON_DISK_OFF	(uint64_t) 0x7F7000FFFFFFFFFFLL
#define IMON_ICON_DISK_ON	(uint64_t) 0x0080FF0000000000LL

#define IMON_ICON_DISK_IN	(uint64_t) 0x0080000000000000LL
#define IMON_ICON_CD_IN		(uint64_t) 0x00806B0000000000LL
#define IMON_ICON_DVD_IN	(uint64_t) 0x0080550000000000LL

/* Byte 5 */
#define IMON_ICON_WMA2	((uint64_t) 0x1 << 39)
#define IMON_ICON_WAV	((uint64_t) 0x1 << 38)
#define IMON_ICON_REP	((uint64_t) 0x1 << 37)
#define IMON_ICON_SFL	((uint64_t) 0x1 << 36)
#define IMON_ICON_ALARM	((uint64_t) 0x1 << 35)
#define IMON_ICON_REC	((uint64_t) 0x1 << 34)
#define IMON_ICON_VOL	((uint64_t) 0x1 << 33)
#define IMON_ICON_TIME	((uint64_t) 0x1 << 32)

/* Byte 4 */
#define IMON_ICON_XVID	((uint64_t) 0x1 << 31)
#define IMON_ICON_WMV	((uint64_t) 0x1 << 30)
#define IMON_ICON_MPG2	((uint64_t) 0x1 << 29)
#define IMON_ICON_AC3	((uint64_t) 0x1 << 28)
#define IMON_ICON_DTS	((uint64_t) 0x1 << 27)
#define IMON_ICON_WMA	((uint64_t) 0x1 << 26)
#define IMON_ICON_MP3	((uint64_t) 0x1 << 25)
#define IMON_ICON_OGG	((uint64_t) 0x1 << 24)

/* Byte 3 */
#define IMON_ICON_SRC	((uint64_t) 0x1 << 23)
#define IMON_ICON_FIT	((uint64_t) 0x1 << 22)
#define IMON_ICON_TV_2	((uint64_t) 0x1 << 21)
#define IMON_ICON_HDTV	((uint64_t) 0x1 << 20)
#define IMON_ICON_SCR1	((uint64_t) 0x1 << 19)
#define IMON_ICON_SCR2	((uint64_t) 0x1 << 18)
#define IMON_ICON_MPG	((uint64_t) 0x1 << 17)
#define IMON_ICON_DIVX	((uint64_t) 0x1 << 16)

/* Byte 2 */
#define IMON_SPKR_FC	((uint64_t) 0x1 << 15)
#define IMON_SPKR_FR	((uint64_t) 0x1 << 14)
#define IMON_SPKR_SL	((uint64_t) 0x1 << 13)
#define IMON_SPKR_LFE	((uint64_t) 0x1 << 12)
#define IMON_SPKR_SR	((uint64_t) 0x1 << 11)
#define IMON_SPKR_RL	((uint64_t) 0x1 << 10)
#define IMON_SPKR_SPDIF	((uint64_t) 0x1 << 9)
#define IMON_SPKR_RR	((uint64_t) 0x1 << 8)

/* Byte 1 */
#define IMON_ICON_MUSIC	((uint64_t) 0x1 << 7)
#define IMON_ICON_MOVIE	((uint64_t) 0x1 << 6)
#define IMON_ICON_PHOTO	((uint64_t) 0x1 << 5)
#define IMON_ICON_CD_DVD  ((uint64_t) 0x1 << 4)
#define IMON_ICON_TV	((uint64_t) 0x1 << 3)
#define IMON_ICON_WEBCAST  ((uint64_t) 0x1 << 2)
#define IMON_ICON_NEWS	((uint64_t) 0x1 << 1)
#define IMON_SPKR_FL	((uint64_t) 0x1)


/* prototypes for driver internal functions */
static void imonlcd_display_init(Driver *drvthis);
static void draw_bigchar(imon_bigfont *font, int ch, int x, int y, PrivateData *p);
static void setLineLength(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p);
static void setBuiltinProgressBars(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p);
static int lengthToPixels(int length);
static void send_command_data(uint64_t commandData, PrivateData *p);
static int send_packet(PrivateData *p);

/**
 * Initialize the driver.
 * \param drvthis  Pointer to driver structure.
 * \retval 0	   Success.
 * \retval <0	  Error.
 */
MODULE_EXPORT int
imonlcd_init(Driver *drvthis)
{
	PrivateData *p = NULL;
	char buf[256];
	int graph_width, graph_height;	/* width and height of screen, in pixels */
	int tmp;

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

	p->imon_fd = -1;

	graph_width = 0;
	graph_height = 0;

	p->width = 0;		/* Display width, in characters */
	p->height = 0;		/* Display height, in characters */
	p->cellwidth = LCD_DEFAULT_CELL_WIDTH;	 /* width of a character, in pixels */
	p->cellheight = LCD_DEFAULT_CELL_HEIGHT; /* height of a character, in pixels */

	p->last_cd_state = 0;
	p->last_output_state = 0x0;	/* no icons turned on at startup */
	p->last_output_bar_state = 0x0;	/* no bars turned on at startup */
	p->discMode = 0;

	/* 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 kernel module?", drvthis->name);
		return -1;
	}
	/* Get protocol version */
	tmp = drvthis->config_get_int(drvthis->name, "Protocol", 0, DEFAULT_PROTOCOL);
	if ((tmp < 0) || (tmp > 1)) {
		report(RPT_WARNING, "%s: Protocol must be between 0 and 1; using default %d",
		       drvthis->name, DEFAULT_PROTOCOL);
		tmp = DEFAULT_PROTOCOL;
	}
	p->protocol = tmp;

	/* Set commands based on protocol version */
	if (p->protocol == PROTOCOL_FFDC) {
		p->command_display = (uint64_t) (COMMANDS_DISPLAY | COMMANDS_DISPLAY_BYTE_FFDC);
		p->command_shutdown = (uint64_t) (COMMANDS_SHUTDOWN | COMMANDS_DISPLAY_BYTE_FFDC);
		p->command_display_on = (uint64_t) (COMMANDS_DISPLAY_ON | COMMANDS_DISPLAY_BYTE_FFDC);
		p->command_clear_alarm = (uint64_t) (COMMANDS_CLEAR_ALARM | COMMANDS_ALARM_BYTE_FFDC);
	} else if (p->protocol == PROTOCOL_0038) {
		p->command_display = (uint64_t) (COMMANDS_DISPLAY | COMMANDS_DISPLAY_BYTE_0038);
		p->command_shutdown = (uint64_t) (COMMANDS_SHUTDOWN | COMMANDS_DISPLAY_BYTE_0038);
		p->command_display_on = (uint64_t) (COMMANDS_DISPLAY_ON | COMMANDS_DISPLAY_BYTE_0038);
		p->command_clear_alarm = (uint64_t) (COMMANDS_CLEAR_ALARM | COMMANDS_ALARM_BYTE_0038);
	} else {		/* default - use FFDC */
		report(RPT_WARNING, "%s: Protocol version not recognized: %d; using default %d",
		       drvthis->name, p->protocol, PROTOCOL_FFDC);
		p->command_display = (uint64_t) (COMMANDS_DISPLAY | COMMANDS_DISPLAY_BYTE_FFDC);
		p->command_shutdown = (uint64_t) (COMMANDS_SHUTDOWN | COMMANDS_DISPLAY_BYTE_FFDC);
		p->command_display_on = (uint64_t) (COMMANDS_DISPLAY_ON | COMMANDS_DISPLAY_BYTE_FFDC);
		p->command_clear_alarm = (uint64_t) (COMMANDS_CLEAR_ALARM | COMMANDS_ALARM_BYTE_FFDC);
	}

	/* 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", &graph_width, &graph_height) != 2)
	    || (graph_width <= 0) || (graph_height <= 0)
	    || (graph_height < LCD_DEFAULT_CELL_HEIGHT)
	    || ((graph_width / p->cellwidth) > LCD_MAX_WIDTH)
	    || ((graph_height / p->cellheight) > LCD_MAX_HEIGHT)) {
		report(RPT_WARNING, "%s: cannot read Size: %s; using default %s",
		       drvthis->name, buf, DEFAULT_SIZE);
		sscanf(DEFAULT_SIZE, "%dx%d", &graph_width, &graph_height);
	}
	/* Convert dimension in pixels to characters. */
	p->width = (graph_width / p->cellwidth);
	p->height = (graph_height / p->cellheight);

	/* Store this to make working with the frame buffer memory easier. */
	p->bytesperline = graph_width;

	/*
	 * Get the "on exit" setting so we know what to do when we shut the
	 * device down
	 */
	tmp = drvthis->config_get_int(drvthis->name, "OnExit", 0, DEFAULT_ON_EXIT);
	if ((tmp < 0) || (tmp > 3)) {
		report(RPT_WARNING, "%s: OnExit must be between 0 and 3; using default %d",
		       drvthis->name, DEFAULT_ON_EXIT);
		tmp = DEFAULT_ON_EXIT;
	}
	p->on_exit = tmp;

	/* Get the "contrast" setting */
	tmp = drvthis->config_get_int(drvthis->name, "Contrast", 0, DEFAULT_CONTRAST);
	if ((tmp < 0) || (tmp > 1000)) {
		report(RPT_WARNING, "%s: Contrast must be between 0 and 1000; using default %d",
		       drvthis->name, DEFAULT_CONTRAST);
		tmp = DEFAULT_CONTRAST;
	}
	p->contrast = tmp;

	/* Get the "backlight" setting */
	p->backlightOn = drvthis->config_get_bool(drvthis->name, "Backlight", 0, DEFAULT_BACKLIGHT);

	/* Get the "disc-mode" setting */
	p->discMode = drvthis->config_get_bool(drvthis->name, "DiscMode", 0, DEFAULT_DISCMODE);

	/*
	 * We need a little bit of extra memory in the frame buffer so that
	 * all of the last 7-byte-long packet data will be within the frame
	 * buffer. See imonlcd_flush() for where we take advantage of this.
	 */
	tmp = 0;
	while (((p->bytesperline * p->height) + tmp) % IMONLCD_PACKET_DATA_SIZE) {
		tmp++;
	}

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

	/* Clear the framebuffer. */
	imonlcd_clear(drvthis);

	/*
	 * This is the extra memory that will fill out the last packet's
	 * data.
	 */
	memset(p->framebuf + (p->bytesperline * p->height), 0xFF, tmp);

	/* Make sure the framebuffer backing store is there... */
	p->backingstore = (unsigned char *)malloc(p->bytesperline * p->height);
	if (p->backingstore == NULL) {
		report(RPT_ERR, "%s: unable to create framebuffer backing store", drvthis->name);
		return -1;
	}
	memset(p->backingstore, ' ', p->bytesperline * p->height);

	imonlcd_display_init(drvthis);

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

	return 0;
}

/**
 * Send the "initialize" commands to the screen.
 * \param drvthis  Pointer to driver structure.
 */
static void
imonlcd_display_init(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;

	if (p->backlightOn)
		send_command_data(p->command_display_on, p);
	else
		send_command_data(p->command_shutdown, p);

	send_command_data(p->command_clear_alarm, p);
	imonlcd_set_contrast(drvthis, p->contrast);
	send_command_data(COMMANDS_INIT, p);	/* unknown, required init command */
	send_command_data(COMMANDS_SET_ICONS, p);
	/* clear the progress-bars on top and bottom of the display */
	send_command_data(COMMANDS_SET_LINES0, p);
	send_command_data(COMMANDS_SET_LINES1, p);
	send_command_data(COMMANDS_SET_LINES2, p);
}


/**
 * Close the driver (do necessary clean-up).
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
imonlcd_close(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;
	time_t tt;
	struct tm *t;
	uint64_t data;

	if (p != NULL) {
		if (p->imon_fd >= 0) {
			if (p->on_exit == ON_EXIT_SHOWMSG) {
				/*
				 * "show message" means "do nothing" - the
				 * message is there already
				 */
				report(RPT_INFO, "%s: closing, leaving \"goodbye\" message.", drvthis->name);
			} else if (p->on_exit == ON_EXIT_BLANKSCREEN) {
				/*
				 * turning backlight off (confirmed for my
				 * Silverstone LCD) (as "cybrmage" at
				 * mediaportal pointed out, his LCD is an
				 * Antec built-in one and turns completely
				 * off with this command)
				 */
				report(RPT_INFO, "%s: closing, turning backlight off.", drvthis->name);
				send_command_data(p->command_shutdown, p);
				send_command_data(p->command_clear_alarm, p);
			} else {
				/*
				 * by default, show the big clock. We need to
				 * set it to the current time, then it just
				 * keeps counting automatically.
				 */
				report(RPT_INFO, "%s: closing, showing clock.", drvthis->name);

				tt = time(NULL);
				t = localtime(&tt);

				data = p->command_display;
				data += ((uint64_t) t->tm_sec << 48);
				data += ((uint64_t) t->tm_min << 40);
				data += ((uint64_t) t->tm_hour << 32);
				data += ((uint64_t) t->tm_mday << 24);
				data += ((uint64_t) t->tm_mon << 16);
				data += (((uint64_t) t->tm_year) << 8);
				data += 0x80;
				send_command_data(data, p);
				send_command_data(p->command_clear_alarm, p);
			}

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

	strcpy(p->info, "SoundGraph iMON LCD driver v0.6 : 15c2:ffdc and 15c2:0038 ");
	return p->info;
}


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

	memset(p->framebuf, 0x00, p->bytesperline * p->height);
}


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

	unsigned char msb;
	int offset = 0, ret;

	/*
	 * The display only provides for a complete screen refresh. If
	 * nothing has changed, don't refresh.
	 */
	if (memcmp(p->backingstore, p->framebuf, (p->bytesperline * p->height)) == 0)
		return;

	for (msb = 0x20; msb < 0x3c; msb++) {
		/* Copy the packet data from the frame buffer. */
		memcpy(p->tx_buf, p->framebuf + offset, IMONLCD_PACKET_DATA_SIZE);

		/* Add the memory register byte to the packet data. */
		p->tx_buf[IMONLCD_PACKET_DATA_SIZE] = msb;

		ret = send_packet(p);
		if (ret < 0)
			report(RPT_ERR, "imonlcd_flush: sending data for msb=%x: %s\n",
					(int) msb, strerror(errno));
		else if (ret != sizeof(p->tx_buf))
			report(RPT_ERR, "imonlcd: incomplete write\n");

		offset += IMONLCD_PACKET_DATA_SIZE;
	}

	/* Update the backing store. */
	memcpy(p->backingstore, p->framebuf, (p->bytesperline * 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
imonlcd_string(Driver *drvthis, int x, int y, const char string[])
{
	int i;

	for (i = 0; string[i] != '\0'; i++)
		imonlcd_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 ch       Character that gets written.  (ISO 8859-1 + special characters)
 */
MODULE_EXPORT void
imonlcd_chr(Driver *drvthis, int x, int y, char ch)
{
	PrivateData *p = drvthis->private_data;
	imon_font *defn;
	int col;

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

	x--; y--;
	/* Convert from characters to pixels. */
	x *= p->cellwidth;
	y *= p->bytesperline;

	defn = &font[(unsigned char) ch];

	/* Copy character from font into frame buffer */
	for (col = 0; col < p->cellwidth; col++) {
		p->framebuf[x + y] = defn->pixels[col];
		x++;
	}
}

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

	/*
	 * use the imonlcd_font narrow vbar characters and let lcd_lib do the
	 * rest
	 */
	lib_vbar_static(drvthis, x, y, len, promille, options,
			p->cellheight, IMONLCD_FONT_START_VBAR_NARROW-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 (i.e. from 0 to 1000).
 * \param options  Options (currently unused).
 */
MODULE_EXPORT void
imonlcd_hbar(Driver *drvthis, int x, int y, int len, int promille, int options)
{
	PrivateData *p = drvthis->private_data;

	lib_hbar_static(drvthis, x, y, len, promille, options | BAR_SEAMLESS,
			p->cellwidth, IMONLCD_FONT_START_HBAR_NARROW - 1);
}


/**
 * 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
imonlcd_icon(Driver *drvthis, int x, int y, int icon)
{
	char ch1 = '?';
	char ch2 = '\0';

	switch (icon) {
	case ICON_BLOCK_FILLED:
		ch1 = IMONLCD_FONT_FULL_BLOCK;
		break;
	case ICON_HEART_OPEN:
		ch1 = IMONLCD_FONT_SMALL_HEART;
		break;
	case ICON_HEART_FILLED:
		ch1 = IMONLCD_FONT_FULL_HEART;
		break;
	case ICON_ARROW_UP:
		ch1 = IMONLCD_FONT_UP_ARROW;
		break;
	case ICON_ARROW_DOWN:
		ch1 = IMONLCD_FONT_DOWN_ARROW;
		break;
	case ICON_ARROW_LEFT:
		ch1 = IMONLCD_FONT_LEFT_ARROW;
		break;
	case ICON_ARROW_RIGHT:
		ch1 = IMONLCD_FONT_RIGHT_ARROW;
		break;
	case ICON_STOP:
		ch1 = IMONLCD_FONT_STOP;
		ch2 = ' ';
		break;
	case ICON_PAUSE:
		ch1 = IMONLCD_FONT_PAUSE;
		ch2 = ' ';
		break;
	case ICON_PLAY:
		ch1 = IMONLCD_FONT_PLAY;
		ch2 = ' ';
		break;
	case ICON_PLAYR:
		ch1 = IMONLCD_FONT_REVERSE;
		ch2 = ' ';
		break;
	case ICON_FF:
		ch1 = IMONLCD_FONT_PLAY;
		ch2 = IMONLCD_FONT_PLAY;
		break;
	case ICON_FR:
		ch1 = IMONLCD_FONT_REVERSE;
		ch2 = IMONLCD_FONT_REVERSE;
		break;
	case ICON_NEXT:
		ch1 = IMONLCD_FONT_PLAY;
		ch2 = IMONLCD_FONT_FWD;
		break;
	case ICON_PREV:
		ch1 = IMONLCD_FONT_RWD;
		ch2 = IMONLCD_FONT_REVERSE;
		break;
	case ICON_REC:
		ch1 = IMONLCD_FONT_RECORD;
		ch2 = ' ';
		break;
	default:
		/* let the server core do the rest */
		return -1;
	}

	imonlcd_chr(drvthis, x, y, ch1);
	if (ch2 != '\0')
		imonlcd_chr(drvthis, x + 1, y, ch2);

	return 0;
}

/**
 * Draws a "big" number at the specified x-coordinate.
 *
 * Normally, the number that is displayed is "meant" to be 3x4 characters, but
 * because we have a bit more flexibility, I've drawn the numbers as just being
 * 12x16 pixels. That means that while the client will pass x-values between 0
 * and 16, we need to scale it and make sure the numbers remain centered.
 *
 * \param drvthis  A pointer to the Driver structure.
 * \param x        The x-coordinate to display the character at.
 * \param num      The number to display ("10" is the colon).
 */
MODULE_EXPORT void
imonlcd_num(Driver *drvthis, int x, int num)
{
	PrivateData *p = drvthis->private_data;

	/*
	 * This isn't that great, it makes assumptions about the coordinates
	 * the client passes to us. However, it works for MythTV... and looks
	 * pretty cool, too.
	 */
	if (num < 10)
		x = 12 + (int)(((x - 1) * p->cellwidth) * 0.75);
	else
		x = 12 + (int)(((x - 1) * p->cellwidth) * 0.72);

	draw_bigchar(bigfont, (num >= 10 ? ':' : (num + '0')), x, 0, p);
}

/**
 * Sets the "output state" for the device. We use this to control the icons
 * around the outside the display. The bits in \c state correspond to the
 * icons as follows:
 *
 * \verbatim
 * bit 0       : disc icon (0=off, 1='spin') , if Toprow==4, use CD-animation,
 *               else use "HDD-recording-animation"
 * bit 1,2,3   : top row (0=none, 1=music, 2=movie, 3=photo, 4=CD/DVD, 5=TV, 6=Web,
 *               7=News/Weather)
 * bit 4,5     : 'speaker' icons (0=off, 1=LR, 2=5.1ch, 3=7.1ch)
 * bit 6       : S/PDIF icon
 * bit 7       : 'SRC'
 * bit 8       : 'FIT'
 * bit 9       : 'TV'
 * bit 10      : 'HDTV'
 * bit 11      : 'SRC1'
 * bit 12      : 'SRC2'
 * bit 13,14,15: bottom-right icons (0=off, 1=MP3, 2=OGG, 3=WMA, 4=WAV)
 * bit 16,17,18: bottom-middle icons (0=off, 1=MPG, 2=AC3, 3=DTS, 4=WMA)
 * bit 19,20,21: bottom-left icons (0=off, 1=MPG, 2=DIVX, 3=XVID, 4=WMV)
 * bit 22      : 'VOL' (volume)
 * bit 23      : 'TIME'
 * bit 24      : 'ALARM'
 * bit 25      : 'REC' (recording)
 * bit 26      : 'REP' (repeat)
 * bit 27      : 'SFL' (shuffle)
 * bit 28      : Abuse this for progress bars (if set to 1), lower bits represent
 *               the length (6 bits each: P|6xTP|6xTL|6xBL|6xBP with P = bit 28,
 *               TP=Top Progress, TL = Top Line, BL = Bottom Line, BP = Bottom Progress).
 *               If bit 28 is set to 1, lower bits are interpreted as
 *               lengths; otherwise setting the symbols as usual.
 *               0 <= length <= 32, bars extend from left to right.
 *               length > 32, bars extend from right to left, length is counted
 *               from 32 up (i.e. 35 means a length of 3).
 *
 *     Remember: There are two kinds of calls!
 *               With bit 28 set to 1: Set all bars (leaving the symbols as is),
 *               with bit 28 set to 0: Set the symbols (leaving the bars as is).
 *     Beware:   TODO: May become a race condition, if both calls are executed
 *                     before the display gets updated. Keep this in mind in your
 *                     client-code.
 * bit 29      : 'disc-in icon' - half ellipsoid under the disc symbols (0=off, 1=on)
 * \endverbatim
 *
 * \param drvthis  Pointer to driver structure.
 * \param state    This symbols or bars to display.
 */
MODULE_EXPORT void
imonlcd_output(Driver *drvthis, int state)
{

	PrivateData *p = drvthis->private_data;
	uint64_t icon = 0x0;

	/* bit 28 : Abuse this for progress bars. See above for usage. */
	if ( state & IMON_OUTPUT_PBARS_MASK || state == 0 ) {
		if (state != p->last_output_bar_state) {
			/* extract the bar-values for each bar separately */
			int topProgress = (state & 63);
			int topLine = (state & (63 << 6)) >> 6;
			int botProgress = (state & (63 << 12)) >> 12;
			int botLine = (state & (63 << 18)) >> 18;

			botProgress = botProgress > 32 ? -(botProgress - 32) : botProgress;
			topProgress = topProgress > 32 ? -(topProgress - 32) : topProgress;
			botLine = botLine > 32 ? -(botLine - 32) : botLine;
			topLine = topLine > 32 ? -(topLine - 32) : topLine;

			setLineLength(topLine, botLine, topProgress, botProgress, p);

			p->last_output_bar_state = state;
		}

		/*
		 * If the current output state is 'all on' (-1) or 'all off' (0), update
		 * the icons with that state. Otherwise, update the icons with the last
		 * icon output state (this is only used to keep the CD spinning).
		 */
		if (state != 0 && state != -1 ) {
			state = p->last_output_state;
		}
	}

	/* Don't update if no icons need to be changed. */
	if (state == p->last_output_state && !(state != -1 && (state & IMON_OUTPUT_CD_MASK))) {
		return;
	}
	p->last_output_state = state;

	if (state == -1) {	/* the value for "on" in the lcdproc-protocol */
		icon = (uint64_t) IMON_ICON_ALL;
		send_command_data(COMMANDS_SET_ICONS | icon, p);
		return;
	}

	/* bit 0 : disc icon (0=off, 1='spin') */
	if (state & IMON_OUTPUT_CD_MASK) {
		/* Each icon bit represents a section of the cd,
		 * starting at the top as msb, and going counter-clockwise.
		 * Start with the top-right & bottom-left on.
		 */
		unsigned char tmp_cd_bitmap = (0x01 | (0x01 << 4));

		if (p->last_cd_state >= 3 )
			p->last_cd_state = 0;
		else
			p->last_cd_state++;

		/* Shift the bits to the left, and the cd moves clock-wise. */
		tmp_cd_bitmap <<= p->last_cd_state;

		if (p->discMode == 1)
			tmp_cd_bitmap = ~tmp_cd_bitmap;

		icon |= ((uint64_t)tmp_cd_bitmap) << 40;
	}
	/*
	 * bit 1,2,3 : top row
	 * (0=off, 1=MUSIC, 2=MOVIE, 3=PHOTO, 4=CD/DVD, 5=TV, 6=WEB, 7=NEWS/WEATHER)
	 */
	if (state & IMON_OUTPUT_TOPROW_MASK) {
		switch (((state & IMON_OUTPUT_TOPROW_MASK) >> 1)) {
		case 1:
			icon |= IMON_ICON_MUSIC;
			break;
		case 2:
			icon |= IMON_ICON_MOVIE;
			break;
		case 3:
			icon |= IMON_ICON_PHOTO;
			break;
		case 4:
			icon |= IMON_ICON_CD_DVD;
			break;
		case 5:
			icon |= IMON_ICON_TV;
			break;
		case 6:
			icon |= IMON_ICON_WEBCAST;
			break;
		case 7:
			icon |= IMON_ICON_NEWS;
			break;
		default:
			break;
		}
	}
	/* bit 4,5 : 'speaker' icons (0=off, 1=L+R, 2=5.1ch, 3=7.1ch) */
	if (state & IMON_OUTPUT_SPEAKER_MASK) {
		switch (((state & IMON_OUTPUT_SPEAKER_MASK) >> 4)) {
		case 3:
			icon |= (IMON_SPKR_SL | IMON_SPKR_SR);
		case 2:
			icon |= (IMON_SPKR_FC | IMON_SPKR_RL | IMON_SPKR_RR);
		case 1:
			icon |= (IMON_SPKR_FL | IMON_SPKR_FR);
		default:
			break;
		}
	}
	/* bit 6 : S/PDIF icon */
	if (state & IMON_OUTPUT_SPDIF_MASK)	icon |= IMON_SPKR_SPDIF;
	/* bit 7 : 'SRC' */
	if (state & IMON_OUTPUT_SRC_MASK)	icon |= IMON_ICON_SRC;
	/* bit 8 : 'FIT' */
	if (state & IMON_OUTPUT_FIT_MASK)	icon |= IMON_ICON_FIT;
	/* bit 9 : 'TV' */
	if (state & IMON_OUTPUT_TV_MASK)	icon |= IMON_ICON_TV_2;
	/* bit 10 : 'HDTV' */
	if (state & IMON_OUTPUT_HDTV_MASK)	icon |= IMON_ICON_HDTV;
	/* bit 11 : 'SRC1' */
	if (state & IMON_OUTPUT_SCR1_MASK)	icon |= IMON_ICON_SCR1;
	/* bit 12 : 'SRC2' */
	if (state & IMON_OUTPUT_SCR2_MASK)	icon |= IMON_ICON_SCR2;
	/*
	 * bit 13,14,15: bottom-right icons
	 * (0=off, 1=MP3, 2=OGG, 3=WMA, 4=WAV)
	 */
	if (state & IMON_OUTPUT_BRICONS_MASK) {
		switch (((state & IMON_OUTPUT_BRICONS_MASK) >> 13)) {
		case 1:
			icon |= IMON_ICON_MP3;
			break;
		case 2:
			icon |= IMON_ICON_OGG;
			break;
		case 3:
			icon |= IMON_ICON_WMA2;
			break;
		case 4:
			icon |= IMON_ICON_WAV;
			break;
		default:
			break;
		}
	}
	/*
	 * bit 16,17,18: bottom-middle icons
	 * (0=off, 1=MPG, 2=AC3, 3=DTS, 4=WMA)
	 */
	if (state & IMON_OUTPUT_BMICONS_MASK) {
		switch (((state & IMON_OUTPUT_BMICONS_MASK) >> 16)) {
		case 1:
			icon |= IMON_ICON_MPG2;
			break;
		case 2:
			icon |= IMON_ICON_AC3;
			break;
		case 3:
			icon |= IMON_ICON_DTS;
			break;
		case 4:
			icon |= IMON_ICON_WMA;
			break;
		default:
			break;
		}
	}
	/*
	 * bit 19,20,21: bottom-left icons
	 * (0=off, 1=MPG, 2=DIVX, 3=XVID, 4=WMV)
	 */
	if (state & IMON_OUTPUT_BLICONS_MASK) {
		switch (((state & IMON_OUTPUT_BLICONS_MASK) >> 19)) {
		case 1:
			icon |= IMON_ICON_MPG;
			break;
		case 2:
			icon |= IMON_ICON_DIVX;
			break;
		case 3:
			icon |= IMON_ICON_XVID;
			break;
		case 4:
			icon |= IMON_ICON_WMV;
			break;
		default:
			break;
		}
	}
	/* bit 22 : 'VOL' (volume) */
	if (state & IMON_OUTPUT_VOL_MASK)	icon |= IMON_ICON_VOL;
	/* bit 23 : 'TIME' */
	if (state & IMON_OUTPUT_TIME_MASK)	icon |= IMON_ICON_TIME;
	/* bit 24 : 'ALARM' */
	if (state & IMON_OUTPUT_ALARM_MASK)	icon |= IMON_ICON_ALARM;
	/* bit 25 : 'REC' (recording) */
	if (state & IMON_OUTPUT_REC_MASK)	icon |= IMON_ICON_REC;
	/* bit 26 : 'REP' (repeat) */
	if (state & IMON_OUTPUT_REP_MASK)	icon |= IMON_ICON_REP;
	/* bit 27 : 'SFL' (shuffle) */
	if (state & IMON_OUTPUT_SFL_MASK)	icon |= IMON_ICON_SFL;
	/* bit 29 : 'disc-in' */
	if (state & IMON_OUTPUT_DISK_IN_MASK)	icon |= IMON_ICON_DISK_IN;

	send_command_data(COMMANDS_SET_ICONS | icon, p);
}

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

	return p->cellheight;
}


/**
 * Sends a command to the screen. The kernel module expects data to be
 * sent in 8 byte chunks, so for simplicity, we allow you to define
 * the data as a 64-bit integer.
 * However, we have to reverse the bytes to the order the display requires.
 *
 * \param value  The data to send. Must be in a format that is recognized by
 *               the device. The kernel module doesn't actually do validation.
 * \param p      The private data containing the file descriptor to write to.
 */
static void
send_command_data(uint64_t value, PrivateData *p)
{
	int i;

	/* Fill the send buffer. */
	for (i = 0; i < sizeof(p->tx_buf); i++) {
		p->tx_buf[i] = (unsigned char)((value >> (i * 8)) & 0xFF);
	}

	i = send_packet(p);
	if (i < 0)
		report(RPT_ERR, "imonlcd: error sending command %llx: %s\n",
				value, strerror(errno));
	else if (i != sizeof(p->tx_buf))
		report(RPT_ERR, "imonlcd: send_command_data: incomplete write\n");
}

/**
 * Sends data to the screen.
 *
 * \param p The private data structure containing a tx_buf with the data to send.
 * \return  Number of byte written or error code.
 */
static int
send_packet(PrivateData *p)
{
	return write(p->imon_fd, p->tx_buf, sizeof(p->tx_buf));
}


/**
 * Sets the contrast of the display.
 *
 * \param drvthis   Pointer to driver structure.
 * \param promille  The value the contrast is set to in promille (0 = lowest
 *                  contrast; 1000 = highest contrast).
 * \return 0 on failure, >0 on success.
 */
MODULE_EXPORT
void
imonlcd_set_contrast(Driver *drvthis, int promille)
{
	PrivateData *p = drvthis->private_data;

	if (promille < 0) {
		promille = 0;
	} else if (promille > 1000) {
		promille = 1000;
	}
	p->contrast = promille;

	/*
	 * Send contrast normalized to the hardware-understandable-value (0
	 * to 40). 0 is the lowest and 40 is the highest. The actual
	 * perceived contrast varies depending on the type of display.
	 */
	send_command_data(COMMANDS_LOW_CONTRAST + (uint64_t) (p->contrast / 25), p);
}

/**
 * Gets the current contrast of the display.
 *
 * \param drvthis Pointer to driver structure.
 * \return The current contrast in promille (0 = lowest contrast;
 *         1000 = highest contrast).
 */
MODULE_EXPORT int
imonlcd_get_contrast(Driver *drvthis)
{
	PrivateData *p = drvthis->private_data;
	return p->contrast;
}

/**
 * Sets the backlight state of the display.
 *
 * \param drvthis Pointer to driver structure.
 * \param on The backlight state boolean-like: 0 = off; >0 = on.
 */
MODULE_EXPORT void
imonlcd_backlight(Driver *drvthis, int on)
{
	PrivateData *p = drvthis->private_data;

	/* To prevent superfluous (and erroneous) communication */
	if (p->backlightOn == on)
		return;
	else
		p->backlightOn = on;

	if (on)
		send_command_data(p->command_display_on, p);
	else
		send_command_data(p->command_shutdown, p);
}


/**
 * Draws a "big" character - that is, one that's twice as big as a
 * normal character - at the specified position on the screen.
 * \param font  font definition
 * \param ch    the character to display
 * \param x     horizontal position
 * \param y     vertical position
 * \param p     pointer to the PrivateData structure
 */
static void
draw_bigchar(imon_bigfont *font, int ch, int x, int y, PrivateData *p)
{
	imon_bigfont *defn = font;
	int i;
	int colBorder;

	while (defn->ch != ch && defn->ch != '\0') {
		defn++;
	}

	/*
	 * correction for the number flashing with the colon running
	 * "lcdproc K"
	 */
	colBorder = 12;
	if (ch == ':')
		colBorder = 6;

	for (i = 0; i < colBorder; i++) {
		p->framebuf[x + i + (y * colBorder)] = (defn->pixels[i] & 0xFF00) >> 8;
	}
	for (i = 0; i < colBorder; i++) {
		p->framebuf[x + i + (y * colBorder) + p->bytesperline] = (defn->pixels[i] & 0x00FF);
	}
}


/**
 * Sets the length of the built-in progress-bars and lines.
 * Values from -32 to 32 are allowed. Positive values indicate that bars extend
 * from left to right, negative values indicate that the run from right to left.
 * Conventient method to simplify setting the bars with "human understandable
 * values".
 *
 * \see setBuiltinProgressBars, lengthToPixels
 *
 * \param topLine      Length of the top line (-32 to 32)
 * \param botLine      Length of the bottom line (-32 to 32)
 * \param topProgress  Length of the top progress bar (-32 to 32)
 * \param botProgress  Length of the bottom progress bar (-32 to 32)
 * \param p The private data structure containing the file descriptor to write to.
 */
static void
setLineLength(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p)
{
	setBuiltinProgressBars(lengthToPixels(topLine),
			       lengthToPixels(botLine),
			       lengthToPixels(topProgress),
			       lengthToPixels(botProgress),
			       p
	);
}


/**
 * Sets the length of the built-in progress-bars and lines.
 * Values from -32 to 32 are allowed. Positive values indicate that bars extend
 * from left to right, negative values indicate that the run from right to left.
 *
 * \param topLine      Pitmap of the top line
 * \param botLine      Pitmap of the bottom line
 * \param topProgress  Pitmap of the top progress bar
 * \param botProgress  Pitmap of the bottom progress bar
 * \param p The private data structure containing the file descriptor to write to.
 */
static void
setBuiltinProgressBars(int topLine, int botLine,
		       int topProgress, int botProgress, PrivateData *p)
{
	/* Least sig. bit is on the right */
	uint64_t data;

	/* send bytes 1-4 of topLine and 1-3 of topProgress */
	data = (uint64_t) topLine & 0x00000000FFFFFFFFLL;
	data |= (((uint64_t) topProgress) << 8 * 4) & 0x00FFFFFF00000000LL;
	send_command_data(COMMANDS_SET_LINES0 | data, p);

	/* send byte 4 of topProgress, bytes 1-4 of botProgress and 1-2 of botLine */
	data = (((uint64_t) topProgress) >> 8 * 3) & 0x00000000000000FFLL;
	data |= (((uint64_t) botProgress) << 8) & 0x000000FFFFFFFF00LL;
	data |= (((uint64_t) botLine) << 8 * 5) & 0x00FFFF0000000000LL;
	send_command_data(COMMANDS_SET_LINES1 | data, p);

	/* send remaining bytes 3-4 of botLine */
	data = ((uint64_t) botLine) >> 8 * 2;
	send_command_data(COMMANDS_SET_LINES2 | data, p);
}

/**
 * Maps values to corresponding pixmaps for the built-in progress bars.
 * Values from -32 to 32 are allowed. Positive values indicate that bars extend
 * from left to right, negative values indicate that they run from right to left.
 *
 * \param length The length of the bar.
 * \return The pixmap that represents the given length.
 */
static int
lengthToPixels(int length)
{
	int pixLen[] =
	{
		0x00, 0x00000080, 0x000000c0, 0x000000e0, 0x000000f0,
		0x000000f8, 0x000000fc, 0x000000fe, 0x000000ff,
		0x000080ff, 0x0000c0ff, 0x0000e0ff, 0x0000f0ff,
		0x0000f8ff, 0x0000fcff, 0x0000feff, 0x0000ffff,
		0x0080ffff, 0x00c0ffff, 0x00e0ffff, 0x00f0ffff,
		0x00f8ffff, 0x00fcffff, 0x00feffff, 0x00ffffff,
		0x80ffffff, 0xc0ffffff, 0xe0ffffff, 0xf0ffffff,
		0xf8ffffff, 0xfcffffff, 0xfeffffff, 0xffffffff
	};

	if (abs(length) > 32)
		return (0);

	if (length >= 0)
		return pixLen[length];
	else
		return (pixLen[32 + length] ^ 0xffffffff);
}

/* EOF */
