// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef _OTEL_TYPES_H
#define _OTEL_TYPES_H

#include "go_types.h"
#include "go_common.h"

// Injected in init
// volatile const u64 attr_type_invalid;

// volatile const u64 attr_type_bool;
// volatile const u64 attr_type_int64;
// volatile const u64 attr_type_float64;
// volatile const u64 attr_type_string;

// volatile const u64 attr_type_boolslice;
// volatile const u64 attr_type_int64slice;
// volatile const u64 attr_type_float64slice;
// volatile const u64 attr_type_stringslice;

/* Defintions should mimic structs defined in go.opentelemetry.io/otel/attribute */

typedef struct go_otel_attr_value {
	u64              vtype;
	u64              numeric;
	struct go_string_ot string;
	struct go_iface	 slice;
} go_otel_attr_value_t;

typedef struct go_otel_key_value {
	struct go_string_ot     key;
	go_otel_attr_value_t value;
} go_otel_key_value_t;

#define OTEL_ATTRIBUTE_KEY_MAX_LEN      (32)
#define OTEL_ATTRIBUTE_VALUE_MAX_LEN    (128)
#define OTEL_ATTRUBUTE_MAX_COUNT        (16)

typedef struct otel_attirbute {
	u16 val_length;
	u8 vtype;
	u8 reserved;
	char key[OTEL_ATTRIBUTE_KEY_MAX_LEN];
	char value[OTEL_ATTRIBUTE_VALUE_MAX_LEN];
} otel_attirbute_t;

typedef struct otel_attributes {
	otel_attirbute_t attrs[OTEL_ATTRUBUTE_MAX_COUNT];
	u8 valid_attrs;
}__attribute__((packed)) otel_attributes_t;

static __always_inline bool set_attr_value(otel_attirbute_t *attr, go_otel_attr_value_t *go_attr_value)
{
	u64 vtype = go_attr_value->vtype;

	if (vtype == 0) {
		bpf_printk("Invalid attribute value type\n");
		return false;
	}

	// Constant size values
	if (vtype == 0 ||
		vtype == 0 ||
		vtype == 0) {
		bpf_probe_read(attr->value, sizeof(s64), &go_attr_value->numeric);
		return true;
	}

	// String values
	if (vtype == 0) {
		if (go_attr_value->string.len >= OTEL_ATTRIBUTE_VALUE_MAX_LEN) {
			bpf_printk("Aattribute string value is too long\n");
			return false;
		}
		return get_go_string_from_user_ptr(&go_attr_value->string, attr->value, OTEL_ATTRIBUTE_VALUE_MAX_LEN);
	}

	// TODO (#525): handle slices
	return false;
}

static __always_inline void convert_go_otel_attributes(void *attrs_buf, u64 slice_len, otel_attributes_t *enc_attrs)
{
	if (attrs_buf == NULL || enc_attrs == NULL){
		return;
	}

	if (slice_len < 1) {
		return;
	}

	u8 num_attrs = slice_len < OTEL_ATTRUBUTE_MAX_COUNT ? slice_len : OTEL_ATTRUBUTE_MAX_COUNT;
	go_otel_key_value_t *go_attr = (go_otel_key_value_t*)attrs_buf;
	go_otel_attr_value_t go_attr_value = {0};
	struct go_string_ot go_str = {0};
	u8 valid_attrs = enc_attrs->valid_attrs;
	if (valid_attrs >= OTEL_ATTRUBUTE_MAX_COUNT) {
		return;
	}

	for (u8 go_attr_index = 0; go_attr_index < num_attrs; go_attr_index++) {
		__builtin_memset(&go_attr_value, 0, sizeof(go_otel_attr_value_t));
		// Read the value struct
		bpf_probe_read(&go_attr_value, sizeof(go_otel_attr_value_t), &go_attr[go_attr_index].value);

		if (go_attr_value.vtype == 0) {
			continue;
		}

		// Read the key string
		bpf_probe_read(&go_str, sizeof(struct go_string_ot), &go_attr[go_attr_index].key);
		if (go_str.len >= OTEL_ATTRIBUTE_KEY_MAX_LEN) {
			// key string is too large
			bpf_printk("Attribute key string is too long\n");
			continue;
		}

		// Need to check valid_attrs otherwise the ebpf verifier thinks it's possible to exceed
		// the max register value for a downstream call, even though it's not possible with
		// this same check at the end of the loop.
		if (valid_attrs >= OTEL_ATTRUBUTE_MAX_COUNT) {
			break;
		}

		if (!get_go_string_from_user_ptr(&go_str, enc_attrs->attrs[valid_attrs].key, OTEL_ATTRIBUTE_KEY_MAX_LEN)) {
			continue;
		}

		if (!set_attr_value(&enc_attrs->attrs[valid_attrs], &go_attr_value)) {
			continue;
		}

		enc_attrs->attrs[valid_attrs].vtype = go_attr_value.vtype;
		valid_attrs++;
		if (valid_attrs >= OTEL_ATTRUBUTE_MAX_COUNT) {
			// No more space for attributes
			break;
		}
	}

	enc_attrs->valid_attrs = valid_attrs;
}

#endif