#include "ccx_decoders_708_output.h"
#include "ccx_decoders_708.h"
#include "ccx_encoders_common.h"
#include "utility.h"
#include "ccx_common_common.h"

#if defined(WIN32)
extern void ccxr_close_handle(void *handle);
#endif

int dtvcc_is_row_empty(dtvcc_tv_screen *tv, int row_index)
{
	for (int j = 0; j < CCX_DTVCC_SCREENGRID_COLUMNS; j++)
	{
		if (CCX_DTVCC_SYM_IS_SET(tv->chars[row_index][j]))
			return 0;
	}
	return 1;
}

int dtvcc_is_screen_empty(dtvcc_tv_screen *tv, struct encoder_ctx *encoder)
{
	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			// we will write subtitle
			encoder->cea_708_counter++;
			return 0;
		}
	}
	return 1;
}

void dtvcc_get_write_interval(dtvcc_tv_screen *tv, int row_index, int *first, int *last)
{
	for (*first = 0; *first < CCX_DTVCC_SCREENGRID_COLUMNS; (*first)++)
		if (CCX_DTVCC_SYM_IS_SET(tv->chars[row_index][*first]))
			break;
	for (*last = CCX_DTVCC_SCREENGRID_COLUMNS - 1; *last > 0; (*last)--)
		if (CCX_DTVCC_SYM_IS_SET(tv->chars[row_index][*last]))
			break;
}

void dtvcc_color_to_hex(int color, unsigned *hR, unsigned *hG, unsigned *hB)
{
	*hR = (unsigned)(color >> 4);
	*hG = (unsigned)((color >> 2) & 0x3);
	*hB = (unsigned)(color & 0x3);
	ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] Color: %d [%06x] %u %u %u\n",
				     color, color, *hR, *hG, *hB);
}

void dtvcc_change_pen_colors(dtvcc_tv_screen *tv, dtvcc_pen_color pen_color, int row_index, int column_index, struct encoder_ctx *encoder, size_t *buf_len, int open)
{
	if (encoder->no_font_color)
		return;

	char *buf = (char *)encoder->buffer;
	size_t remaining = INITIAL_ENC_BUFFER_CAPACITY - *buf_len;

	dtvcc_pen_color new_pen_color;
	if (column_index >= CCX_DTVCC_SCREENGRID_COLUMNS)
		new_pen_color = dtvcc_default_pen_color;
	else
		new_pen_color = tv->pen_colors[row_index][column_index];
	if (pen_color.fg_color != new_pen_color.fg_color)
	{
		if (pen_color.fg_color != 0x3f && !open)
		{
			int written = snprintf(buf + (*buf_len), remaining, "</font>"); // should close older non-white color
			if (written > 0 && (size_t)written < remaining)
				(*buf_len) += written;
		}

		if (new_pen_color.fg_color != 0x3f && open)
		{
			unsigned red, green, blue;
			dtvcc_color_to_hex(new_pen_color.fg_color, &red, &green, &blue);
			red = (255 / 3) * red;
			green = (255 / 3) * green;
			blue = (255 / 3) * blue;
			remaining = INITIAL_ENC_BUFFER_CAPACITY - *buf_len;
			int written = snprintf(buf + (*buf_len), remaining, "<font color=\"#%02x%02x%02x\">", red, green, blue);
			if (written > 0 && (size_t)written < remaining)
				(*buf_len) += written;
		}
	}
}

