/*
 * PWCBSD - Philips USB webcam driver for FreeBSD 5.4 and higher
 *
 *  Copyright (C) 2006 Raaf 
 *
 * Based on the Linux pwc driver.
 *  
 *  Copyright (C) 1999-2003 Nemosoft Unv.
 *  Copyright (C) 2004-2006 Luc Saillard
 *  
 * 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
 * (at your option) 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  USA
 */
#include "pwc.h"
#include "pwc-timon.h"
#include "pwc-kiara.h"
#include "pwc-dec23.h"
#include "pwc-ioctl.h"


static void fill_table_a000(unsigned int *p)
{
	static unsigned int initial_values[12] = {
		0xFFAD9B00, 0xFFDDEE00, 0x00221200, 0x00526500,
		0xFFC21E00, 0x003DE200, 0xFF924B80, 0xFFD2A300,
		0x002D5D00, 0x006DB480, 0xFFED3E00, 0x0012C200
	};
	static unsigned int values_derivated[12] = {
		0x0000A4CA, 0x00004424, 0xFFFFBBDC, 0xFFFF5B36,
		0x00007BC4, 0xFFFF843C, 0x0000DB69, 0x00005ABA,
		0xFFFFA546, 0xFFFF2497, 0x00002584, 0xFFFFDA7C
	};
	unsigned int temp_values[12];
	int i, j;

	memcpy(temp_values, initial_values, sizeof(initial_values));
	for (i = 0; i < 256; i++) {
		for (j = 0; j < 12; j++) {
			*p++ = temp_values[j];
			temp_values[j] += values_derivated[j];
		}
	}
}

static void fill_table_d000(unsigned char *p)
{
	int bit, byte;

	for (bit = 0; bit < 8; bit++) {
		unsigned char bitpower = 1 << bit;
		unsigned char mask = bitpower - 1;
		for (byte = 0; byte < 256; byte++) {
			if (byte & bitpower)
				*p++ = -(byte & mask);
			else
				*p++ = (byte & mask);
		}
	}
}

/*
 *
 * Kiara: 0 <= ver <= 7
 * Timon: 0 <= ver <= 15
 *
 */
static void fill_table_color(unsigned int version, const unsigned int *romtable,
		      unsigned char *p0004, unsigned char *p8004)
{
	const unsigned int *table;
	unsigned char *p0, *p8;
	int i, j, k;
	int dl, bit, pw;

	romtable += version * 256;

	for (i = 0; i < 2; i++) {
		table = romtable + i * 128;

		for (dl = 0; dl < 16; dl++) {
			p0 = p0004 + (i << 14) + (dl << 10);
			p8 = p8004 + (i << 12) + (dl << 8);

			for (j = 0; j < 8; j++, table++, p0 += 128) {
				for (k = 0; k < 16; k++) {
					if (k == 0)
						bit = 1;
					else if (k >= 1 && k < 3)
						bit = (table[0] >> 15) & 7;
					else if (k >= 3 && k < 6)
						bit = (table[0] >> 12) & 7;
					else if (k >= 6 && k < 10)
						bit = (table[0] >> 9) & 7;
					else if (k >= 10 && k < 13)
						bit = (table[0] >> 6) & 7;
					else if (k >= 13 && k < 15)
						bit = (table[0] >> 3) & 7;
					else
						bit = (table[0]) & 7;
					if (k == 0)
						*(unsigned char *)p8++ = 8;
					else
						*(unsigned char *)p8++ =
						    j - bit;
					*(unsigned char *)p8++ = bit;

					pw = 1 << bit;
					p0[k + 0x00] = (1 * pw) + 0x80;
					p0[k + 0x10] = (2 * pw) + 0x80;
					p0[k + 0x20] = (3 * pw) + 0x80;
					p0[k + 0x30] = (4 * pw) + 0x80;
					p0[k + 0x40] = (-pw) + 0x80;
					p0[k + 0x50] = (2 * -pw) + 0x80;
					p0[k + 0x60] = (3 * -pw) + 0x80;
					p0[k + 0x70] = (4 * -pw) + 0x80;
				}	/* end of for (k=0; k<16; k++, p8++) */
			}	/* end of for (j=0; j<8; j++ , table++) */
		}		/* end of for (dl=0; dl<16; dl++) */
	}			/* end of for (i=0; i<2; i++) */
}

