// SPDX-License-Identifier: (LGPL-2.1-only OR BSD-3-Clause)
/*
 * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 */

#include <string>

#include <unistd.h>

#include <linux/cec-funcs.h>

#include "cec-htng-funcs.h"
#include "cec-info.h"
#include "cec-log.h"
#include "compiler.h"

static const struct cec_arg arg_u8 = {
	CEC_ARG_TYPE_U8,
};

static const struct cec_arg arg_u16 = {
	CEC_ARG_TYPE_U16,
};

static const struct cec_arg arg_u32 = {
	CEC_ARG_TYPE_U32,
};

static const struct cec_arg arg_string = {
	CEC_ARG_TYPE_STRING,
};

static void log_arg(const struct cec_arg *arg, const char *arg_name, __u32 val)
{
	unsigned i;

	switch (arg->type) {
	case CEC_ARG_TYPE_ENUM:
		for (i = 0; i < arg->num_enum_values; i++) {
			if (arg->values[i].value == val) {
				printf("\t%s: %s (0x%02x)\n", arg_name,
				       arg->values[i].type_name, val);
				return;
			}
		}
		fallthrough;
	case CEC_ARG_TYPE_U8:
		if (!strcmp(arg_name, "video-latency") ||
		    !strcmp(arg_name, "audio-out-delay")) {
			printf("\t%s: %u (0x%02x, %d ms)\n", arg_name, val, val,
			       (val - 1) * 2);
		} else if (!strcmp(arg_name, "abort-msg")) {
			if (cec_opcode2s(val))
				printf("\t%s: %u (0x%02x, %s)\n",
				       arg_name, val, val, cec_opcode2s(val));
			else
				printf("\t%s: %u (0x%02x)\n", arg_name, val, val);
		} else {
			printf("\t%s: %u (0x%02x)\n", arg_name, val, val);
		}
		return;
	case CEC_ARG_TYPE_U16:
		if (strstr(arg_name, "phys-addr"))
			printf("\t%s: %x.%x.%x.%x\n", arg_name, cec_phys_addr_exp(val));
		else
			printf("\t%s: %u (0x%04x)\n", arg_name, val, val);
		return;
	case CEC_ARG_TYPE_U32:
		printf("\t%s: %u (0x%08x)\n", arg_name, val, val);
		return;
	default:
		break;
	}
	printf("\t%s: unknown type\n", arg_name);
}

static void log_arg(const struct cec_arg *arg, const char *arg_name,
		    const char *s)
{
	switch (arg->type) {
	case CEC_ARG_TYPE_STRING:
		printf("\t%s: %s\n", arg_name, s);
		return;
	default:
		break;
	}
	printf("\t%s: unknown type\n", arg_name);
}

static const struct cec_arg_enum_values type_rec_src_type[] = {
	{ "own", CEC_OP_RECORD_SRC_OWN },
	{ "digital", CEC_OP_RECORD_SRC_DIGITAL },
	{ "analog", CEC_OP_RECORD_SRC_ANALOG },
	{ "ext-plug", CEC_OP_RECORD_SRC_EXT_PLUG },
	{ "ext-phys-addr", CEC_OP_RECORD_SRC_EXT_PHYS_ADDR },
};

static const struct cec_arg arg_rec_src_type = {
	CEC_ARG_TYPE_ENUM, 5, type_rec_src_type
};

static void log_digital(const char *arg_name, const struct cec_op_digital_service_id *digital);
static void log_rec_src(const char *arg_name, const struct cec_op_record_src *rec_src);
static void log_tuner_dev_info(const char *arg_name, const struct cec_op_tuner_device_info *tuner_dev_info);
static void log_features(const struct cec_arg *arg, const char *arg_name, const __u8 *p);
static void log_ui_command(const char *arg_name, const struct cec_op_ui_command *ui_cmd);
static void log_vendor_id(const char *arg_name, __u32 vendor_id);
static void log_descriptors(const char *arg_name, unsigned num, const __u32 *descriptors);
static void log_u8_array(const char *arg_name, unsigned num, const __u8 *vals);
static void log_unknown_msg(const struct cec_msg *msg);
static void log_htng_unknown_msg(const struct cec_msg *msg);

#include "cec-log-gen.h"

const struct cec_msg_args *cec_log_msg_args(unsigned int index)
{
	if (index >= sizeof(messages) / sizeof(messages[0]))
		return nullptr;
	return &messages[index];
}

