/*
 * mid2253.c
 * Selected mid2253 OSD functions from Windows SDK
 */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#include <wand/magick_wand.h>

#include "mid2253.h"
#include "s2253.h"
#include "s2253ioctl.h"


#define MAX_RUN		(255 + 16)
#define FINISH_RUN(new_rgb)  do {	\
	if (run > 15) {			\
		data[len++] = rgb;	\
		data[len++] = run - 16;	\
	} else if (run > 0) {		\
		data[len++] = rgb | run;\
	}				\
	run = 0;			\
	rgb = new_rgb;			\
} while (0)


static unsigned char *S2253_EncodeOverlayImage(unsigned char *pixels, int width, int height, int rgb, int stride, int rgba, struct s2253_overlay_image *ovl)
{
	unsigned char *data;
	int x, y, run = 0, len = 10;

	data = (unsigned char*) malloc(len + width * height); // worst case
	if (!data) {
		ovl->length = 0;
		return NULL;
	}

	data[0] = 'R';
	data[1] = 'L';
	data[2] = 'E';
	data[3] = ' ';
	data[4] = width & 0xff;
	data[5] = (width >> 8) & 0xff;
	data[6] = height & 0xff;
	data[7] = (height >> 8) & 0xff;
	// convert to RGB565
	rgb = ((rgb & 0xf80000) >> 8) |
		  ((rgb & 0x00fc00) >> 5) |
		  ((rgb & 0x0000f8) >> 3);
	data[8] = rgb & 0xff;
	data[9] = (rgb >> 8) & 0xff;

	rgb = 0;
	run = 0;
	for (y = 0; y < height; y++) {
		unsigned char *p = pixels;
		pixels += stride;
		for (x = 0; x < width; x++) {
			unsigned char c, alpha;
			c = ((int)p[0] * (0x10000 * 299 / 1000) + // red
				 (int)p[1] * (0x10000 * 587 / 1000) + // green
				 (int)p[2] * (0x10000 * 114 / 1000)   // blue
				) >> 17;
			if (rgba) {
				alpha = p[3];
				p += 4;
			} else {
				alpha = 0xff;
				p += 3;
			}
			if (c < 0xf || alpha == 0)
				c = (alpha >> 1) & 0x70;
			else
				c = (c & 0x70) + 0x80;
			if (rgb != c || run == MAX_RUN)
				FINISH_RUN(c);
			run++;
		}
		if (rgb != 0)
			FINISH_RUN(0);
	}
	FINISH_RUN(0);
	ovl->length = len;
	return data;
}

static unsigned char *S2253_RenderRect(int width, int height, int rgb, struct s2253_overlay_image *ovl)
{
	unsigned char *data;
	int x, y, run = 0, len = 10;

	data = (unsigned char*) malloc((height+1) * 10); // worst case
	if (!data) {
		ovl->length = 0;
		return NULL;
	}

	data[0] = 'R';
	data[1] = 'L';
	data[2] = 'E';
	data[3] = ' ';
	data[4] = width & 0xff;
	data[5] = (width >> 8) & 0xff;
	data[6] = height & 0xff;
	data[7] = (height >> 8) & 0xff;
	// convert to RGB565
	rgb = ((rgb & 0xf80000) >> 8) |
		  ((rgb & 0x00fc00) >> 5) |
		  ((rgb & 0x0000f8) >> 3);
	data[8] = rgb & 0xff;
	data[9] = (rgb >> 8) & 0xff;

	run = 0;
	rgb = 0xf0;
	for (x = 0; x < width; x += MAX_RUN) {
		run = width - x;
		if (run > MAX_RUN)
			run = MAX_RUN;
		FINISH_RUN(0xf0);
	}
	for (y = 2; y < height; y++) {
		run = 1;
		FINISH_RUN(0xf0);
		rgb = 0;
		for (x = 2; x < width; x += MAX_RUN) {
			run = width - x;
			if (run > MAX_RUN)
				run = MAX_RUN;
			FINISH_RUN(0);
		}
		rgb = 0xf0;
		run = 1;
		FINISH_RUN(0xf0);
	}
	for (x = 0; x < width; x += MAX_RUN) {
		run = width - x;
		if (run > MAX_RUN)
			run = MAX_RUN;
		FINISH_RUN(0xf0);
	}
	ovl->length = len;
	return data;
}

