/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - property editor plugin
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *  (Adapted from propsel code in pcb-rnd by the same author)
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

#include <libcschem/config.h>
#include "props.h"
/*#include "propsel.h"*/
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/hid/hid.h>
#include <genht/hash.h>
#define HT(x) htprop_ ## x
#include <genht/ht.c>
#undef HT

/* Type names in text */
static const char *type_names[] = { "invalid", "string", "coord", "angle", "double", "int" };

/* A hash function for each known type */
static unsigned int prophash_coord(csch_propval_t key)  { return longhash(key.coord); }
static unsigned int prophash_angle(csch_propval_t key)  { return longhash(key.angle); }
static unsigned int prophash_double(csch_propval_t key) { return longhash(key.d); }
static unsigned int prophash_int(csch_propval_t key)    { return longhash(key.i); }
static unsigned int prophash_bool(csch_propval_t key)   { return longhash(key.i); }
static unsigned int prophash_comms(csch_propval_t key)  { return key.comms.str == NULL ? 0 : strhash(key.comms.str); }
static unsigned int prophash_string(csch_propval_t key) { return key.string == NULL ? 0 : strhash(key.string); }
static unsigned int prophash_enum(csch_propval_t key)   { return longhash(key.i); }
static unsigned int prophash_attrarr(csch_propval_t key){ return longhash(key.i); }
static unsigned int prophash_uuid(csch_propval_t key)   { return murmurhash(key.u, sizeof(key.u)); }


typedef unsigned int (*prophash_ft)(csch_propval_t key);
static prophash_ft prophash[CSCH_PROPT_max] = {
	NULL, prophash_string, prophash_coord, prophash_angle, prophash_double, prophash_int, prophash_bool, prophash_comms, prophash_enum, prophash_attrarr, prophash_uuid
};

/* A keyeq function for each known type */
static int propkeyeq_coord(csch_propval_t a, csch_propval_t b)  { return a.coord == b.coord; }
static int propkeyeq_angle(csch_propval_t a, csch_propval_t b)  { return a.angle == b.angle; }
static int propkeyeq_double(csch_propval_t a, csch_propval_t b) { return a.d == b.d; }
static int propkeyeq_int(csch_propval_t a, csch_propval_t b)    { return a.i == b.i; }
static int propkeyeq_bool(csch_propval_t a, csch_propval_t b)   { return a.i == b.i; }
static int propkeyeq_comms(csch_propval_t a, csch_propval_t b)    { return a.comms.str == b.comms.str; }
static int propkeyeq_string(csch_propval_t a, csch_propval_t b)
{
	if ((b.string == NULL) && (a.string == NULL))
		return 1;
	if ((b.string == NULL) || (a.string == NULL))
		return 0;
	return (strcmp(a.string, b.string) == 0);
}
static int propkeyeq_enum(csch_propval_t a, csch_propval_t b)    { return a.i == b.i; }
static int propkeyeq_attrarr(csch_propval_t a, csch_propval_t b) { return a.i == b.i; }
static int propkeyeq_uuid(csch_propval_t a, csch_propval_t b)    { return minuid_cmp(a.u, b.u) == 0; }


typedef int (*propkeyeq_ft)(csch_propval_t a, csch_propval_t b);
static propkeyeq_ft propkeyeq[CSCH_PROPT_max] = {
	NULL, propkeyeq_string, propkeyeq_coord, propkeyeq_angle, propkeyeq_double, propkeyeq_int, propkeyeq_bool, propkeyeq_comms, propkeyeq_enum, propkeyeq_attrarr, propkeyeq_uuid
};


/* Init & uninit */
void csch_props_init(csch_propedit_t *ctx, csch_sheet_t *sheet, csch_cgrp_t *grp)
{
	memset(ctx, 0, sizeof(csch_propedit_t));
	htsp_init(&ctx->props, strhash, strkeyeq);
	ctx->sheet = sheet;
	ctx->grp = grp == NULL ? &sheet->direct : grp;
}