static void log_digital(const char *arg_name, const struct cec_op_digital_service_id *digital)
{
	log_arg(&arg_service_id_method, "service-id-method", digital->service_id_method);
	log_arg(&arg_dig_bcast_system, "dig-bcast-system", digital->dig_bcast_system);
	if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) {
		log_arg(&arg_channel_number_fmt, "channel-number-fmt", digital->channel.channel_number_fmt);
		log_arg(&arg_u16, "major", digital->channel.major);
		log_arg(&arg_u16, "minor", digital->channel.minor);
		return;
	}

	switch (digital->dig_bcast_system) {
	case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN:
	case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE:
	case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT:
	case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T:
		log_arg(&arg_u16, "transport-id", digital->atsc.transport_id);
		log_arg(&arg_u16, "program-number", digital->atsc.program_number);
		break;
	default:
		log_arg(&arg_u16, "transport-id", digital->dvb.transport_id);
		log_arg(&arg_u16, "service-id", digital->dvb.service_id);
		log_arg(&arg_u16, "orig-network-id", digital->dvb.orig_network_id);
		break;
	}
}

static void log_rec_src(const char *arg_name, const struct cec_op_record_src *rec_src)
{
	log_arg(&arg_rec_src_type, "rec-src-type", rec_src->type);
	switch (rec_src->type) {
	case CEC_OP_RECORD_SRC_OWN:
	default:
		break;
	case CEC_OP_RECORD_SRC_DIGITAL:
		log_digital(arg_name, &rec_src->digital);
		break;
	case CEC_OP_RECORD_SRC_ANALOG:
		log_arg(&arg_ana_bcast_type, "ana-bcast-type", rec_src->analog.ana_bcast_type);
		log_arg(&arg_u16, "ana-freq", rec_src->analog.ana_freq);
		log_arg(&arg_bcast_system, "bcast-system", rec_src->analog.bcast_system);
		break;
	case CEC_OP_RECORD_SRC_EXT_PLUG:
		log_arg(&arg_u8, "plug", rec_src->ext_plug.plug);
		break;
	case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR:
		log_arg(&arg_u16, "phys-addr", rec_src->ext_phys_addr.phys_addr);
		break;
	}
}

static void log_tuner_dev_info(const char *arg_name, const struct cec_op_tuner_device_info *tuner_dev_info)
{
	log_arg(&arg_rec_flag, "rec-flag", tuner_dev_info->rec_flag);
	log_arg(&arg_tuner_display_info, "tuner-display-info", tuner_dev_info->tuner_display_info);
	if (tuner_dev_info->is_analog) {
		log_arg(&arg_ana_bcast_type, "ana-bcast-type", tuner_dev_info->analog.ana_bcast_type);
		log_arg(&arg_u16, "ana-freq", tuner_dev_info->analog.ana_freq);
		log_arg(&arg_bcast_system, "bcast-system", tuner_dev_info->analog.bcast_system);
	} else {
		log_digital(arg_name, &tuner_dev_info->digital);
	}
}

static void log_features(const struct cec_arg *arg,
			 const char *arg_name, const __u8 *p)
{
	do {
		log_arg(arg, arg_name, static_cast<__u32>((*p) & ~CEC_OP_FEAT_EXT));
	} while ((*p++) & CEC_OP_FEAT_EXT);
}

static void log_ui_command(const char *arg_name,
			   const struct cec_op_ui_command *ui_cmd)
{
	log_arg(&arg_ui_cmd, arg_name, ui_cmd->ui_cmd);
	if (!ui_cmd->has_opt_arg)
		return;
	switch (ui_cmd->ui_cmd) {
	case CEC_OP_UI_CMD_SELECT_BROADCAST_TYPE:
		log_arg(&arg_ui_bcast_type, "ui-broadcast-type",
			ui_cmd->ui_broadcast_type);
		break;
	case CEC_OP_UI_CMD_SELECT_SOUND_PRESENTATION:
		log_arg(&arg_ui_snd_pres_ctl, "ui-sound-presentation-control",
			ui_cmd->ui_sound_presentation_control);
		break;
	case CEC_OP_UI_CMD_PLAY_FUNCTION:
		log_arg(&arg_u8, "play-mode", ui_cmd->play_mode);
		break;
	case CEC_OP_UI_CMD_TUNE_FUNCTION:
		log_arg(&arg_channel_number_fmt, "channel-number-fmt",
			ui_cmd->channel_identifier.channel_number_fmt);
		log_arg(&arg_u16, "major", ui_cmd->channel_identifier.major);
		log_arg(&arg_u16, "minor", ui_cmd->channel_identifier.minor);
		break;
	case CEC_OP_UI_CMD_SELECT_MEDIA_FUNCTION:
		log_arg(&arg_u8, "ui-function-media", ui_cmd->ui_function_media);
		break;
	case CEC_OP_UI_CMD_SELECT_AV_INPUT_FUNCTION:
		log_arg(&arg_u8, "ui-function-select-av-input", ui_cmd->ui_function_select_av_input);
		break;
	case CEC_OP_UI_CMD_SELECT_AUDIO_INPUT_FUNCTION:
		log_arg(&arg_u8, "ui-function-select-audio-input", ui_cmd->ui_function_select_audio_input);
		break;
	}
}