typedef unsigned int UINT32;
typedef unsigned short UINT16;
typedef int INT32;

static unsigned char *S2253_RenderBMP(unsigned char *src, struct s2253_overlay_image *ovl)
{
    struct {
        UINT32 filesz;
        UINT16 creator1;
        UINT16 creator2;
        UINT32 bmp_offset;
    } bmp;
    struct {
        UINT32 header_sz;
        UINT32 width;
        UINT32 height;
        UINT16 nplanes;
        UINT16 bitspp;
        UINT32 compress_type;
        UINT32 bmp_bytesz;
        INT32 hres;
        INT32 vres;
        UINT32 ncolors;
        UINT32 nimpcolors;
    } bmpinfo;
    unsigned int bytesperline;
    unsigned char *data;

    if (src[0] != 'B' || src[1] != 'M')
        return NULL;
    src += 2;
    memcpy(&bmp, src, sizeof(bmp));
    data = src - 2 + bmp.bmp_offset;
    src += sizeof(bmp);

	memcpy(&bmpinfo, src, sizeof(bmpinfo));
    src += sizeof(bmpinfo);

    if (bmpinfo.nplanes != 1 || bmpinfo.compress_type != 0)
        return NULL;
    if (bmpinfo.bitspp != 24 && bmpinfo.bitspp != 32)
        return NULL;

    
    bytesperline = bmpinfo.width * (bmpinfo.bitspp >> 3);
    if ((bmpinfo.width % 2) == 1)
        bytesperline += 3;
    return S2253_EncodeOverlayImage(
		data + (bmpinfo.height - 1) * bytesperline,
		bmpinfo.width,
		bmpinfo.height,
		0xffffff,
		-(int)bytesperline, 
		bmpinfo.bitspp == 32, // 0=RGB 1=RGBA
		ovl);
}

static unsigned char *S2253_RenderText(struct mid2253_osd_styledtext *st, struct s2253_overlay_image *ovl)
{
	MagickWand *wand = NULL;
	MagickWand *c_wand = NULL;
	DrawingWand *d_wand = NULL;
	PixelWand *p_wand = NULL;
	double *fm = NULL; // font metrics
	unsigned char *pixels = NULL;
	int width, height, y;
	double pad = st->style & MID2253_STYLE_OUTLINE ? 2 : 
		st->style & MID2253_STYLE_OUTLINE ? 1+(double)st->size / 8 : 1;
	unsigned char *data = NULL;

	static int mw_init = 0;
	if (!mw_init) {
		mw_init = 1;
		MagickWandGenesis();  // initialize once
	}

	wand = NewMagickWand();
	d_wand = NewDrawingWand();
	p_wand = NewPixelWand();

	PixelSetColor(p_wand, "none");
	MagickNewImage(wand, 1, 1, p_wand);  // need a temporary image to get fontmetrics

	if (st->font) 
		DrawSetFont(d_wand, st->font);
	DrawSetFontSize(d_wand, st->size);
	DrawSetTextAntialias(d_wand, MagickTrue);
	DrawSetStrokeWidth(d_wand, (double)(st->style & MID2253_STYLE_SHADOW ? 2 : pad*2));

	DrawSetFontWeight(d_wand, st->style & MID2253_STYLE_BOLD ? 900 : 100);
	DrawSetFontStyle(d_wand, st->style & MID2253_STYLE_ITALIC ? ItalicStyle : NormalStyle);
	// Underline bug: Stroke width ignored? looks bad
	DrawSetTextDecoration(d_wand, st->style & MID2253_STYLE_UNDERLINE ? UnderlineDecoration : NoDecoration);

	fm = MagickQueryFontMetrics(wand, d_wand, st->line);
	if (fm == NULL)
		goto done;
	width = (int)(fm[4] + 3 * pad) + (st->style & MID2253_STYLE_SHADOW ? 5 : 0);
	height = (int)(fm[5] + 2 * pad) + (st->style & MID2253_STYLE_SHADOW ? 5 : 0);
	y = (int)(fm[5] + fm[3] + pad);

	MagickRemoveImage(wand);
	MagickNewImage(wand, width, height, p_wand);

	PixelSetColor(p_wand, "white");
	DrawSetFillColor(d_wand, p_wand);

	if (st->style & MID2253_STYLE_OUTLINE) {
		PixelSetColor(p_wand, "black");
		PixelSetAlpha(p_wand, (double)st->outline/8);
		DrawSetStrokeColor(d_wand, p_wand);
		DrawAnnotation(d_wand, pad, y, (unsigned char*) st->line);
	}

	PixelSetColor(p_wand, "none");
	DrawSetStrokeColor(d_wand, p_wand);
	DrawAnnotation(d_wand, pad, y, (unsigned char*) st->line);
	MagickDrawImage(wand, d_wand);

	if (st->style & MID2253_STYLE_SHADOW) {
		MagickResetImagePage(wand, "");
		c_wand = CloneMagickWand(wand);
		PixelSetColor(p_wand, "none");
		PixelSetAlpha(p_wand, 1.0);
		MagickSetImageBackgroundColor(wand, p_wand);
		MagickShadowImage(wand, 70, 4, 5, 5);
		MagickCompositeImage(wand, c_wand, OverCompositeOp, 5, 5);
		DestroyMagickWand(c_wand);
	}

	c_wand = NewMagickWand();
	PixelSetColor(p_wand, "none");
	PixelSetAlpha(p_wand, (double)st->background/8);
	MagickNewImage(c_wand, width, height, p_wand);
	MagickCompositeImage(c_wand, wand, OverCompositeOp, 0, 0);

	pixels = (unsigned char*) malloc(width * height * 4);
	if (!pixels)
		goto done;
	
	MagickExportImagePixels(c_wand, 0, 0, width, height, "RGBA", CharPixel, pixels);
	data = S2253_EncodeOverlayImage(pixels, width, height, st->color, width*4, 1, ovl);

done:
	if (pixels)
		free((void *)pixels);
	if (wand)
		DestroyMagickWand(wand);
	if (c_wand)
		DestroyMagickWand(c_wand);
	if (d_wand)
		DestroyDrawingWand(d_wand);
	if (p_wand)
		DestroyPixelWand(p_wand);
	return data;
}

