#define C_KINO_SHAREDLOCK
#include "KinoSearch/Util/ToolSet.h"

#include <errno.h>
#include <stdio.h>
#include <ctype.h>

#include "KinoSearch/Store/SharedLock.h"
#include "KinoSearch/Store/DirHandle.h"
#include "KinoSearch/Store/Folder.h"
#include "KinoSearch/Store/OutStream.h"

SharedLock*
ShLock_new(Folder *folder, const CharBuf *name, const CharBuf *host, 
           int32_t timeout, int32_t interval)
{
    SharedLock *self = (SharedLock*)VTable_Make_Obj(SHAREDLOCK);
    return ShLock_init(self, folder, name, host, timeout, interval);
}

SharedLock*
ShLock_init(SharedLock *self, Folder *folder, const CharBuf *name, 
            const CharBuf *host, int32_t timeout, int32_t interval)
{
    LFLock_init((LockFileLock*)self, folder, name, host, timeout, interval);

    // Override. 
    DECREF(self->lock_path);
    self->lock_path = (CharBuf*)INCREF(&EMPTY);

    return self;
}

bool_t
ShLock_shared(SharedLock *self) { UNUSED_VAR(self); return true; }

bool_t
ShLock_request(SharedLock *self)
{
    uint32_t i = 0;
    ShLock_request_t super_request 
        = (ShLock_request_t)SUPER_METHOD(SHAREDLOCK, ShLock, Request);

    // EMPTY lock_path indicates whether this particular instance is locked. 
    if (   self->lock_path != (CharBuf*)&EMPTY 
        && Folder_Exists(self->folder, self->lock_path)
    ) {
        // Don't allow double obtain. 
        Err_set_error((Err*)LockErr_new(CB_newf(
            "Lock already obtained via '%o'", self->lock_path)));
        return false;
    }

    DECREF(self->lock_path);
    self->lock_path = CB_new(CB_Get_Size(self->name) + 10);
    do {
        CB_setf(self->lock_path, "locks/%o-%u32.lock", self->name, ++i);
    } while ( Folder_Exists(self->folder, self->lock_path) );

    bool_t success = super_request(self);
    if (!success) { ERR_ADD_FRAME(Err_get_error()); }
    return success;
}

void
ShLock_release(SharedLock *self)
{
    if (self->lock_path != (CharBuf*)&EMPTY) {
        ShLock_release_t super_release
            = (ShLock_release_t)SUPER_METHOD(SHAREDLOCK, ShLock, Release);
        super_release(self);

        // Empty out lock_path. 
        DECREF(self->lock_path);
        self->lock_path = (CharBuf*)INCREF(&EMPTY);
    }
}


void
ShLock_clear_stale(SharedLock *self)
{
    DirHandle *dh;
    CharBuf   *entry;
    CharBuf   *candidate = NULL;
    CharBuf   *lock_dir_name = (CharBuf*)ZCB_WRAP_STR("locks", 5);

    if (Folder_Find_Folder(self->folder, lock_dir_name)) {
        dh = Folder_Open_Dir(self->folder, lock_dir_name);
        if (!dh) { RETHROW(INCREF(Err_get_error())); }
        entry = DH_Get_Entry(dh);
    }
    else {
        return;
    }

    // Take a stab at any file that begins with our lock name. 
    while (DH_Next(dh)) {
        if (   CB_Starts_With(entry, self->name)
            && CB_Ends_With_Str(entry, ".lock", 5)
        ) {
            candidate = candidate ? candidate : CB_new(0);
            CB_setf(candidate, "%o/%o", lock_dir_name, entry);
            ShLock_Maybe_Delete_File(self, candidate, false, true);
        }
    }

    DECREF(candidate);
    DECREF(dh);
}

bool_t
ShLock_is_locked(SharedLock *self)
{
    DirHandle *dh;
    CharBuf   *entry;

    CharBuf *lock_dir_name = (CharBuf*)ZCB_WRAP_STR("locks", 5);
    if (Folder_Find_Folder(self->folder, lock_dir_name)) {
        dh = Folder_Open_Dir(self->folder, lock_dir_name);
        if (!dh) { RETHROW(INCREF(Err_get_error())); }
        entry = DH_Get_Entry(dh);
    }
    else {
        return false;
    }
    
    while (DH_Next(dh)) {
        // Translation:  $locked = 1 if $entry =~ /^\Q$name-\d+\.lock$/ 
        if (   CB_Starts_With(entry, self->name)
            && CB_Ends_With_Str(entry, ".lock", 5)
        ) {
            ZombieCharBuf *scratch = ZCB_WRAP(entry);
            ZCB_Chop(scratch, sizeof(".lock") - 1);
            while(isdigit(ZCB_Code_Point_From(scratch, 1))) {
                ZCB_Chop(scratch, 1);
            }
            if (ZCB_Code_Point_From(scratch, 1) == '-') {
                ZCB_Chop(scratch, 1);
                if (ZCB_Equals(scratch, (Obj*)self->name)) {
                    DECREF(dh);
                    return true;
                }
            }
        }
    }

    DECREF(dh);
    return false;
}

/* Copyright 2007-2011 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */

