#include "queue.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "harness.h"

/* Notice: sometimes, Cppcheck would find the potential NULL pointer bugs,
 * but some of them cannot occur. You can suppress them by adding the
 * following line.
 *   cppcheck-suppress nullPointer
 */

/*
 * Create empty queue.
 * Return NULL if could not allocate space.
 */
struct list_head *q_new()
{
    struct list_head *head = malloc(sizeof(struct list_head));
    // if space cannot be allocated malloc will return NULL　resulting into a
    // NULL pointer
    if (head) {
        INIT_LIST_HEAD(head);
    }
    return head;
}

/* Free all storage used by queue */
void q_free(struct list_head *l)
{
    if (!l) {
        return;
    }
    // element_t *entry, *safe;
    // list_for_each_entry_safe (entry, safe, l, list) {
    //     q_release_element(entry);
    // }
    struct list_head *entry, *safe;
    list_for_each_safe (entry, safe, l) {
        element_t *node = container_of(entry, element_t, list);
        q_release_element(node);
    }
    free(l);
}

/*
 * Attempt to insert element at head of queue.
 * Return true if successful.
 * Return false if q is NULL or could not allocate space.
 * Argument s points to the string to be stored.
 * The function must explicitly allocate space and copy the string into it.
 */
bool q_insert_head(struct list_head *head, char *s)
{
    if (!head) {
        return false;
    }
    element_t *node = malloc(sizeof(element_t));
    if (!node) {
        return false;
    }
    int strsize = strlen(s);
    node->value = malloc(strsize + 1);
    if (!(node->value)) {
        free(node);
        return false;
    }
    memcpy(node->value, s, strsize);
    node->value[strsize] = '\0';
    list_add(&node->list, head);
    // list_add(&(node->list), head); why does this results into a memleak???
    return true;
}

/*
 * Attempt to insert element at tail of queue.
 * Return true if successful.
 * Return false if q is NULL or could not allocate space.
 * Argument s points to the string to be stored.
 * The function must explicitly allocate space and copy the string into it.
 */
bool q_insert_tail(struct list_head *head, char *s)
{
    if (!head) {
        return false;
    }
    element_t *node = malloc(sizeof(element_t));
    if (!node) {
        return false;
    }
    int strsize = strlen(s);
    node->value = malloc(strsize + 1);
    if (!(node->value)) {
        free(node);
        return false;
    }
    memcpy(node->value, s, strsize);
    node->value[strsize] = '\0';
    list_add_tail(&node->list, head);
    return true;
}

/*
 * Attempt to remove element from head of queue.
 * Return target element.
 * Return NULL if queue is NULL or empty.
 * If sp is non-NULL and an element is removed, copy the removed string to *sp
 * (up to a maximum of bufsize-1 characters, plus a null terminator.)
 *
 * NOTE: "remove" is different from "delete"
 * The space used by the list element and the string should not be freed.
 * The only thing "remove" need to do is unlink it.
 *
 * REF:
 * https://english.stackexchange.com/questions/52508/difference-between-delete-and-remove
 */
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
    if (!head || head->next == head) {
        return NULL;
    }
    struct list_head *rm = head->next;
    element_t *node = container_of(rm, element_t, list);
    list_del(rm);
    if (node->value != NULL) {
        if (sp) {
            memcpy(sp, node->value, bufsize - 1);
            sp[bufsize - 1] = '\0';
        }
    }
    return node;
}

/*
 * Attempt to remove element from tail of queue.
 * Other attribute is as same as q_remove_head.
 */
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
    if (!head || head->next == head) {
        return NULL;
    }
    struct list_head *rm = head->prev;
    element_t *node = container_of(rm, element_t, list);
    list_del(rm);
    if (node->value != NULL) {
        if (sp) {
            memcpy(sp, node->value, bufsize - 1);
            sp[bufsize - 1] = '\0';
        }
    }
    return node;
}

/*
 * WARN: This is for external usage, don't modify it
 * Attempt to release element.
 */
void q_release_element(element_t *e)
{
    free(e->value);
    free(e);
}

/*
 * Return number of elements in queue.
 * Return 0 if q is NULL or empty
 */
int q_size(struct list_head *head)
{
    if (!head) {
        return 0;
    }
    int size = 0;
    struct list_head *node;
    list_for_each (node, head) {
        size++;
    }
    return size;
}