static int osd_to_ovl_channel(int osdChan)
{
	switch (osdChan) {
	case 0: return 1; // 1=StreamA
	case 1: return 2; // 2=StreamB
	case 2: return 0; // 0=Output
	case 3: return 3; // 3=StreamA&B
	default: return 0; // 0=Output
	}
}

static int overlaySend(int fd, void *buf)
{
	int rc;
	rc = ioctl(fd, S2253_VIDIOC_OVERLAY, buf);
	if (rc < 0)
		perror("S2253_VIDIOC_OVERLAY");
	return rc;
}

static unsigned char *overlayCat(struct s2253_overlay_image *buf, unsigned char *data)
{
	int hdr_len = sizeof(struct s2253_overlay_image);
	int len = buf->length;
	data = realloc(data, len + hdr_len);
	if(!data) {
		fprintf(stderr, "Failed to reallocate %d bytes\n", len + hdr_len);
		abort();
	}
	memmove(data + hdr_len, data, len);
	memcpy(data, buf, hdr_len);
	return data;
}



MID2253_API int S2253_SetOsd(enum mid2253_osd_type type, union mid2253_osd_data *osd, int devid, int strmidx)
{
	//int chan;
	int hr = 0;
	struct s2253_osd m_osd = {};

	//S2253_PREAMBLE_DECODE(devid, strmidx);
	if (strmidx < 0 || strmidx > 2)
		return -1;

	if (osd == NULL)
		return -1;

	if (type == MID2253_OSDTYPE_TEXT) {
		//chan = osd->osdtext.osdChan;
		m_osd.ddmm = osd->osdtext.ddmm;
		m_osd.fraction = osd->osdtext.fraction;
		m_osd.osdChan = osd->osdtext.osdChan;
		m_osd.osdOn = osd->osdtext.osdOn;
		m_osd.positionTop = osd->osdtext.positionTop;
		m_osd.transparent = osd->osdtext.transparent;
		m_osd.year2 = osd->osdtext.year2;
		m_osd.xOffset = osd->osdtext.xoffset;
		m_osd.yOffset = osd->osdtext.yoffset;
		memcpy(m_osd.line, osd->osdtext.line, MID2253_MAX_OSDTEXT);

	} else if (type == MID2253_OSDTYPE_LONGTEXT) {
        	//chan = osd->osdlongtext.osdChan;
		m_osd.ddmm = osd->osdlongtext.ddmm;
		m_osd.fraction = osd->osdlongtext.fraction;
		m_osd.osdChan = osd->osdlongtext.osdChan;
		m_osd.osdOn = osd->osdlongtext.osdOn;
		m_osd.positionTop = osd->osdlongtext.positionTop;
		m_osd.transparent = osd->osdlongtext.transparent;
		m_osd.year2 = osd->osdlongtext.year2;
		m_osd.xOffset = osd->osdlongtext.xoffset;
		m_osd.yOffset = osd->osdlongtext.yoffset;
		memcpy(m_osd.line, osd->osdlongtext.line, MID2253_MAX_OSDLONGTEXT);

	} else if (type == MID2253_OSDTYPE_STYLEDTEXT) {
		struct s2253_overlay_image ovl1;
		unsigned char *data = NULL;
		ovl1.id = osd->osdstyledtext.id;
		ovl1.channel = osd_to_ovl_channel(osd->osdstyledtext.osdChan);
		ovl1.transparent = osd->osdstyledtext.background;
		ovl1.update = MID2253_OVERLAY_UPDATE_DISPLAY;
		ovl1.length = 0;
		if (osd->osdstyledtext.osdOn != 0) {
			ovl1.update |= MID2253_OVERLAY_UPDATE_POSITION;
			ovl1.xOffset = osd->osdstyledtext.xoffset;
			ovl1.yOffset = osd->osdstyledtext.yoffset;
			if (osd->osdstyledtext.line != NULL) {
				data = S2253_RenderText(&osd->osdstyledtext, &ovl1);
				if (!data)
					return -1;
			}
		}
		
		//hr = theDLL.m_pDC[devid][strmidx]->SetOverlay(&ovl1, data);
		data = overlayCat(&ovl1, data);
		hr = overlaySend(devid, data);
		free(data);
		return hr;

	} else if (type == MID2253_OSDTYPE_RECT) {
		struct s2253_overlay_image ovl1;
		unsigned char *data = NULL;
		ovl1.id = osd->osdrect.id;
		ovl1.channel = osd_to_ovl_channel(osd->osdrect.osdChan);
		ovl1.transparent = 0;
		ovl1.update = MID2253_OVERLAY_UPDATE_DISPLAY;
		ovl1.length = 0;
		if (osd->osdrect.osdOn != 0) {
			ovl1.update |= MID2253_OVERLAY_UPDATE_POSITION;
			ovl1.xOffset = osd->osdrect.xoffset;
			ovl1.yOffset = osd->osdrect.yoffset;
			if (osd->osdrect.width != 0 && osd->osdrect.height != 0) {
				data = S2253_RenderRect(osd->osdrect.width, osd->osdrect.height, osd->osdrect.color, &ovl1);
				if (!data)
					return -1;
			}
		}
		
		//hr = theDLL.m_pDC[devid][strmidx]->SetOverlay(&ovl1, data);
		data = overlayCat(&ovl1, data);
		hr = overlaySend(devid, data);
		free(data);
		return hr;

	} else if (type == MID2253_OSDTYPE_BMP) {
		struct s2253_overlay_image ovl1;
		unsigned char *data = NULL;

		ovl1.id = osd->osdbmp.id;
		ovl1.channel = osd_to_ovl_channel(osd->osdbmp.osdChan);
		ovl1.transparent = 8;
		ovl1.update = MID2253_OVERLAY_UPDATE_DISPLAY;
		ovl1.length = 0;
		if (osd->osdbmp.osdOn != 0) {
			ovl1.update |= MID2253_OVERLAY_UPDATE_POSITION;
			ovl1.xOffset = osd->osdbmp.xoffset;
			ovl1.yOffset = osd->osdbmp.yoffset;
			data = S2253_RenderBMP((unsigned char *)osd->osdbmp.bmp, &ovl1);
			if (!data)
				return -1;
		}
		
		//theDLL.m_pDC[devid][strmidx]->SetOverlay(&ovl1, data);
		data = overlayCat(&ovl1, data);
		hr = overlaySend(devid, data);
		free(data);
		return 0;

	} else {
		return -1;
	}

	m_osd.osdBmp = 0;
	overlaySend(devid, &m_osd);

	return 0;
}