void dtvcc_change_pen_attribs(dtvcc_tv_screen *tv, dtvcc_pen_attribs pen_attribs, int row_index, int column_index, struct encoder_ctx *encoder, size_t *buf_len, int open)
{
	if (encoder->no_font_color)
		return;

	char *buf = (char *)encoder->buffer;
	size_t remaining;
	int written;

	dtvcc_pen_attribs new_pen_attribs;
	if (column_index >= CCX_DTVCC_SCREENGRID_COLUMNS)
		new_pen_attribs = dtvcc_default_pen_attribs;
	else
		new_pen_attribs = tv->pen_attribs[row_index][column_index];
	if (pen_attribs.italic != new_pen_attribs.italic)
	{
		remaining = INITIAL_ENC_BUFFER_CAPACITY - *buf_len;
		if (pen_attribs.italic && !open)
		{
			written = snprintf(buf + (*buf_len), remaining, "</i>");
			if (written > 0 && (size_t)written < remaining)
				(*buf_len) += written;
		}
		if (!pen_attribs.italic && open)
		{
			written = snprintf(buf + (*buf_len), remaining, "<i>");
			if (written > 0 && (size_t)written < remaining)
				(*buf_len) += written;
		}
	}
	if (pen_attribs.underline != new_pen_attribs.underline)
	{
		remaining = INITIAL_ENC_BUFFER_CAPACITY - *buf_len;
		if (pen_attribs.underline && !open)
		{
			written = snprintf(buf + (*buf_len), remaining, "</u>");
			if (written > 0 && (size_t)written < remaining)
				(*buf_len) += written;
		}
		if (!pen_attribs.underline && open)
		{
			written = snprintf(buf + (*buf_len), remaining, "<u>");
			if (written > 0 && (size_t)written < remaining)
				(*buf_len) += written;
		}
	}
}

size_t write_utf16_char(unsigned short utf16_char, char *out)
{
	// Always write 2 bytes for consistent UTF-16BE encoding.
	// Previously, this function wrote 1 byte for ASCII characters and 2 bytes
	// for non-ASCII, creating an invalid mix that iconv couldn't handle properly.
	// This caused garbled output with Japanese/Chinese characters (issue #1451).
	out[0] = (unsigned char)(utf16_char >> 8);
	out[1] = (unsigned char)(utf16_char & 0xff);
	return 2;
}

void dtvcc_write_row(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, int row_index, struct encoder_ctx *encoder, int use_colors)
{
	dtvcc_tv_screen *tv = decoder->tv;
	char *buf = (char *)encoder->buffer;
	size_t buf_len = 0;
	memset(buf, 0, INITIAL_ENC_BUFFER_CAPACITY * sizeof(char));
	int first, last;

	int fd = encoder->dtvcc_writers[tv->service_number - 1].fd;

	dtvcc_pen_color pen_color = dtvcc_default_pen_color;
	dtvcc_pen_attribs pen_attribs = dtvcc_default_pen_attribs;
	dtvcc_get_write_interval(tv, row_index, &first, &last);

	for (int i = 0; i < last + 1; i++)
	{

		if (use_colors)
			dtvcc_change_pen_colors(tv, pen_color, row_index, i, encoder, &buf_len, 0);
		dtvcc_change_pen_attribs(tv, pen_attribs, row_index, i, encoder, &buf_len, 0);
		dtvcc_change_pen_attribs(tv, pen_attribs, row_index, i, encoder, &buf_len, 1);
		if (use_colors)
			dtvcc_change_pen_colors(tv, pen_color, row_index, i, encoder, &buf_len, 1);

		pen_color = tv->pen_colors[row_index][i];
		pen_attribs = tv->pen_attribs[row_index][i];
		if (i < first)
		{
			size_t size = write_utf16_char(' ', buf + buf_len);
			buf_len += size;
		}
		else
		{
			size_t size = write_utf16_char(tv->chars[row_index][i].sym, buf + buf_len);
			buf_len += size;
		}
	}

	// there can be unclosed tags or colors after the last symbol in a row
	if (use_colors)
		dtvcc_change_pen_colors(tv, pen_color, row_index, CCX_DTVCC_SCREENGRID_COLUMNS, encoder, &buf_len, 0);
	dtvcc_change_pen_attribs(tv, pen_attribs, row_index, CCX_DTVCC_SCREENGRID_COLUMNS, encoder, &buf_len, 0);
	// Tags can still be crossed e.g <f><i>text</f></i>, but testing HTML code has shown that they still are handled correctly.
	// In case of errors fix it once and for all.

	if (writer->cd != (iconv_t)-1)
	{
		char *encoded_buf = calloc(INITIAL_ENC_BUFFER_CAPACITY, sizeof(char));
		if (!encoded_buf)
			ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "dtvcc_write_row");

		char *encoded_buf_start = encoded_buf;

		size_t in_bytes_left = buf_len;
		size_t out_bytes_left = INITIAL_ENC_BUFFER_CAPACITY * sizeof(char);

		size_t result = iconv(writer->cd, &buf, &in_bytes_left, &encoded_buf, &out_bytes_left);

		if (result == -1)
			ccx_common_logging.log_ftn("[CEA-708] dtvcc_write_row: "
						   "conversion failed: %s\n",
						   strerror(errno));

		write_wrapped(fd, encoded_buf_start, encoded_buf - encoded_buf_start);

		free(encoded_buf_start);
	}
	else
	{
		write_wrapped(fd, buf, buf_len);
	}
}