void csch_props_reset(csch_propedit_t *ctx)
{
	htsp_entry_t *e;
	for(e = htsp_first(&ctx->props); e != NULL; e = htsp_next(&ctx->props, e)) {
		csch_props_t *p = e->value;
		htprop_clear(&p->values);
	}
}


void csch_props_clear(csch_propedit_t *ctx)
{
	htsp_entry_t *e;
	for(e = htsp_first(&ctx->props); e != NULL; e = htsp_next(&ctx->props, e)) {
		csch_props_t *p = e->value;
		htprop_uninit(&p->values);
		free(p);
		free(e->key);
	}
	htsp_clear(&ctx->props);
}


void csch_props_uninit(csch_propedit_t *ctx)
{
	csch_props_clear(ctx);
	TODO("clear the vectors")
	memset(ctx, 0, sizeof(csch_propedit_t));
}

/* Retrieve values for a prop */
csch_props_t *csch_props_get(csch_propedit_t *ctx, const char *propname)
{
	if (propname == NULL)
		return NULL;
	return htsp_get(&ctx->props, propname);
}

/* Store a new value */
csch_props_t *csch_props_add(csch_propedit_t *ctx, const char *propname, csch_prop_type_t type, csch_propval_t val)
{
	csch_props_t *p;
	htprop_entry_t *e;

	if ((type <= CSCH_PROPT_invalid) || (type >= CSCH_PROPT_max))
		return NULL;

	/* look up or create the value list (p) associated with the property name */
	p = htsp_get(&ctx->props, propname);
	if (p == NULL) {
		p = malloc(sizeof(csch_props_t));
		p->type = type;
		p->comms_type = CSCH_PROPCT_STRING;
		p->enum_names = NULL;
		htprop_init(&p->values, prophash[type], propkeyeq[type]);
		htsp_set(&ctx->props, rnd_strdup(propname), p);
	}
	else {
		if (type != p->type)
			return NULL;
	}

	/* Check if we already have this value */
	e = htprop_getentry(&p->values, val);
	if (e == NULL)
		htprop_set(&p->values, val, 1);
	else
		e->value++;

	return p;
}


const char *csch_props_type_name(csch_prop_type_t type)
{
	if (((int)type < 0) || (type >= CSCH_PROPT_max))
		return NULL;

	return type_names[type];
}

#define STAT(val, field, cnt) \
do { \
	if (val.field < minp.field) minp = val; \
	if (val.field > maxp.field) maxp = val; \
	avgp.field += val.field * cnt; \
} while(0)

