package com.oath.micro.server.curator.lock;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.oath.micro.server.dist.lock.DistributedLockService;

import lombok.AllArgsConstructor;
import lombok.experimental.Wither;

/**
 * DistributedLockService suitable for single threaded use only
 *
 */
@Wither
@AllArgsConstructor
public class DistributedLockServiceCuratorImpl implements DistributedLockService, ConnectionStateListener {



    private final ConcurrentMap<String, InterProcessMutex> locks = new ConcurrentHashMap<>();

    private final String basePath;

    private final CuratorFramework curatorFramework;

    private final int timeout;

    private static final Logger logger = LoggerFactory.getLogger(DistributedLockServiceCuratorImpl.class);

    public DistributedLockServiceCuratorImpl(CuratorFramework curatorFramework, String basePath, int timeout)
            throws Exception {
        this.curatorFramework = curatorFramework;
        this.basePath = basePath;
        this.timeout = timeout;
        createIfNotExists(basePath);
    }

    private void createIfNotExists(String path) throws Exception {
        if (curatorFramework.checkExists().forPath(path) == null) {
            curatorFramework.create().creatingParentContainersIfNeeded().forPath(path, new byte[0]);
        }
    }

    @Override
    public boolean tryLock(String key) {
        try {
            InterProcessMutex mutex = locks.computeIfAbsent(key,
                    __ -> new InterProcessMutex(curatorFramework, String.join("/", basePath, key)));


            boolean owned = mutex.isAcquiredInThisProcess();
            if(owned) {
                return true;
            } else {
                mutex.acquire(timeout, TimeUnit.MILLISECONDS);
            }
            return mutex.isAcquiredInThisProcess();
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean tryReleaseLock(String key) {
    	return Optional.ofNullable(locks.get(key)).map(c -> {
            try {
                c.release();
                return true;
            } catch (Exception e) {
                return false;
            }
        }).orElse(false);
    }

    @Override
    public void stateChanged(CuratorFramework client, ConnectionState newState) {

        switch (newState) {
        case LOST:
        case SUSPENDED:

            Collection<InterProcessMutex> oldLocks = new ArrayList<>(locks.values());
            locks.clear();

            oldLocks.stream().parallel().forEach(lock -> {
                try {
                    lock.release();
                } catch (Exception e) {
                    logger.trace("Can't release lock on " + newState);
                }
            });
            break;
        default:
        }
    }

}