/*
 * precision = (pdev->xx + pdev->yy)
 *
 */
static void fill_table_dc00_d800(unsigned int precision, unsigned int *pdc00,
			  unsigned int *pd800)
{
	int i;
	unsigned int offset1, offset2;

	for (i = 0, offset1 = 0x4000, offset2 = 0; i < 256;
	     i++, offset1 += 0x7BC4, offset2 += 0x7BC4) {
		unsigned int msb = offset1 >> 15;

		if (msb > 255) {
			if (msb)
				msb = 0;
			else
				msb = 255;
		}

		*pdc00++ = msb << precision;
		*pd800++ = offset2;
	}

}

/*
 * struct {
 *   unsigned char op;	    // operation to execute
 *   unsigned char bits;    // bits use to perform operation
 *   unsigned char offset1; // offset to add to access in the table_0004 % 16
 *   unsigned char offset2; // offset to add to access in the table_0004
 * }
 *
 */
static unsigned int table_ops[64*4] = {
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x10,
	0x00, 0x06, 0x01, 0x30,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x01, 0x20,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x50,
	0x00, 0x05, 0x02, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x03, 0x00,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x10,
	0x00, 0x06, 0x02, 0x10,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x01, 0x60,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x50,
	0x00, 0x05, 0x02, 0x40,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x03, 0x40,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x10,
	0x00, 0x06, 0x01, 0x70,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x01, 0x20,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x50,
	0x00, 0x05, 0x02, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x03, 0x00,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x10,
	0x00, 0x06, 0x02, 0x50,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x01, 0x60,
	0x01, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x00,
	0x00, 0x04, 0x01, 0x50,
	0x00, 0x05, 0x02, 0x40,
	0x02, 0x00, 0x00, 0x00,
	0x00, 0x03, 0x01, 0x40,
	0x00, 0x05, 0x03, 0x40,
	0x01, 0x00, 0x00, 0x00
};

/*
 * TODO: multiply by 4 all values
 *
 */
static unsigned int MulIdx[256] = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3,
	0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
	4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4,
	6, 7, 8, 9, 7, 10, 11, 8, 8, 11, 10, 7, 9, 8, 7, 6,
	4, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 4,
	1, 3, 0, 2, 1, 3, 0, 2, 1, 3, 0, 2, 1, 3, 0, 2,
	0, 3, 3, 0, 1, 2, 2, 1, 2, 1, 1, 2, 3, 0, 0, 3,
	0, 1, 2, 3, 3, 2, 1, 0, 3, 2, 1, 0, 0, 1, 2, 3,
	1, 1, 1, 1, 3, 3, 3, 3, 0, 0, 0, 0, 2, 2, 2, 2,
	7, 10, 11, 8, 9, 8, 7, 6, 6, 7, 8, 9, 8, 11, 10, 7,
	4, 5, 5, 4, 5, 4, 4, 5, 5, 4, 4, 5, 4, 5, 5, 4,
	7, 9, 6, 8, 10, 8, 7, 11, 11, 7, 8, 10, 8, 6, 9, 7,
	1, 3, 0, 2, 2, 0, 3, 1, 2, 0, 3, 1, 1, 3, 0, 2,
	1, 2, 2, 1, 3, 0, 0, 3, 0, 3, 3, 0, 2, 1, 1, 2,
	10, 8, 7, 11, 8, 6, 9, 7, 7, 9, 6, 8, 11, 7, 8, 10
};

