/*
 * 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.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;

/**
 * @author JimP
 * 
 */
public class NativeLock extends Lock {

  protected FileLock lock = null;

  /**
   * @param lockfile
   * @param block
   *          true means thread will block until a lock is obtained.
   */
  public NativeLock(File lockfile, boolean block) {
    super(lockfile);
    // try and get a lock.
    lock = null;

    try {
      /*
       * if (!lockfile.createNewFile()) {
       * log.warn("Failed to create locked file "+lockfile); return; }
       */
      rafile = new RandomAccessFile(lockfile, "rw");
      if (block)
        lock = rafile.getChannel().lock();
      else
        lock = rafile.getChannel().tryLock();
      if (lock == null || !lock.isValid()) {
        // failed to get lock. Close the file channel
        log.debug("failed to get lock for " + lockfile);
        rafile.getChannel().close();
        lock = null;
      }
    } catch (FileNotFoundException e) {
      //
      log.debug("Lock failed - normal behaviour for windows locking.");
      // log.error("Error! Couldn't create a lockfile at "
      // + lockfile.getAbsolutePath(), e);
    } catch (IOException e) {
      log.error("Error! Problems with IO when creating a lock on "
          + lockfile.getAbsolutePath(), e);
    } // catch (Exception )
  }

  public boolean isLocked() {
    if (lock != null && lock.isValid()) {
      return true;
    }
    return false;
  }

  public void release() {
    release(true);
  }

  public void release(boolean closeChannel) {
    if (lock == null)
      return;
    try {
      // channel.close should be called before release() for rigourous locking.
      if (lock.isValid() && rafile != null && rafile.getFD().valid()
          && rafile.getChannel() != null) {
        if (closeChannel && rafile.getChannel().isOpen()) {
          rafile.close();
          rafile = null;
        } else {
          // just release the lock without doing anything to the channe;l
          if (lock != null && lock.isValid())
            lock.release();
        }
      }
    } catch (IOException e) {
      log.warn("Whilst releasing lock", e);
    }
    lock = null;
  }

  /**
   * gets Locked Stream for reading from
   * 
   * @param atStart
   *          true to start reading at beginning of file.
   * @return null if file not locked
   * @throws IOException
   */
  public FileInputStream getFileInputStream(boolean atStart) throws IOException {
    if (!isLocked())
      return null;
    if (atStart)
      rafile.seek(0);
    return new FileInputStream(rafile.getFD());
  }

  /**
   * gets Locked stream to write to FileInput always starts at the *end* of the
   * file (after any truncation)
   * 
   * @param clear
   *          true means file will be cleared to zero length
   * @return null if file is not locked
   * @throws IOException
   */
  public FileOutputStream getFileOutputStream(boolean clear) throws IOException {
    if (!isLocked())
      return null;
    if (clear) {
      rafile.seek(0);
      rafile.setLength(0);
    } else
      rafile.seek(rafile.length());
    return new LockedFileOutputStream(rafile.getFD());
  }

  /**
   * return buffered output stream to locked file.
   * 
   * @param clear
   *          - true means file is truncated to 0 length before writing
   * @return
   */
  public BufferedOutputStream getBufferedOutputStream(boolean clear)
      throws IOException {
    OutputStream fos = getFileOutputStream(clear);
    if (fos != null)
      return new BufferedOutputStream(fos);
    return null;
  }

  /**
   * @see uk.ac.vamsas.client.simpleclient.Lock#finalize()
   */
  protected void finalize() throws Throwable {
    release(true); // we explicitly lose the lock here.
    // log.debug("lock closing through garbage collection ?");
    super.finalize();
  }

  /*
   * (non-Javadoc)
   * 
   * @see uk.ac.vamsas.client.simpleclient.Lock#getLength()
   */
  public long length() {
    if (isLocked()) {
      try {
        return rafile.length();
      } catch (Exception e) {
        log.debug("getLength exception:", e);
      }
    }
    return -1;
  }

  public RandomAccessFile getRaFile() throws IOException {
    if (isLocked())
      return rafile;
    else
      log.debug("Failed to getRaFile on " + target);
    return null;
  }

  public FileChannel getRaChannel() throws IOException {
    if (isLocked())
      return rafile.getChannel();
    else
      log.debug("Failed to getRaChannel on " + target);
    return null;
  }

  public boolean isTargetLockFile(File afile) {
    if (isLocked() && target.equals(afile))
      return true;
    return false;
  }

}
