/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.index;

import java.io.*;
import java.util.*;
import org.apache.log4j.Logger;
import org.apache.lucene.store.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;

class IndexDirectoryImpl extends Directory
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing the Lock object
   *--------------------------------------------------------------------------------
   */

  private class MyLock extends Lock
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private String m_name;

    /*====================================================================
     * Constructor
     *====================================================================
     */

    MyLock(String name)
    {
      m_name = name;

    } // end constructor

    /*====================================================================
     * Abstract implementations from class Lock
     *====================================================================
     */

    public boolean obtain() throws IOException
    {
      try
      { // call through to the database
	return m_ops.obtainLock(m_ndx,m_name);

      } // end try
      catch (DatabaseException e)
      { // turn it into an I/O exception
	throw translateException(e);

      } // end catch

    } // end obtain

    public void release()
    {
      try
      { // call through to the database
	m_ops.releaseLock(m_ndx,m_name);

      } // end try
      catch (DatabaseException e)
      { // just log the exception
	logger.warn("IndexDirectoryImpl.MyLock.release(): failed operation",e);

      } // end catch

    } // end release

  } // end class MyLock

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(IndexDirectoryImpl.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private IndexOps m_ops;
  private int m_ndx;
  private Hashtable m_current = new Hashtable();

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  IndexDirectoryImpl(int ndx, IndexOps ops)
  {
    super();
    m_ops = ops;
    m_ndx = ndx;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final IOException translateException(DatabaseException e)
  {
    logger.info("database operation threw exception",e);
    IOException ie = new IOException("Database error on file operation");
    ie.initCause(e);
    return ie;

  } // end translateException

  /*--------------------------------------------------------------------------------
   * Abstract implementations from class Directory
   *--------------------------------------------------------------------------------
   */

  public String[] list() throws IOException
  {
    try
    { // call through to the database
      return m_ops.listFiles(m_ndx);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end list

  public boolean fileExists(String name) throws IOException
  {
    try
    { // call through to the database
      CurrentFile cf = (CurrentFile)(m_current.get(name));
      if (cf!=null)
	return true;
      return (m_ops.getModTime(m_ndx,name)>=0);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end fileExists

  public long fileModified(String name) throws IOException
  {
    try
    { // call through to the database
      CurrentFile cf = (CurrentFile)(m_current.get(name));
      if (cf!=null)
	return cf.getModTime();
      return m_ops.getModTime(m_ndx,name);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end fileModified

  public void touchFile(String name) throws IOException
  {
    try
    { // call through to the database
      long mtime = System.currentTimeMillis();
      CurrentFile cf = (CurrentFile)(m_current.get(name));
      if (cf!=null)
	cf.setModTime(mtime);
      else
	m_ops.setModTime(m_ndx,name,mtime);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end touchFile

  public void deleteFile(String name) throws IOException
  {
    try
    { // call through to the database
      m_ops.deleteFile(m_ndx,name);
      CurrentFile cf = (CurrentFile)(m_current.get(name));
      if (cf!=null)
      { // detach file from current and make sure we don't update
	m_current.remove(name);
	cf.noUpdate();

      } // end if

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end deleteFile

  public void renameFile(String from, String to) throws IOException
  {
    try
    { // call through to the database
      m_ops.renameFile(m_ndx,from,to);
      CurrentFile cf = (CurrentFile)(m_current.get(to));
      if (cf!=null)
      { // this file was deleted - detach file from current and make sure we don't update
	m_current.remove(to);
	cf.noUpdate();

      } // end if

      cf = (CurrentFile)(m_current.get(from));
      if (cf!=null)
      { // reset the name and juggle things
	m_current.remove(from);
	cf.setName(to);
	m_current.put(to,cf);

      } // end if

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end renameFile

  public long fileLength(String name) throws IOException
  {
    try
    { // call through to the database
      CurrentFile cf = (CurrentFile)(m_current.get(name));
      if (cf!=null)
	return cf.length();
      return m_ops.getLength(m_ndx,name);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end fileLength

  public org.apache.lucene.store.OutputStream createFile(String name) throws IOException
  {
    long mtime = -1L;
    CurrentFile cf = null;
    try
    { // create the file entry
      mtime = m_ops.createNewEntry(m_ndx,name);
      if (mtime==-1L)
	throw new IOException("file " + name + " already exists");

      // now create a CurrentFile object
      cf = new CurrentFile(this,File.createTempFile("dynamo-index",null),name,0L,mtime);
      m_current.put(name,cf);

      // and open it for writing
      org.apache.lucene.store.OutputStream rc = cf.openOutput();
      cf.release();
      cf = null;
      mtime = -1L;
      return rc;

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch
    finally
    { // if we screwed up, back out of stuff
      try
      { // but do it without raising any more exceptions
	if (cf!=null)
	  cf.abandon();
	if (mtime>=0)
	  m_ops.deleteFile(m_ndx,name);

      } // end try
      catch (Exception e)
      { // just ignore exceptions here
	logger.warn("IndexDirectoryImpl.createFile(): error recovery threw exception",e);

      } // end catch

    } // end finally

  } // end try

  public org.apache.lucene.store.InputStream openFile(String name) throws IOException
  {
    File tempfile = null;
    CurrentFile cf = null;
    try
    { // first see if the file's already open
      CurrentFile cf2 = (CurrentFile)(m_current.get(name));
      if (cf2!=null)
	return cf2.openInput();  // open it again

      // load the file data
      tempfile = File.createTempFile("dynamo-index",null);
      long mtime = m_ops.loadFile(m_ndx,name,tempfile);
      if (mtime==-1)
	throw new IOException("file " + name + " does not exist");

      // now create a CurrentFile object
      cf = new CurrentFile(this,tempfile,name,tempfile.length(),mtime);
      tempfile = null;  // done with tempfile
      m_current.put(name,cf);

      // and open it for reading
      org.apache.lucene.store.InputStream rc = cf.openInput();
      cf.release();
      cf = null;
      return rc;

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch
    finally
    { // if we screwed up, back out of stuff
      if (cf!=null)
	cf.abandon();
      if (tempfile!=null)
	tempfile.delete();

    } // end finally

  } // end openFile

  public Lock makeLock(String name)
  {
    return new MyLock(name);

  } // end makeLock

  public void close() throws IOException
  {
    try
    { // Get all the "current" files and close them.
      LinkedList files = new LinkedList(m_current.values());
      m_current.clear();
      while (!(files.isEmpty()))
      { // get each file in turn and release it till it dies
	CurrentFile cf = (CurrentFile)(files.removeFirst());
	int foo = 1;
	while (foo>0)
	  foo = cf.release();

      } // end while

      // release any outstanding locks
      m_ops.releaseAllLocks(m_ndx);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end close

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  void storeFile(String name, long mtime, File data) throws IOException
  {
    try
    { // call through to the database
      m_ops.storeFile(m_ndx,name,mtime,data);

    } // end try
    catch (DatabaseException e)
    { // turn it into an I/O exception
      throw translateException(e);

    } // end catch

  } // end storeFile

  void detach(String name)
  {
    m_current.remove(name);

  } // end detach

  void abandon()
  {
    LinkedList files = new LinkedList(m_current.values());
    m_current.clear();
    while (!(files.isEmpty()))
    { // get each file in turn and release it till it dies
      CurrentFile cf = (CurrentFile)(files.removeFirst());
      cf.abandon();

    } // end while

  } // end abandon

} // end class IndexDirectoryImpl
