/* hacktv - Analogue video transmitter for the HackRF                    */
/*=======================================================================*/
/* Copyright 2019 Philip Heron <phil@sanslogic.co.uk>                    */
/*                                                                       */
/* 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 3 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, see <http://www.gnu.org/licenses/>. */

/* -=== Videocrypt S encoder ===-
 * 
 * This is untested on real hardware and should be considered just a
 * simulation. The VBI data *may* be valid but the shuffling sequence
 * is definitly not. There may also be colour distortion due to hacktv
 * not operating at the specified sample rate of FPAL * 4.
*/

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "video.h"

#include "videocrypts-sequence.h"

/* The first line of each block */
static const int _block_start[12] = {
	 28,  75, 122, 169, 216, 263,
	340, 387, 434, 481, 528, 575,
};

/* Header synchronisation sequence */
static const uint8_t _sequence[8] = {
	0x81,0x92,0xA3,0xB4,0xC5,0xD6,0xE7,0xF0,
};

/* Hamming codes */
static const uint8_t _hamming[16] = {
	0x15,0x02,0x49,0x5E,0x64,0x73,0x38,0x2F,
	0xD0,0xC7,0x8C,0x9B,0xA1,0xB6,0xFD,0xEA,
};

/* The mode byte:
 * 
 * 0x01 = Clear
 * 0x11 = Free access, scrambled
 * 0x21 = Conditional access, scrambled
 *
 * The LSB of the channel byte controls audio inversion.
 *
 * 0 = No inversion
 * 1 = Inversion
*/

/* Blocks for VCS free-access decoding */
static const _vcs_block_t _fa_blocks[] = { { 0x11, 0x00, 0x0000000000000000 }};

/* Blocks for VCS conditional-access decoding, sampled from BBC Select */
static const _vcs_block_t _bbc_blocks[] = {
	{
		0x21, 0x05, 0x0000000000000000,
		{
			{ 0xE1,0x3A,0xA9,0x00,0x01,0x00,0x40,0xCC,0x52,0xDD,0xF7,0x87,0x88,0x89,0x8A,0x8B,0x8D,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0xC1,0x84,0x96,0xCD },
			{ 0xE1,0x3A,0x28,0x00,0x01,0x00,0x40,0x31,0x03,0x27,0x6C,0x3E,0x3F,0x41,0x42,0x43,0x44,0x45,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0xD3,0x00,0xC4,0xD2 },
			{ 0xE1,0x3A,0xA4,0x00,0x01,0x00,0x40,0x72,0x4D,0x83,0xF3,0x51,0x52,0x53,0x54,0x55,0x56,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x6B,0x76,0xAD,0x86 },
			{ 0xE1,0x3A,0xA5,0x00,0x01,0x00,0x40,0x04,0x81,0xFC,0xF1,0x63,0x64,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x6B,0xC7,0xC1,0x36 },
			{ 0xF9,0x3A,0xA1,0x25,0x07,0x20,0x20,0x02,0x4C,0x7A,0x8E,0xCA,0x7D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x97,0x39,0xDB },
			{ 0xF9,0x3A,0xA1,0x25,0x07,0x20,0x20,0x02,0x4C,0x7A,0x8E,0xCA,0x7D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x97,0x39,0xDB },
			{ 0xE1,0x3A,0x3F,0x00,0x01,0x00,0x40,0x8E,0xED,0x2B,0xA5,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,0x80,0x82,0x83,0x85,0x86,0x15,0x74,0xFD,0x97 },
			{ 0x21,0x00,0x78,0x01,0x18,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x48,0x41,0x43,0x4B,0x54,0x56,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x05,0x05,0x05,0x05,0x95,0x37 },

		}
	},
	{
		0x21, 0x05, 0x0000000000000000, /* FCNT: 00, OSD: "!.............................O" */
		{
			{ 0xE1,0x3A,0xAC,0x00,0x01,0x00,0x40,0x82,0xEE,0x46,0xF0,0xD4,0xD5,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xE0,0xE1,0xE2,0xE3,0xE4,0xE6,0x88,0xFF,0xF6,0xC6 },
			{ 0xE1,0x3A,0xAB,0x00,0x01,0x00,0x40,0x30,0xF9,0x0C,0x32,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA7,0xA8,0xA9,0xD9,0x89,0x48,0xB2 },
			{ 0xE1,0x3A,0x33,0x00,0x01,0x00,0x40,0xB6,0x7D,0x8A,0xA6,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xC2,0xCC,0xCC,0xA8,0xAA,0xAB,0xAC,0x5E,0x95,0xC4,0x18 },
			{ 0xE1,0x3A,0x2A,0x00,0x01,0x00,0x40,0x15,0xAB,0x58,0xB4,0xAD,0xAE,0xAF,0xB0,0xB1,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBF,0xC0,0xC1,0x21,0xC4,0xB2,0x4F },
			{ 0xF9,0x3A,0xA1,0x25,0x07,0x20,0x20,0x02,0x4C,0x72,0xB1,0xF3,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x50,0xD6,0xD2 },
			{ 0xF9,0x3A,0xA1,0x25,0x07,0x20,0x20,0x02,0x4C,0x72,0xB1,0xF3,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x50,0xD6,0xD2 },
			{ 0xE1,0x3A,0xB5,0x00,0x01,0x00,0x40,0x23,0xBE,0x75,0xE7,0xC2,0xC3,0xC4,0xC5,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xD0,0xD1,0xD2,0xD3,0xE3,0x66,0x51,0x8D },
			{ 0x21,0x00,0x18,0x01,0x01,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDD,0x4F },
		}
	},
	{
		0x21, 0x05, 0x0000000000000000, /* FCNT: 20, OSD: "!............................ /" */
		{
			{ 0xE1,0x3A,0xB0,0x00,0x01,0x00,0x40,0x49,0xBA,0x1D,0xE4,0x2E,0x2F,0x30,0x31,0x32,0x33,0x35,0x36,0x37,0x38,0x39,0x0E,0x2B,0x2C,0x2D,0x2F,0x29,0x9F,0x54,0x60 },
			{ 0xE1,0x3A,0x22,0x00,0x01,0x00,0x40,0x85,0x0C,0x99,0xB1,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF6,0xF7,0x61,0x0E,0xD5,0x0F },
			{ 0xE1,0x3A,0xAB,0x00,0x01,0x00,0x40,0x30,0xF9,0x0C,0x34,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x07,0x08,0x0A,0xB9,0xE5,0xAB,0x61 },
			{ 0xE1,0x3A,0xA1,0x00,0x01,0x00,0x40,0xBC,0xB2,0x1E,0xF3,0x0B,0x0D,0x0E,0x0F,0x10,0x11,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x00,0x98,0x89,0x9C },
			{ 0xF9,0x3A,0xA1,0x25,0x07,0x20,0x20,0x02,0x4C,0xC7,0xD3,0x1E,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF9,0x85,0xB1,0x15 },
			{ 0xF9,0x3A,0xA1,0x25,0x07,0x20,0x20,0x02,0x4C,0xC7,0xD3,0x1E,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF9,0x85,0xB1,0x15 },
			{ 0xE1,0x3A,0xAB,0x00,0x01,0x00,0x40,0x30,0xF9,0x0C,0x37,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x17,0xF5,0x93,0xCA },
			{ 0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x2F },
		}
	},
};

