/**
 * 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.
 */

#ifdef DLL_EXPORT
#define USE_STATIC_LIB
#endif

#if defined(__CYGWIN__)
#define USE_IPV6
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <zookeeper_log.h>
#include <time.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <limits.h>
#include <zoo_queue.h>
#include <stdbool.h>
#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif

#ifdef HAVE_GETPWUID_R
#include <pwd.h>
#endif

#define IF_DEBUG(x) if (logLevel==ZOO_LOG_LEVEL_DEBUG) {x;}


static void free_String_vector(struct String_vector *v) {
    if (v->data) {
        int32_t i;
        for (i=0; i<v->count; i++) {
            free(v->data[i]);
        }
        free(v->data);
        v->data = 0;
    }
}


static int vstrcmp(const void* str1, const void* str2) {
    const char **a = (const char**)str1;
    const char **b = (const char**) str2;
    return strcmp(*a, *b); 
}

static void sort_children(struct String_vector *vector) {
    qsort( vector->data, vector->count, sizeof(char*), &vstrcmp);
}


static void concat_path_nodename_n(char *buffer, int len, const char *path, const char *node_name){
    snprintf(buffer, len, "%s/%s", path, node_name); 
}

static char *concat_path_nodename(const char *path, const char *node_name){
    int node_path_length = strlen(path) + 1+ strlen(node_name) +1;
    char *node_path = (char *) malloc(node_path_length * sizeof(char));
    concat_path_nodename_n(node_path, node_path_length, path, node_name);
    return node_path;
}  


static void zkr_queue_cache_create_path(zkr_queue_t *queue){
    if(queue->cached_create_path != NULL){
        free(queue->cached_create_path);
    }
    queue->cached_create_path = concat_path_nodename(queue->path, queue->node_name);
}

ZOOAPI int zkr_queue_init(zkr_queue_t *queue, zhandle_t* zh, char* path, struct ACL_vector *acl){
    queue->zh = zh;
    queue->path = path;
    queue->node_name = "qn-";
    queue->node_name_length = strlen(queue->node_name);
    queue->cached_create_path = NULL;
    queue->acl = acl;
    pthread_mutex_init(&(queue->pmutex), NULL);
    zkr_queue_cache_create_path(queue);
    return 0;
}

static ZOOAPI int create_queue_root(zkr_queue_t *queue){
    return zoo_create(queue->zh, queue->path, NULL, 0, queue->acl, 0, NULL, 0 );
}

static int valid_child_name(zkr_queue_t *queue, const char *child_name){
    return strncmp(queue->node_name, child_name, queue->node_name_length);
}

ZOOAPI int zkr_queue_offer(zkr_queue_t *queue, const char *data, int buffer_len){
    for(;;){
        int rc = zoo_create(queue->zh, queue->cached_create_path, data, buffer_len, queue->acl, ZOO_SEQUENCE, NULL, 0 );
        switch(rc){
            int create_root_rc;
        case ZNONODE:
            create_root_rc = create_queue_root(queue);
            switch(create_root_rc){
            case ZNODEEXISTS:
            case ZOK:
                break;
            default:
                return create_root_rc; 
            }
            break;
        default:
            return rc;
        }
    }
}


ZOOAPI int zkr_queue_element(zkr_queue_t *queue, char *buffer, int *buffer_len){
    int path_length = strlen(queue->path);
    for(;;){
        struct String_vector stvector;
        struct String_vector *vector = &stvector;
        /*Get sorted children*/
        int get_children_rc = zoo_get_children(queue->zh, queue->path, 0, vector);
        switch(get_children_rc){
        case ZOK:
            break;
        case ZNONODE:
            *buffer_len = -1;
            return ZOK;
        default:
            return get_children_rc;
        }
        if(stvector.count == 0){
            *buffer_len = -1;
            return ZOK;
        }

        sort_children(vector);
        /*try all*/
        int i;
        for(i=0; i < stvector.count; i++){
            char *child_name = stvector.data[i];
            int child_path_length = path_length + 1 + strlen(child_name) +1; 
            char child_path[child_path_length];
            concat_path_nodename_n(child_path, child_path_length, queue->path, child_name);
            int get_rc = zoo_get(queue->zh, child_path, 0, buffer, buffer_len, NULL);
            switch(get_rc){
            case ZOK:
                free_String_vector(vector);
                return ZOK;
            case ZNONODE:
                break;
            default:
                free_String_vector(vector);
                return get_rc;
            }
        }
    
        free_String_vector(vector);
    }
}