void dtvcc_write_srt(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
	dtvcc_tv_screen *tv = decoder->tv;
	if (dtvcc_is_screen_empty(tv, encoder))
		return;

	if (tv->time_ms_show + encoder->subs_delay < 0)
		return;

	char *buf = (char *)encoder->buffer;
	memset(buf, 0, INITIAL_ENC_BUFFER_CAPACITY);
	size_t buf_len = 0;
	size_t remaining = INITIAL_ENC_BUFFER_CAPACITY;
	int written;

	written = snprintf(buf, remaining, "%u%s", encoder->cea_708_counter, encoder->encoded_crlf);
	if (written > 0 && (size_t)written < remaining)
		buf_len += written;
	remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;
	print_mstime_buff(tv->time_ms_show + encoder->subs_delay,
			  "%02u:%02u:%02u,%03u", buf + buf_len);
	buf_len = strlen(buf);
	remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;
	written = snprintf(buf + buf_len, remaining, " --> ");
	if (written > 0 && (size_t)written < remaining)
		buf_len += written;
	remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;
	print_mstime_buff(tv->time_ms_hide + encoder->subs_delay,
			  "%02u:%02u:%02u,%03u", buf + buf_len);
	buf_len = strlen(buf);
	remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;
	written = snprintf(buf + buf_len, remaining, "%s", (char *)encoder->encoded_crlf);
	if (written > 0 && (size_t)written < remaining)
		buf_len += written;

	write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, buf_len);

	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			dtvcc_write_row(writer, decoder, i, encoder, 1);
			write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd,
				      encoder->encoded_crlf, encoder->encoded_crlf_length);
		}
	}
	write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd,
		      encoder->encoded_crlf, encoder->encoded_crlf_length);
}

void dtvcc_write_debug(dtvcc_tv_screen *tv)
{
	char tbuf1[SUBLINESIZE],
	    tbuf2[SUBLINESIZE];

	print_mstime_buff(tv->time_ms_show, "%02u:%02u:%02u:%03u", tbuf1);
	print_mstime_buff(tv->time_ms_hide, "%02u:%02u:%02u:%03u", tbuf2);

	ccx_common_logging.debug_ftn(CCX_DMT_GENERIC_NOTICES, "\r%s --> %s\n", tbuf1, tbuf2);
	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			int first, last;
			dtvcc_get_write_interval(tv, i, &first, &last);
			for (int j = first; j <= last; j++)
				ccx_common_logging.debug_ftn(CCX_DMT_GENERIC_NOTICES, "%c", tv->chars[i][j]);
			ccx_common_logging.debug_ftn(CCX_DMT_GENERIC_NOTICES, "\n");
		}
	}
}