/* Reverse bits in an 8-bit value */
static uint8_t _reverse(uint8_t b)
{
	b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
	b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
	b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
	return(b);
}

/* Reverse nibbles in a byte */
static inline uint8_t _rnibble(uint8_t a)
{
	return((a >> 4) | (a << 4));
}

/* Apply VBI frame interleaving */
static void _interleave(uint8_t *frame)
{
	int b, i, j;
	int offset[6] = { 0, 6, 12, 20, 26, 32 };
	uint8_t r[8];
	uint8_t m;
	
	for(b = 0; b < 6; b++)
	{
		uint8_t *s = frame + offset[b];
		
		s[0] = _reverse(s[0]);
		s[7] = _reverse(s[7]);
		
		for(i = 0, m = 0x80; i < 8; i++, m >>= 1)
		{
			r[i] = 0x00;
			for(j = 0; j < 8; j++)
			{
				r[i] |= ((m & s[j]) ? 1 : 0) << j;
			}
		}
		
		memcpy(s, r, 8);
	}
}

/* Encode VBI data */
static void _encode_vbi(uint8_t vbi[40], const uint8_t data[16], uint8_t a, uint8_t b)
{
	int x;
	
	/* Set the information (a, b) and initial check bytes for each field */
	vbi[ 9] = vbi[ 0] = a;
	vbi[19] = vbi[10] = b;
	
	/* Copy the eight security bytes for each field,
	 * while updating the check byte */
	for(x = 0; x < 8; x++)
	{
		vbi[ 9] += vbi[ 1 + x] = data[0 + x];
		vbi[19] += vbi[11 + x] = data[8 + x];
	}
	
	/* Hamming code the VBI data */
	for(x = 19; x >= 0; x--)
	{
		vbi[x * 2 + 1] = _hamming[vbi[x] & 0x0F];
		vbi[x * 2 + 0] = _hamming[vbi[x] >> 4];
	}
	
	/* Interleave the VBI data */
	_interleave(vbi);
}

int vcs_init(vcs_t *s, vid_t *vid, const char *mode)
{
	double f;
	int x;
	
	memset(s, 0, sizeof(vcs_t));
	
	s->counter  = 0;
	
	if(strcmp(mode, "free") == 0)
	{
		s->blocks    = _fa_blocks;
		s->block_len = sizeof(_fa_blocks) / sizeof(_vcs_block_t);
	}
	else if(strcmp(mode, "conditional") == 0)
	{
		s->blocks    = _bbc_blocks;
		s->block_len = sizeof(_bbc_blocks) / sizeof(_vcs_block_t);
	}
	else
	{
		fprintf(stderr, "Unrecognised Videocrypt S mode '%s'.\n", mode);
		return(VID_ERROR);
	}
	
	s->block_num = 0;
	
	/* Sample rate ratio */
	f = (double) vid->width / VCS_WIDTH;
	
	/* Quick and dirty sample rate conversion array */
	for(x = 0; x < VCS_WIDTH; x++)
	{
		s->video_scale[x] = round(x * f);
	}
	
	return(VID_OK);
}

