package net.mlvaplus.xmlwatcher;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.mlvaplus.log.DefaultLog;
import net.mlvaplus.tools.XMLUtils;

/**
 * Watches the given <code>path</code>. Checks before each getList() after <code>checkIntervall</code> milliseconds 
 * if the files with <code>extension</code> have changed. If yes, rebuilds the <code>list</code> that is returned
 * in getList().
 * A {@link PropertyChangeListener} can be used to listen to updates of the 
 * list (will fire with property name PROPERTY_UPDATE). The method pauseUpdates() and resumeUpdates() can 
 * be used to temporary deactivate the update checks.
 * 
 * Example:
 * <pre>
 * 
 *  private init(){
 *    ...
 *    this.watcherACL = new RefreshingXMLDirLister(path, EXTENSION, CHECK_INTERVALL, AccessXML.class);
 *    watcherACL.addPropertyChangeListener(this);
 *    ... 
 *  }
 *
 *  private List getAllAcls() {
 *    return (List)watcherACL.getList();
 *  }
 * <pre>
 */
public class RefreshingXMLDirLister<T> {

    public static final String PROPERTY_UPDATE = "update";
    
    private String extension;
    
    @SuppressWarnings("unchecked")
    private Class clazz;
    
    private String path;
    private volatile int checkIntervall;
    
    private volatile long lastWatchedTime;    
    private long lastCheckSum;
    
    private List<T> list;
    
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    
    private volatile boolean doUpdateChecks = true;
    
    /** last time readFiles() was called. Used for debug */
    private long lastUpdateTime;
    
    public RefreshingXMLDirLister(String aPath, String anExtension, int anCheckIntervall, Class<T> aClazz){
        this.extension = anExtension;
        this.clazz = aClazz;
        this.path = aPath;
        this.checkIntervall = anCheckIntervall;
    }
    
    public List<T> getList(){
        checkAndUpdate();
        lock.readLock().lock();
        try{
            return list;
        }
        finally{
            lock.readLock().unlock();
        }
    }
    
    private long calcCheckSum(){
        return XMLUtils.calcCheckSum(path, extension);
    }
    
    private void checkAndUpdate(){
        if (!doUpdateChecks){
            return;
        }
        boolean updated = false;
        if (System.currentTimeMillis() - lastWatchedTime > checkIntervall){
            lock.writeLock().lock();
            try{
                if (System.currentTimeMillis() - lastWatchedTime > checkIntervall){
                    long checkSum = calcCheckSum();
                    if (checkSum != lastCheckSum){
                        readFiles();
                        updated = true;
                    }
                }
            }
            catch (Exception e){
                DefaultLog.getInstance().err(e);
            }
            finally {
                lastWatchedTime = System.currentTimeMillis();
                lock.writeLock().unlock();
            }
        }
        if (updated){
            propertyChangeSupport.firePropertyChange(PROPERTY_UPDATE, false, true);
        }
    }
    
    @SuppressWarnings("unchecked")
    private void readFiles(){
        lock.writeLock().lock();
        try{
            List<T> listNew = (List)XMLUtils.readFiles(path, extension, clazz);
            list = Collections.unmodifiableList(listNew);
            lastCheckSum = calcCheckSum();
            lastUpdateTime = System.currentTimeMillis();
        }
        finally {
            lock.writeLock().unlock();
        }
    }
    
    /** Use this method for testing only! */
    @Deprecated
    public void clearLastWatchedTime(){
        lock.writeLock().lock();
        lastWatchedTime = 0;
        lock.writeLock().unlock();
    }
    
    /** Use this method for testing only! 
     * @return last time readFiles() was called. */
    @Deprecated
    public long getLastUpdateTime() {
        return lastUpdateTime;
    }
    
    /** 
     * Fires a {@link PropertyChangeEvent} with PROPERTY_UPDATE as property when files are read.
     */
    public void addPropertyChangeListener(PropertyChangeListener listener){
        propertyChangeSupport.addPropertyChangeListener(listener);
    }
    
    public void removePropertyChangeListener(PropertyChangeListener listener){
        propertyChangeSupport.removePropertyChangeListener(listener);
    }
    
    /** disables updates check for the given directory.
     * Users should call this method prior to update the directory 
     */
    public void pauseUpdates(){
        lock.writeLock().lock();
        doUpdateChecks = false;
        lock.writeLock().unlock();
    }
    
    /** 
     * re-enabled updates check for the given directory.
     * Users should call this method after calling pauseUpdates() and updating the directory 
     */
    public void resumeUpdates(){
        lock.writeLock().lock();
        doUpdateChecks = true;
        lastWatchedTime = 0;
        lock.writeLock().unlock();
    }
    
    /**
     * forces the listener to update all files the next time getList() is called.
     */
    public void forceUpdate(){
        lock.writeLock().lock();
        lastCheckSum = 0;
        lastWatchedTime = 0;
        lock.writeLock().unlock();
    }
    
    
}