package org.hcjf.cloud.impl;

import org.hcjf.cloud.impl.network.CloudOrchestrator;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author javaito
 */
public final class LockImpl implements Lock {

    private static final String LOCK_NAME = "__lock__";
    private static final String DEFAULT_CONDITION_NAME = "__default_condition_name__";

    private final String name;
    private final Map<String, ConditionImpl> conditionMap;
    private long lockedId;

    public LockImpl(String name) {
        this.name = name;
        this.conditionMap = new HashMap<>();
        CloudOrchestrator.getInstance().publishPath(Lock.class.getName(), name);
    }

    @Override
    public void lock() {
        CloudOrchestrator.getInstance().lock(Lock.class.getName(), name, LOCK_NAME);
        lockedId = Thread.currentThread().getId();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean tryLock() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void unlock() {
        lockedId = -1;
        CloudOrchestrator.getInstance().unlock(Lock.class.getName(), name, LOCK_NAME);
    }

    @Override
    public Condition newCondition() {
        return newCondition(DEFAULT_CONDITION_NAME);
    }

    public synchronized Condition newCondition(String conditionName) {
        ConditionImpl result = conditionMap.get(conditionName);
        if(result == null) {
            result = new ConditionImpl(this, conditionName);
            conditionMap.put(conditionName, result);
        }
        return result;
    }

    public final static class ConditionImpl implements Condition {

        private final LockImpl lock;
        private final String name;

        private ConditionImpl(LockImpl lock, String name) {
            this.lock = lock;
            this.name = name;
        }

        @Override
        public void await() throws InterruptedException {
            if(Thread.currentThread().getId() == lock.lockedId) {
                CloudOrchestrator.getInstance().unlock(Lock.class.getName(), lock.name, LOCK_NAME);
                synchronized (this) {
                    wait();
                }
                CloudOrchestrator.getInstance().lock(Lock.class.getName(), lock.name, LOCK_NAME);
            } else {
                throw new IllegalMonitorStateException();
            }
        }

        @Override
        public void awaitUninterruptibly() {
            throw new UnsupportedOperationException();
        }

        @Override
        public long awaitNanos(long nanosTimeout) throws InterruptedException {
            await(nanosTimeout, TimeUnit.NANOSECONDS);
            return nanosTimeout;
        }

        @Override
        public boolean await(long time, TimeUnit unit) throws InterruptedException {
            boolean result;
            if(Thread.currentThread().getId() == lock.lockedId) {
                CloudOrchestrator.getInstance().unlock(Lock.class.getName(), lock.name, LOCK_NAME);
                long startTime = System.currentTimeMillis();
                synchronized (this) {
                    unit.timedWait(this, time);
                }
                result = (System.currentTimeMillis() - startTime) >= unit.toMillis(time);
                CloudOrchestrator.getInstance().lock(Lock.class.getName(), lock.name, LOCK_NAME);
            } else {
                throw new IllegalMonitorStateException();
            }
            return result;
        }

        @Override
        public boolean awaitUntil(Date deadline) throws InterruptedException {
            boolean result = false;
            long time = deadline.getTime() - System.currentTimeMillis();
            if(time > 0) {
                result = await(time, TimeUnit.MILLISECONDS);
            }
            return result;
        }

        @Override
        public void signal() {
            synchronized(this) {
                notify();
            }
            CloudOrchestrator.getInstance().signal(lock.name, name);
        }

        @Override
        public void signalAll() {
            synchronized(this) {
                notifyAll();
            }
            CloudOrchestrator.getInstance().signalAll(lock.name, name);
        }

        public void distributedSignal() {
            synchronized(this) {
                notify();
            }
        }

        public void distributedSignalAll() {
            synchronized(this) {
                notifyAll();
            }
        }
    }
}