/*
 * Delete the middle node in list.
 * The middle node of a linked list of size n is the
 * ⌊n / 2⌋th node from the start using 0-based indexing.
 * If there're six element, the third member should be return.
 * Return true if successful.
 * Return false if list is NULL or empty.
 */
bool q_delete_mid(struct list_head *head)
{
    // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
    if (!head || head->next == head) {
        return false;
    }
    struct list_head *iter1 = head->next, *iter2 = head->next->next;
    while (iter2 != head && iter2->next != head) {
        iter1 = iter1->next;
        iter2 = iter2->next->next;
    }
    element_t *delnode = container_of(iter1, element_t, list);
    list_del(iter1);
    q_release_element(delnode);
    return true;
}

/*
 * Delete all nodes that have duplicate string,
 * leaving only distinct strings from the original list.
 * Return true if successful.
 * Return false if list is NULL.
 *
 * Note: this function always be called after sorting, in other words,
 * list is guaranteed to be sorted in ascending order.
 */

bool q_delete_dup(struct list_head *head)
{
    // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
    if (!head) {
        return false;
    }
    element_t *entry, *safe;
    char *laststring = "";
    list_for_each_entry_safe (entry, safe, head, list) {
        // record string if not equal del element if equal;
        if (strcmp(entry->value, laststring) == 0) {
            list_del(&entry->list);
            q_release_element(entry);
        } else {
            if (entry->list.prev != head) {
                element_t *first =
                    container_of(entry->list.prev, element_t, list);
                list_del(entry->list.prev);
                q_release_element(first);
            }
            laststring = entry->value;
        }
    }
    entry = list_last_entry(head, element_t, list);
    if (strcmp(entry->value, laststring) == 0) {
        list_del(&entry->list);
        q_release_element(entry);
    }
    return true;
}

/*
 * Attempt to swap every two adjacent nodes.
 */
void q_swap(struct list_head *head)
{
    // https://leetcode.com/problems/swap-nodes-in-pairs/
    if (!head) {
        return;
    }
    struct list_head *curr, *after;
    curr = head->next;
    while (curr != head && curr->next != head) {
        after = curr->next;

        curr->next = after->next;
        curr->next->prev = curr;

        after->prev = curr->prev;
        after->prev->next = after;

        after->next = curr;
        curr->prev = after;

        curr = curr->next;
    }
}

/*
 * Reverse elements in queue
 * No effect if q is NULL or empty
 * This function should not allocate or free any list elements
 * (e.g., by calling q_insert_head, q_insert_tail, or q_remove_head).
 * It should rearrange the existing ones.
 */
void interchange_link(struct list_head *node)
{
    struct list_head *temp;
    temp = node->next;
    node->next = node->prev;
    node->prev = temp;
}

void q_reverse(struct list_head *head)
{
    if (!head || head == head->next) {
        return;
    }
    struct list_head *node, *safe;
    list_for_each_safe (node, safe, head) {
        interchange_link(node);
    }
    interchange_link(head);
}

/*
 * Sort elements of queue in ascending order
 * No effect if q is NULL or empty. In addition, if q has only one
 * element, do nothing.
 */
struct list_head *mergeTwoLists(struct list_head *L1, struct list_head *L2)
{
    struct list_head *head = NULL, **ptr = &head, **node;
    for (node = NULL; L1 && L2; *node = (*node)->next) {
        element_t *L1_node = container_of(L1, element_t, list);
        element_t *L2_node = container_of(L2, element_t, list);
        node = (strcmp(L1_node->value, L2_node->value) <= 0) ? &L1 : &L2;
        *ptr = *node;
        ptr = &(*ptr)->next;
    }
    *ptr = (struct list_head *) ((uintptr_t) L1 | (uintptr_t) L2);
    return head;
}

struct list_head *mergesort_list(struct list_head *head)
{
    if (!head->next)
        return head;
    struct list_head *slow = head;
    for (struct list_head *fast = head->next; fast && fast->next;
         fast = fast->next->next)
        slow = slow->next;
    struct list_head *mid = slow->next;
    slow->next = NULL;

    struct list_head *left = mergesort_list(head), *right = mergesort_list(mid);
    return mergeTwoLists(left, right);
}

void q_sort(struct list_head *head)
{
    if (!head || list_empty(head))
        return;
    head->prev->next = NULL;
    head->next = mergesort_list(head->next);
    struct list_head *ptr = head;
    for (; ptr->next; ptr = ptr->next)
        ptr->next->prev = ptr;
    ptr->next = head;
    head->prev = ptr;
}
