/* Glazed Lists                                                 (c) 2003-2007 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.hibernate;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.event.ListEventAssembler;
import ca.odell.glazedlists.event.ListEventPublisher;
import ca.odell.glazedlists.util.concurrent.LockFactory;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;

import java.util.HashMap;
import java.util.Map;

/**
 * An EventListFactory implementation that uses a list category as key to determine, which
 * {@link ListEventPublisher} and {@link ReadWriteLock} should be used to create EventLists.
 * All factory instances that use the same category produce EventLists wich share the same publisher
 * and lock.
 * 
 * @author Holger Brands
 */
public final class CategoryEventListFactory implements EventListFactory {

    /** Map holding {@link ListInfo} per unique category. */
    private static final Map<String, ListInfo> CATEGORY_MAP = new HashMap<String, ListInfo>();
    
    /** List category used by this factory instance. */
    private final String category;
    
    /**
     * Constructor with list category to use. If the category is not registered yet, it will be
     * registered with a new ReadWriteLock and ListEventPublisher.
     */
    public CategoryEventListFactory(String category) {
        if (category == null) throw new IllegalArgumentException("Category must not be null");
        this.category = category;
        registerCategory(category);
    }

    /**
     * Constructor with list category, lock and publisher to use. If the category is not registered
     * yet, it will be registered with the given ReadWriteLock and ListEventPublisher.
     * 
     * @throws IllegalStateException if the same category is already registered with different values
     */
    public CategoryEventListFactory(String category, ReadWriteLock lock, ListEventPublisher publisher) {
        if (category == null) throw new IllegalArgumentException("Category must not be null");
        if (lock == null) throw new IllegalArgumentException("ReadWriteLock must not be null");
        if (publisher == null) throw new IllegalArgumentException("ListEventPublisher must not be null");
        this.category = category;
        registerCategory(category, lock, publisher);
    }
    
    /**
     * Registers a new list category, if not already there.
     */
    private void registerCategory(String newCategory) {
        synchronized (CATEGORY_MAP) {
            if (!CATEGORY_MAP.containsKey(newCategory)) {
                CATEGORY_MAP.put(newCategory, new ListInfo());
            }            
        }
    }

    /**
     * Registers a new list category with the given lock and publisher, if not already there.
     * 
     * @throws IllegalStateException if the same category is already registered with different values
     */
    private void registerCategory(String newCategory, ReadWriteLock lock, ListEventPublisher publisher) {
        synchronized (CATEGORY_MAP) {
            if (CATEGORY_MAP.containsKey(newCategory)) {
                final ListInfo info = getListInfo();
                if (!lock.equals(info.lock) || !publisher.equals(info.publisher)) {
                    throw new IllegalStateException("List category " + newCategory
                            + " already in use with different lock or publisher");
                }
            } else {
                CATEGORY_MAP.put(newCategory, new ListInfo(lock, publisher));
            }            
        }
    }

    /**
     * Gets the list category.
     */
    public String getCategory() {
        return category;
    }
    
    /** {@inheritDoc} */
    public EventList createEventList() {
        final ListInfo info = getListInfo();
        return new BasicEventList(info.publisher, info.lock);
    }

    /** {@inheritDoc} */
    public EventList createEventList(int initalCapacity) {
        final ListInfo info = getListInfo();
        return new BasicEventList(initalCapacity, info.publisher, info.lock);
    }

    /**
     * Gets the ListInfo for the category.
     */
    private ListInfo getListInfo() {
        synchronized (CATEGORY_MAP) {
            return CATEGORY_MAP.get(getCategory());
        }
    }
    
    /**
     * Helper method to clear the mapping of categories to publisher/lock pairs.
     */
    public static void clearCategoryMapping() {
        synchronized (CATEGORY_MAP) {
            CATEGORY_MAP.clear();
        }        
    }
    
    /**
     * Helper class to hold a ReadWriteLock and a ListEventPublisher.
     *  
     * @author Holger Brands
     */
    private static class ListInfo {
        public ReadWriteLock lock;
        public ListEventPublisher publisher;

        ListInfo() {
            lock = LockFactory.DEFAULT.createReadWriteLock();
            publisher = ListEventAssembler.createListEventPublisher();
        }
        ListInfo(ReadWriteLock lock, ListEventPublisher publisher) {
            this.lock = lock; 
            this.publisher = publisher;
        }
    }
}