ZOOAPI int zkr_queue_remove(zkr_queue_t *queue, char *buffer, int *buffer_len){
    int path_length = strlen(queue->path);
    for(;;){
        struct String_vector stvector;
        struct String_vector *vector = &stvector;
        /*Get sorted children*/
        int get_children_rc = zoo_get_children(queue->zh, queue->path, 0, &stvector);
        switch(get_children_rc){
        case ZOK:
            break;
        case ZNONODE:
            *buffer_len = -1;
            return ZOK;
            
        default:
            *buffer_len = -1;
            return get_children_rc;
        }
        if(stvector.count == 0){
            *buffer_len = -1;
            return ZOK;
        }

        sort_children(vector);
        /*try all*/
        int i;
        for( i=0; i < stvector.count; i++){
            char *child_name = stvector.data[i];
            int child_path_length = path_length + 1 + strlen(child_name) +1; 
            char child_path[child_path_length];
            concat_path_nodename_n(child_path, child_path_length, queue->path, child_name);
            int get_rc = zoo_get(queue->zh, child_path, 0, buffer, buffer_len, NULL);
            switch(get_rc){
                int delete_rc;
            case ZOK:
                delete_rc = zoo_delete(queue->zh, child_path, -1);
                switch(delete_rc){
                case ZOK:
                    free_String_vector(vector);
                    return delete_rc;
                case ZNONODE:
                    break;
                default:
                    free_String_vector(vector);
                    *buffer_len = -1;
                    return delete_rc;
                }
                break;
            case ZNONODE:
                break;
            default:
                free_String_vector(vector);
                *buffer_len = -1;
                return get_rc;
            }
        }
        free_String_vector(vector);
    }
}

/**
 * The take_latch structure roughly emulates a Java CountdownLatch with 1 as the initial value.
 * It is meant to be used by a setter thread and a waiter thread.
 * 
 * This latch is specialized to be used with the queue, all latches created for the same queue structure will use the same mutex.
 *
 * The setter thread at some point will call take_latch_setter_trigger_latch() on the thread.
 *
 * The waiter thread creates the latch and at some point either calls take_latch_waiter_await()s or take_latch_waiter_mark_unneeded()s it.
 * The await function will return after the setter thread has triggered the latch.
 * The mark unneeded function will return immediately and avoid some unneeded initialization.
 *
 * Whichever thread is last to call their required function disposes of the latch.
 *
 * The latch may disposed if no threads will call the waiting, marking, or triggering functions using take_latch_destroy_syncrhonized().
 */

struct take_latch {
    enum take_state {take_init, take_waiting, take_triggered, take_not_needed} state;
    pthread_cond_t latch_condition;
    zkr_queue_t *queue;
};


typedef struct take_latch take_latch_t;  


static void take_latch_init( take_latch_t *latch, zkr_queue_t *queue){
    pthread_mutex_t *mutex = &(queue->pmutex);
    pthread_mutex_lock(mutex);
    latch->state = take_init;
    latch->queue = queue;
    pthread_mutex_unlock(mutex);
}

static take_latch_t *create_take_latch(zkr_queue_t *queue){
    take_latch_t *new_take_latch = (take_latch_t *) malloc(sizeof(take_latch_t));
    take_latch_init(new_take_latch, queue);
    return new_take_latch;
}


//Only call this when you own the mutex
static void take_latch_destroy_unsafe(take_latch_t *latch){
    if(latch->state == take_waiting){
        pthread_cond_destroy(&(latch->latch_condition));
    }
    free(latch);
}

static void take_latch_destroy_synchronized(take_latch_t *latch){
    pthread_mutex_t *mutex = &(latch->queue->pmutex);
    pthread_mutex_lock(mutex);
    take_latch_destroy_unsafe(latch);
    pthread_mutex_unlock(mutex);
}

static void take_latch_setter_trigger_latch(take_latch_t *latch){
    pthread_mutex_t *mutex = &(latch->queue->pmutex);
    pthread_mutex_lock(mutex);
    switch(latch->state){
    case take_init:
        latch->state = take_triggered;
        break;
    case take_not_needed:
        take_latch_destroy_unsafe(latch);
        break;
    case take_triggered:
        LOG_DEBUG(("Error! Latch was triggered twice."));
        break;
    case take_waiting:
        pthread_cond_signal(&(latch->latch_condition));
        break;
    }
    pthread_mutex_unlock(mutex);
}

