// SPDX-License-Identifier: GPL-2.0
/*
 * Vidtv serves as a reference DVB driver and helps validate the existing APIs
 * in the media subsystem. It can also aid developers working on userspace
 * applications.
 *
 * This file contains the code for a 'channel' abstraction.
 *
 * When vidtv boots, it will create some hardcoded channels.
 * Their services will be concatenated to populate the SDT.
 * Their programs will be concatenated to populate the PAT
 * For each program in the PAT, a PMT section will be created
 * The PMT section for a channel will be assigned its streams.
 * Every stream will have its corresponding encoder polled to produce TS packets
 * These packets may be interleaved by the mux and then delivered to the bridge
 *
 *
 * Copyright (C) 2020 Daniel W. S. Almeida
 */

#include <linux/types.h>
#include <linux/slab.h>
#include <linux/printk.h>
#include <linux/ratelimit.h>

#include "vidtv_channel.h"
#include "vidtv_psi.h"
#include "vidtv_encoder.h"
#include "vidtv_mux.h"
#include "vidtv_common.h"
#include "vidtv_s302m.h"

static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e)
{
	struct vidtv_encoder *curr = e;
	struct vidtv_encoder *tmp = NULL;

	while (curr) {
		/* forward the call to the derived type */
		tmp = curr;
		curr = curr->next;
		tmp->destroy(tmp);
	}
}

#define ENCODING_ISO8859_15 "\x0b"

struct vidtv_channel
*vidtv_channel_s302m_init(struct vidtv_channel *head, u16 transport_stream_id)
{
	/*
	 * init an audio only channel with a s302m encoder
	 */
	const u16 s302m_service_id          = 0x880;
	const u16 s302m_program_num         = 0x880;
	const u16 s302m_program_pid         = 0x101; /* packet id for PMT*/
	const u16 s302m_es_pid              = 0x111; /* packet id for the ES */
	const __be32 s302m_fid              = cpu_to_be32(VIDTV_S302M_FORMAT_IDENTIFIER);

	char *name = ENCODING_ISO8859_15 "Beethoven";
	char *provider = ENCODING_ISO8859_15 "LinuxTV.org";

	struct vidtv_channel *s302m = kzalloc(sizeof(*s302m), GFP_KERNEL);
	struct vidtv_s302m_encoder_init_args encoder_args = {};

	s302m->name = kstrdup(name, GFP_KERNEL);

	s302m->service = vidtv_psi_sdt_service_init(NULL, s302m_service_id);

	s302m->service->descriptor = (struct vidtv_psi_desc *)
				     vidtv_psi_service_desc_init(NULL,
								 DIGITAL_TELEVISION_SERVICE,
								 name,
								 provider);

	s302m->transport_stream_id = transport_stream_id;

	s302m->program = vidtv_psi_pat_program_init(NULL,
						    s302m_service_id,
						    s302m_program_pid);

	s302m->program_num = s302m_program_num;

	s302m->streams = vidtv_psi_pmt_stream_init(NULL,
						   STREAM_PRIVATE_DATA,
						   s302m_es_pid);

	s302m->streams->descriptor = (struct vidtv_psi_desc *)
				     vidtv_psi_registration_desc_init(NULL,
								      s302m_fid,
								      NULL,
								      0);
	encoder_args.es_pid = s302m_es_pid;

	s302m->encoders = vidtv_s302m_encoder_init(encoder_args);

	if (head) {
		while (head->next)
			head = head->next;

		head->next = s302m;
	}

	return s302m;
}

static struct vidtv_psi_table_sdt_service
*vidtv_channel_sdt_serv_cat_into_new(struct vidtv_mux *m)
{
	/* Concatenate the services */
	const struct vidtv_channel *cur_chnl = m->channels;

	struct vidtv_psi_table_sdt_service *curr = NULL;
	struct vidtv_psi_table_sdt_service *head = NULL;
	struct vidtv_psi_table_sdt_service *tail = NULL;

	struct vidtv_psi_desc *desc = NULL;
	u16 service_id;

	if (!cur_chnl)
		return NULL;

	while (cur_chnl) {
		curr = cur_chnl->service;

		if (!curr)
			dev_warn_ratelimited(m->dev,
					     "No services found for channel %s\n", cur_chnl->name);

		while (curr) {
			service_id = be16_to_cpu(curr->service_id);
			tail = vidtv_psi_sdt_service_init(tail, service_id);

			desc = vidtv_psi_desc_clone(curr->descriptor);
			vidtv_psi_desc_assign(&tail->descriptor, desc);

			if (!head)
				head = tail;

			curr = curr->next;
		}

		cur_chnl = cur_chnl->next;
	}

	return head;
}