void pwc_dec23_init(int type, int release, unsigned char *mode, void *data)
{
	int flags;
	struct pwc_dec23_private *pdev = data;
	release = release;

	switch (type) {
	case 720:
	case 730:
	case 740:
	case 750:
		flags = mode[2] & 0x18;	/* our: flags = 8, mode[2]==e8 */
		if (flags == 8)
			pdev->zz = 7;
		else if (flags == 0x10)
			pdev->zz = 8;
		else
			pdev->zz = 6;
		flags = mode[2] >> 5;	/* our: 7 */

		fill_table_color(flags, (unsigned int *)KiaraRomTable,
				 pdev->table_0004, pdev->table_8004);
		break;

	case 675:
	case 680:
	case 690:
		flags = mode[2] & 6;
		if (flags == 2)
			pdev->zz = 7;
		else if (flags == 4)
			pdev->zz = 8;
		else
			pdev->zz = 6;
		flags = mode[2] >> 3;

		fill_table_color(flags, (unsigned int *)TimonRomTable,
				 pdev->table_0004, pdev->table_8004);
		break;

	default:
		/* Not supported */
		return;
	}

	/* * * * ** */
	pdev->xx = 8 - pdev->zz;
	pdev->yy = 15 - pdev->xx;
	pdev->zzmask = 0xFF >> pdev->xx;
	//pdev->zzmask = (1U<<pdev->zz)-1;

	fill_table_dc00_d800(pdev->xx + pdev->yy, pdev->table_dc00,
			     pdev->table_d800);
	fill_table_a000(pdev->table_a004);
	fill_table_d000(pdev->table_d004);
}

/*
 * To manage the stream, we keep in a 32 bits variables,
 * the next bits in the stream. fill_reservoir() add to
 * the reservoir at least wanted nbits.
 *
 */
#define fill_nbits(reservoir,nbits_in_reservoir,stream,nbits_wanted) do { \
   while (nbits_in_reservoir<nbits_wanted) \
    { \
      reservoir |= (*(stream)++) << nbits_in_reservoir; \
      nbits_in_reservoir+=8; \
    } \
}  while(0);

#define get_nbits(reservoir,nbits_in_reservoir,stream,nbits_wanted,result) do { \
   fill_nbits(reservoir,nbits_in_reservoir,stream,nbits_wanted); \
   result = (reservoir) & ((1U<<nbits_wanted)-1); \
   reservoir >>= nbits_wanted; \
   nbits_in_reservoir -= nbits_wanted; \
}  while(0);

