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

#include <cctype>
#include <cstring>
#include <string>

#include <unistd.h>

#include <linux/cec-funcs.h>
#include <v4l-getsubopt.h>
#include "cec-htng-funcs.h"
#include "cec-log.h"
#include "cec-parse.h"

#define xstr(s) str(s)
#define str(s) #s

static struct cec_op_digital_service_id *args2digital_service_id(__u8 service_id_method,
								 __u8 dig_bcast_system,
								 __u16 transport_id,
								 __u16 service_id,
								 __u16 orig_network_id,
								 __u16 program_number,
								 __u8 channel_number_fmt,
								 __u16 major,
								 __u16 minor)
{
	static struct cec_op_digital_service_id dsid;

	dsid.service_id_method = service_id_method;
	dsid.dig_bcast_system = dig_bcast_system;
	if (service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) {
		dsid.channel.channel_number_fmt = channel_number_fmt;
		dsid.channel.major = major;
		dsid.channel.minor = minor;
		return &dsid;
	}
	switch (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:
		dsid.atsc.transport_id = transport_id;
		dsid.atsc.program_number = program_number;
		break;
	default:
		dsid.dvb.transport_id = transport_id;
		dsid.dvb.service_id = service_id;
		dsid.dvb.orig_network_id = orig_network_id;
		break;
	}
	return &dsid;
}

static struct cec_op_ui_command *args2ui_command(__u8 ui_cmd,
						 __u8 has_opt_arg,
						 __u8 play_mode,
						 __u8 ui_function_media,
						 __u8 ui_function_select_av_input,
						 __u8 ui_function_select_audio_input,
						 __u8 ui_bcast_type,
						 __u8 ui_snd_pres_ctl,
						 __u8 channel_number_fmt,
						 __u16 major,
						 __u16 minor)
{
	static struct cec_op_ui_command ui_command;

	ui_command.ui_cmd = ui_cmd;
	ui_command.has_opt_arg = has_opt_arg;
	if (!has_opt_arg)
		return &ui_command;
	switch (ui_cmd) {
	case CEC_OP_UI_CMD_SELECT_BROADCAST_TYPE:
		ui_command.ui_broadcast_type = ui_bcast_type;
		break;
	case CEC_OP_UI_CMD_SELECT_SOUND_PRESENTATION:
		ui_command.ui_sound_presentation_control = ui_snd_pres_ctl;
		break;
	case CEC_OP_UI_CMD_PLAY_FUNCTION:
		ui_command.play_mode = play_mode;
		break;
	case CEC_OP_UI_CMD_TUNE_FUNCTION:
		ui_command.channel_identifier.channel_number_fmt = channel_number_fmt;
		ui_command.channel_identifier.major = major;
		ui_command.channel_identifier.minor = minor;
		break;
	case CEC_OP_UI_CMD_SELECT_MEDIA_FUNCTION:
		ui_command.ui_function_media = ui_function_media;
		break;
	case CEC_OP_UI_CMD_SELECT_AV_INPUT_FUNCTION:
		ui_command.ui_function_select_av_input = ui_function_select_av_input;
		break;
	case CEC_OP_UI_CMD_SELECT_AUDIO_INPUT_FUNCTION:
		ui_command.ui_function_select_audio_input = ui_function_select_audio_input;
		break;
	default:
		ui_command.has_opt_arg = false;
		break;
	}
	return &ui_command;
}

static __u32 *args2short_descrs(__u32 descriptor1,
				__u32 descriptor2,
				__u32 descriptor3,
				__u32 descriptor4)
{
	static __u32 descriptors[4];

	descriptors[0] = descriptor1;
	descriptors[1] = descriptor2;
	descriptors[2] = descriptor3;
	descriptors[3] = descriptor4;
	return descriptors;
}

static __u8 *args2short_aud_fmt_ids(__u8 audio_format_id1,
				    __u8 audio_format_id2,
				    __u8 audio_format_id3,
				    __u8 audio_format_id4)
{
	static __u8 audio_format_ids[4];

	audio_format_ids[0] = audio_format_id1;
	audio_format_ids[1] = audio_format_id2;
	audio_format_ids[2] = audio_format_id3;
	audio_format_ids[3] = audio_format_id4;
	return audio_format_ids;
}

static __u8 *args2short_aud_fmt_codes(__u8 audio_format_code1,
				      __u8 audio_format_code2,
				      __u8 audio_format_code3,
				      __u8 audio_format_code4)
{
	static __u8 audio_format_codes[4];

	audio_format_codes[0] = audio_format_code1;
	audio_format_codes[1] = audio_format_code2;
	audio_format_codes[2] = audio_format_code3;
	audio_format_codes[3] = audio_format_code4;
	return audio_format_codes;
}

int cec_parse_subopt(char **subs, const char * const *subopts, char **value)
{
	int opt = v4l_getsubopt(subs, const_cast<char * const *>(subopts), value);

	if (opt == -1) {
		fprintf(stderr, "Invalid suboptions specified\n");
		return -1;
	}
	if (*value == nullptr) {
		fprintf(stderr, "No value given to suboption <%s>\n",
				subopts[opt]);
		return -1;
	}
	return opt;
}

static unsigned parse_enum(const char *value, const struct cec_arg *a)
{
	if (isdigit(*value))
		return strtoul(value, nullptr, 0);
	for (int i = 0; i < a->num_enum_values; i++) {
		if (!strcmp(value, a->values[i].type_name))
			return a->values[i].value;
	}
	return 0;
}

unsigned cec_parse_phys_addr(const char *value)
{
	unsigned p1, p2, p3, p4;

	if (!std::strchr(value, '.'))
		return strtoul(value, nullptr, 0);
	if (sscanf(value, "%x.%x.%x.%x", &p1, &p2, &p3, &p4) != 4) {
		fprintf(stderr, "Expected a physical address of the form x.x.x.x\n");
		return 0;
	}
	if (p1 > 0xf || p2 > 0xf || p3 > 0xf || p4 > 0xf) {
		fprintf(stderr, "Physical address components should never be larger than 0xf\n");
		return 0;
	}
	return (p1 << 12) | (p2 << 8) | (p3 << 4) | p4;
}

static unsigned parse_latency(const char *value)
{
	char *end;
	unsigned delay = strtoul(value, &end, 0);

	if (!memcmp(end, "ms", 2))
		delay = (delay / 2) + 1;
	if (delay < 1)
		delay = 1;
	else if (delay > 251)
		delay = 251;
	return delay;
}


#define VENDOR_EXTRA \
	"  --vendor-command payload=<byte>[:<byte>]*\n" \
	"                                  Send VENDOR_COMMAND message (" xstr(CEC_MSG_VENDOR_COMMAND) ")\n" \
	"  --vendor-command-with-id vendor-id=<val>,[reply=<reply>,]cmd=<byte>[:<byte>]*\n" \
	"                                  Send VENDOR_COMMAND_WITH_ID message (" xstr(CEC_MSG_VENDOR_COMMAND_WITH_ID) ")\n" \
	"  --vendor-remote-button-down rc-code=<byte>[:<byte>]*\n" \
	"                                  Send VENDOR_REMOTE_BUTTON_DOWN message (" xstr(CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN) ")\n"

#include "cec-parse-src-gen.h"