int csch_props_stat(csch_propedit_t *ctx, csch_props_t *p, csch_propval_t *most_common, csch_propval_t *min, csch_propval_t *max, csch_propval_t *avg)
{
	htprop_entry_t *e;
	csch_propval_t bestp, minp, maxp, avgp;
	unsigned long best = 0, num_vals = 0;

	if (most_common != NULL) {
		most_common->string = NULL;
		most_common->comms.str = NULL;
	}
	if (min != NULL) {
		min->string = NULL;
		min->comms.str = NULL;
	}
	if (max != NULL) {
		max->string = NULL;
		max->comms.str = NULL;
	}
	if (avg != NULL) {
		avg->string = NULL;
		avg->comms.str = NULL;
	}

	if ((min != NULL) || (max != NULL) || (avg != NULL)) {
		if (p->type == CSCH_PROPT_STRING)
			return -1;
		if (p->type == CSCH_PROPT_COMMS)
			return -1;
		if (p->type == CSCH_PROPT_UUID)
			return -1;
		if (p->type == CSCH_PROPT_ENUM)
			return -1;
	}

	/* set up internal avg, min, max */
	memset(&avgp, 0, sizeof(avgp));
	switch(p->type) {
		case CSCH_PROPT_invalid: break;
		case CSCH_PROPT_max: break;
		case CSCH_PROPT_STRING: break;
		case CSCH_PROPT_COORD:  minp.coord = RND_COORD_MAX; maxp.coord = -minp.coord;  break;
		case CSCH_PROPT_ANGLE:  minp.angle = 100000;    maxp.angle = -minp.angle; break;
		case CSCH_PROPT_DOUBLE: minp.d     = 100000;    maxp.d = -minp.d; break;
		case CSCH_PROPT_INT:    minp.i     = INT_MAX;   maxp.i = -minp.i; break;
		case CSCH_PROPT_BOOL:   minp.i     = 1;         maxp.i = 0; break;
		case CSCH_PROPT_COMMS:  break;
		case CSCH_PROPT_UUID:   break;
		case CSCH_PROPT_ENUM:   break;
		case CSCH_PROPT_ATTRARR:break;
	}

	/* walk through all known values */
	for (e = htprop_first(&p->values); e; e = htprop_next(&p->values, e)) {
		if (e->value > best) {
			best = e->value;
			bestp = e->key;
		}
		num_vals += e->value;
		switch(p->type) {
			case CSCH_PROPT_invalid: break;
			case CSCH_PROPT_max: break;
			case CSCH_PROPT_STRING: break;
			case CSCH_PROPT_COORD:  STAT(e->key, coord, e->value); break;
			case CSCH_PROPT_ANGLE:  STAT(e->key, angle, e->value); break;
			case CSCH_PROPT_DOUBLE: STAT(e->key, d,     e->value); break;
			case CSCH_PROPT_INT:    STAT(e->key, i,     e->value); break;
			case CSCH_PROPT_BOOL:   STAT(e->key, i,     e->value); break;
			case CSCH_PROPT_COMMS:  break;
			case CSCH_PROPT_UUID:   break;
			case CSCH_PROPT_ENUM:   break;
			case CSCH_PROPT_ATTRARR:break;
		}
	}

	/* generate the result */
	if (num_vals != 0) {
		switch(p->type) {
			case CSCH_PROPT_invalid: break;
			case CSCH_PROPT_max: break;
			case CSCH_PROPT_STRING: break;
			case CSCH_PROPT_COORD:  avgp.coord = avgp.coord/num_vals;  break;
			case CSCH_PROPT_ANGLE:  avgp.angle = avgp.angle/num_vals;  break;
			case CSCH_PROPT_DOUBLE: avgp.angle = avgp.d/num_vals;  break;
			case CSCH_PROPT_INT:    avgp.i     = avgp.i/num_vals;  break;
			case CSCH_PROPT_BOOL:   avgp.i     = avgp.i * 100/num_vals;  break;
			case CSCH_PROPT_COMMS:  break;
			case CSCH_PROPT_UUID:   break;
			case CSCH_PROPT_ENUM:   break;
			case CSCH_PROPT_ATTRARR:break;
		}
		if (avg != NULL) *avg = avgp;
		if (min != NULL) *min = minp;
		if (max != NULL) *max = maxp;
		if (most_common != NULL) *most_common = bestp;
	}
	return 0;
}

int prop_cmp(const void *e1_, const void *e2_)
{
	const htsp_entry_t *e1 = e1_, *e2 = e2_;
	if (e1->key[0] != e2->key[0]) /* special exception: list p/ first then a/ */
		return e1->key[0] < e2->key[0] ? 1 : -1;
	return strcmp(e1->key, e2->key) > 0 ? 1 : -1;
}

htsp_entry_t *csch_props_sort(csch_propedit_t *ctx)
{
	htsp_entry_t *e, *arr = malloc(sizeof(htsp_entry_t) * (ctx->props.used + 1));
	int n;

	for(e = htsp_first(&ctx->props), n = 0; e != NULL; e = htsp_next(&ctx->props, e), n++)
		arr[n] = *e;

	qsort(arr, n, sizeof(htsp_entry_t), prop_cmp);

	arr[ctx->props.used].key = NULL;
	return arr;
}


#undef STAT
