/*
 * This code 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 2.1 of the License, or (at your option) any later version.
 *
 * This code 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 this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA  02111-1307, USA.
 */
package no.geosoft.cc.io;



import java.io.*;
import java.nio.channels.*;
import java.util.zip.*;



/**
 * Base class for file accessors (file readers and writers).
 *
 * <p>
 * Features:
 * <ul>
 *   <li>Logging</li>
 *   <li>Progress monitoring</li>
 *   <li>ASCII and binary</li>
 *   <li>Checksum generation</li>
 * </ul>
 *
 * <p>
 * This class is not abstract as a subclass will overload only one of
 * readFile() and writeFile() depending wether it is a reader or a writer
 * class (it can only be one of the two).
 *
 * <p>
 * There are some convenience methods for actually accessing the files
 * (readLine() and write()), for more detailed access a reader class
 * will use the reader_/binaryReader_ and a writer will use the
 * writer/binaryWriter_ objects.
 * 
 * @author <a href="mailto:info@geosoft.no">GeoSoft</a>
 */   
public class FileAccessor
{
  protected File              file_;
  protected Object            stream_;  // InputStream or OutputStream
  protected BufferedReader    reader_;
  protected BufferedWriter    writer_;
  protected DataInputStream   binaryReader_;
  protected DataOutputStream  binaryWriter_;
  protected FileLogger        logger_;
  protected int               lineNo_;
  protected Checksum          checksum_;
  private   FileChannel       fileChannel_;
  
  
  
  /**
   * Create a file accessor for the specified file and with the
   * specified logger instance.
   * 
   * @param file    File to create accessor for.
   * @param logger  Logger.
   */
  public FileAccessor (File file, FileLogger logger)
  {
    file_ = file;

    stream_       = null;
    reader_       = null;
    writer_       = null;
    binaryReader_ = null;
    binaryWriter_ = null;
    
    setLogger (logger);
  }



  /**
   * Create a file accessor without a logger.
   * 
   * @param file  File to create accessor for.
   */
  public FileAccessor (File file)
  {
    this (file, null);
  }


  
  /**
   * Set logger.
   * 
   * @param logger  File accessor logger.
   */
  public void setLogger (FileLogger logger)
  {
    logger_ = logger == null ? new DefaultLogger() : logger;
  }

  

  /**
   * Open file for reading.
   */
  protected void openForRead()
  {
    logger_.log (FileLogger.INFO, 0, 0, "Open", file_);
    logger_.reportProgress (file_, 0);
    
    checksum_ = new Adler32();

    try {
      FileInputStream  fileStream  = new FileInputStream (file_);
      fileChannel_ = fileStream.getChannel();
      InputStream stream = new CheckedInputStream (fileStream, checksum_);
      reader_       = new BufferedReader (new InputStreamReader (stream));
      binaryReader_ = new DataInputStream (new BufferedInputStream (stream));
      
      lineNo_ = -1;
      stream_ = stream;
    }
    catch (IOException exception) {
      exception.printStackTrace();
      logger_.log (FileLogger.ERROR, 0, 0, "UnableToOpen", file_);
    }
  }


  
  /**
   * Open file for writing.
   */
  protected void openForWrite()
  {
    logger_.log (FileLogger.INFO, 0, 0, "Open", file_);
    logger_.reportProgress (file_, 0);
    
    checksum_ = new Adler32();
    
    try {
      FileOutputStream fileStream = new FileOutputStream (file_);
      fileChannel_  = fileStream.getChannel();
      OutputStream stream = new CheckedOutputStream (fileStream, checksum_);
      writer_       = new BufferedWriter (new OutputStreamWriter (stream));
      binaryWriter_ = new DataOutputStream (new BufferedOutputStream (stream));

      stream_ = stream;
    }
    catch (IOException exception) {
      exception.printStackTrace();
      logger_.log (FileLogger.ERROR, 0, 0, "UnableToOpen", file_);
    }
  }


  
  /**
   * Check if the entire file has been read.
   * 
   * @return  True if the entire file has been read, false otherwise.
   */
  protected boolean isDone()
  {
    return getPosition() == file_.length();
  }
  

  
  /**
   * Close streams.
   */
  protected void close()
  {
    try {
      if (writer_ != null) writer_.flush();

      if (stream_ instanceof OutputStream) {
        OutputStream stream = (OutputStream) stream_;
        stream.flush();
        stream.close();
      }
      else {
        InputStream stream = (InputStream) stream_;
        stream.close();
      }
    }
    catch (Exception exception) {
      logger_.log (FileLogger.ERROR, 0, 0, "UnableToClose", file_);      
    }

    logger_.reportProgress (file_, (int) file_.length());
    
    reader_       = null;
    writer_       = null;
    binaryReader_ = null;
    binaryWriter_ = null;

    stream_       = null;
  }
  


  /**
   * Return the checksum for this file.
   * 
   * @return  Checksum for this file.
   */
  public long getChecksum()
  {
    return checksum_.getValue();
  }
  


  /**
   * Return current file position.
   * 
   * @return  Current file position.
   */
  protected long getPosition()
  {
    long position = 0;
    
    try {
      position = fileChannel_.position();
    }
    catch (Exception exception) {
    }

    return position;
  }



  /**
   * Read one line from the file.
   * 
   * @return  Next line from the file (or null if done or an error
   *          occured).
   */
  public String readLine()
    throws IOException
  {
    if (reader_ == null) return null;
    
    String line = reader_.readLine();
    lineNo_++;
    reportProgress();
    return line;
  }


  
  /**
   * Write a line to the file.
   * 
   * @param string  Line to write.
   */
  public void write (String string)
    throws IOException
  {
    if (writer_ == null) return;
    
    writer_.write (string, 0, string.length());
    reportProgress();
  }

  

  /**
   * Skip n bytes during read.
   * 
   * @param n  Number of bytes to skip.
   */
  public void skip (long n)
    throws IOException
  {
    if      (reader_       != null) reader_.skip (n);
    else if (binaryReader_ != null) binaryReader_.skipBytes ((int) n);
    
    reportProgress();
  }



  /**
   * Force the progress to be reported to the logger.
   */
  public void reportProgress()
  {
    logger_.reportProgress (file_, (int) getPosition());    
  }
    

  
  /**
   * To be overridden by a file reader class.
   * 
   * @return  File content as some java object.
   */
  public Object readFile()
  {
    return null;
  }
  

  
  /**
   * To be overridden by a file writer class.
   * 
   * @param object  Object to write.
   */
  public void writeFile (Object object)
  {
  }

  

  /**
   * Dummy logger instance in case none is specified.
   */   
  public class DefaultLogger implements FileLogger
  {
    public void log (int type, int lineNo, int columnNo, String message,
                     Object object)
    {
      // Debgugging
      System.out.println (lineNo + "," + columnNo + ": " + message +
                          ": " + object.toString());
    }
    

    public void reportProgress (File file, int amount)
    {
    }
  }
}