/*
 * Copyright (C) 1994-2018 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * PBS Pro is free software. You can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * PBS Pro 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * For a copy of the commercial license terms and conditions,
 * go to: (http://www.pbspro.com/UserArea/agreement.html)
 * or contact the Altair Legal Department.
 *
 * Altair’s dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of PBS Pro and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair’s trademarks, including but not limited to "PBS™",
 * "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
 * trademark licensing policies.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pbs_array_list.h"

/**
 * @brief
 *	return the reallocated chunk of memory to hold range of ip addresses,
 *
 * @return	pntPBS_IP_RANGE
 * @retval	reallocated memory 	
 *
 */
pntPBS_IP_RANGE
create_pbs_range(void)
{
	return ((pntPBS_IP_RANGE)calloc(CHUNK, sizeof(PBS_IP_RANGE)));
}

/**
 * @brief
 *	resize the present ip list
 *
 * @param[in] list - list of ip address
 *
 * @return	pntPBS_IP_LIST
 * @retval	new list	success
 * @retval	NULL		error
 */
pntPBS_IP_LIST
resize_pbs_iplist(pntPBS_IP_LIST list)
{
	pntPBS_IP_RANGE temp;
	temp = (pntPBS_IP_RANGE)realloc(list->li_range, ((CHUNK + list->li_totalsize)* sizeof(PBS_IP_RANGE)));
	if (temp != NULL) {
		list->li_range = temp;
		memset(((char *)list->li_range + (list->li_totalsize * sizeof(PBS_IP_RANGE))), 0, CHUNK * sizeof(PBS_IP_RANGE));
		list->li_totalsize += CHUNK;
		return list;
	}
	else {
		delete_pbs_iplist(list);
		return NULL;
	}
}

/**
 * @brief
 *	creates ip address list.
 *
 * @return	pntPBS_IP_LIST
 * @retval	reference to created ip addr list	success
 * @retval	NULL					error
 *
 */
pntPBS_IP_LIST
create_pbs_iplist(void)
{
	pntPBS_IP_LIST list= (pntPBS_IP_LIST)calloc(1, sizeof(PBS_IP_LIST));
	if (list) {
		list->li_range = create_pbs_range();
		if (! list->li_range) {
			free(list);
			return NULL;
		}
		list->li_totalsize = CHUNK;
	}
	return list;
}

/**
 * @brief
 *      deletes ip address list.
 *
 * @param[in] pntPBS_IP_LIST pointer to list
 *
 */
void
delete_pbs_iplist(pntPBS_IP_LIST list)
{
	if (list) {
		if (list->li_range)
			free(list->li_range);
		free(list);
	}
	return;
}


/**
 * @brief
 *	searches the the key value in list.
 *
 * @param[in] list - list
 * @param[in] key - key value
 * @param[out] location - index where key found
 *
 * @return	int
 * @retval	location	if found
 * @retval	-1		if not found
 *
 */
int
search_location(pntPBS_IP_LIST list, T key, int *location)
{
	int bottom, middle, top;
	bottom = 0;
	top = list->li_nrowsused - 1;
	while (top >= bottom) {
		middle = (top + bottom) / 2;
		if (key == IPLIST_GET_LOW(list, middle)) {
			*location = middle;
			return middle;
		}
		else if (key < IPLIST_GET_LOW(list, middle))
			top = middle - 1;
		else
			bottom = middle + 1;
	}
	*location = top;
	if (top != -1 && key <= (IPLIST_GET_LOW(list, *location) + IPLIST_GET_HIGH(list, *location))) {
		return (*location);
	}
	return -1;
}

/**
 * @brief
 *      insert the the key value into list.
 *
 * @param[in] list - list
 * @param[in] key - key value
 *
 * @return      int
 * @retval      0	insertion of key successful        
 * @retval      !0	insertion of key  failed
 *
 */