void dtvcc_write_transcript(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
	dtvcc_tv_screen *tv = decoder->tv;
	if (dtvcc_is_screen_empty(tv, encoder))
		return;

	if (tv->time_ms_show + encoder->subs_delay < 0) // Drop screens that because of subs_delay start too early
		return;

	char *buf = (char *)encoder->buffer;
	size_t buf_len;
	size_t remaining;
	int written;

	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			buf[0] = 0;
			buf_len = 0;

			if (encoder->transcript_settings->showStartTime)
			{
				print_mstime_buff(tv->time_ms_show + encoder->subs_delay,
						  "%02u:%02u:%02u,%03u|", buf + buf_len);
				buf_len = strlen(buf);
			}

			if (encoder->transcript_settings->showEndTime)
			{
				print_mstime_buff(tv->time_ms_hide + encoder->subs_delay,
						  "%02u:%02u:%02u,%03u|", buf + buf_len);
				buf_len = strlen(buf);
			}

			if (encoder->transcript_settings->showCC)
			{
				remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;
				written = snprintf(buf + buf_len, remaining, "CC1|"); // always CC1 because CEA-708 is field-independent
				if (written > 0 && (size_t)written < remaining)
					buf_len += written;
			}

			if (encoder->transcript_settings->showMode)
			{
				remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;
				written = snprintf(buf + buf_len, remaining, "POP|"); // TODO caption mode(pop, rollup, etc.)
				if (written > 0 && (size_t)written < remaining)
					buf_len += written;
			}

			if (buf_len != 0)
				write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, buf_len);

			dtvcc_write_row(writer, decoder, i, encoder, 0);
			write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd,
				      encoder->encoded_crlf, encoder->encoded_crlf_length);
		}
	}
}

void dtvcc_write_sami_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder)
{
	char *buf = (char *)encoder->buffer;
	memset(buf, 0, INITIAL_ENC_BUFFER_CAPACITY);
	size_t buf_len = 0;
	size_t remaining = INITIAL_ENC_BUFFER_CAPACITY;
	int written;

#define SAMI_SNPRINTF(fmt, ...)                                                   \
	do                                                                        \
	{                                                                         \
		remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;                \
		written = snprintf(buf + buf_len, remaining, fmt, ##__VA_ARGS__); \
		if (written > 0 && (size_t)written < remaining)                   \
			buf_len += written;                                       \
	} while (0)

	SAMI_SNPRINTF("<sami>%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("<head>%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("<style type=\"text/css\">%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("<!--%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("p {margin-left: 16pt; margin-right: 16pt; margin-bottom: 16pt; margin-top: 16pt;%s",
		      encoder->encoded_crlf);
	SAMI_SNPRINTF("text-align: center; font-size: 18pt; font-family: arial; font-weight: bold; color: #f0f0f0;}%s",
		      encoder->encoded_crlf);
	SAMI_SNPRINTF(".unknowncc {Name:Unknown; lang:en-US; SAMIType:CC;}%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("-->%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("</style>%s", encoder->encoded_crlf);
	SAMI_SNPRINTF("</head>%s%s", encoder->encoded_crlf, encoder->encoded_crlf);
	SAMI_SNPRINTF("<body>%s", encoder->encoded_crlf);

#undef SAMI_SNPRINTF

	write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, buf_len);
}

void dtvcc_write_sami_footer(dtvcc_tv_screen *tv, struct encoder_ctx *encoder)
{
	char *buf = (char *)encoder->buffer;
	int written = snprintf(buf, INITIAL_ENC_BUFFER_CAPACITY, "</body></sami>");
	if (written > 0 && (size_t)written < INITIAL_ENC_BUFFER_CAPACITY)
		write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, written);
	write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd,
		      encoder->encoded_crlf, encoder->encoded_crlf_length);
}

void dtvcc_write_sami(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
	dtvcc_tv_screen *tv = decoder->tv;
	if (dtvcc_is_screen_empty(tv, encoder))
		return;

	if (tv->time_ms_show + encoder->subs_delay < 0)
		return;

	if (tv->cc_count == 1)
		dtvcc_write_sami_header(tv, encoder);

	char *buf = (char *)encoder->buffer;
	int written;

	buf[0] = 0;
	written = snprintf(buf, INITIAL_ENC_BUFFER_CAPACITY, "<sync start=%llu><p class=\"unknowncc\">%s",
			   (unsigned long long)tv->time_ms_show + encoder->subs_delay,
			   encoder->encoded_crlf);
	if (written > 0 && (size_t)written < INITIAL_ENC_BUFFER_CAPACITY)
		write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, written);

	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			dtvcc_write_row(writer, decoder, i, encoder, 1);
			write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd,
				      encoder->encoded_br, encoder->encoded_br_length);
			write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd,
				      encoder->encoded_crlf, encoder->encoded_crlf_length);
		}
	}

	written = snprintf(buf, INITIAL_ENC_BUFFER_CAPACITY, "<sync start=%llu><p class=\"unknowncc\">&nbsp;</p></sync>%s%s",
			   (unsigned long long)tv->time_ms_hide + encoder->subs_delay,
			   encoder->encoded_crlf, encoder->encoded_crlf);
	if (written > 0 && (size_t)written < INITIAL_ENC_BUFFER_CAPACITY)
		write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, written);
}