static void DecompressBand23(const struct pwc_dec23_private *pdev,
			     const unsigned char *rawyuv,
			     unsigned char *planar_y,
			     unsigned char *planar_u,
			     unsigned char *planar_v,
			     unsigned int image_x,		/* aka number of pixels wanted ??? */
			     unsigned int pixels_per_line,	/* aka number of pixels per line */
			     int flags)
{
	unsigned int reservoir, nbits_in_reservoir;
	int first_4_bits;
	unsigned int bytes_per_channel;
	int line_size;		/* size of the line (4Y+U+V) */
	int passes;
	const unsigned char *ptable0004, *ptable8004;

	int even_line;
	unsigned int temp_colors[16];
	int nblocks;

	const unsigned char *stream;
	unsigned char *dest_y, *dest_u = NULL, *dest_v = NULL;
	unsigned int offset_to_plane_u, offset_to_plane_v;

	int i;

	reservoir = 0;
	nbits_in_reservoir = 0;
	stream = rawyuv + 1;	/* The first byte of the stream is skipped */
	even_line = 1;

	get_nbits(reservoir, nbits_in_reservoir, stream, 4, first_4_bits);

	line_size = pixels_per_line * 3;

	for (passes = 0; passes < 2; passes++) {
		if (passes == 0) {
			bytes_per_channel = pixels_per_line;
			dest_y = planar_y;
			nblocks = image_x / 4;
		} else {
			/* Format planar: All Y, then all U, then all V */
			bytes_per_channel = pixels_per_line / 2;
			dest_u = planar_u;
			dest_v = planar_v;
			dest_y = dest_u;
			nblocks = image_x / 8;
		}

		offset_to_plane_u = bytes_per_channel * 2;
		offset_to_plane_v = bytes_per_channel * 3;
		/*
		   printk(KERN_DEBUG "bytes_per_channel = %d\n",bytes_per_channel);
		   printk(KERN_DEBUG "offset_to_plane_u = %d\n",offset_to_plane_u);
		   printk(KERN_DEBUG "offset_to_plane_v = %d\n",offset_to_plane_v);
		 */

		while (nblocks-- > 0) {
			unsigned int gray_index;

			fill_nbits(reservoir, nbits_in_reservoir, stream, 16);
			gray_index = reservoir & pdev->zzmask;
			reservoir >>= pdev->zz;
			nbits_in_reservoir -= pdev->zz;

			fill_nbits(reservoir, nbits_in_reservoir, stream, 2);

			if ((reservoir & 3) == 0) {
				reservoir >>= 2;
				nbits_in_reservoir -= 2;
				for (i = 0; i < 16; i++)
					temp_colors[i] =
					    pdev->table_dc00[gray_index];

			} else {
				unsigned int channel_v, offset1;

				/* swap bit 0 and 2 of offset_OR */
				channel_v =
				    ((reservoir & 1) << 2) | (reservoir & 2) |
				    ((reservoir & 4) >> 2);
				reservoir >>= 3;
				nbits_in_reservoir -= 3;

				for (i = 0; i < 16; i++)
					temp_colors[i] =
					    pdev->table_d800[gray_index];

				ptable0004 =
				    pdev->table_0004 + (passes * 16384) +
				    (first_4_bits * 1024) + (channel_v * 128);
				ptable8004 =
				    pdev->table_8004 + (passes * 4096) +
				    (first_4_bits * 256) + (channel_v * 32);

				offset1 = 0;
				while (1) {
					unsigned int index_in_table_ops, op,
					    rows = 0;
					fill_nbits(reservoir,
						   nbits_in_reservoir, stream,
						   16);

					/* mode is 0,1 or 2 */
					index_in_table_ops = (reservoir & 0x3F);
					op = table_ops[index_in_table_ops * 4];
					if (op == 2) {
						reservoir >>= 2;
						nbits_in_reservoir -= 2;
						break;	/* exit the while(1) */
					}
					if (op == 0) {
						unsigned int shift;

						offset1 =
						    (offset1 +
						     table_ops
						     [index_in_table_ops * 4 +
						      2]) & 0x0F;
						shift =
						    table_ops[index_in_table_ops
							      * 4 + 1];
						reservoir >>= shift;
						nbits_in_reservoir -= shift;
						rows =
						    ptable0004[offset1 +
							       table_ops
							       [index_in_table_ops
								* 4 + 3]];
					}
					if (op == 1) {
						/* 10bits [ xxxx xxxx yyyy 000 ]
						 * yyy => offset in the table8004
						 * xxx => offset in the tabled004
						 */
						unsigned int mask, shift;
						unsigned int col1, row1,
						    total_bits;

						offset1 =
						    (offset1 +
						     ((reservoir >> 3) & 0x0F) +
						     1) & 0x0F;

						col1 = (reservoir >> 7) & 0xFF;
						row1 = ptable8004[offset1 * 2];

						/* Bit mask table */
						mask =
						    pdev->
						    table_d004[(row1 << 8) +
							       col1];
						shift =
						    ptable8004[offset1 * 2 + 1];
						rows =
						    ((mask << shift) +
						     0x80) & 0xFF;

						total_bits = row1 + 8;
						reservoir >>= total_bits;
						nbits_in_reservoir -=
						    total_bits;
					}
					{
						const unsigned int *table_a004 =
						    pdev->table_a004 +
						    rows * 12;
						unsigned int *poffset = MulIdx + offset1 * 16;	/* 64/4 (int) */
						for (i = 0; i < 16; i++) {
							temp_colors[i] +=
							    table_a004
							    [*poffset];
							poffset++;
						}
					}
				}
			}
#define USE_SIGNED_INT_FOR_COLOR
#ifdef USE_SIGNED_INT_FOR_COLOR
#  define CLAMP(x) ((x)>255?255:((x)<0?0:x))
#else
#  define CLAMP(x) ((x)>255?255:x)
#endif

			if (passes == 0) {
#ifdef USE_SIGNED_INT_FOR_COLOR
				const int *c = temp_colors;
#else
				const unsigned int *c = temp_colors;
#endif
				unsigned char *d;

				d = dest_y;
				for (i = 0; i < 4; i++, c++)
					*d++ = CLAMP((*c) >> pdev->yy);

				d = dest_y + bytes_per_channel;
				for (i = 0; i < 4; i++, c++)
					*d++ = CLAMP((*c) >> pdev->yy);

				d = dest_y + offset_to_plane_u;
				for (i = 0; i < 4; i++, c++)
					*d++ = CLAMP((*c) >> pdev->yy);

				d = dest_y + offset_to_plane_v;
				for (i = 0; i < 4; i++, c++)
					*d++ = CLAMP((*c) >> pdev->yy);

				dest_y += 4;
			} else if (passes == 1) {
#ifdef USE_SIGNED_INT_FOR_COLOR
				int *c1 = temp_colors;
				int *c2 = temp_colors + 4;
#else
				unsigned int *c1 = temp_colors;
				unsigned int *c2 = temp_colors + 4;
#endif
				unsigned char *d;

				d = dest_y;
				for (i = 0; i < 4; i++, c1++, c2++) {
					*d++ = CLAMP((*c1) >> pdev->yy);
					*d++ = CLAMP((*c2) >> pdev->yy);
				}
				c1 = temp_colors + 12;
				//c2 = temp_colors+8;
				d = dest_y + bytes_per_channel;
				for (i = 0; i < 4; i++, c1++, c2++) {
					*d++ = CLAMP((*c1) >> pdev->yy);
					*d++ = CLAMP((*c2) >> pdev->yy);
				}

				if (even_line) {	/* Each line, swap u/v */
					even_line = 0;
					dest_y = dest_v;
					dest_u += 8;
				} else {
					even_line = 1;
					dest_y = dest_u;
					dest_v += 8;
				}
			}

		}		/* end of while (nblocks-->0) */

	}			/* end of for (passes=0;passes<2;passes++) */

}

