#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <dk_buttons_and_leds.h>

#include <zboss_api.h>
#include <zigbee/zigbee_error_handler.h>
#include <zigbee/zigbee_app_utils.h>
#include <zb_nrf_platform.h>
#include "zb_range_extender.h"

#include "zb_ha_on_off_switch.h"
#include <addons/zcl/zb_zcl_temp_measurement_addons.h>

#include "zb_zcl_co2_measurement.h"
#include "zb_zcl_pm25_measurement.h"

#include "measurement_device.h"

#include "bh1750.h"
#include "htu21.h"
#include "bme280.h"
#include "mhz19b.h"
#include "gp2y1010au.h"

/* Device endpoint, used to receive ZCL commands. */
#define APP_TEMPLATE_ENDPOINT               10
#define ON_OFF_SWITCH_ENDPOINT          	11
#define MEASUREMENT_DEVICE_ENDPOINT 		42

/* Type of power sources available for the device.
 * For possible values see section 3.2.2.2.8 of ZCL specification.
 */
#define TEMPLATE_INIT_BASIC_POWER_SOURCE    ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE

/* LED indicating that device successfully joined Zigbee network. */
#define ZIGBEE_NETWORK_STATE_LED            DK_LED2

/* LED used for device identification. */
#define IDENTIFY_LED                        DK_LED1

/* Button used to enter the Identify mode. */
#define IDENTIFY_MODE_BUTTON                DK_BTN1_MSK

/* Button to start Factory Reset */
#define FACTORY_RESET_BUTTON                IDENTIFY_MODE_BUTTON

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

/* Main application */

struct zb_zcl_illuminance_measurement_attrs_t {
	zb_uint32_t measure_value;
	zb_int32_t min_measure_value;
	zb_int32_t max_measure_value;
};

struct zb_zcl_humidity_measurement_attrs_t {
	zb_int16_t measure_value;
	zb_int16_t min_measure_value;
	zb_int16_t max_measure_value;
};

struct zb_zcl_pressure_measurement_attrs_t {
	zb_uint32_t measure_value;
	zb_int32_t min_measure_value;
	zb_int32_t max_measure_value;
	zb_uint32_t tolerance;
};

struct zb_zcl_co2_measurement_attrs_t {
	zb_int16_t measure_value;
	zb_int16_t min_measure_value;
	zb_int16_t max_measure_value;
	zb_uint16_t tolerance;
};

struct zb_zcl_pm25_measurement_attrs_t {
	zb_int16_t measure_value;
	zb_int16_t min_measure_value;
	zb_int16_t max_measure_value;
	zb_uint16_t tolerance;
};


struct zb_device_ctx {
	zb_zcl_basic_attrs_t basic_attr;
	zb_zcl_identify_attrs_t identify_attr;
	zb_uint8_t on_off_switch_type_attr;
	zb_uint8_t on_off_switch_actions_attr;
	zb_zcl_temp_measurement_attrs_t temp_measure_attrs;
	struct zb_zcl_illuminance_measurement_attrs_t illuminance_attrs;
	struct zb_zcl_humidity_measurement_attrs_t humidity_attrs;
	struct zb_zcl_pressure_measurement_attrs_t pres_attrs;
	struct zb_zcl_co2_measurement_attrs_t co2_attrs;
	struct zb_zcl_pm25_measurement_attrs_t pm25_attrs;
};

/* Zigbee device application context storage. */
static struct zb_device_ctx dev_ctx;

ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(
	identify_attr_list,
	&dev_ctx.identify_attr.identify_time);

ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST(
	basic_attr_list,
	&dev_ctx.basic_attr.zcl_version,
	&dev_ctx.basic_attr.power_source);

ZB_ZCL_DECLARE_ON_OFF_SWITCH_CONFIGURATION_ATTRIB_LIST(
    on_off_switch_attr_list,
    &dev_ctx.on_off_switch_type_attr,
	&dev_ctx.on_off_switch_actions_attr);

ZB_ZCL_DECLARE_TEMP_MEASUREMENT_ATTRIB_LIST(
	temp_measurement_attr_list,
    &dev_ctx.temp_measure_attrs.measure_value,
    &dev_ctx.temp_measure_attrs.min_measure_value,
    &dev_ctx.temp_measure_attrs.max_measure_value,
    &dev_ctx.temp_measure_attrs.tolerance);

ZB_ZCL_DECLARE_ILLUMINANCE_MEASUREMENT_ATTRIB_LIST(
	illuminance_measurement_attr_list,
	&dev_ctx.illuminance_attrs.measure_value,
    &dev_ctx.illuminance_attrs.min_measure_value,
    &dev_ctx.illuminance_attrs.max_measure_value
);