static void log_vendor_id(const char *arg_name, __u32 vendor_id)
{
	const char *vendor = cec_vendor2s(vendor_id);

	if (vendor)
		printf("\t%s: 0x%06x (%s)\n", arg_name, vendor_id, vendor);
	else
		printf("\t%s: 0x%06x, %u\n", arg_name, vendor_id, vendor_id);
}

static void log_descriptors(const char *arg_name, unsigned num, const __u32 *descriptors)
{
	for (unsigned i = 0; i < num; i++)
		log_arg(&arg_u32, arg_name, descriptors[i]);
}

static void log_u8_array(const char *arg_name, unsigned num, const __u8 *vals)
{
	for (unsigned i = 0; i < num; i++)
		log_arg(&arg_u8, arg_name, vals[i]);
}

static void log_htng_unknown_msg(const struct cec_msg *msg)
{
	__u32 vendor_id;
	const __u8 *bytes;
	__u8 size;
	unsigned i;

	cec_ops_vendor_command_with_id(msg, &vendor_id, &size, &bytes);
	printf("VENDOR_COMMAND_WITH_ID (0x%02x):\n",
	       CEC_MSG_VENDOR_COMMAND_WITH_ID);
	log_vendor_id("vendor-id", vendor_id);
	printf("\tvendor-specific-data:");
	for (i = 0; i < size; i++)
		printf(" 0x%02x", bytes[i]);
	printf("\n");
}

static void log_unknown_msg(const struct cec_msg *msg)
{
	__u32 vendor_id;
	__u16 phys_addr;
	const __u8 *bytes;
	__u8 size;
	unsigned i;

	switch (msg->msg[1]) {
	case CEC_MSG_VENDOR_COMMAND:
		printf("VENDOR_COMMAND (0x%02x):\n",
		       CEC_MSG_VENDOR_COMMAND);
		cec_ops_vendor_command(msg, &size, &bytes);
		printf("\tvendor-specific-data:");
		for (i = 0; i < size; i++)
			printf(" 0x%02x", bytes[i]);
		printf("\n");
		break;
	case CEC_MSG_VENDOR_COMMAND_WITH_ID:
		cec_ops_vendor_command_with_id(msg, &vendor_id, &size, &bytes);
		switch (vendor_id) {
		case VENDOR_ID_HTNG:
			log_htng_msg(msg);
			break;
		default:
			printf("VENDOR_COMMAND_WITH_ID (0x%02x):\n",
			       CEC_MSG_VENDOR_COMMAND_WITH_ID);
			log_vendor_id("vendor-id", vendor_id);
			printf("\tvendor-specific-data:");
			for (i = 0; i < size; i++)
				printf(" 0x%02x", bytes[i]);
			printf("\n");
			break;
		}
		break;
	case CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN:
		printf("VENDOR_REMOTE_BUTTON_DOWN (0x%02x):\n",
		       CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN);
		cec_ops_vendor_remote_button_down(msg, &size, &bytes);
		printf("\tvendor-specific-rc-code:");
		for (i = 0; i < size; i++)
			printf(" 0x%02x", bytes[i]);
		printf("\n");
		break;
	case CEC_MSG_CDC_MESSAGE:
		phys_addr = (msg->msg[2] << 8) | msg->msg[3];

		printf("CDC_MESSAGE (0x%02x): 0x%02x:\n",
		       CEC_MSG_CDC_MESSAGE, msg->msg[4]);
		log_arg(&arg_u16, "phys-addr", phys_addr);
		printf("\tpayload:");
		for (i = 5; i < msg->len; i++)
			printf(" 0x%02x", msg->msg[i]);
		printf("\n");
		break;
	default:
		printf("UNKNOWN (0x%02x)%s", msg->msg[1], msg->len > 2 ? ":\n\tpayload:" : "");
		for (i = 2; i < msg->len; i++)
			printf(" 0x%02x", msg->msg[i]);
		printf("\n");
		break;
	}
}

const char *cec_log_ui_cmd_string(__u8 ui_cmd)
{
	for (unsigned i = 0; i < arg_ui_cmd.num_enum_values; i++) {
		if (type_ui_cmd[i].value == ui_cmd)
			return type_ui_cmd[i].type_name;
	}
	return nullptr;
}
