/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Modified to use APR and APR pools.
 *  TODO: Is malloc() better? Will long running skiplists grow too much?
 *  Keep the skiplist_alloc() and skiplist_free() until we know
 *  Yeah, if using pools it means some bogus cycles for checks
 *  (and an useless function call for skiplist_free) which we
 *  can removed if/when needed.
 */

#include "apr_skiplist.h"

typedef struct {
    apr_skiplistnode **data;
    size_t size, pos;
    apr_pool_t *p;
} apr_skiplist_q; 

struct apr_skiplist {
    apr_skiplist_compare compare;
    apr_skiplist_compare comparek;
    int height;
    int preheight;
    size_t size;
    apr_skiplistnode *top;
    apr_skiplistnode *bottom;
    /* These two are needed for appending */
    apr_skiplistnode *topend;
    apr_skiplistnode *bottomend;
    apr_skiplist *index;
    apr_array_header_t *memlist;
    apr_skiplist_q nodes_q,
                   stack_q;
    apr_pool_t *pool;
};

struct apr_skiplistnode {
    void *data;
    apr_skiplistnode *next;
    apr_skiplistnode *prev;
    apr_skiplistnode *down;
    apr_skiplistnode *up;
    apr_skiplistnode *previndex;
    apr_skiplistnode *nextindex;
    apr_skiplist *sl;
};

static unsigned int get_b_rand(void)
{
    static unsigned int ph = 32;         /* More bits than we will ever use */
    static unsigned int randseq;
    if (ph > 31) {              /* Num bits in return of rand() */
        ph = 0;
        randseq = rand();
    }
    return randseq & (1U << ph++);
}

typedef struct {
    size_t size;
    apr_array_header_t *list;
} memlist_t;

typedef struct {
    void *ptr;
    char inuse;
} chunk_t;

APR_DECLARE(void *) apr_skiplist_alloc(apr_skiplist *sl, size_t size)
{
    if (sl->pool) {
        void *ptr;
        int found_size = 0;
        int i;
        chunk_t *newchunk;
        memlist_t *memlist = (memlist_t *)sl->memlist->elts;
        for (i = 0; i < sl->memlist->nelts; i++) {
            if (memlist->size == size) {
                int j;
                chunk_t *chunk = (chunk_t *)memlist->list->elts;
                found_size = 1;
                for (j = 0; j < memlist->list->nelts; j++) {
                    if (!chunk->inuse) {
                        chunk->inuse = 1;
                        return chunk->ptr;
                    }
                    chunk++;
                }
                break; /* no free of this size; punt */
            }
            memlist++;
        }
        /* no free chunks */
        ptr = apr_palloc(sl->pool, size);
        if (!ptr) {
            return ptr;
        }
        /*
         * is this a new sized chunk? If so, we need to create a new
         * array of them. Otherwise, re-use what we already have.
         */
        if (!found_size) {
            memlist = apr_array_push(sl->memlist);
            memlist->size = size;
            memlist->list = apr_array_make(sl->pool, 20, sizeof(chunk_t));
        }
        newchunk = apr_array_push(memlist->list);
        newchunk->ptr = ptr;
        newchunk->inuse = 1;
        return ptr;
    }
    else {
        return malloc(size);
    }
}

APR_DECLARE(void) apr_skiplist_free(apr_skiplist *sl, void *mem)
{
    if (!sl->pool) {
        free(mem);
    }
    else {
        int i;
        memlist_t *memlist = (memlist_t *)sl->memlist->elts;
        for (i = 0; i < sl->memlist->nelts; i++) {
            int j;
            chunk_t *chunk = (chunk_t *)memlist->list->elts;
            for (j = 0; j < memlist->list->nelts; j++) {
                if (chunk->ptr == mem) {
                    chunk->inuse = 0;
                    return;
                }
                chunk++;
            }
            memlist++;
        }
    }
}