static struct vidtv_psi_table_pat_program*
vidtv_channel_pat_prog_cat_into_new(struct vidtv_mux *m)
{
	/* Concatenate the programs */
	const struct vidtv_channel *cur_chnl = m->channels;
	struct vidtv_psi_table_pat_program *curr = NULL;
	struct vidtv_psi_table_pat_program *head = NULL;
	struct vidtv_psi_table_pat_program *tail = NULL;
	u16 serv_id;
	u16 pid;

	if (!cur_chnl)
		return NULL;

	while (cur_chnl) {
		curr = cur_chnl->program;

		if (!curr)
			dev_warn_ratelimited(m->dev,
					     "No programs found for channel %s\n",
					     cur_chnl->name);

		while (curr) {
			serv_id = be16_to_cpu(curr->service_id);
			pid = vidtv_psi_get_pat_program_pid(curr);
			tail = vidtv_psi_pat_program_init(tail,
							  serv_id,
							  pid);

			if (!head)
				head = tail;

			curr = curr->next;
		}

		cur_chnl = cur_chnl->next;
	}

	return head;
}

static void
vidtv_channel_pmt_match_sections(struct vidtv_channel *channels,
				 struct vidtv_psi_table_pmt **sections,
				 u32 nsections)
{
	/*
	 * Match channels to their respective PMT sections, then assign the
	 * streams
	 */
	struct vidtv_psi_table_pmt *curr_section = NULL;
	struct vidtv_channel *cur_chnl = channels;

	struct vidtv_psi_table_pmt_stream *s = NULL;
	struct vidtv_psi_table_pmt_stream *head = NULL;
	struct vidtv_psi_table_pmt_stream *tail = NULL;

	struct vidtv_psi_desc *desc = NULL;
	u32 j;
	u16 curr_id;
	u16 e_pid; /* elementary stream pid */

	while (cur_chnl) {
		for (j = 0; j < nsections; ++j) {
			curr_section = sections[j];

			if (!curr_section)
				continue;

			curr_id = be16_to_cpu(curr_section->header.id);

			/* we got a match */
			if (curr_id == cur_chnl->program_num) {
				s = cur_chnl->streams;

				/* clone the streams for the PMT */
				while (s) {
					e_pid = vidtv_psi_pmt_stream_get_elem_pid(s);
					tail = vidtv_psi_pmt_stream_init(tail,
									 s->type,
									 e_pid);

					if (!head)
						head = tail;

					desc = vidtv_psi_desc_clone(s->descriptor);
					vidtv_psi_desc_assign(&tail->descriptor, desc);

					s = s->next;
				}

				vidtv_psi_pmt_stream_assign(curr_section, head);
				break;
			}
		}

		cur_chnl = cur_chnl->next;
	}
}

void vidtv_channel_si_init(struct vidtv_mux *m)
{
	struct vidtv_psi_table_pat_program *programs = NULL;
	struct vidtv_psi_table_sdt_service *services = NULL;

	m->si.pat = vidtv_psi_pat_table_init(m->transport_stream_id);

	m->si.sdt = vidtv_psi_sdt_table_init(m->transport_stream_id);

	programs = vidtv_channel_pat_prog_cat_into_new(m);
	services = vidtv_channel_sdt_serv_cat_into_new(m);

	/* assemble all programs and assign to PAT */
	vidtv_psi_pat_program_assign(m->si.pat, programs);

	/* assemble all services and assign to SDT */
	vidtv_psi_sdt_service_assign(m->si.sdt, services);

	m->si.pmt_secs = vidtv_psi_pmt_create_sec_for_each_pat_entry(m->si.pat, m->pcr_pid);

	vidtv_channel_pmt_match_sections(m->channels,
					 m->si.pmt_secs,
					 m->si.pat->programs);
}

void vidtv_channel_si_destroy(struct vidtv_mux *m)
{
	u32 i;
	u16 num_programs = m->si.pat->programs;

	vidtv_psi_pat_table_destroy(m->si.pat);

	for (i = 0; i < num_programs; ++i)
		vidtv_psi_pmt_table_destroy(m->si.pmt_secs[i]);

	kfree(m->si.pmt_secs);
	vidtv_psi_sdt_table_destroy(m->si.sdt);
}

void vidtv_channels_init(struct vidtv_mux *m)
{
	/* this is the place to add new 'channels' for vidtv */
	m->channels = vidtv_channel_s302m_init(NULL, m->transport_stream_id);
}

void vidtv_channels_destroy(struct vidtv_mux *m)
{
	struct vidtv_channel *curr = m->channels;
	struct vidtv_channel *tmp = NULL;

	while (curr) {
		kfree(curr->name);
		vidtv_psi_sdt_service_destroy(curr->service);
		vidtv_psi_pat_program_destroy(curr->program);
		vidtv_psi_pmt_stream_destroy(curr->streams);
		vidtv_channel_encoder_destroy(curr->encoders);

		tmp = curr;
		curr = curr->next;
		kfree(tmp);
	}
}