ZB_ZCL_DECLARE_REL_HUMIDITY_MEASUREMENT_ATTRIB_LIST(
	humidity_measurement_attr_list,
	&dev_ctx.humidity_attrs.measure_value,
	&dev_ctx.humidity_attrs.min_measure_value,
	&dev_ctx.humidity_attrs.max_measure_value
	);

ZB_ZCL_DECLARE_PRESSURE_MEASUREMENT_ATTRIB_LIST(
	pressure_measurement_attr_list,
	&dev_ctx.pres_attrs.measure_value,
	&dev_ctx.pres_attrs.min_measure_value,
	&dev_ctx.pres_attrs.max_measure_value,
	&dev_ctx.pres_attrs.tolerance
	);

ZB_ZCL_DECLARE_CO2_MEASUREMENT_ATTRIB_LIST(
	co2_measurement_attr_list,
	&dev_ctx.co2_attrs.measure_value,
	&dev_ctx.co2_attrs.min_measure_value,
	&dev_ctx.co2_attrs.max_measure_value,
	&dev_ctx.co2_attrs.tolerance
	);

ZB_ZCL_DECLARE_PM25_MEASUREMENT_ATTRIB_LIST(
	pm25_measurement_attr_list,
	&dev_ctx.pm25_attrs.measure_value,
	&dev_ctx.pm25_attrs.min_measure_value,
	&dev_ctx.pm25_attrs.max_measure_value,
	&dev_ctx.pm25_attrs.tolerance
	);


ZB_DECLARE_RANGE_EXTENDER_CLUSTER_LIST(
	app_template_clusters,
	basic_attr_list,
	identify_attr_list);

ZB_DECLARE_RANGE_EXTENDER_EP(
	app_template_ep,
	APP_TEMPLATE_ENDPOINT,
	app_template_clusters);


ZB_HA_DECLARE_ON_OFF_SWITCH_CLUSTER_LIST(
	on_off_switch_clusters,
	on_off_switch_attr_list,
	basic_attr_list,
	identify_attr_list);

ZB_HA_DECLARE_ON_OFF_SWITCH_EP(
	on_off_switch_ep,
	ON_OFF_SWITCH_ENDPOINT,
	on_off_switch_clusters);


ZB_HA_DECLARE_MEASUREMENT_DEVICE_CLUSTER_LIST(
	measurement_device_clusters,
	basic_attr_list,
	identify_attr_list,
	temp_measurement_attr_list,
	illuminance_measurement_attr_list,
	humidity_measurement_attr_list,
	pressure_measurement_attr_list,
	co2_measurement_attr_list,
	pm25_measurement_attr_list);

ZB_HA_DECLARE_MEASUREMENT_DEVICE_EP(
	measurement_device_ep,
	MEASUREMENT_DEVICE_ENDPOINT,
	measurement_device_clusters);


ZBOSS_DECLARE_DEVICE_CTX_3_EP(app_template_ctx, measurement_device_ep, on_off_switch_ep, app_template_ep);

/**@brief Function for initializing all clusters attributes. */
static void app_clusters_attr_init(void)
{
	/* Basic cluster attributes data */
	dev_ctx.basic_attr.zcl_version = ZB_ZCL_VERSION;
	dev_ctx.basic_attr.power_source = ZB_ZCL_BASIC_POWER_SOURCE_BATTERY;

	/* Identify cluster attributes data. */
	dev_ctx.identify_attr.identify_time =
		ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE;
}

/**@brief Function to toggle the identify LED
 *
 * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
 */
static void toggle_identify_led(zb_bufid_t bufid)
{
	static int blink_status;

	dk_set_led(IDENTIFY_LED, (++blink_status) % 2);
	ZB_SCHEDULE_APP_ALARM(toggle_identify_led, bufid, ZB_MILLISECONDS_TO_BEACON_INTERVAL(100));
}

/**@brief Function to handle identify notification events on the first endpoint.
 *
 * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
 */
static void identify_cb(zb_bufid_t bufid)
{
	zb_ret_t zb_err_code;

	if (bufid) {
		/* Schedule a self-scheduling function that will toggle the LED */
		ZB_SCHEDULE_APP_CALLBACK(toggle_identify_led, bufid);
	} else {
		/* Cancel the toggling function alarm and turn off LED */
		zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(toggle_identify_led, ZB_ALARM_ANY_PARAM);
		ZVUNUSED(zb_err_code);

		dk_set_led(IDENTIFY_LED, 0);
	}
}

/**@brief Function to handle measure events
 *
 * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
 */