unsigned char adjust_odd_parity(const unsigned char value)
{
	unsigned int i, ones = 0;
	for (i = 0; i < 8; i++)
	{
		if ((value & (1 << i)) != 0)
		{
			ones += 1;
		}
	}
	if (ones % 2 == 0)
	{
		// make the number of ones always odd
		return value | 0b10000000;
	}
	return value;
}

void dtvcc_write_scc_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder)
{
	char *buf = (char *)encoder->buffer;
	// 18 characters long + 2 new lines = 20 characters total
	memset(buf, 0, INITIAL_ENC_BUFFER_CAPACITY);
	int written = snprintf(buf, INITIAL_ENC_BUFFER_CAPACITY, "Scenarist_SCC V1.0\n\n");

	if (written > 0 && (size_t)written < INITIAL_ENC_BUFFER_CAPACITY)
		write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, written);
}

int count_captions_lines_scc(dtvcc_tv_screen *tv)
{
	int count = 0;
	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			count++;
		}
	}

	return count;
}

/** This function is designed to assign appropriate SSC labels for positioning subtitles based on their length.
 * In some scenarios where the video stream provides lengthy subtitles that cannot fit within a single line.
 * Single-line subtitle can be placed in 15th row(most bottom row)
 * 2 line length subtitles can be placed in 14th and 15th row
 * 3 line length subtitles can be placed in 13th, 14th and 15th row
 */
void add_needed_scc_labels(char *buf, size_t buf_size, size_t *buf_len, int total_subtitle_count, int current_subtitle_count)
{
	size_t remaining = buf_size - *buf_len;
	int written;
	const char *label;

	switch (total_subtitle_count)
	{
		case 1:
			// row 15, column 00
			label = " 94e0 94e0";
			break;
		case 2:
			// 9440: row 14, column 00 | 94e0: row 15, column 00
			label = (current_subtitle_count == 1) ? " 9440 9440" : " 94e0 94e0";
			break;
		default:
			// 13e0: row 13, column 04 | 9440: row 14, column 00 | 94e0: row 15, column 00
			label = (current_subtitle_count == 1) ? " 13e0 13e0" : ((current_subtitle_count == 2) ? " 9440 9440" : " 94e0 94e0");
			break;
	}

	written = snprintf(buf + *buf_len, remaining, "%s", label);
	if (written > 0 && (size_t)written < remaining)
		*buf_len += written;
}

