/*
 * Copyright (C) 2023, IOPSYS Software Solutions AB.
 *
 * Author: Vivek Dutta <vivek.dutta@iopsys.eu>
 *
 * 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 of the copyright holder 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 HOLDER 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.
 *
 */

#include "vendor_utils.h"
#include "common_defs.h"
#include <libubox/blobmsg_json.h>

#define DB_CONF_PATH "/etc/board-db/config"

extern dm_node_t *FindNodeFromHash(dm_hash_t hash);

const char *DMT_STR_MAP[] = {
	[DMT_STRING] = "xsd:string",
	[DMT_UNINT] = "xsd:unsignedInt",
	[DMT_INT] = "xsd:int",
	[DMT_UNLONG] = "xsd:unsignedLong",
	[DMT_LONG] = "xsd:long",
	[DMT_BOOL] = "xsd:boolean",
	[DMT_TIME] = "xsd:dateTime",
	[DMT_HEXBIN] = "xsd:hexBinary",
	[DMT_BASE64] = "xsd:base64",
	[DMT_COMMAND] = "xsd:command",
	[DMT_EVENT] = "xsd:event",
	[DMT_OBJECT] = "xsd:object",
};

static int mod_table[] = {0, 2, 1};
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                                'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                                'w', 'x', 'y', 'z', '0', '1', '2', '3',
                                '4', '5', '6', '7', '8', '9', '+', '/'};

static int get_base64_char(char b64)
{
	for (int i = 0; i < 64; i++)
		if (encoding_table[i] == b64)
			return i;

	return -1;
}

struct blob_attr *get_parameters(struct blob_attr *msg, const char *key)
{
	struct blob_attr *params = NULL;
	struct blob_attr *cur;
	int rem;

	blobmsg_for_each_attr(cur, msg, rem) {
		const char *name = blobmsg_name(cur);

		if ((strcmp(key, name) == 0) && (blobmsg_type(cur) == BLOBMSG_TYPE_ARRAY)) {
			params = cur;
			break;
		}
	}
	return params;
}

struct blob_attr *get_param_blob(struct blob_attr *msg, const char *key)
{
	struct blob_attr *params = NULL;
	struct blob_attr *cur;
	int rem;

	blobmsg_for_each_attr(cur, msg, rem) {
		const char *name = blobmsg_name(cur);

		if (strcmp(key, name) == 0) {
			params = cur;
			break;
		}
	}
	return params;
}

dmt_type_t get_dmt_type(char *type)
{
	int i;

	if (type == NULL) {
		USP_LOG_Error("Type info missing the dm parameter");
		return DM_STRING;
	}

	for (i = 0; i < ARRAY_SIZE(DMT_STR_MAP); i++) {
		if (strcmp(type, DMT_STR_MAP[i]) == 0)
			return i;
	}

	USP_LOG_Warning("Type info not matching, defaults to string");
	return DM_STRING;
}

int get_deviceinfo_from_db(const char *param, char *value, size_t max_value_len)
{
	struct uci_context *uci_ctx;
	struct uci_ptr ptr;
	int ret = 0;
	char buf[MAX_DM_PATH] = {0};

	if (!param || !value || max_value_len == 0)
		return -1;

	uci_ctx = uci_alloc_context();
	if (!uci_ctx)
		return -1;

	// Set db conf dir
	uci_set_confdir(uci_ctx, DB_CONF_PATH);

	USP_SNPRINTF(buf, MAX_DM_PATH, "device.deviceinfo.%s", param);
	if (uci_lookup_ptr(uci_ctx, &ptr, buf, true) != UCI_OK) {
		ret = -1;
		goto exit;
	}

	if ((ptr.flags & UCI_LOOKUP_COMPLETE)
		&& (ptr.o != NULL)
		&& (ptr.o->v.string!=NULL)) {
		ret = 0;
		USP_STRNCPY(value, ptr.o->v.string, max_value_len);
	}

exit:
	uci_free_context(uci_ctx);
	return ret;
}

int get_value_from_uci(const char *path, char *value, size_t max_value_len)
{
	struct uci_context *uci_ctx;
	struct uci_ptr ptr;
	int ret = 0;
	char *str;
	char buf[MAX_DM_PATH] = {0};

	if (!path || !value || max_value_len == 0)
		return -1;

	uci_ctx = uci_alloc_context();
	if (!uci_ctx)
		return -1;

	USP_STRNCPY(buf, path, MAX_DM_PATH);
	str = USP_STRDUP(buf);
	if (uci_lookup_ptr(uci_ctx, &ptr, str, true) != UCI_OK) {
		ret = -1;
		goto exit;
	}

	if ((ptr.flags & UCI_LOOKUP_COMPLETE)
		&& (ptr.o != NULL)
		&& (ptr.o->v.string!=NULL)) {
		ret = 0;
		USP_STRNCPY(value, ptr.o->v.string, max_value_len);
	}

exit:
	USP_SAFE_FREE(str);
	uci_free_context(uci_ctx);
	return ret;
}

