/*
 * Copyright (C) 2007-2009 KenD00
 * 
 * This file is part of DumpHD.
 * 
 * DumpHD is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dumphd.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Class to read from an InputStream or write to an OutputStream through the ByteSource interface.
 * The read and write methods simply map to the corresponding methods of the stream.
 * 
 * This Class does not support seeking and windows, trying to seek or add / remove a window causes an exception.
 * 
 * This class supports ByteSource.R_MODE and ByteSource.W_MODE.
 * 
 * @author KenD00
 */
public final class StreamSource implements ByteSource {

   /**
    * InputStream to read from, null if not in read mode
    */
   private InputStream is = null;
   /**
    * OutputStream to write to, null if not in write mode
    */
   private OutputStream os = null;

   /**
    * Dummy File object that gets returned if getFile() is called
    */
   private File dummyFile = null;
   /**
    * If false, the close() method actually does nothing 
    */
   private boolean closeable = true;


   /**
    * Creates a new StreamSource.
    * 
    * @param source The source stream, object type must be according to the mode
    * @param mode The mode of the object, must be either R_MODE (source must be an InputStream) or W_MODE (source must be an OutputStream)
    * @param closeable If true, the close method actually closes the underlying stream, otherwise not
    *                  (set to false e.g. when using stdin or stdout as source)
    * @throws IllegalArgumentException Invalid mode specified or the given source is not of the correct type for the given mode
    */
   public StreamSource(Object source, int mode, boolean closeable) {
      this.closeable = closeable;
      switch (mode) {
      case R_MODE:
         if (source instanceof InputStream) {
            is = (InputStream)source;
            dummyFile = new File("InputStreamSource");
         } else {
            throw new IllegalArgumentException("StreamSource: Read mode requested but given stream is not an InputStream");
         }
         break;
      case W_MODE:
         if (source instanceof OutputStream) {
            os = (OutputStream)source;
            dummyFile = new File("OutputStreamSource");
         } else {
            throw new IllegalArgumentException("StreamSource: Write mode requested but given stream is not an OutputStream");
         }
         break;
      default:
         throw new IllegalArgumentException("StreamSource: Unsupported mode argument");
      }
   }

   /**
    * @see dumphd.util.ByteSource#read(byte[], int, int)
    * @throws IllegalStateException This StreamSource has not been created in reading mode
    */
   public int read(byte[] dst, int offset, int length) throws IOException {
      if (is != null) {
         int totalRead = 0;
         int read = 0;
         while ((read = is.read(dst, offset, length)) != -1) {
            totalRead += read;
         }
         return totalRead;
      } else {
         throw new IllegalStateException("StreamSource: This object has not been created in reading mode");
      }
   }

   /**
    * @see dumphd.util.ByteSource#write(byte[], int, int)
    * @throws IllegalStateException This StreamSource has not been created in writing mode
    */
   public int write(byte[] src, int offset, int length) throws IOException {
      if (os != null) {
         os.write(src, offset, length);
         return length;
      } else {
         throw new IllegalStateException("StreamSource: This object has not been created in writing mode");
      }
   }

   /**
    * Returns always 0.
    * 
    * @see dumphd.util.ByteSource#size()
    */
   public long size() throws IOException {
      // TODO: What value should be returned? Or an exception?
      return 0;
   }

   /**
    * Returns always 0.
    * 
    * @see dumphd.util.ByteSource#getPosition()
    */
   public long getPosition() throws IOException {
      // TODO: What value should be returned? Or an exception?
      return 0;
   }

   /**
    * @see dumphd.util.ByteSource#setPosition(long)
    * @throws UnsupportedOperationException Always thrown since seeking is not supported 
    */
   public void setPosition(long position) throws IOException {
      throw new UnsupportedOperationException("StreamSource: Seeking is not supported");
   }

   /**
    * Returns always 0.
    * 
    * @see dumphd.util.ByteSource#windowCount()
    */
   public int windowCount() {
      return 0;
   }

   /**
    * @see dumphd.util.ByteSource#addWindow(long)
    * @throws UnsupportedOperationException Always thrown since windows are not supported
    */
   public void addWindow(long size) throws IOException {
      throw new UnsupportedOperationException("StreamSource: Windows are not supported");
   }

   /**
    * @see dumphd.util.ByteSource#removeWindow()
    * @throws UnsupportedOperationException Always thrown since windows are not supported
    */
   public void removeWindow() throws IOException {
      throw new UnsupportedOperationException("StreamSource: Windows are not supported");
   }

   /* (non-Javadoc)
    * @see dumphd.util.ByteSource#getMode()
    */
   public int getMode() {
      if (is == null) {
         return W_MODE; 
      } else if (os == null) {
         return R_MODE;
      } else {
         // This case can never happen, its only here for completeness
         return RW_MODE;
      }
   }

   /**
    * Returns always the same dummy file which either has the filename InputStreamSource or OutputStreamSource, depending on the set mode.
    * 
    * @see dumphd.util.ByteSource#getFile()
    */
   public File getFile() {
      return dummyFile;
   }

   /**
    * Closes the underlying stream if the object has been created with closeable = true and clears the reference to it,
    * otherwise only the reference to the stream is cleared.
    * 
    * @see dumphd.util.ByteSource#close()
    */
   public void close() throws IOException {
      //TODO: This way of exception handling only works if there is only one stream, otherwise one exception is lost.
      //      Anyway, is the lost exception a problem?
      IOException exception = null;
      if (closeable) {
         if (is != null) {
            try {
               is.close();
            }
            catch (IOException e) {
               exception = e;
            }
         }
         if (os != null) {
            try {
               os.close();
            }
            catch (IOException e) {
               exception = e;
            }
         }
      }
      is = null;
      os = null;
      if (exception != null) {
         throw exception;
      }
   }

}
