package de.accso.flexinale.common.application.caching;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

public final class InMemoryCache<ID, OBJECT> {
    // as an alternative: extract interface, use Cache2K as a backing implementation
    // see https://cache2k.org/docs/latest/user-guide.html#getting-started
    private final ConcurrentHashMap<ID, OBJECT> backingMap = new ConcurrentHashMap<>();

    private final ReentrantLock lock = new ReentrantLock();

    public OBJECT get(final ID id) {
        lock.lock();
        try {
            return backingMap.get(id);
        }
        finally {
            lock.unlock();
        }
    }

    public OBJECT put(final ID id, final OBJECT o) {
        lock.lock();
        try {
            return backingMap.put(id, o);
        }
        finally {
            lock.unlock();
        }
    }

    public OBJECT remove(final ID id) {
        lock.lock();
        try {
            return backingMap.remove(id);
        }
        finally {
            lock.unlock();
        }
    }

    public Set<ID> keys() {
        lock.lock();
        try {
            return backingMap.keySet().stream().collect(Collectors.toUnmodifiableSet());
        }
        finally {
            lock.unlock();
        }
    }

    public Set<OBJECT> values() {
        lock.lock();
        try {
            return backingMap.values().stream().collect(Collectors.toUnmodifiableSet());
        }
        finally {
            lock.unlock();
        }
    }

    public Set<Map.Entry<ID, OBJECT>> entrySet() {
        lock.lock();
        try {
            return backingMap.entrySet();
        }
        finally {
            lock.unlock();
        }
    }

    public int size() {
        lock.lock();
        try {
            return backingMap.size();
        }
        finally {
            lock.unlock();
        }
    }

    public boolean isEmpty() {
        lock.lock();
        try {
            return backingMap.isEmpty();
        }
        finally {
            lock.unlock();
        }
    }

    public boolean contains(final OBJECT o) {
        lock.lock();
        try {
            return backingMap.contains(o);
        }
        finally {
            lock.unlock();
        }
    }

    public boolean containsKey(final ID key) {
        return backingMap.containsKey(key);
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}

        final InMemoryCache<?, ?> that = (InMemoryCache<?, ?>) o;

        return backingMap.equals(that.backingMap);
    }

    @Override
    public int hashCode() {
        return backingMap.hashCode();
    }

    @Override
    public String toString() {
        return backingMap.toString();
    }
}