void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
	dtvcc_tv_screen *tv = decoder->tv;

	if (dtvcc_is_screen_empty(tv, encoder))
		return;

	if (tv->time_ms_show + encoder->subs_delay < 0)
		return;

	if (tv->cc_count == 2)
		dtvcc_write_scc_header(tv, encoder);

	char *buf = (char *)encoder->buffer;
	size_t buf_len;
	size_t remaining;
	int written;

	struct ccx_boundary_time time_show = get_time(tv->time_ms_show + encoder->subs_delay);
	// when hiding subtract a frame (1 frame = 34 ms)
	struct ccx_boundary_time time_end = get_time(tv->time_ms_hide + encoder->subs_delay - 34);

#define SCC_SNPRINTF(fmt, ...)                                                    \
	do                                                                        \
	{                                                                         \
		remaining = INITIAL_ENC_BUFFER_CAPACITY - buf_len;                \
		written = snprintf(buf + buf_len, remaining, fmt, ##__VA_ARGS__); \
		if (written > 0 && (size_t)written < remaining)                   \
			buf_len += written;                                       \
	} while (0)

	if (tv->old_cc_time_end > time_show.time_in_ms)
	{
		// Correct the frame delay
		time_show.time_in_ms -= 1000 / 29.97;
		print_scc_time(time_show, buf);
		buf_len = strlen(buf);
		SCC_SNPRINTF("\t942c 942c");
		time_show.time_in_ms += 1000 / 29.97;
		// Clear the buffer and start pop on caption
		SCC_SNPRINTF("94ae 94ae 9420 9420");
	}
	else if (tv->old_cc_time_end < time_show.time_in_ms)
	{
		// Clear the screen for new caption
		struct ccx_boundary_time time_to_display = get_time(tv->old_cc_time_end);
		print_scc_time(time_to_display, buf);
		buf_len = strlen(buf);
		SCC_SNPRINTF("\t942c 942c \n\n");
		// Correct the frame delay
		time_show.time_in_ms -= 1000 / 29.97;
		// Clear the buffer and start pop on caption in new time
		print_scc_time(time_show, buf + buf_len);
		buf_len = strlen(buf);
		SCC_SNPRINTF("\t94ae 94ae 9420 9420");
		time_show.time_in_ms += 1000 / 29.97;
	}
	else
	{
		time_show.time_in_ms -= 1000 / 29.97;
		print_scc_time(time_show, buf);
		buf_len = strlen(buf);
		SCC_SNPRINTF("\t942c 942c 94ae 94ae 9420 9420");
		time_show.time_in_ms += 1000 / 29.97;
	}

	int total_subtitle_count = count_captions_lines_scc(tv);
	int current_subtitle_count = 0;

	for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
	{
		if (!dtvcc_is_row_empty(tv, i))
		{
			current_subtitle_count++;
			add_needed_scc_labels(buf, INITIAL_ENC_BUFFER_CAPACITY, &buf_len, total_subtitle_count, current_subtitle_count);

			int first, last, bytes_written = 0;
			dtvcc_get_write_interval(tv, i, &first, &last);
			for (int j = first; j <= last; j++)
			{
				if (bytes_written % 2 == 0)
					SCC_SNPRINTF(" ");
				SCC_SNPRINTF("%x", adjust_odd_parity(tv->chars[i][j].sym));
				bytes_written += 1;
			}
			// if byte pair are not even then make it even by adding 0x80 as padding
			if (bytes_written % 2 == 1)
				SCC_SNPRINTF("80 ");
			else
				SCC_SNPRINTF(" ");
		}
	}

	// Display caption (942f 942f)
	SCC_SNPRINTF("942f 942f \n\n");

#undef SCC_SNPRINTF
	write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf));

	tv->old_cc_time_end = time_end.time_in_ms;
}

