/*****************************************************************************\
 *  sort.c - sprio sorting functions
 *****************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm 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.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include <sys/types.h>

#include "src/common/uid.h"
#include "src/common/xstring.h"

#include "src/sprio/sprio.h"

/* Constants */
#define DEFAULT_SORT "i"

/* Macros */
#define COND_NEGATE(cond,val) ((cond)?(-(val)):(val))
#define CMP_INT(a,b) (((a) < (b)) ? -1 : ((a) > (b)))

/* Global variables */
static bool sort_descend;

/* Local sort functions */
static int _sort_by_cluster_name(void *v1, void *v2);
static int _sort_by_job_id(void *v1, void *v2);
static int _sort_by_nice_level(void *v1, void *v2);
static int _sort_by_qos_name(void *v1, void *v2);
static int _sort_by_account(void *v1, void *v2);
static int _sort_by_partition(void *v1, void *v2);
static int _sort_by_username(void *v1, void *v2);
static int _sort_by_age_prio(void *v1, void *v2);
static int _sort_by_fairshare_prio(void *v1, void *v2);
static int _sort_by_jobsize_prio(void *v1, void *v2);
static int _sort_by_partition_prio(void *v1, void *v2);
static int _sort_by_qos_prio(void *v1, void *v2);
static int _sort_by_job_prio(void *v1, void *v2);
static int _sort_by_tres_prio(void *v1, void *v2);


extern void sort_job_list(list_t *job_list)
{
	int i;
	char c;
	if (params.sort == NULL)
		params.sort = xstrdup(DEFAULT_SORT); /* default: job priority */
	/*
	 * parse sort spec string from end, so the sorts happen in reverse
	 * order.
	 */
	for (i = strlen(params.sort); i --> 0;) {
		c = params.sort[i];
		if (c == ',' || c == '-' || c == '+')
			continue;

		/* + means ascending (default) and - means descending */
		sort_descend = false;
		if (params.sort[i-1] == '-')
			sort_descend = true;

		/*
		 * handle list sorting - weighted and normalized should
		 * sort the same, so sort by weighted
		 */
		switch (c) {
		case 'c': /* sort by cluster name */
			list_sort(job_list, _sort_by_cluster_name);
			break;
		case 'i': /* sort by job id */
			list_sort(job_list, _sort_by_job_id);
			break;
		case 'N': /* sort by nice level ??? */
			list_sort(job_list, _sort_by_nice_level);
			break;
		case 'n': /* sort by QOS name */
			list_sort(job_list, _sort_by_qos_name);
			break;
		case 'o': /* sort by account name */
			list_sort(job_list, _sort_by_account);
			break;
		case 'r': /* sort by partition name */
			list_sort(job_list, _sort_by_partition);
			break;
		case 'u': /* sort by username */
			list_sort(job_list, _sort_by_username);
			break;
		case 'A': /* sort by age priority */
		case 'a':
			list_sort(job_list, _sort_by_age_prio);
			break;
		case 'F': /* sort by fair share priority */
		case 'f':
			list_sort(job_list, _sort_by_fairshare_prio);
			break;
		case 'J': /* sort by job size priority */
		case 'j':
			list_sort(job_list, _sort_by_jobsize_prio);
			break;
		case 'P': /* sort by partition priority */
		case 'p':
			list_sort(job_list, _sort_by_partition_prio);
			break;
		case 'Q': /* sort by qos priority */
		case 'q':
			list_sort(job_list, _sort_by_qos_prio);
			break;
		case 'T': /* sort by TRES priority */
		case 't':
			list_sort(job_list, _sort_by_tres_prio);
			break;
		case 'Y': /* sort by job priority */
		case 'y':
			list_sort(job_list, _sort_by_job_prio);
			break;
		default:
			error("Invalid sort specification: %c",
			      params.sort[i]);
			exit(1);
		}
	}
}


/*****************************************************************************
 * Local utility functions
 *****************************************************************************/
static inline void _get_job_prio_from_void(priority_factors_object_t **j1,
					   priority_factors_object_t **j2,
					   void *v1, void *v2)
{
	*j1 = *(priority_factors_object_t **) v1;
	*j2 = *(priority_factors_object_t **) v2;
}

static inline double _compare_double(double a, double b)
{
	if (fuzzy_equal(a, b))
		return 0;
	return (a < b) ? -1 : 1;
}

/*****************************************************************************
 * Local sort functions
 *****************************************************************************/
static int _sort_by_cluster_name(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	cmp = xstrcmp(job1->cluster_name, job2->cluster_name);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_job_id(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	cmp = CMP_INT(job1->job_id, job2->job_id);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_qos_name(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	cmp = xstrcmp(job1->qos, job2->qos);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_nice_level(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	int val1, val2;
	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->nice : 0;
	val2 = job1->prio_factors ? job2->prio_factors->nice : 0;

	cmp = CMP_INT(val1, val2);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_account(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	cmp = xstrcmp(job1->account, job2->account);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_partition(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	cmp = xstrcmp(job1->partition, job2->partition);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_username(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	char *name1, *name2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	name1 = uid_to_string_cached((uid_t) job1->user_id);
	name2 = uid_to_string_cached((uid_t) job2->user_id);
	cmp = xstrcmp(name1, name2);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_age_prio(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	double val1, val2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->priority_age : 0;
	val2 = job1->prio_factors ? job2->prio_factors->priority_age : 0;

	cmp = _compare_double(val1, val2);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_fairshare_prio(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	double val1, val2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->priority_fs : 0;
	val2 = job1->prio_factors ? job2->prio_factors->priority_fs : 0;

	cmp = _compare_double(val1, val2);
	return COND_NEGATE(sort_descend, cmp);
}


static int _sort_by_jobsize_prio(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	double val1, val2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->priority_js : 0;
	val2 = job1->prio_factors ? job2->prio_factors->priority_js : 0;

	cmp = _compare_double(val1, val2);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_partition_prio(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	double val1, val2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->priority_part : 0;
	val2 = job1->prio_factors ? job2->prio_factors->priority_part : 0;

	cmp = _compare_double(val1, val2);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_qos_prio(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	double val1, val2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->priority_qos : 0;
	val2 = job1->prio_factors ? job2->prio_factors->priority_qos : 0;

	cmp = _compare_double(val1, val2);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_tres_prio(void *v1, void *v2)
{
	int cmp, i;
	priority_factors_object_t *job1, *job2;
	double job1_sum, job2_sum;
	int val1, val2;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	val1 = job1->prio_factors ? job1->prio_factors->tres_cnt : 0;
	val2 = job1->prio_factors ? job2->prio_factors->tres_cnt : 0;

	for (i = 0, job1_sum = 0; i < val1; i++)
		job1_sum += job1->prio_factors->priority_tres[i];
	for (i = 0, job2_sum = 0; i < val2; i++)
		job2_sum += job2->prio_factors->priority_tres[i];

	cmp = _compare_double(job1_sum, job2_sum);
	return COND_NEGATE(sort_descend, cmp);
}

static int _sort_by_job_prio(void *v1, void *v2)
{
	int cmp;
	priority_factors_object_t *job1, *job2;
	double j1_prio, j2_prio;

	_get_job_prio_from_void(&job1, &job2, v1, v2);

	j1_prio = get_priority_from_factors(job1);
	j2_prio = get_priority_from_factors(job2);
	cmp = _compare_double(j1_prio, j2_prio);
	return COND_NEGATE(sort_descend, cmp);
}