static apr_status_t skiplist_qpush(apr_skiplist_q *q, apr_skiplistnode *m)
{
    if (q->pos >= q->size) {
        apr_skiplistnode **data;
        size_t size = (q->pos) ? q->pos * 2 : 32;
        if (q->p) {
            data = apr_palloc(q->p, size * sizeof(*data));
            if (data && q->data) {
                memcpy(data, q->data, q->pos * sizeof(*data));
            }
        }
        else {
            data = realloc(q->data, size * sizeof(*data));
        }
        if (!data) {
            return APR_ENOMEM;
        }
        q->data = data;
        q->size = size;
    }
    q->data[q->pos++] = m;
    return APR_SUCCESS;
}

static APR_INLINE apr_skiplistnode *skiplist_qpop(apr_skiplist_q *q)
{
    return (q->pos > 0) ? q->data[--q->pos] : NULL;
}

static APR_INLINE void skiplist_qclear(apr_skiplist_q *q)
{
    q->pos = 0;
}

static apr_skiplistnode *skiplist_new_node(apr_skiplist *sl)
{
    apr_skiplistnode *m = skiplist_qpop(&sl->nodes_q);
    if (!m) {
        if (sl->pool) {
            m = apr_palloc(sl->pool, sizeof *m);
        }
        else {
            m = malloc(sizeof *m);
        }
    }
    return m;
}

static apr_status_t skiplist_put_node(apr_skiplist *sl, apr_skiplistnode *m)
{
    return skiplist_qpush(&sl->nodes_q, m);
}

static apr_status_t skiplisti_init(apr_skiplist **s, apr_pool_t *p)
{
    apr_skiplist *sl;
    if (p) {
        sl = apr_pcalloc(p, sizeof(apr_skiplist));
        sl->memlist = apr_array_make(p, 20, sizeof(memlist_t));
        sl->pool = sl->nodes_q.p = sl->stack_q.p = p;
    }
    else {
        sl = calloc(1, sizeof(apr_skiplist));
        if (!sl) {
            return APR_ENOMEM;
        }
    }
    *s = sl;
    return APR_SUCCESS;
}

static int indexing_comp(void *a, void *b)
{
    void *ac = (void *) (((apr_skiplist *) a)->compare);
    void *bc = (void *) (((apr_skiplist *) b)->compare);
    return ((ac < bc) ? -1 : ((ac > bc) ? 1 : 0));
}

static int indexing_compk(void *ac, void *b)
{
    void *bc = (void *) (((apr_skiplist *) b)->compare);
    return ((ac < bc) ? -1 : ((ac > bc) ? 1 : 0));
}

APR_DECLARE(apr_status_t) apr_skiplist_init(apr_skiplist **s, apr_pool_t *p)
{
    apr_status_t rv;
    apr_skiplist *sl;
    rv = skiplisti_init(&sl, p);
    if (rv != APR_SUCCESS) {
        *s = NULL;
        return rv;
    }
    rv = skiplisti_init(&sl->index, p);
    if (rv != APR_SUCCESS) {
        *s = NULL;
        return rv;
    }
    apr_skiplist_set_compare(sl->index, indexing_comp, indexing_compk);
    *s = sl;
    return APR_SUCCESS;
}

APR_DECLARE(void) apr_skiplist_set_compare(apr_skiplist *sl,
                          apr_skiplist_compare comp,
                          apr_skiplist_compare compk)
{
    if (sl->compare && sl->comparek) {
        apr_skiplist_add_index(sl, comp, compk);
    }
    else {
        sl->compare = comp;
        sl->comparek = compk;
    }
}

APR_DECLARE(void) apr_skiplist_add_index(apr_skiplist *sl,
                        apr_skiplist_compare comp,
                        apr_skiplist_compare compk)
{
    apr_skiplistnode *m;
    apr_skiplist *ni;
    int icount = 0;
    apr_skiplist_find(sl->index, (void *)comp, &m);
    if (m) {
        return;                 /* Index already there! */
    }
    if (skiplisti_init(&ni, sl->pool) != APR_SUCCESS) {
        abort();
        return;
    }
    apr_skiplist_set_compare(ni, comp, compk);
    /* Build the new index... This can be expensive! */
    m = apr_skiplist_insert(sl->index, ni);
    while (m->prev) {
        m = m->prev;
        icount++;
    }
    for (m = apr_skiplist_getlist(sl); m; apr_skiplist_next(sl, &m)) {
        int j = icount - 1;
        apr_skiplistnode *nsln;
        nsln = apr_skiplist_insert(ni, m->data);
        /* skip from main index down list */
        while (j > 0) {
            m = m->nextindex;
            j--;
        }
        /* insert this node in the indexlist after m */
        nsln->nextindex = m->nextindex;
        if (m->nextindex) {
            m->nextindex->previndex = nsln;
        }
        nsln->previndex = m;
        m->nextindex = nsln;
    }
}

