/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.io.uri.impl;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.ranides.assira.collection.maps.HashMap;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public abstract class CHandleManager<P,C> {
    
    private final Map<P, CResource<C>> resources = new HashMap<>();
    
    protected abstract C connect(P path) throws IOException;
    
    protected abstract void disconnect(C channel) throws IOException;
    
    protected abstract void reset(C channel) throws IOException;
    
    public void open(P path) {
        synchronized(resources) {
            CResource<C> res = resources.get(path);
            if(res == null) {
                resources.put(path, res = new CResource());
            }
            res.counter++;
        }
    }

    public void close(P path) throws IOException {
        synchronized(resources) {
            CResource<C> res = resources.get(path);
            if(res == null) {
                return;
            }
            if(0 == --res.counter && null != res.shared) {
                disconnect(res.shared);
                res.shared = null;
            }
        }
    }

    public C shared(P path) throws IOException {
        synchronized(this) {
            CResource<C> res = resources.get(path);
            if(res == null) {
                resources.put(path, res = new CResource());
            }
            if(null == res.shared) {
                res.shared = connect(path);
            }
            return res.shared;
        }
    }
    
    public C fork(P path) throws IOException {
        synchronized(resources) {
            CResource<C> res = resources.get(path);
            if(res == null) {
                resources.put(path, res = new CResource());
            }

            C channel;
            if(null != res.shared) {
                res.forked.add(channel = res.shared);
                res.shared = null;
            } else {
                res.forked.add(channel = connect(path));
            }
            return channel;
        }
    }
    
    public void close(P path, C channel) throws IOException {
        synchronized(resources) {
            CResource<C> res = resources.get(path);
            if(res == null) {
                return;
            }
            if(!res.forked.remove(channel)) {
                return;
            }
            if(null == res.shared && 0 != res.counter) {
                reset(res.shared = channel);
            } else {
                disconnect(channel);
            }
        }
    }
    
    private static final class CResource<V> {
        
        public V shared;
        
        public Set<V> forked = new HashSet<>();
        
        private int counter;

    }
    
}
