/****************************************************************************
 *
 *   Copyright (c) 2012-2015 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/**
 * @file uORB.cpp
 * A lightweight object broker.
 */

#include "uORB.h"

#include "uORBManager.hpp"
#include "uORBCommon.hpp"


#include <lib/drivers/device/Device.hpp>
#include <matrix/Quaternion.hpp>
#include <mathlib/mathlib.h>

#ifdef __PX4_NUTTX
#include <sys/boardctl.h>
#endif

static uORB::DeviceMaster *g_dev = nullptr;

int uorb_start(void)
{
	if (g_dev != nullptr) {
		PX4_WARN("already loaded");
		/* user wanted to start uorb, its already running, no error */
		return 0;
	}

	if (!uORB::Manager::initialize()) {
		PX4_ERR("uorb manager alloc failed");
		return -ENOMEM;
	}

#if !defined(__PX4_NUTTX) || defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)
	/* create the driver */
	g_dev = uORB::Manager::get_instance()->get_device_master();

	if (g_dev == nullptr) {
		return -errno;
	}

#endif

	return OK;
}

int uorb_status(void)
{
#if !defined(__PX4_NUTTX) || defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)

	if (g_dev != nullptr) {
		g_dev->printStatistics();

	} else {
		PX4_INFO("uorb is not running");
	}

#else
	boardctl(ORBIOCDEVMASTERCMD, ORB_DEVMASTER_STATUS);
#endif
	return OK;
}

int uorb_top(char **topic_filter, int num_filters)
{
#if !defined(__PX4_NUTTX) || defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)

	if (g_dev != nullptr) {
		g_dev->showTop(topic_filter, num_filters);

	} else {
		PX4_INFO("uorb is not running");
	}

#else
	boardctl(ORBIOCDEVMASTERCMD, ORB_DEVMASTER_TOP);
#endif
	return OK;
}

orb_advert_t orb_advertise(const struct orb_metadata *meta, const void *data)
{
	return uORB::Manager::get_instance()->orb_advertise(meta, data);
}

orb_advert_t orb_advertise_queue(const struct orb_metadata *meta, const void *data, unsigned int queue_size)
{
	return uORB::Manager::get_instance()->orb_advertise(meta, data, queue_size);
}

orb_advert_t orb_advertise_multi(const struct orb_metadata *meta, const void *data, int *instance)
{
	return uORB::Manager::get_instance()->orb_advertise_multi(meta, data, instance);
}

orb_advert_t orb_advertise_multi_queue(const struct orb_metadata *meta, const void *data, int *instance,
				       unsigned int queue_size)
{
	return uORB::Manager::get_instance()->orb_advertise_multi(meta, data, instance, queue_size);
}

int orb_unadvertise(orb_advert_t handle)
{
	return uORB::Manager::get_instance()->orb_unadvertise(handle);
}

int orb_publish(const struct orb_metadata *meta, orb_advert_t handle, const void *data)
{
	return uORB::Manager::get_instance()->orb_publish(meta, handle, data);
}

int orb_subscribe(const struct orb_metadata *meta)
{
	return uORB::Manager::get_instance()->orb_subscribe(meta);
}

int orb_subscribe_multi(const struct orb_metadata *meta, unsigned instance)
{
	return uORB::Manager::get_instance()->orb_subscribe_multi(meta, instance);
}

int orb_unsubscribe(int handle)
{
	return uORB::Manager::get_instance()->orb_unsubscribe(handle);
}

int orb_copy(const struct orb_metadata *meta, int handle, void *buffer)
{
	return uORB::Manager::get_instance()->orb_copy(meta, handle, buffer);
}

int orb_check(int handle, bool *updated)
{
	return uORB::Manager::get_instance()->orb_check(handle, updated);
}

int orb_exists(const struct orb_metadata *meta, int instance)
{
	return uORB::Manager::get_instance()->orb_exists(meta, instance);
}

int orb_group_count(const struct orb_metadata *meta)
{
	unsigned instance = 0;

	while (uORB::Manager::get_instance()->orb_exists(meta, instance) == OK) {
		++instance;
	};

	return instance;
}

int orb_set_interval(int handle, unsigned interval)
{
	return uORB::Manager::get_instance()->orb_set_interval(handle, interval);
}

int orb_get_interval(int handle, unsigned *interval)
{
	return uORB::Manager::get_instance()->orb_get_interval(handle, interval);
}