int
insert_iplist_element(pntPBS_IP_LIST list, T key)
{
	int location = -1;
	int first_row = 0;
	/* If list is empty, go ahed and input the ip at first location */
	if (IPLIST_GET_LOW(list, 0) == 0 && list->li_nrowsused == 0) {
		IPLIST_SET_LOW(list, 0, key);
		list->li_nrowsused++;
		return IPLIST_INSERT_SUCCESS;
	}

	if (list->li_nrowsused == list->li_totalsize) {
		list = resize_pbs_iplist(list);
		if (!list) {
			return IPLIST_INSERT_FAILURE;
		}
	}

	if (search_location(list, key, &location) >= 0)
		return IPLIST_INSERT_SUCCESS;
	if (location == -1) {
		first_row = 1;
		location++;
	}

	if (IPLIST_IS_CONTINUOUS_ROW(list, location, key)) {
		IPLIST_SET_HIGH(list, location, IPLIST_GET_HIGH(list, location) + 1);
		if (IPLIST_IS_CONTINUOUS_ROW(list, location, IPLIST_GET_LOW(list, location +1))) {
			IPLIST_SET_HIGH(list, location, IPLIST_GET_HIGH(list, location) + 1 + IPLIST_GET_HIGH(list, location + 1));
			/** memove rows up , decrement nrowsused, memset one row with INIT_VALUE **/
			list->li_nrowsused--;
			if (IPLIST_SHIFT_ALL_UP_BY_ONE(list, location +1, list->li_nrowsused - (location +1)) == NULL) {
				list->li_nrowsused++;
				return IPLIST_INSERT_FAILURE;
			}
			memset(list->li_range + list->li_nrowsused, 0 , sizeof(PBS_IP_RANGE));
		}
	} else {
		if (first_row)
			location--;
		if (IPLIST_IS_CONTINUOUS(key, IPLIST_GET_LOW(list, location +1))) {
			IPLIST_SET_LOW(list, location + 1, key);
			IPLIST_SET_HIGH(list, location + 1, IPLIST_GET_HIGH(list, location + 1) + 1);
		}
		else {
			if (IPLIST_GET_LOW(list, location +1) == INIT_VALUE) {
				IPLIST_SET_LOW(list, location+1, key);
				list->li_nrowsused++;
			} else {
				/** Add new Row **/
				if (IPLIST_SHIFT_ALL_DOWN_BY_ONE(list, location + 1, list->li_nrowsused - (location +1)) == NULL)
					return IPLIST_INSERT_FAILURE;
				IPLIST_SET_LOW(list, location + 1, key);
				IPLIST_SET_HIGH(list, location + 1, INIT_VALUE);
				list->li_nrowsused++;
			}
		}
	}
	return IPLIST_INSERT_SUCCESS;
}

/**
 * @brief
 *      delete the the key value into list.
 *
 * @param[in] list - list
 * @param[in] key - key value
 *
 * @return      int
 * @retval      0       deletion of key successful
 * @retval      !0      deletion of key  failed
 *
 */

int
delete_iplist_element(pntPBS_IP_LIST list, T key)
{
	int location = -1;
	T high = 0;

	if (list->li_nrowsused == list->li_totalsize) {
		list = resize_pbs_iplist(list);
		if (!list) {
			return IPLIST_INSERT_FAILURE;
		}
	}

	if (search_location(list, key, &location) == -1)
		return IPLIST_DELETE_FAILURE;
	if ((IPLIST_GET_LOW(list, location) == key) && list->li_nrowsused) { /** If the Lower IP of range **/
		if (IPLIST_GET_HIGH(list, location)==INIT_VALUE) {
			if (IPLIST_SHIFT_ALL_UP_BY_ONE(list, location, list->li_nrowsused - (location +1)) == NULL) {
				list->li_nrowsused++;
				return IPLIST_DELETE_FAILURE;
			}
			list->li_nrowsused--;
			memset(list->li_range + list->li_nrowsused , 0 , sizeof(PBS_IP_RANGE));
		}
		else {
			IPLIST_SET_LOW(list, location, IPLIST_GET_LOW(list, location)+1);
			IPLIST_SET_HIGH(list, location, IPLIST_GET_HIGH(list, location)-1);
		}
	} else if ((IPLIST_GET_LOW(list, location) + IPLIST_GET_HIGH(list, location)) == key) { /** Is the biggest IP of range **/
		IPLIST_SET_HIGH(list, location, IPLIST_GET_HIGH(list, location)-1);
	} else { /** Lies somewhere in between LOW & HIGH **/
		/* temp = IPLIST_GET_HIGH(list,location); */
		high = IPLIST_GET_LOW(list, location) + IPLIST_GET_HIGH(list, location);
		IPLIST_SET_HIGH(list, location, key-IPLIST_GET_LOW(list, location) -1);
		if (IPLIST_SHIFT_ALL_DOWN_BY_ONE(list, location + 1, list->li_nrowsused - (location +1)) == NULL)
			return IPLIST_DELETE_FAILURE;
		IPLIST_SET_LOW(list, location+1, key+1);
		IPLIST_SET_HIGH(list, location+1, high-IPLIST_GET_LOW(list, location+1));
		list->li_nrowsused++;
	}
	return IPLIST_DELETE_SUCCESS;
}