void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
	switch (encoder->write_format)
	{
		case CCX_OF_NULL:
			break;
		case CCX_OF_SRT:
			dtvcc_write_srt(writer, decoder, encoder);
			break;
		case CCX_OF_TRANSCRIPT:
			dtvcc_write_transcript(writer, decoder, encoder);
			break;
		case CCX_OF_SAMI:
			dtvcc_write_sami(writer, decoder, encoder);
			break;
		case CCX_OF_SCC:
			dtvcc_write_scc(writer, decoder, encoder);
			break;
		case CCX_OF_MCC:
			printf("REALLY BAD... [%s:%d]\n", __FILE__, __LINE__);
			break;
		default:
			dtvcc_write_debug(decoder->tv);
			break;
	}
}

void dtvcc_write_done(dtvcc_tv_screen *tv, struct encoder_ctx *encoder)
{
	switch (encoder->write_format)
	{
		case CCX_OF_SAMI:
			dtvcc_write_sami_footer(tv, encoder);
			break;
		default:
			ccx_common_logging.debug_ftn(
			    CCX_DMT_708, "[CEA-708] dtvcc_write_done: no handling required\n");
			break;
	}
}

void dtvcc_writer_init(dtvcc_writer_ctx *writer,
		       char *base_filename,
		       int program_number,
		       int service_number,
		       enum ccx_output_format write_format,
		       struct encoder_cfg *cfg)
{
	ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] dtvcc_writer_init\n");
	writer->fd = -1;
	writer->cd = (iconv_t)-1;
	if ((write_format == CCX_OF_NULL) || (write_format == CCX_OF_MCC))
	{
		writer->filename = NULL;
		return;
	}

	ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] dtvcc_writer_init: "
						  "[%s][%d][%d]\n",
				     base_filename, program_number, service_number);

	const char *ext = get_file_extension(write_format);
	char suffix[32];
	snprintf(suffix, sizeof(suffix), CCX_DTVCC_FILENAME_TEMPLATE, program_number, service_number);

	writer->filename = create_outfilename(base_filename, suffix, ext);
	if (!writer->filename)
		ccx_common_logging.fatal_ftn(
		    EXIT_NOT_ENOUGH_MEMORY, "[CEA-708] dtvcc_decoder_init_write: not enough memory");

	ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] dtvcc_writer_init: inited [%s]\n", writer->filename);

	char *charset = cfg->all_services_charset ? cfg->all_services_charset : cfg->services_charsets[service_number - 1];

	writer->fhandle = NULL;
	writer->charset = charset;

	if (charset)
	{
		writer->cd = iconv_open("UTF-8", charset);
		if (writer->cd == (iconv_t)-1)
		{
			ccx_common_logging.fatal_ftn(EXIT_FAILURE, "[CEA-708] dtvcc_init: "
								   "can't create iconv for charset \"%s\": %s\n",
						     charset, strerror(errno));
		}
	}
}

void dtvcc_writer_cleanup(dtvcc_writer_ctx *writer)
{
	if (writer->fd >= 0 && writer->fd != STDOUT_FILENO)
		close(writer->fd);
#if defined(WIN32)
	ccxr_close_handle(writer->fhandle);
	writer->charset = NULL;
#endif
	free(writer->filename);
	if (writer->cd == (iconv_t)-1)
	{
		// TODO nothing to do here
	}
	else
		iconv_close(writer->cd);
}

void dtvcc_writer_output(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
	ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] dtvcc_writer_output: "
						  "writing... [%s][%d]\n",
				     writer->filename, writer->fd);

	if (!writer->filename && writer->fd < 0)
		return;

	if (writer->filename && writer->fd < 0) // first request to write
	{
		ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] "
							  "dtvcc_writer_output: creating %s\n",
					     writer->filename);
		writer->fd = open(writer->filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE);
		if (writer->fd == -1)
		{
			ccx_common_logging.fatal_ftn(
			    CCX_COMMON_EXIT_FILE_CREATION_FAILED, "[CEA-708] Failed to open a file\n");
		}
		if (!encoder->no_bom)
			write_wrapped(writer->fd, UTF8_BOM, sizeof(UTF8_BOM));
	}

	dtvcc_write(writer, decoder, encoder);
}