int get_int_value_from_uci(const char *uci_path)
{
	char value[64] = {0};

	get_value_from_uci(uci_path, value, sizeof(value));

	return strtol(value, NULL, 10);
}

int convert_dmt_to_dmtype(dmt_type_t dmt_type)
{
	int dm_type;

	switch(dmt_type) {
		case DMT_STRING:
			dm_type = DM_STRING;
			break;
		case DMT_UNINT:
			dm_type = DM_UINT;
			break;
		case DMT_INT:
			dm_type = DM_INT;
			break;
		case DMT_UNLONG:
			dm_type = DM_ULONG;
			break;
		case DMT_LONG:
			dm_type = DM_LONG;
			break;
		case DMT_BOOL:
			dm_type = DM_BOOL;
			break;
		case DMT_TIME:
			dm_type = DM_DATETIME;
			break;
		case DMT_HEXBIN:
			dm_type = DM_HEXBIN;
			break;
		case DMT_BASE64:
			dm_type = DM_BASE64;
			break;
		default:
			dm_type = DM_STRING;
	}

	return dm_type;
}

void manual_vec_free(str_vector_t *vec)
{
	int i;

	for (i = 0; i < vec->num_entries; i++) {
		free(vec->vector[i]);
	}

	if (vec->vector)
		free(vec->vector);

	STR_VECTOR_Init(vec);
}

void get_parameters_in_kv(struct blob_attr *attr, kv_vector_t *kv_out)
{
	struct blob_attr *cur;
	size_t rem;

	if (kv_out == NULL)
		return;

	blobmsg_for_each_attr(cur, attr, rem) {
		char path[MAX_DM_PATH] = {0}, data[MAX_DM_VALUE_LEN] = {0};

		if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE)
			continue;

		get_details_from_blob(cur, path, data, NULL, NULL, NULL);
		USP_ARG_Add(kv_out, path, data);
	}
}

char **blob_array_str_list(struct blob_attr *attr, int *list_nr)
{
	struct blob_attr *cur;
	int len, i;
	char **out;
	size_t rem;

	if (attr == NULL) {
		*list_nr = INVALID;
		return NULL;
	}

	len = blobmsg_check_array(attr, BLOBMSG_TYPE_TABLE);
	out = (char **) USP_MALLOC(len * sizeof(char *));
	if (out == NULL) {
		return NULL;
	}

	i = 0;
	blobmsg_for_each_attr(cur, attr, rem) {
		char path[MAX_DM_PATH] = {0};
		if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE)
			continue;

		get_details_from_blob(cur, path, NULL, NULL, NULL, NULL);
		out[i++] = strdup(path);
	}

	*list_nr = i;

	return out;
}

void free_str_list(int num, char **table)
{
	int i;

	for (i=0; i <num; i++) {
		USP_FREE(table[i]);
	}
	USP_FREE(table);
}

void blob_add_optinal_params(struct blob_buf *bp)
{
	void *table;

	table = blobmsg_open_table(bp, "optional");
	blobmsg_add_string(bp, "proto", "usp");
	blobmsg_add_string(bp, "format", "raw");
	blobmsg_close_table(bp, table);
}


