/*
 * Copyright 2010-2014 Ning, Inc.
 * Copyright 2014-2020 Groupon, Inc
 * Copyright 2020-2020 Equinix, Inc
 * Copyright 2014-2020 The Billing Project, LLC
 *
 * The Billing Project 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.
 */

package org.killbill.commons.locker;

import org.killbill.commons.locker.ReentrantLock.ReentrantLockState;
import org.killbill.commons.locker.ReentrantLock.TryAcquireLockState;
import org.killbill.commons.request.Request;
import org.killbill.commons.request.RequestData;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class TestReentrantLock {


    private ReentrantLock lockTable;

    @BeforeMethod(groups = "fast")
    public void beforeMethod() throws Exception {
        Request.resetPerThreadRequestData();
        lockTable = new ReentrantLock();
    }

    @AfterMethod(groups = "fast")
    public void afterMethod() throws Exception {
        Request.resetPerThreadRequestData();
        lockTable = new ReentrantLock();
    }


    @Test(groups = "fast")
    public void testHeldByOwner() {

        Request.setPerThreadRequestData(new RequestData("12345"));

        TryAcquireLockState lockState = lockTable.tryAcquireLockForExistingOwner("foo");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.FREE);
        // Assume we were able to get distributed lock, so the next poeration would be to createLock
        lockTable.createLock("foo", null);

        lockState = lockTable.tryAcquireLockForExistingOwner("foo");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.HELD_OWNER);

        lockState = lockTable.tryAcquireLockForExistingOwner("foo");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.HELD_OWNER);

        // Ref count should be 3, so last attempt should return true
        boolean free = lockTable.releaseLock("foo");
        Assert.assertFalse(free);

        free = lockTable.releaseLock("foo");
        Assert.assertFalse(free);

        free = lockTable.releaseLock("foo");
        Assert.assertTrue(free);
    }

    @Test(groups = "fast")
    public void testNotHeldByOwner() {

        Request.setPerThreadRequestData(new RequestData("12345"));

        TryAcquireLockState lockState = lockTable.tryAcquireLockForExistingOwner("bar");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.FREE);
        // Assume we were able to get distributed lock, so the next poeration would be to createLock
        lockTable.createLock("bar", null);

        Request.setPerThreadRequestData(new RequestData("54321"));

        lockState = lockTable.tryAcquireLockForExistingOwner("bar");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.HELD_NOT_OWNER);

        try {
            lockTable.releaseLock("foo");
            Assert.fail("Should fail to decrement lock we don't hold");
        } catch (final IllegalStateException ignore) {
        }

        Request.setPerThreadRequestData(new RequestData("12345"));

        final boolean free = lockTable.releaseLock("bar");
        Assert.assertTrue(free);
    }

    @Test(groups = "fast")
    public void testInvalidCreateLock() {

        Request.setPerThreadRequestData(new RequestData("55555"));

        lockTable.createLock("bar", null);
        try {
            lockTable.createLock("bar", null);
            Assert.fail("Should fail to creating lock");
        } catch (final IllegalStateException ignore) {
        }
    }

    @Test(groups = "fast")
    public void testInvalidReleaseLock() {

        Request.setPerThreadRequestData(new RequestData("222222"));

        try {
            lockTable.releaseLock("bar");
            Assert.fail("Should fail to releasing lock");
        } catch (final IllegalStateException ignore) {
        }
    }


    @Test(groups = "fast")
    public void testWithNoRequestId() {

        // We have no requestId and nobody owns it
        TryAcquireLockState lockState = lockTable.tryAcquireLockForExistingOwner("snoopy");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.FREE);

        Request.setPerThreadRequestData(new RequestData("33333"));
        lockTable.createLock("snoopy", null);

        Request.resetPerThreadRequestData();

        // We have no requestId but somebody owns it
        lockState = lockTable.tryAcquireLockForExistingOwner("snoopy");
        Assert.assertEquals(lockState.getLockState(), ReentrantLockState.HELD_NOT_OWNER);
    }
}