/**
 *
 * image: size of the image wanted
 * view : size of the image returned by the camera
 * offset: (x,y) to displayer image in the view
 *
 * src: raw data
 * dst: image output
 * flags: PWCX_FLAG_PLANAR
 * pdev: private buffer
 * bandlength:
 *
 */
void pwc_dec23_decompress(const struct pwc_coord *image,
			  const struct pwc_coord *view,
			  const struct pwc_coord *offset,
			  const char*src,
			  char *dst,
			  int flags, const void *data, int bandlength)
{
	const struct pwc_dec23_private *pdev = data;
	unsigned char *pout, *pout_planar_y = NULL, *pout_planar_u =
	    NULL, *pout_planar_v = NULL;
	int i, n, stride, pixel_size;

	if (flags & PWCX_FLAG_BAYER) {
		pout = dst + (view->x * offset->y) + offset->x;
		pixel_size = view->x * 4;
	} else {
		n = view->x * view->y;

		/* offset in Y plane */
		stride = view->x * offset->y;
		pout_planar_y = dst + stride + offset->x;

		/* offsets in U/V planes */
		stride = (view->x * offset->y) / 4 + offset->x / 2;
		pout_planar_u = dst + n + +stride;
		pout_planar_v = dst + n + n / 4 + stride;

		pixel_size = view->x * 4;
	}

	for (i = 0; i < image->y; i += 4) {
		if (flags & PWCX_FLAG_BAYER) {
			//TODO:
			//DecompressBandBayer(pdev,src,pout,image.x,view->x,flags);
			src += bandlength;
			pout += pixel_size;
		} else {
			DecompressBand23(pdev, src, pout_planar_y,
					 pout_planar_u, pout_planar_v, image->x,
					 view->x, flags);
			src += bandlength;
			pout_planar_y += pixel_size;
			pout_planar_u += view->x;
			pout_planar_v += view->x;
		}
	}
}

void pwc_dec23_exit(void)
{
	/* Do nothing */

}