static int skiplisti_find_compare(apr_skiplist *sl, void *data,
                                  apr_skiplistnode **ret,
                                  apr_skiplist_compare comp,
                                  int last)
{
    int count = 0;
    apr_skiplistnode *m, *found = NULL;
    for (m = sl->top; m; count++) {
        if (m->next) {
            int compared = comp(data, m->next->data);
            if (compared == 0) {
                found = m = m->next;
                if (!last) {
                    break;
                }
                continue;
            }
            if (compared > 0) {
                m = m->next;
                continue;
            }
        }
        m = m->down;
    }
    if (found) {
        while (found->down) {
            found = found->down;
        }
        *ret = found;
    }
    else {
        *ret = NULL;
    }
    return count;
}

static void *find_compare(apr_skiplist *sli, void *data,
                          apr_skiplistnode **iter,
                          apr_skiplist_compare comp,
                          int last)
{
    apr_skiplistnode *m;
    apr_skiplist *sl;
    if (!comp) {
        if (iter) {
            *iter = NULL;
        }
        return NULL;
    }
    if (comp == sli->compare || !sli->index) {
        sl = sli;
    }
    else {
        apr_skiplist_find(sli->index, (void *)comp, &m);
        if (!m) {
            if (iter) {
                *iter = NULL;
            }
            return NULL;
        }
        sl = (apr_skiplist *) m->data;
    }
    skiplisti_find_compare(sl, data, &m, sl->comparek, last);
    if (iter) {
        *iter = m;
    }
    return (m) ? m->data : NULL;
}

APR_DECLARE(void *) apr_skiplist_find_compare(apr_skiplist *sl, void *data,
                                              apr_skiplistnode **iter,
                                              apr_skiplist_compare comp)
{
    return find_compare(sl, data, iter, comp, 0);
}

APR_DECLARE(void *) apr_skiplist_find(apr_skiplist *sl, void *data, apr_skiplistnode **iter)
{
    return find_compare(sl, data, iter, sl->compare, 0);
}

APR_DECLARE(void *) apr_skiplist_last_compare(apr_skiplist *sl, void *data,
                                              apr_skiplistnode **iter,
                                              apr_skiplist_compare comp)
{
    return find_compare(sl, data, iter, comp, 1);
}

APR_DECLARE(void *) apr_skiplist_last(apr_skiplist *sl, void *data,
                                      apr_skiplistnode **iter)
{
    return find_compare(sl, data, iter, sl->compare, 1);
}


APR_DECLARE(apr_skiplistnode *) apr_skiplist_getlist(apr_skiplist *sl)
{
    if (!sl->bottom) {
        return NULL;
    }
    return sl->bottom->next;
}

APR_DECLARE(void *) apr_skiplist_next(apr_skiplist *sl, apr_skiplistnode **iter)
{
    if (!*iter) {
        return NULL;
    }
    *iter = (*iter)->next;
    return (*iter) ? ((*iter)->data) : NULL;
}

APR_DECLARE(void *) apr_skiplist_previous(apr_skiplist *sl, apr_skiplistnode **iter)
{
    if (!*iter) {
        return NULL;
    }
    *iter = (*iter)->prev;
    return (*iter) ? ((*iter)->data) : NULL;
}

APR_DECLARE(void *) apr_skiplist_element(apr_skiplistnode *iter)
{
    return (iter) ? iter->data : NULL;
}

/* forward declared */
static int skiplisti_remove(apr_skiplist *sl, apr_skiplistnode *m,
                            apr_skiplist_freefunc myfree);

static APR_INLINE int skiplist_height(const apr_skiplist *sl)
{
    /* Skiplists (even empty) always have a top node, although this
     * implementation defers its creation until the first insert, or
     * deletes it with the last remove. We want the real height here.
     */
    return sl->height ? sl->height : 1;
}

