static u16 channel_id_to_papd(u16 ch_id)
{
	if (WARN_ON(!is_valid_channel(ch_id)))
		return 0xff;

	if (1 <= ch_id && ch_id <= 14)
		return 0;
	if (36 <= ch_id && ch_id <= 64)
		return 1;
	if (100 <= ch_id && ch_id <= 140)
		return 2;
	return 3;
}
static u8 ch_id_to_ch_index(u16 ch_id)
{
	if (WARN_ON(!is_valid_channel(ch_id)))
		return 0xff;

	if (ch_id <= 14)
		return ch_id - 1;
	if (ch_id <= 64)
		return (ch_id + 20) / 4;
	if (ch_id <= 140)
		return (ch_id - 12) / 4;
	return (ch_id - 13) / 4;
}
static uint8_t
ch_id_to_ch_index(uint16_t ch_id)
{
	if (!is_valid_channel(ch_id))
		return 0xff;

	if (ch_id <= 14)
		return ch_id - 1;
	if (ch_id <= 64)
		return (ch_id + 20) / 4;
	if (ch_id <= 140)
		return (ch_id - 12) / 4;
	return (ch_id - 13) / 4;
}
static uint16_t
channel_id_to_papd(uint16_t ch_id)
{
	if (!is_valid_channel(ch_id))
		return 0xff;

	if (1 <= ch_id && ch_id <= 14)
		return 0;
	if (36 <= ch_id && ch_id <= 64)
		return 1;
	if (100 <= ch_id && ch_id <= 140)
		return 2;
	return 3;
}
uint8_t execute_pdu(pdu_type *pdu) {
	switch (pdu->opcode) {
		case OPCODE_GET_TYPE: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = MODULE_TYPE;
			break;
		}

		case OPCODE_GET_ID: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = get_module_id();
			break;
		}

		case OPCODE_GET_FW_VERSION: {
			pdu->number_of_arguments = 3;
			pdu->arguments[0] = FIRMWARE_MAJOR;
			pdu->arguments[1] = FIRMWARE_MINOR;
			pdu->arguments[2] = FIRMWARE_PATCHLEVEL;

			break;
		}

		case OPCODE_GET_NR_CHANNELS: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = NUM_CHANNELS;

			break;
		}

		case OPCODE_GET_FEATURES: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = 0;

			break;
		}

		case OPCODE_GET_SW_THRESHOLD: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = get_switch_threshold();

			break;
		}

		case OPCODE_GET_DIMMER_THRESHOLD: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = get_long_press_threshold();

			break;
		}


		case OPCODE_GET_DIMMER_DELAY: {
			pdu->number_of_arguments = 1;
			pdu->arguments[0] = get_dimmer_delay();

			break;
		}

		case OPCODE_GET_SW_TIMER: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint16_t channel_timer = get_switch_timer(channel_number);

					pdu->number_of_arguments = 1;
					pdu->arguments[0] = channel_timer;
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_GET_CHANNEL_MAPPING: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t channel_mapping = get_switch_mapping(channel_number);

					pdu->number_of_arguments = 1;
					pdu->arguments[0] = channel_mapping;
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_GET_DEFAULT_STATE: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t channel_state = get_default_channel_state(channel_number);

					pdu->number_of_arguments = 1;
					pdu->arguments[0] = channel_state;
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_GET_DEFAULT_PERCENTAGE: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t percentage = get_default_dimmer_percentage(channel_number);

					pdu->number_of_arguments = 1;
					pdu->arguments[0] = percentage;
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_GET_DEFAULT_DIMMER_DIRECTION: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t direction = get_default_dimmer_direction(channel_number);

					pdu->number_of_arguments = 1;
					pdu->arguments[0] = direction;
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_SW_THRESHOLD: {
			if (pdu->number_of_arguments == 1) {
				uint16_t threshold = pdu->arguments[0];
				set_switch_threshold(threshold);
				pdu->number_of_arguments = 0;
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_DIMMER_DELAY: {
			if (pdu->number_of_arguments == 1) {
				uint8_t delay = pdu->arguments[0];
				set_dimmer_delay(delay);
				pdu->number_of_arguments = 0;
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_DIMMER_THRESHOLD: {
			if (pdu->number_of_arguments == 1) {
				uint16_t threshold = pdu->arguments[0];
				set_long_press_threshold(threshold);
				pdu->number_of_arguments = 0;
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_SW_TIMER: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];
				uint16_t timer = pdu->arguments[1];

				if (is_valid_channel(channel_number)) {
					set_switch_timer(channel_number, timer);
					pdu->number_of_arguments = 0;
				} else {
					return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_CHANNEL_MAPPING: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];
				uint8_t mapping = pdu->arguments[1];

				if (is_valid_channel(channel_number) && is_valid_channel(mapping)) {
					set_switch_mapping(channel_number, mapping);
					pdu->number_of_arguments = 0;
				} else {
					return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_DEFAULT_STATE: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];
				uint8_t default_state = pdu->arguments[1];

				if (is_valid_channel(channel_number)) {
					if (default_state == 0 || default_state == 1) {
						set_default_channel_state(channel_number, default_state);
						pdu->number_of_arguments = 0;
					} else {
						return ERROR_INVALID_STATE;
					}
				} else {
					return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_DEFAULT_PERCENTAGE: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];
				uint8_t default_percentage = pdu->arguments[1];

				if (is_valid_channel(channel_number)) {
					if (default_percentage >= 0 || default_percentage <= 100) {
						set_default_dimmer_percentage(channel_number, default_percentage);
						pdu->number_of_arguments = 0;
					} else {
						return ERROR_INVALID_PERCENTAGE;
					}
				} else {
					return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SET_DEFAULT_DIMMER_DIRECTION: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];
				uint8_t default_direction = pdu->arguments[1];

				if (is_valid_channel(channel_number)) {
					if (default_direction == 0 || default_direction == 1) {
						set_default_dimmer_direction(channel_number, default_direction);
						pdu->number_of_arguments = 0;
					} else {
						return ERROR_INVALID_STATE;
					}
				} else {
					return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_RELOAD_CONFIGURATION: {
			configuration_load();
			pdu->number_of_arguments = 0;

			break;
		}

		case OPCODE_SAVE_CONFIGURATION: {
			configuration_save();
			pdu->number_of_arguments = 0;

			break;
		}

		case OPCODE_GET_OUTPUT_STATE: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t output_state = get_output_state(channel_number);

					pdu->number_of_arguments = 2;
					pdu->arguments[0] = output_state;
					pdu->arguments[1] = get_percentage(channel_number);
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_GET_INPUT_STATE: {
			if (pdu->number_of_arguments == 1) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t switch_state = get_switch_state(channel_number);

					pdu->number_of_arguments = 1;
					pdu->arguments[0] = switch_state;
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_SWITCH_OUTPUT: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t new_state = pdu->arguments[1];

					if (new_state == STATE_OFF || new_state == STATE_ON) {
						switch_output(channel_number, new_state);

						pdu->number_of_arguments = 0;
					} else {
						return ERROR_INVALID_STATE;
					}
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		case OPCODE_DIM: {
			if (pdu->number_of_arguments == 2) {
				uint8_t channel_number = pdu->arguments[0];

				if (is_valid_channel(channel_number)) {
					uint8_t percentage = pdu->arguments[1];

					if (percentage >= 0 || percentage <= 100) {
						dim(channel_number, percentage);

						pdu->number_of_arguments = 0;
					} else {
						return ERROR_INVALID_PERCENTAGE;
					}
				} else {
					return ERROR_INVALID_CHANNEL_NUMBER;
				}
			} else {
				return ERROR_WRONG_NUMBER_OF_ARGUMENTS;
			}

			break;
		}

		default: {
			return ERROR_INVALID_OPCODE;
		}
	}

	return 0;
}