int get_details_from_blob(struct blob_attr *pattr, char *path, char *data, char *type, char *fault_msg, str_vector_t *flags_vec)
{
	int ret = USP_ERR_OK;
	enum {
		P_PATH,
		P_TYPE,
		P_DATA,
		P_FAULT,
		P_FAULT_MSG,
		P_FLAGS,
		__P_MAX
	};
	const struct blobmsg_policy p[__P_MAX] = {
		[P_PATH] = { "path", BLOBMSG_TYPE_STRING },
		[P_TYPE] = { "type", BLOBMSG_TYPE_STRING },
		[P_DATA] = { "data", BLOBMSG_TYPE_STRING },
		[P_FAULT] = { "fault", BLOBMSG_TYPE_INT32 },
		[P_FAULT_MSG] = { "fault_msg", BLOBMSG_TYPE_STRING },
		[P_FLAGS] = { "flags", BLOBMSG_TYPE_ARRAY }
	};
	struct blob_attr *tb[__P_MAX] = {NULL};

	if (pattr == NULL) {
		USP_LOG_Error("Empty parameter blob");
		return USP_ERR_INTERNAL_ERROR;
	}

	if (blobmsg_type(pattr) != BLOBMSG_TYPE_TABLE) {
		USP_LOG_Error("Parameter is not an table");
		return USP_ERR_INTERNAL_ERROR;
	}

	ret = blobmsg_parse(p, __P_MAX, tb, blobmsg_data(pattr), blobmsg_len(pattr));
	if (ret != 0) {
		char *ptr;
		ptr = blobmsg_format_json(pattr, true);
		USP_LOG_Error("Failed to parse blob[%s]", ptr);
		USP_FREE(ptr);
		return USP_ERR_INTERNAL_ERROR;
	}

	if (path && tb[P_PATH]) {
		USP_STRNCPY(path, blobmsg_get_string(tb[P_PATH]), MAX_DM_PATH);
	}

	if (type && tb[P_TYPE]) {
		USP_STRNCPY(type, blobmsg_get_string(tb[P_TYPE]), MAX_DM_PATH);
	}

	if (data && tb[P_DATA] != NULL) {
		USP_STRNCPY(data, blobmsg_get_string(tb[P_DATA]), MAX_DM_VALUE_LEN);
	}

	if (fault_msg && tb[P_FAULT_MSG] != NULL) {
		USP_STRNCPY(fault_msg, blobmsg_get_string(tb[P_FAULT_MSG]), MAX_DM_PATH);
	}

	if (flags_vec && tb[P_FLAGS] != NULL) {
		struct blob_attr *dm_flags = tb[P_FLAGS];
		struct blob_attr *flag = NULL;
		size_t rem;

		blobmsg_for_each_attr(flag, dm_flags, rem) {
			char *flag_name = blobmsg_get_string(flag);
			if (flag_name) {
				STR_VECTOR_Add(flags_vec, flag_name);
			}
		}
	}

	if (tb[P_FAULT]) {
		ret = blobmsg_get_u32(tb[P_FAULT]);
	}

	return ret;
}

dm_node_t *dm_node_present(char *path)
{
	dm_hash_t hash;
	int err;

	err = DM_PRIV_CalcHashFromPath(path, NULL, &hash, DONT_LOG_ERRORS);
	if (err != USP_ERR_OK)
	{
		return NULL;
	}

	return FindNodeFromHash(hash);
}

char *base64_encode(char *data, size_t input_length, size_t *datalen)
{
	int i, j;

	if (data == NULL || datalen == NULL)
		return NULL;

	unsigned long int output_length = 4 * ((input_length + 2) / 3);

	char *encoded_data = calloc(output_length + 1, sizeof(char));
	if (encoded_data == NULL)
		return NULL;

	for (i = 0, j = 0; i < input_length;) {
		uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
		uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
		uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;

		uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;

		encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
		encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
		encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
		encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
	}

	for (i = 0; i < mod_table[input_length % 3]; i++)
		encoded_data[output_length - 1 - i] = '=';

	*datalen = output_length;
	return encoded_data;
}

int base64_decode(const char *data, size_t *output_length, const char *fname)
{
	if (data == NULL || output_length == NULL || fname == NULL)
		return -1;

	// cppcheck-suppress cert-MSC24-C
	FILE *fd = fopen(data, "rb");
	if (!fd)
		return -1;

	//read content of the file
	fseek(fd, 0, SEEK_END);
	size_t input_length = ftell(fd);
	fseek(fd, 0, SEEK_SET);

	if (input_length % 4 != 0) {
		USP_LOG_Info("Encoded data not divisible by 4");
		fclose(fd);
		return -1;
	}

	*output_length = input_length / 4 * 3;

	fseek(fd, input_length - 1, SEEK_SET);
	if (fgetc(fd) == '=')
		(*output_length)--;

	fseek(fd, input_length - 2, SEEK_SET);
	if (fgetc(fd) == '=')
		(*output_length)--;

	fseek(fd, 0, SEEK_SET);

	// TODO check available diskspace
	// cppcheck-suppress cert-MSC24-C
	FILE *fp = fopen(fname, "wb");
	if (fp == NULL) {
		USP_LOG_Info("Failed to open file %s", fname);
		fclose(fd);
		return -1;
	}

	for (int i = 0, j = 0; i < input_length;) {
		char en_char;

		en_char = fgetc(fd);
		uint32_t a = (en_char == '=') ? 0 : get_base64_char(en_char);
		i++;

		en_char = fgetc(fd);
		uint32_t b = (en_char == '=') ? 0 : get_base64_char(en_char);
		i++;

		en_char = fgetc(fd);
		uint32_t c = (en_char == '=') ? 0 : get_base64_char(en_char);
		i++;

		en_char = fgetc(fd);
		uint32_t d = (en_char == '=') ? 0 : get_base64_char(en_char);
		i++;

		uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);

		if (j < *output_length) {
			j++;
			fputc((triple >> 2 * 8) & 0xFF, fp);
		}

		if (j < *output_length) {
			j++;
			fputc((triple >> 1 * 8) & 0xFF, fp);
		}

		if (j < *output_length) {
			j++;
			fputc((triple >> 0 * 8) & 0xFF, fp);
		}
	}

	fclose(fp);
	fclose(fd);
	return 0;
}