static apr_skiplistnode *insert_compare(apr_skiplist *sl, void *data,
                                        apr_skiplist_compare comp, int add,
                                        apr_skiplist_freefunc myfree)
{
    apr_skiplistnode *m, *p, *tmp, *ret = NULL;
    int ch, top_nh, nh = 1;

    ch = skiplist_height(sl);
    if (sl->preheight) {
        while (nh < sl->preheight && get_b_rand()) {
            nh++;
        }
    }
    else {
        while (nh <= ch && get_b_rand()) {
            nh++;
        }
    }
    top_nh = nh;

    /* Now we have in nh the height at which we wish to insert our new node,
     * and in ch the current height: don't create skip paths to the inserted
     * element until the walk down through the tree (which decrements ch)
     * reaches nh. From there, any walk down pushes the current node on a
     * stack (the node(s) after which we would insert) to pop back through
     * for insertion later.
     */
    m = sl->top;
    while (m) {
        /*
         * To maintain stability, dups (compared == 0) must be added
         * AFTER each other.
         */
        if (m->next) {
            int compared = comp(data, m->next->data);
            if (compared == 0) {
                if (!add) {
                    /* Keep the existing element(s) */
                    skiplist_qclear(&sl->stack_q);
                    return NULL;
                }
                if (add < 0) {
                    /* Remove this element and continue with the next node
                     * or the new top if the current one is also removed.
                     */
                    apr_skiplistnode *top = sl->top;
                    skiplisti_remove(sl, m->next, myfree);
                    if (top != sl->top) {
                        m = sl->top;
                        skiplist_qclear(&sl->stack_q);
                        ch = skiplist_height(sl);
                        nh = top_nh;
                    }
                    continue;
                }
            }
            if (compared >= 0) {
                m = m->next;
                continue;
            }
        }
        if (ch <= nh) {
            /* push on stack */
            skiplist_qpush(&sl->stack_q, m);
        }
        m = m->down;
        ch--;
    }
    /* Pop the stack and insert nodes */
    p = NULL;
    while ((m = skiplist_qpop(&sl->stack_q))) {
        tmp = skiplist_new_node(sl);
        tmp->next = m->next;
        if (m->next) {
            m->next->prev = tmp;
        }
        m->next = tmp;
        tmp->prev = m;
        tmp->up = NULL;
        tmp->nextindex = tmp->previndex = NULL;
        tmp->down = p;
        if (p) {
            p->up = tmp;
        }
        else {
            /* This sets ret to the bottom-most node we are inserting */
            ret = tmp;
        }
        tmp->data = data;
        tmp->sl = sl;
        p = tmp;
    }

    /* Now we are sure the node is inserted, grow our tree to 'nh' tall */
    for (; sl->height < nh; sl->height++) {
        m = skiplist_new_node(sl);
        tmp = skiplist_new_node(sl);
        m->up = m->prev = m->nextindex = m->previndex = NULL;
        m->next = tmp;
        m->down = sl->top;
        m->data = NULL;
        m->sl = sl;
        if (sl->top) {
            sl->top->up = m;
        }
        else {
            sl->bottom = sl->bottomend = m;
        }
        sl->top = sl->topend = tmp->prev = m;
        tmp->up = tmp->next = tmp->nextindex = tmp->previndex = NULL;
        tmp->down = p;
        tmp->data = data;
        tmp->sl = sl;
        if (p) {
            p->up = tmp;
        }
        else {
            /* This sets ret to the bottom-most node we are inserting */
            ret = tmp;
        }
        p = tmp;
    }
    if (sl->index != NULL) {
        /*
         * this is a external insertion, we must insert into each index as
         * well
         */
        apr_skiplistnode *ni, *li;
        li = ret;
        for (p = apr_skiplist_getlist(sl->index); p; apr_skiplist_next(sl->index, &p)) {
            apr_skiplist *sli = (apr_skiplist *)p->data;
            ni = insert_compare(sli, ret->data, sli->compare, 1, NULL);
            li->nextindex = ni;
            ni->previndex = li;
            li = ni;
        }
    }
    sl->size++;
    return ret;
}