const char *orb_get_c_type(unsigned char short_type)
{
	// this matches with the uorb o_fields generator
	switch (short_type) {
	case 0x82: return "int8_t";

	case 0x83: return "int16_t";

	case 0x84: return "int32_t";

	case 0x85: return "int64_t";

	case 0x86: return "uint8_t";

	case 0x87: return "uint16_t";

	case 0x88: return "uint32_t";

	case 0x89: return "uint64_t";

	case 0x8a: return "float";

	case 0x8b: return "double";

	case 0x8c: return "bool";

	case 0x8d: return "char";
	}

	return nullptr;
}


void orb_print_message_internal(const orb_metadata *meta, const void *data, bool print_topic_name)
{
	if (print_topic_name) {
		PX4_INFO_RAW(" %s\n", meta->o_name);
	}

	const hrt_abstime now = hrt_absolute_time();
	hrt_abstime topic_timestamp = 0;

	const uint8_t *data_ptr = (const uint8_t *)data;
	int data_offset = 0;

	for (int format_idx = 0; meta->o_fields[format_idx] != 0;) {
		const char *end_field = strchr(meta->o_fields + format_idx, ';');

		if (!end_field) {
			PX4_ERR("Format error in %s", meta->o_fields);
			return;
		}

		const char *c_type = orb_get_c_type(meta->o_fields[format_idx]);
		const int end_field_idx = end_field - meta->o_fields;

		int array_idx = -1;
		int field_name_idx = -1;

		for (int field_idx = format_idx; field_idx != end_field_idx; ++field_idx) {
			if (meta->o_fields[field_idx] == '[') {
				array_idx = field_idx + 1;

			} else if (meta->o_fields[field_idx] == ' ') {
				field_name_idx = field_idx + 1;
				break;
			}
		}

		int array_size = 1;

		if (array_idx >= 0) {
			array_size = strtol(meta->o_fields + array_idx, nullptr, 10);
		}

		char field_name[80];
		size_t field_name_len = end_field_idx - field_name_idx;

		if (field_name_len >= sizeof(field_name)) {
			PX4_ERR("field name too long %s (max: %u)", meta->o_fields, (unsigned)sizeof(field_name));
			return;
		}

		memcpy(field_name, meta->o_fields + field_name_idx, field_name_len);
		field_name[field_name_len] = '\0';

		if (c_type) { // built-in type
			bool dont_print = false;

			// handle special cases
			if (strncmp(field_name, "_padding", 8) == 0) {
				dont_print = true;

			} else if (strcmp(c_type, "char") == 0 && array_size > 1) { // string
				PX4_INFO_RAW("    %s: \"%.*s\"\n", field_name, array_size, (char *)(data_ptr + data_offset));
				dont_print = true;
			}

			if (!dont_print) {
				PX4_INFO_RAW("    %s: ", field_name);
			}

			if (!dont_print && array_size > 1) {
				PX4_INFO_RAW("[");
			}

			const int previous_data_offset = data_offset;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align" // the caller ensures data is aligned

			for (int i = 0; i < array_size; ++i) {
				if (strcmp(c_type, "int8_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIi8, *(int8_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(int8_t);

				} else if (strcmp(c_type, "int16_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIi16, *(int16_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(int16_t);

				} else if (strcmp(c_type, "int32_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIi32, *(int32_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(int32_t);

				} else if (strcmp(c_type, "int64_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIi64, *(int64_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(int64_t);

				} else if (strcmp(c_type, "uint8_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIu8, *(uint8_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(uint8_t);

				} else if (strcmp(c_type, "uint16_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIu16, *(uint16_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(uint16_t);

				} else if (strcmp(c_type, "uint32_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIu32, *(uint32_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(uint32_t);

				} else if (strcmp(c_type, "uint64_t") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%" PRIu64, *(uint64_t *)(data_ptr + data_offset)); }

					data_offset += sizeof(uint64_t);

				} else if (strcmp(c_type, "float") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%.4f", (double) * (float *)(data_ptr + data_offset)); }

					data_offset += sizeof(float);

				} else if (strcmp(c_type, "double") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%.4f", *(double *)(data_ptr + data_offset)); }

					data_offset += sizeof(double);

				} else if (strcmp(c_type, "bool") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%s", *(bool *)(data_ptr + data_offset) ? "True" : "False"); }

					data_offset += sizeof(bool);

				} else if (strcmp(c_type, "char") == 0) {
					if (!dont_print) { PX4_INFO_RAW("%i", (int) * (char *)(data_ptr + data_offset)); }

					data_offset += sizeof(char);

				} else {
					PX4_ERR("unknown type: %s", c_type);
					return;
				}

				if (!dont_print && i < array_size - 1) {
					PX4_INFO_RAW(", ");
				}
			}

			if (!dont_print && array_size > 1) {
				PX4_INFO_RAW("]");
			}

			// handle special cases
			if (array_size == 1) {
				if (strcmp(c_type, "uint64_t") == 0 && strcmp(field_name, "timestamp") == 0) {
					topic_timestamp = *(uint64_t *)(data_ptr + previous_data_offset);

					if (topic_timestamp != 0) {
						PX4_INFO_RAW(" (%.6f seconds ago)", (double)((now - topic_timestamp) / 1e6f));
					}

				} else if (strcmp(c_type, "uint64_t") == 0 && strcmp(field_name, "timestamp_sample") == 0) {
					hrt_abstime timestamp = *(uint64_t *)(data_ptr + previous_data_offset);

					if (topic_timestamp != 0 && timestamp != 0) {
						PX4_INFO_RAW(" (%i us before timestamp)", (int)(topic_timestamp - timestamp));
					}

				} else if (strstr(field_name, "flags") != nullptr) {
					// bitfield
					unsigned field_size = 0;
					unsigned long value = 0;

					if (strcmp(c_type, "uint8_t") == 0) {
						field_size = sizeof(uint8_t);
						value = *(uint8_t *)(data_ptr + previous_data_offset);

					} else if (strcmp(c_type, "uint16_t") == 0) {
						field_size = sizeof(uint16_t);
						value = *(uint16_t *)(data_ptr + previous_data_offset);

					} else if (strcmp(c_type, "uint32_t") == 0) {
						field_size = sizeof(uint32_t);
						value = *(uint32_t *)(data_ptr + previous_data_offset);
					}

					if (field_size > 0) {
						PX4_INFO_RAW(" (0b");

						for (int i = (field_size * 8) - 1; i >= 0; i--) {
							PX4_INFO_RAW("%lu%s", (value >> i) & 1, ((unsigned)i < (field_size * 8) - 1 && i % 4 == 0 && i > 0) ? "'" : "");
						}

						PX4_INFO_RAW(")");
					}

				} else if (strcmp(c_type, "uint32_t") == 0 && strstr(field_name, "device_id") != nullptr) {
					// Device ID
					uint32_t device_id = *(uint32_t *)(data_ptr + previous_data_offset);
					char device_id_buffer[80];
					device::Device::device_id_print_buffer(device_id_buffer, sizeof(device_id_buffer), device_id);
					PX4_INFO_RAW(" (%s)", device_id_buffer);
				}

			} else if (array_size == 4 && strcmp(c_type, "float") == 0 && (strcmp(field_name, "q") == 0
					|| strncmp(field_name, "q_", 2) == 0)) {
				// attitude
				float *attitude = (float *)(data_ptr + previous_data_offset);
				matrix::Eulerf euler{matrix::Quatf{attitude}};
				PX4_INFO_RAW(" (Roll: %.1f deg, Pitch: %.1f deg, Yaw: %.1f deg)",
					     (double)math::degrees(euler(0)), (double)math::degrees(euler(1)), (double)math::degrees(euler(2)));
			}

#pragma GCC diagnostic pop

			PX4_INFO_RAW("\n");

		} else {

			// extract the topic name
			char topic_name[80];
			const size_t topic_name_len = array_size > 1 ? array_idx - format_idx - 1 : field_name_idx - format_idx - 1;

			if (topic_name_len >= sizeof(topic_name)) {
				PX4_ERR("topic name too long in %s (max: %u)", meta->o_name, (unsigned)sizeof(topic_name));
				return;
			}

			memcpy(topic_name, meta->o_fields + format_idx, topic_name_len);
			topic_name[topic_name_len] = '\0';

			// find the metadata
			const orb_metadata *const *topics = orb_get_topics();
			const orb_metadata *found_topic = nullptr;

			for (size_t i = 0; i < orb_topics_count(); i++) {
				if (strcmp(topics[i]->o_name, topic_name) == 0) {
					found_topic = topics[i];
					break;
				}
			}

			if (!found_topic) {
				PX4_ERR("Topic %s did not match any known topics", topic_name);
				return;
			}

			// print recursively
			for (int i = 0; i < array_size; ++i) {
				PX4_INFO_RAW("  %s", field_name);

				if (array_size > 1) {
					PX4_INFO_RAW("[%i]", i);
				}

				PX4_INFO_RAW(" (%s):\n", topic_name);
				orb_print_message_internal(found_topic, data_ptr + data_offset, false);
				data_offset += found_topic->o_size;
			}
		}

		format_idx = end_field_idx + 1;
	}
}
