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

#define C_LUCY_LOCK
#include "Lucy/Util/ToolSet.h"

#include <ctype.h>

#include "Lucy/Store/Lock.h"
#include "Lucy/Store/Folder.h"
#include "Lucy/Util/Sleep.h"

Lock*
Lock_init(Lock *self, Folder *folder, String *name, int32_t timeout,
          int32_t interval) {
    LockIVARS *const ivars = Lock_IVARS(self);

    // Validate.
    if (interval <= 0) {
        DECREF(self);
        THROW(ERR, "Invalid value for 'interval': %i32", interval);
    }
    StringIterator *iter = Str_Top(name);
    int32_t code_point;
    while (STR_OOB != (code_point = StrIter_Next(iter))) {
        if (isalnum(code_point)
            || code_point == '.'
            || code_point == '-'
            || code_point == '_'
           ) {
            continue;
        }
        DECREF(self);
        THROW(ERR, "Lock name contains disallowed characters: '%o'", name);
    }
    DECREF(iter);

    // Assign.
    ivars->folder       = (Folder*)INCREF(folder);
    ivars->timeout      = timeout;
    ivars->name         = Str_Clone(name);
    ivars->interval     = interval;

    return self;
}

void
Lock_Destroy_IMP(Lock *self) {
    LockIVARS *const ivars = Lock_IVARS(self);
    DECREF(ivars->folder);
    DECREF(ivars->name);
    SUPER_DESTROY(self, LOCK);
}

bool
Lock_make_lock_dir(Folder *folder) {
    String *lock_dir_name = SSTR_WRAP_C("locks");

    if (!Folder_MkDir(folder, lock_dir_name)) {
        Err *err = (Err*)INCREF(Err_get_error());
        // Maybe our attempt failed because another process succeeded.
        if (Folder_Find_Folder(folder, lock_dir_name)) {
            DECREF(err);
        }
        else {
            // Nope, everything failed, so bail out.
            Err_set_error(err);
            return false;
        }
    }

    return true;
}

bool
Lock_Obtain_Shared_IMP(Lock *self) {
    LockIVARS *const ivars = Lock_IVARS(self);
    int32_t time_left = ivars->interval == 0 ? 0 : ivars->timeout;
    bool locked = Lock_Request_Shared(self);

    while (!locked) {
        time_left -= ivars->interval;
        if (time_left <= 0) { break; }
        Sleep_millisleep((uint32_t)ivars->interval);
        locked = Lock_Request_Shared(self);
    }

    if (!locked) { ERR_ADD_FRAME(Err_get_error()); }
    return locked;
}

bool
Lock_Obtain_Exclusive_IMP(Lock *self) {
    LockIVARS *const ivars = Lock_IVARS(self);
    int32_t time_left = ivars->interval == 0 ? 0 : ivars->timeout;
    bool locked = Lock_Request_Exclusive(self);

    while (!locked) {
        time_left -= ivars->interval;
        if (time_left <= 0) { break; }
        Sleep_millisleep((uint32_t)ivars->interval);
        locked = Lock_Request_Exclusive(self);
    }

    if (!locked) { ERR_ADD_FRAME(Err_get_error()); }
    return locked;
}

/***************************************************************************/

LockErr*
LockErr_new(String *message) {
    LockErr *self = (LockErr*)Class_Make_Obj(LOCKERR);
    return LockErr_init(self, message);
}

LockErr*
LockErr_init(LockErr *self, String *message) {
    Err_init((Err*)self, message);
    return self;
}