APR_DECLARE(apr_skiplistnode *) apr_skiplist_insert_compare(apr_skiplist *sl, void *data,
                                      apr_skiplist_compare comp)
{
    if (!comp) {
        return NULL;
    }
    return insert_compare(sl, data, comp, 0, NULL);
}

APR_DECLARE(apr_skiplistnode *) apr_skiplist_insert(apr_skiplist *sl, void *data)
{
    return apr_skiplist_insert_compare(sl, data, sl->compare);
}

APR_DECLARE(apr_skiplistnode *) apr_skiplist_add_compare(apr_skiplist *sl, void *data,
                                      apr_skiplist_compare comp)
{
    if (!comp) {
        return NULL;
    }
    return insert_compare(sl, data, comp, 1, NULL);
}

APR_DECLARE(apr_skiplistnode *) apr_skiplist_add(apr_skiplist *sl, void *data)
{
    return apr_skiplist_add_compare(sl, data, sl->compare);
}

APR_DECLARE(apr_skiplistnode *) apr_skiplist_replace_compare(apr_skiplist *sl,
                                    void *data, apr_skiplist_freefunc myfree,
                                    apr_skiplist_compare comp)
{
    if (!comp) {
        return NULL;
    }
    return insert_compare(sl, data, comp, -1, myfree);
}

APR_DECLARE(apr_skiplistnode *) apr_skiplist_replace(apr_skiplist *sl,
                                    void *data, apr_skiplist_freefunc myfree)
{
    return apr_skiplist_replace_compare(sl, data, myfree, sl->compare);
}

#if 0
void skiplist_print_struct(apr_skiplist * sl, char *prefix)
{
    apr_skiplistnode *p, *q;
    fprintf(stderr, "Skiplist Structure (height: %d)\n", sl->height);
    p = sl->bottom;
    while (p) {
        q = p;
        fprintf(stderr, prefix);
        while (q) {
            fprintf(stderr, "%p ", q->data);
            q = q->up;
        }
        fprintf(stderr, "\n");
        p = p->next;
    }
}
#endif

static int skiplisti_remove(apr_skiplist *sl, apr_skiplistnode *m,
                            apr_skiplist_freefunc myfree)
{
    apr_skiplistnode *p;
    if (!m) {
        return 0;
    }
    if (m->nextindex) {
        skiplisti_remove(m->nextindex->sl, m->nextindex, NULL);
    }
    while (m->up) {
        m = m->up;
    }
    do {
        p = m;
        /* take me out of the list */
        p->prev->next = p->next;
        if (p->next) {
            p->next->prev = p->prev;
        }
        m = m->down;
        /* This only frees the actual data in the bottom one */
        if (!m && myfree && p->data) {
            myfree(p->data);
        }
        skiplist_put_node(sl, p);
    } while (m);
    sl->size--;
    while (sl->top && sl->top->next == NULL) {
        /* While the row is empty and we are not on the bottom row */
        p = sl->top;
        sl->top = sl->top->down;/* Move top down one */
        if (sl->top) {
            sl->top->up = NULL; /* Make it think its the top */
        }
        skiplist_put_node(sl, p);
        sl->height--;
    }
    if (!sl->top) {
        sl->bottom = sl->bottomend = NULL;
        sl->topend = NULL;
    }
    return skiplist_height(sl);
}

APR_DECLARE(int) apr_skiplist_remove_node(apr_skiplist *sl,
                                          apr_skiplistnode *iter,
                                          apr_skiplist_freefunc myfree)
{
    apr_skiplistnode *m = iter;
    if (!m) {
        return 0;
    }
    while (m->down) {
        m = m->down;
    }
    while (m->previndex) {
        m = m->previndex;
    }
    return skiplisti_remove(sl, m, myfree);
}

APR_DECLARE(int) apr_skiplist_remove_compare(apr_skiplist *sli,
                            void *data,
                            apr_skiplist_freefunc myfree, apr_skiplist_compare comp)
{
    apr_skiplistnode *m;
    apr_skiplist *sl;
    if (!comp) {
        return 0;
    }
    if (comp == sli->comparek || !sli->index) {
        sl = sli;
    }
    else {
        apr_skiplist_find(sli->index, (void *)comp, &m);
        if (!m) {
            return 0;
        }
        sl = (apr_skiplist *) m->data;
    }
    skiplisti_find_compare(sl, data, &m, comp, 0);
    if (!m) {
        return 0;
    }
    while (m->previndex) {
        m = m->previndex;
    }
    return skiplisti_remove(sl, m, myfree);
}

