/*
 * This file is part of the Vamsas Client version 0.2. 
 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, 
 *  Andrew Waterhouse and Dominik Lindner.
 * 
 * Earlier versions have also been incorporated into Jalview version 2.4 
 * since 2008, and TOPALi version 2 since 2007.
 * 
 * The Vamsas Client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * The Vamsas Client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.
 */
package uk.ac.vamsas.client.simpleclient;

import java.io.File;

/**
 * Watches a particular file for its creation, deletion, or modification. The
 * watcher is thread safe and different instances watching the state of a
 * particular file carry their own state record for the file.
 */
public class FileWatcher {

  private File subject = null;

  private long lastStat[];

  boolean waslocked = false;

  boolean exists = false;

  /**
   * transient lock on subject - can be passed back to calling class to preserve
   * new state of file for immediate reading.
   */
  private Lock subjectLock = null;

  /**
   * clear local locks on subject.
   * 
   */
  private void clearLock() {
    if (subjectLock != null)
      subjectLock.release();
    subjectLock = null;
  }

  /**
   * 
   * @return true if subject exists and is locked by another process.
   */
  private boolean checkLock() {
    if (subject != null && subject.exists()) {
      if (subjectLock != null) {
        subjectLock.release();
      }
      subjectLock = LockFactory.tryLock(subject);
      if (subjectLock.isLocked()) {
        return false;
      }
      clearLock();
      return true;
    }
    return false;
  }

  private long[] getStat(File subject) {
    return new long[] { subject.lastModified(), subject.length() };
  }

  private boolean compStat(long[] stat, long[] newstat) {
    if (stat[0] != newstat[0] || stat[1] != newstat[1])
      return false;
    return true;
  }

  /**
   * Detect changes in file state and release of any lock in place during
   * change.
   * 
   * @return true if file state has changed. Leaves lock in subjectLock (ready
   *         to be passed to caller)
   */
  private boolean check() {
    if (subject != null) {
      if (!subject.exists()) {
        if (exists) {
          if (!waslocked) {
            // !checkLock()) {

            exists = false;
            // waslocked=false;
            return true;
          }
        }
        // locked - state change registered after lock is released
        return false;
      } else {
        long[] newStat = getStat(subject); // subject.lastModified();
        if (!checkLock()) {
          // file is free to access, return state change
          if (!exists || !compStat(lastStat, newStat)) {
            waslocked = false;
            exists = true;
            lastStat = newStat;
            return true;
          }
          // no change
          return false;
        } else {
          waslocked = true;
          return false;
        }
      }
    }
    return false;
  }

  /**
   * updates internal record of file state when caller has intentionally
   * modified subject. (ignores locked-state of subject)
   */
  public void setState() {
    if (subject != null) {
      if (exists = subject.exists()) {
        lastStat = getStat(subject);
        waslocked = false;
      }
    }
  }

  /**
   * Make a watcher for a particular file. If the file doesn't exist, the
   * watcher will watch for its creation (and indicate a change of state) For
   * locked files, the removal of a lock constitutes a change of state if the
   * file was modified.
   * 
   * @param subject
   */

  public FileWatcher(File subject) {
    this.subject = subject;
    setState();
  }

  /**
   * Test for change in file state. Only indicates a change after any lock on a
   * file has been released.
   * 
   * @return true if file has been modified.
   */
  public boolean hasChanged() {
    boolean res = check();
    clearLock();
    return res;
  }

  /**
   * passes lock back to caller if hasChanged returned true.
   * 
   * @return
   */
  public Lock getChangedState() {
    boolean res = check();
    if (res)
      return subjectLock;
    else {
      clearLock();
      return null;
    }
  }

  /**
   * safely? getting current state of the watched file
   * 
   * @return
   */
  public long[] getCurrentState() {
    return getStat(subject);
  }

  /**
   * safely compare an externally recorded state with the current state for
   * significant modifications.
   * 
   * @param a
   * @param b
   * @return
   */
  public boolean diffState(long[] a, long[] b) {
    return compStat(a, b);
  }

  /**
   * @return the subject
   */
  public File getSubject() {
    return subject;
  }
}