void vcs_free(vcs_t *s)
{
	/* Nothing */
}

int vcs_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines)
{
	vcs_t *v = arg;
	int x, j;
	uint8_t *bline = NULL;
	vid_line_t *l = lines[0];
	
	/* Swap the active line with the oldest line in the delay buffer,
	 * with active video offset in j if necessary. */
	j = 0;
	
	if((l->line >=  28 && l->line <= 309) ||
	   (l->line >= 340 && l->line <= 621))
	{
		int block;
		int bline;
		
		/* Calculate the line number,
		 *   0 - 281 top field,
		 * 282 - 563 bottom field
		*/
		x = l->line - (l->line < 340 ? 28 : 340 - 282);
		
		/* Calculate block number and block line */
		block = x / 47;
		bline = x % 47;
		
		if(bline == 0)
		{
			int i;
			
			for(i = 0; i < 47; i++)
			{
				v->block[i] = _fa_sequence[v->counter & 0xFF][block][i];
			}
		}
		
		/* Calculate target block/line */
		block = (block + 1) % 12;
		bline = v->block[bline];
		
		/* Calculate position in delay buffer */
		j = (_block_start[block] + bline) - l->line;
		if(j < 0) j += s->conf.lines;
	}
	
	if(j > 0)
	{
		int16_t *dline = lines[j]->output;
		for(x = s->active_left * 2; x < s->width * 2; x += 2)
		{
			l->output[x] = dline[x];
		}
	}
	
	/* On the first line of each frame, generate the VBI data */
	if(l->line == 1)
	{
		uint8_t crc;
		
		if((v->counter & 3) == 0)
		{
			/* The active message is updated every 4th frame */
			for(crc = x = 0; x < 31; x++)
			{
				crc += v->message[x] = v->blocks[v->block_num].messages[(v->counter >> 2) & 7][x];
			}
			
			v->message[x] = ~crc + 1;
		}
		
		if((v->counter & 2) == 0)
		{
			/* The first half of the message */
			_encode_vbi(
				v->vbi, v->message,
				_sequence[(v->counter >> 2) & 0x07],
				v->counter & 0xFF
			);
		}
		else
		{
			/* The second half of the message */
			_encode_vbi(
				v->vbi, v->message + 16,
				_rnibble(_sequence[(v->counter >> 2) & 0x07]),
				(v->counter & 0x08 ? v->blocks[v->block_num].channel : v->blocks[v->block_num].mode)
			);
		}
		
		v->counter++;
		
		/* After 32 frames, advance to the next VCS block and codeword */
		if((v->counter & 0x1F) == 0)
		{
			/* Apply the current block codeword */
			if(v->blocks)
			{
				//v->cw = v->blocks[v->block_num].codeword;
			}
			
			/* Move to the next block */
			if(++v->block_num == v->block_len)
			{
				v->block_num = 0;
			}
		}
	}
	
	/* Set a pointer to the VBI data to render on this line, or NULL if none */
	if(l->line >= VCS_VBI_FIELD_1_START &&
	   l->line <  VCS_VBI_FIELD_1_START + VCS_VBI_LINES_PER_FIELD)
	{
		/* Top field VBI */
		bline = &v->vbi[(l->line - VCS_VBI_FIELD_1_START) * VCS_VBI_BYTES_PER_LINE];
	}
	else if(l->line >= VCS_VBI_FIELD_2_START &&
	        l->line <  VCS_VBI_FIELD_2_START + VCS_VBI_LINES_PER_FIELD)
	{
		/* Bottom field VBI */
		bline = &v->vbi[(l->line - VCS_VBI_FIELD_2_START + VCS_VBI_LINES_PER_FIELD) * VCS_VBI_BYTES_PER_LINE];
	}
	
	if(bline)
	{
		int b, c;
		
		/* Videocrypt S's VBI data sits in the active video area. Clear it first */
		for(x = s->active_left; x < s->active_left + s->active_width; x++)
		{
			l->output[x * 2] = s->black_level;
		}
		
		x = v->video_scale[VCS_VBI_LEFT];
		
		for(b = 0; b < VCS_VBI_BITS_PER_LINE; b++)
		{
			c = (bline[b / 8] >> (b % 8)) & 1;
			c = c ? s->white_level : s->black_level;
			
			for(; x < v->video_scale[VCS_VBI_LEFT + VCS_VBI_SAMPLES_PER_BIT * (b + 1)]; x++)
			{
				l->output[x * 2] = c;
			}
		}
		
		l->vbialloc = 1;
	}
	
	return(1);
}