APR_DECLARE(int) apr_skiplist_remove(apr_skiplist *sl, void *data, apr_skiplist_freefunc myfree)
{
    return apr_skiplist_remove_compare(sl, data, myfree, sl->comparek);
}

APR_DECLARE(void) apr_skiplist_remove_all(apr_skiplist *sl, apr_skiplist_freefunc myfree)
{
    /*
     * This must remove even the place holder nodes (bottom though top)
     * because we specify in the API that one can free the Skiplist after
     * making this call without memory leaks
     */
    apr_skiplistnode *m, *p, *u;
    m = sl->bottom;
    while (m) {
        p = m->next;
        if (myfree && p && p->data) {
            myfree(p->data);
        }
        do {
            u = m->up;
            skiplist_put_node(sl, m);
            m = u;
        } while (m);
        m = p;
    }
    sl->top = sl->bottom = NULL;
    sl->topend = sl->bottomend = NULL;
    sl->height = 0;
    sl->size = 0;
}

APR_DECLARE(void *) apr_skiplist_pop(apr_skiplist *a, apr_skiplist_freefunc myfree)
{
    apr_skiplistnode *sln;
    void *data = NULL;
    sln = apr_skiplist_getlist(a);
    if (sln) {
        data = sln->data;
        skiplisti_remove(a, sln, myfree);
    }
    return data;
}

APR_DECLARE(void *) apr_skiplist_peek(apr_skiplist *a)
{
    apr_skiplistnode *sln;
    sln = apr_skiplist_getlist(a);
    if (sln) {
        return sln->data;
    }
    return NULL;
}

APR_DECLARE(size_t) apr_skiplist_size(const apr_skiplist *sl)
{
    return sl->size;
}

APR_DECLARE(int) apr_skiplist_height(const apr_skiplist *sl)
{
    return skiplist_height(sl);
}

APR_DECLARE(int) apr_skiplist_preheight(const apr_skiplist *sl)
{
    return sl->preheight;
}

APR_DECLARE(void) apr_skiplist_set_preheight(apr_skiplist *sl, int to)
{
    sl->preheight = (to > 0) ? to : 0;
}

static void skiplisti_destroy(void *vsl)
{
    apr_skiplist_destroy(vsl, NULL);
}

APR_DECLARE(void) apr_skiplist_destroy(apr_skiplist *sl, apr_skiplist_freefunc myfree)
{
    while (apr_skiplist_pop(sl->index, skiplisti_destroy) != NULL)
        ;
    apr_skiplist_remove_all(sl, myfree);
    if (!sl->pool) {
        while (sl->nodes_q.pos)
            free(sl->nodes_q.data[--sl->nodes_q.pos]);
        free(sl->nodes_q.data);
        free(sl->stack_q.data);
        free(sl);
    }
}

APR_DECLARE(apr_skiplist *) apr_skiplist_merge(apr_skiplist *sl1, apr_skiplist *sl2)
{
    /* Check integrity! */
    apr_skiplist temp;
    struct apr_skiplistnode *b2;
    if (sl1->bottomend == NULL || sl1->bottomend->prev == NULL) {
        apr_skiplist_remove_all(sl1, NULL);
        temp = *sl1;
        *sl1 = *sl2;
        *sl2 = temp;
        /* swap them so that sl2 can be freed normally upon return. */
        return sl1;
    }
    if(sl2->bottom == NULL || sl2->bottom->next == NULL) {
        apr_skiplist_remove_all(sl2, NULL);
        return sl1;
    }
    /* This is what makes it brute force... Just insert :/ */
    b2 = apr_skiplist_getlist(sl2);
    while (b2) {
        apr_skiplist_insert(sl1, b2->data);
        apr_skiplist_next(sl2, &b2);
    }
    apr_skiplist_remove_all(sl2, NULL);
    return sl1;
}