static void take_latch_waiter_await(take_latch_t *latch){
    pthread_mutex_t *mutex = &(latch->queue->pmutex);
    pthread_mutex_lock(mutex);
    switch(latch->state){
    case take_init:
        pthread_cond_init(&(latch->latch_condition),NULL);
        latch->state = take_waiting;
        pthread_cond_wait(&(latch->latch_condition),mutex);
        take_latch_destroy_unsafe(latch);
        break;
    case take_waiting:
        LOG_DEBUG(("Error! Called await twice."));
        break;
    case take_not_needed:
        LOG_DEBUG(("Error! Waiting after marking not needed."));
        break;
    case take_triggered:
        take_latch_destroy_unsafe(latch);
        break;
    }
    pthread_mutex_unlock(mutex);
}

static void take_latch_waiter_mark_unneeded(take_latch_t *latch){
    pthread_mutex_t *mutex = &(latch->queue->pmutex);
    pthread_mutex_lock(mutex);
    switch(latch->state){
    case take_init:
        latch->state = take_not_needed;
        break;
    case take_waiting:
        LOG_DEBUG(("Error! Can't mark unneeded after waiting."));
        break;
    case take_not_needed:
        LOG_DEBUG(("Marked unneeded twice."));
        break;
    case take_triggered:
        take_latch_destroy_unsafe(latch);
        break;
    }
    pthread_mutex_unlock(mutex);
}

static void take_watcher(zhandle_t *zh, int type, int state, const char *path, void *watcherCtx){
    take_latch_t *latch = (take_latch_t *) watcherCtx;
    take_latch_setter_trigger_latch(latch);
}



ZOOAPI int zkr_queue_take(zkr_queue_t *queue, char *buffer, int *buffer_len){
    int path_length = strlen(queue->path);
take_attempt:    
    for(;;){
        struct String_vector stvector;
        struct String_vector *vector = &stvector;
        /*Get sorted children*/
        take_latch_t *take_latch = create_take_latch(queue);
        int get_children_rc = zoo_wget_children(queue->zh, queue->path, take_watcher, take_latch, &stvector);
        switch(get_children_rc){
        case ZOK:
            break;
            int create_queue_rc;
        case ZNONODE:
            take_latch_destroy_synchronized(take_latch);
            create_queue_rc = create_queue_root(queue);
            switch(create_queue_rc){
            case ZNODEEXISTS:
            case ZOK:
                goto take_attempt;
            default:
                *buffer_len = -1;
                return create_queue_rc;
            }
        default:
            take_latch_destroy_synchronized(take_latch);
            *buffer_len = -1;
            return get_children_rc;
        }
        if(stvector.count == 0){
            take_latch_waiter_await(take_latch);
        }else{
            take_latch_waiter_mark_unneeded(take_latch);
        }

        sort_children(vector);
        /*try all*/
        int i;
        for( i=0; i < stvector.count; i++){
            char *child_name = stvector.data[i];
            int child_path_length = path_length + 1 + strlen(child_name) +1; 
            char child_path[child_path_length];
            concat_path_nodename_n(child_path, child_path_length, queue->path, child_name);
            int get_rc = zoo_get(queue->zh, child_path, 0, buffer, buffer_len, NULL);
            switch(get_rc){
                int delete_rc;
            case ZOK:
                delete_rc = zoo_delete(queue->zh, child_path, -1);
                switch(delete_rc){
                case ZOK:
                    free_String_vector(vector);
                    return delete_rc;
                case ZNONODE:
                    break;
                default:
                    free_String_vector(vector);
                    *buffer_len = -1;
                    return delete_rc;
                }
                break;
            case ZNONODE:
                break;
            default:
                free_String_vector(vector);
                *buffer_len = -1;
                return get_rc;
            }
        }
        free_String_vector(vector);
    }
}

ZOOAPI void zkr_queue_destroy(zkr_queue_t *queue){
    pthread_mutex_destroy(&(queue->pmutex));
    if(queue->cached_create_path != NULL){
        free(queue->cached_create_path);
    }
}
