/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/02/09, 13:57
!  AUTHOR(S): KOGA, Junichiro
!  File : FileStateObserver.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.inputinterface.filestate;

import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.apache.log4j.Logger;

/**
 * ファイル状態を監視するクラス
 * 
 * @author
 */
public class FileStateObserver {
    private Logger logger = Logger.getLogger(FileStateObserver.class.getName());
    private long period = 20L;
    private String dir;
    private String[] filters;
    private File[] previousFiles;
    private long[] previousLastModified;

    private static String LOCK_FILE = "__LOCK__";

    public static boolean generateLockFile(File dir) {
        if (!dir.isDirectory())
            return false;
        File lock = new File(dir.getAbsolutePath()
                + System.getProperty("file.separator") + LOCK_FILE);
        try {
            lock.createNewFile();
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    public static boolean removeLockFile(File dir) {
        if (!dir.isDirectory())
            return false;
        File lock = new File(dir.getAbsolutePath()
                + System.getProperty("file.separator") + LOCK_FILE);
        lock.delete();
        return true;
    }

    /**
     * 監視したいディレクトリーと引っ掛けたいファイルのフィルターを指定する.
     * 
     * @param dir
     *            監視したいディレクトリー
     * @param filter
     *            ファイルのフィルター; String#matches メソッドで判定する.
     */
    public FileStateObserver(String dir, String[] filters) {
        this.dir = dir;
        this.filters = filters;
        this.init();
    }

    /**
     * 監視したいディレクトリーと引っ掛けたいファイルのフィルター, さらに監視間隔を指定する.
     * 
     * @param dir
     *            監視したいディレクトリー
     * @param filter
     *            ファイルのフィルター; String#matches メソッドで判定する.
     * @param period
     *            看視する間隔(milisecondで)
     */
    public FileStateObserver(String dir, String[] filters, long period) {
        this(dir, filters);
        this.period = period;
    }

    private void init() {
        logger.debug("initializing FileStateObserver...");
        File[] list = new File(dir).listFiles();
        if (list == null || list.length == 0) {
            return;
        }
        Vector vec = new Vector();
        for (int i = 0; i < list.length; i++) {
            for (int j = 0; j < filters.length; j++) {
                if (list[i].getName().matches(filters[j])) {
                    vec.addElement(list[i]);
                }
            }
        }
        if (vec.size() != 0) {
            previousFiles = new File[vec.size()];
            vec.copyInto(previousFiles);
            updatePreviousLastModified();
        }
    }

    private void updatePreviousLastModified() {
        if (previousFiles == null || previousFiles.length == 0) {
            previousLastModified = null;
            return;
        }
        previousLastModified = new long[previousFiles.length];
        for (int i = 0; i < previousFiles.length; i++) {
            previousLastModified[i] = previousFiles[i].lastModified();
        }
    }

    private Vector listeners = null;

    /**
     * ファイル状態変更を通知されるリスナーを登録
     * 
     * @param ファイル状態変更を通知されるリスナー
     */
    public void addFileStateListener(FileStateListener listener) {
        if (listeners == null) {
            listeners = new Vector();
            listeners.addElement(listener);
            Timer timer = new Timer();
            timer.schedule(new FileTimerTask(), 0L, period);
        } else {
            listeners.addElement(listener);
        }
    }

    /**
     * ファイル状態変更を通知されるリスナーの登録を解除
     * 
     * @param 解除したいリスナー
     */
    public void removeFileStateListener(FileStateListener listener) {
        if (listeners == null) {
            logger.debug("no listener to remove");
            return;
        }
        logger.debug("removing listener: " + listener);
        listeners.remove(listener);
    }

    class FileTimerTask extends TimerTask {
        public void run() {
            if (listeners == null || listeners.size() == 0) {
                return;
            }

            File[] files = new File(dir).listFiles();
            if (files == null) {
                return;
            }

            Vector vec = new Vector();
            for (int j = 0; j < filters.length; j++) {
                // logger.debug("checking filter: "+filters[j]);
                for (int i = 0; i < files.length; i++) {
                    if (files[i].getName().matches(filters[j])) {
                        // logger.debug("matched: "+filters[j]+" and "+files[i].getName());
                        vec.addElement(files[i]);
                    }
                }
            }
            File[] filesNow = new File[vec.size()];
            vec.copyInto(filesNow);

            if (filesNow == null || filesNow.length == 0) {
                if (previousFiles == null || previousFiles.length == 0) {
                    return;
                }
                FileState[] state = new FileState[previousFiles.length];
                for (int i = 0; i < state.length; i++) {
                    state[i] = new FileState(previousFiles[i],
                            FileState.FILE_REMOVED);
                }
                FileStateChangeEvent ev = new FileStateChangeEvent(state);
                for (int i = 0; i < listeners.size(); i++) {
                    ((FileStateListener) listeners.get(i)).fileStateChanged(ev);
                }
                previousFiles = null;
                updatePreviousLastModified();
                return;
            }

            if (previousFiles == null || previousFiles.length == 0) {
                if (filesNow == null || filesNow.length == 0) {
                    return;
                }
                FileState[] state = new FileState[filesNow.length];
                for (int i = 0; i < state.length; i++) {
                    state[i] = new FileState(filesNow[i],
                            FileState.FILE_CREATED);
                }
                FileStateChangeEvent ev = new FileStateChangeEvent(state);
                for (int i = 0; i < listeners.size(); i++) {
                    ((FileStateListener) listeners.get(i)).fileStateChanged(ev);
                }
                previousFiles = filesNow;
                updatePreviousLastModified();
                logger.debug("prev null, files were created");
                return;
            }

            Vector modified = new Vector();
            Vector created = new Vector();
            for (int i = 0; i < filesNow.length; i++) {
                boolean bprev = false;
                for (int j = 0; j < previousFiles.length; j++) {
                    if (filesNow[i].equals(previousFiles[j])) {
                        if (filesNow[i].lastModified() != previousLastModified[j]) {
                            modified.addElement(filesNow[i]);
                        }
                        bprev = true;
                    }
                }
                if (!bprev) {
                    created.addElement(filesNow[i]);
                }
            }
            Vector removed = new Vector();
            for (int i = 0; i < previousFiles.length; i++) {
                boolean bnow = false;
                for (int j = 0; j < filesNow.length; j++) {
                    if (previousFiles[i].equals(filesNow[j])) {
                        bnow = true;
                    }
                }
                if (!bnow) {
                    removed.addElement(previousFiles[i]);
                }
            }

            previousFiles = filesNow;
            updatePreviousLastModified();

            if (modified.size() == 0 && created.size() == 0
                    && removed.size() == 0) {
                logger.debug("no file has changed");
                return;
            }

            File lfile = new File(dir + System.getProperty("file.separator")
                    + LOCK_FILE);
            if (lfile.exists()) {
                return;
            }

            Vector fileStates = new Vector();
            for (int i = 0; i < modified.size(); i++) {
                fileStates.addElement(new FileState((File) modified.get(i),
                        FileState.FILE_MODIFIED));
            }
            for (int i = 0; i < created.size(); i++) {
                fileStates.addElement(new FileState((File) created.get(i),
                        FileState.FILE_CREATED));
            }
            for (int i = 0; i < removed.size(); i++) {
                fileStates.addElement(new FileState((File) removed.get(i),
                        FileState.FILE_REMOVED));
            }
            if (fileStates.size() == 0) {
                return;
            }

            FileState[] states = new FileState[fileStates.size()];
            fileStates.copyInto(states);
            FileStateChangeEvent fe = new FileStateChangeEvent(states);
            for (int i = 0; i < listeners.size(); i++) {
                ((FileStateListener) listeners.elementAt(i))
                        .fileStateChanged(fe);
            }
        }
    }

}
