/*
 * 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.
 */
package org.apache.brooklyn.util.core.mutex;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;

public class MutexSupport implements WithMutexes {

    private static final Logger log = LoggerFactory.getLogger(MutexSupport.class);
    private final Map<String,SemaphoreWithOwners> semaphores = new LinkedHashMap<String,SemaphoreWithOwners>();

    protected synchronized SemaphoreWithOwners getSemaphore(String mutexId) {
        return getSemaphore(mutexId, false);
    }
    // NB: the map could be "lock-striped" (hashed on mutexId) to avoid the central lock 
    protected synchronized SemaphoreWithOwners getSemaphore(String mutexId, boolean requestBeforeReturning) {
        SemaphoreWithOwners s = semaphores.get(mutexId);
        if (s==null) {
            s = new SemaphoreWithOwners(mutexId);
            semaphores.put(mutexId, s);
        }
        if (requestBeforeReturning) s.indicateCallingThreadWillRequest();
        return s;
    }
    /** forces deletion of the given mutex if it is unused; 
     * normally not required as is done automatically on close
     * (but possibly needed where there are cancelations and risk of memory leaks) */
    public synchronized void cleanupMutex(String mutexId) {
        SemaphoreWithOwners s = semaphores.get(mutexId);
        if (!s.isInUse()) semaphores.remove(mutexId);
    }
    public synchronized void cleanup() {
        Iterator<SemaphoreWithOwners> si = semaphores.values().iterator();
        while (si.hasNext()) {
            SemaphoreWithOwners s = si.next();
            if (!s.isInUse()) si.remove();
        }
    }

    @Override
    public synchronized boolean hasMutex(String mutexId) {
        SemaphoreWithOwners s = semaphores.get(mutexId);
        if (s!=null) return s.isCallingThreadAnOwner();
        return false;
    }

    @Override
    public void acquireMutex(String mutexId, String description) {
        SemaphoreWithOwners s = getSemaphore(mutexId, true);
        if (description!=null) Tasks.setBlockingDetails(description+" - waiting for "+mutexId);
        if (log.isDebugEnabled())
            log.debug("Acquiring mutex: "+mutexId+"@"+this+" - "+description);

        try {
            s.acquire();
        } catch (InterruptedException ie) {
            throw Exceptions.propagate(ie);
        }

        if (description!=null) Tasks.setBlockingDetails(null);
        s.setDescription(description);
        if (log.isDebugEnabled())
            log.debug("Acquired mutex: "+mutexId+"@"+this+" - "+description);
    }

    @Override
    public boolean tryAcquireMutex(String mutexId, String description) {
        SemaphoreWithOwners s = getSemaphore(mutexId, true);
        if (s.tryAcquire()) {
            if (log.isDebugEnabled())
                log.debug("Acquired mutex (opportunistic): "+mutexId+"@"+this+" - "+description);
            s.setDescription(description);
            return true;
        }
        return false;
    }

    @Override
    public synchronized void releaseMutex(String mutexId) {
        SemaphoreWithOwners s;
        if (log.isDebugEnabled())
            log.debug("Releasing mutex: "+mutexId+"@"+this);
        synchronized (this) { s = semaphores.get(mutexId); }
        if (s==null) throw new IllegalStateException("No mutex known for '"+mutexId+"'");
        s.release();
        cleanupMutex(mutexId);
    }
    
    @Override
    public synchronized String toString() {
        return super.toString()+"["+semaphores.size()+" semaphores: "+semaphores.values()+"]";
    }
    
    /** Returns the semaphores in use at the time the method is called, for inspection purposes (and testing).
     * The semaphores used by this class may change over time so callers are strongly discouraged
     * from manipulating the semaphore objects themselves. 
     */
    public synchronized Map<String,SemaphoreWithOwners> getAllSemaphores() {
        return ImmutableMap.<String,SemaphoreWithOwners>copyOf(semaphores);
    }
}