static void measure(zb_bufid_t bufid)
{
	zb_ret_t zb_err_code;

	if (bufid) {
		/* Measure values */
		dk_set_led(IDENTIFY_LED, 1);
		
		double temperature = htu21d_measure_temperature() + bme280_measure_temperature();
		temperature = temperature/2;
		double humidity = htu21d_measure_humidity() + bme280_measure_humidity();
		humidity = humidity/2;
		double pressure = bme280_measure_pressure();
		double illuminance = measure_bh1750();
		double dust = measure_dust();

		dev_ctx.temp_measure_attrs.measure_value = temperature*100;
		dev_ctx.humidity_attrs.measure_value = humidity*100;
		dev_ctx.illuminance_attrs.measure_value = (zb_uint32_t)(illuminance*100);
		dev_ctx.pres_attrs.measure_value = (zb_uint32_t)(pressure*10);
		dev_ctx.co2_attrs.measure_value = mhz19b_measure_co2();
		dev_ctx.pm25_attrs.measure_value = dust*10000;

		dk_set_led(IDENTIFY_LED, 0);
	} else {
		/* Turn off LED */
		ZVUNUSED(zb_err_code);

		dk_set_led(IDENTIFY_LED, 0);
	}
	
}

/**@brief Starts identifying the device.
 *
 * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
 */
static void start_identifying(zb_bufid_t bufid)
{
	ZVUNUSED(bufid);

	if (ZB_JOINED()) {
		/* Check if endpoint is in identifying mode,
		 * if not put desired endpoint in identifying mode.
		 */
		if (dev_ctx.identify_attr.identify_time ==
		ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE) {

			zb_ret_t zb_err_code = zb_bdb_finding_binding_target(
				APP_TEMPLATE_ENDPOINT);

			if (zb_err_code == RET_OK) {
				LOG_INF("Enter identify mode");
			} else if (zb_err_code == RET_INVALID_STATE) {
				LOG_WRN("RET_INVALID_STATE - Cannot enter identify mode");
			} else {
				ZB_ERROR_CHECK(zb_err_code);
			}
		} else {
			LOG_INF("Cancel identify mode");
			zb_bdb_finding_binding_target_cancel();
		}
	} else {
		LOG_WRN("Device not in a network - cannot enter identify mode");
	}
}

/**@brief Callback for button events.
 *
 * @param[in]   button_state  Bitmask containing buttons state.
 * @param[in]   has_changed   Bitmask containing buttons
 *                            that have changed their state.
 */
static void button_changed(uint32_t button_state, uint32_t has_changed)
{
	if (IDENTIFY_MODE_BUTTON & has_changed) {
		if (IDENTIFY_MODE_BUTTON & button_state) {
			/* Button changed its state to pressed */
		} else {
			/* Button changed its state to released */
			if (was_factory_reset_done()) {
				/* The long press was for Factory Reset */
				LOG_DBG("After Factory Reset - ignore button release");
			} else   {
				/* Button released before Factory Reset */

				/* Start identification mode */
				ZB_SCHEDULE_APP_CALLBACK(start_identifying, 0);
			}
		}
	}

	check_factory_reset_button(button_state, has_changed);
}

/**@brief Function for initializing LEDs and Buttons. */
static void configure_gpio(void)
{
	int err;

	err = dk_buttons_init(button_changed);
	if (err) {
		LOG_ERR("Cannot init buttons (err: %d)", err);
	}

	err = dk_leds_init();
	if (err) {
		LOG_ERR("Cannot init LEDs (err: %d)", err);
	}
}

/**@brief Zigbee stack event handler.
 *
 * @param[in]   bufid   Reference to the Zigbee stack buffer
 *                      used to pass signal.
 */
void zboss_signal_handler(zb_bufid_t bufid)
{
	/* Update network status LED. */
	zigbee_led_status_update(bufid, ZIGBEE_NETWORK_STATE_LED);

	/* No application-specific behavior is required.
	 * Call default signal handler.
	 */
	ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));

	/* All callbacks should either reuse or free passed buffers.
	 * If bufid == 0, the buffer is invalid (not passed).
	 */
	if (bufid) {
		zb_buf_free(bufid);
	}
}

void main(void)
{
	/* Sensors */
	bme280_init();
	mhz19b_init();
	dust_sensor_init();

	LOG_INF("Starting Zigbee application template example");

	/* Initialize */
	configure_gpio();
	register_factory_reset_button(FACTORY_RESET_BUTTON);

	/* Register device context (endpoints). */
	ZB_AF_REGISTER_DEVICE_CTX(&app_template_ctx);

	app_clusters_attr_init();

	/* Register handlers to identify notifications */
	ZB_AF_SET_IDENTIFY_NOTIFICATION_HANDLER(APP_TEMPLATE_ENDPOINT, identify_cb);
	ZB_AF_SET_ENDPOINT_HANDLER(MEASUREMENT_DEVICE_ENDPOINT, measure);

	/* Start Zigbee default thread */
	zigbee_enable();

	LOG_INF("Zigbee application template started");
}
