/*
 * Copyright (c) 2018 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

#include <kern/priority_queue.h>
#include <mach/vm_param.h>

#ifdef __LP64__
static_assert(PRIORITY_QUEUE_ENTRY_CHILD_BITS >= VM_KERNEL_POINTER_SIGNIFICANT_BITS,
		"Priority Queue child pointer packing failed");
#endif

priority_queue_entry_t
pqueue_pair_meld(priority_queue_entry_t elt, priority_queue_compare_fn_t cmp_fn)
{
	priority_queue_entry_t pq_meld_result = NULL;
	priority_queue_entry_t pair_list = NULL;

	assert(elt); // caller needs to check this.

	/* Phase 1: */
	/* Split the list into a set of pairs going front to back. */
	/* Hook these pairs onto an intermediary list in reverse order of traversal.*/

	do {
		/* Consider two elements at a time for pairing */
		priority_queue_entry_t pair_item_a = elt;
		priority_queue_entry_t pair_item_b = elt->next;
		if (pair_item_b == NULL) {
			/* Odd number of elements in the list; link the odd element */
			/* as it is on the intermediate list. */
			pair_item_a->prev = pair_list;
			pair_list = pair_item_a;
			break;
		}
		/* Found two elements to pair up */
		elt = pair_item_b->next;
		priority_queue_entry_t pair = pqueue_merge(pair_item_a, pair_item_b, cmp_fn);
		/* Link the pair onto the intermediary list */
		pair->prev = pair_list;
		pair_list = pair;
	} while (elt != NULL);

	/* Phase 2: Merge all the pairs in the pair_list */
	do {
		elt = pair_list->prev;
		pq_meld_result = pqueue_merge(pq_meld_result, pair_list, cmp_fn);
		pair_list = elt;
	} while (pair_list != NULL);

	return pq_meld_result;
}

void
pqueue_destroy(struct priority_queue *q, size_t offset,
		void (^callback)(void *e))
{
	assert(callback != NULL);
	priority_queue_entry_t head = pqueue_unpack_root(q);
	priority_queue_entry_t tail = head;

	while (head != NULL) {
		priority_queue_entry_t child_list = pqueue_entry_unpack_child(head);
		if (child_list) {
			tail->next = child_list;
			while (tail->next) tail = tail->next;
		}

		priority_queue_entry_t elt = head;
		head = head->next;
		callback((void *)elt - offset);
	}

	/* poison the queue now that it's destroyed */
	q->pq_root_packed = ~0UL;
}
