/*
 * JBoss, the OpenSource J2EE webOS
 * 
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package javax.emb;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;

/**
 * This class implements the Media interface and models media objects that are
 * transient, immutable, and local. In order to avoid excessive memory use, a
 * temporary file may be used to store and access the media content while an
 * instance is alive.
 * 
 * @version <tt>$Revision: 1.11 $</tt>
 * @author <a href="mailto:ricardoarguello@users.sourceforge.net">Ricardo
 *         Argüello</a>
 */
public class MediaBean implements Media
{
   /** Media name. */
   private String name;

   /** Media MIME type. */
   private String mimeType;

   /** Temporary file. */
   private transient File tempFile;

   /**
    * This constructor initializes a new instance with content copied from the
    * given content stream, and the given mime type and name. The given name is
    * a file name that should be associated with the object. If the given
    * mimeType is <code>null</code>, one is chosen automatically by
    * analyzing the given file extension, or <code>Media.MIME_TYPE_UNKNOWN</code>
    * is assigned in case none can be derived.
    * 
    * <p>This constructor is suitable for media content regardless of its
    * size. Please note that the given input stream is closed once the content
    * is read completely. Also note that a temporary file is used internally to
    * maintain the content. The implementation tries to delete this file once
    * the instance is garbage collected, and again once the process dies in
    * case the former attempt fails.
    * 
    * @param contentStream media content stream.
    * @param mimeType media mime type.
    * @param name media name.
    * @throws javax.emb.ContentAccessException if something goes wrong during
    *         content transfer.
    * @throws java.lang.NullPointerException if the given content stream or
    *         name is <code>null</code>.
    */
   public MediaBean(InputStream contentStream, String mimeType, String name)
      throws MediaException
   {
      if (contentStream == null || name == null)
      {
         throw new NullPointerException();
      }

      this.name = name;

      if (mimeType != null)
      {
         this.mimeType = mimeType;
      }
      else
      {
         try
         {
            // Lookup a mime type given the file extension
            MediaFormatRegistry.SINGLETON.lookup(getFileExtension(name));
         }
         catch (FormatNotFoundException e)
         {
            this.mimeType = Media.MIME_TYPE_UNKNOWN;
         }
      }

      String tempFilePrefix = getFileName(name);
      String tempFileSuffix = getFileExtension(name);

      try
      {
         // Create the temporary file
         tempFile = File.createTempFile(tempFilePrefix, tempFileSuffix);
         tempFile.deleteOnExit();

         // Copy the contents of the content stream to the temporary file
         OutputStream tempFileStream = new FileOutputStream(tempFile);

         try
         {
            int DEFAULT_BUFFER_SIZE = 65536;
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int bytesRead;

            // Copy contentStream to tempFileStream
            while ((bytesRead = contentStream.read(buffer)) != -1)
            {
               tempFileStream.write(buffer, 0, bytesRead);
            }
         }
         catch (IOException e)
         {
            // Throwed by OutputStream.write(byte[], int, int);
            throw new ContentAccessException(e.getMessage());
         }
         finally
         {
            try
            {
               tempFileStream.close();
            }
            catch (IOException ignore)
            {
            }
         }
      }
      catch (IOException e)
      {
         // Throwed by File.createTempFile(String, String);
         throw new ContentAccessException(e.getMessage());
      }
      finally
      {
         try
         {
            contentStream.close();
         }
         catch (IOException ignore)
         {
         }
      }
   }

   /**
    * This constructor wraps a new instance around the given media file. The
    * name property of the receiver is initialized to the name of the given
    * file (without path information). If the given mimeType is null, one is
    * chosen automatically by analyzing the file extension, or <code>Media.MIME_TYPE_UNKNOWN</code>
    * is assigned in case none can be derived.
    * 
    * This constructor is useful to invoke a media related service on an
    * existing media file without copying the content of the latter. Please
    * note that the file is not deleted once an instance referencing it is
    * garbage collected.
    * 
    * @param mediaFile a file.
    * @param mimeType the optional MIME type of the content.
    * @throws javax.emb.ContentAccessException if something goes wrong during
    *         content transfer, or if the given file is not present or cannot
    *         be accessed.
    * @throws java.lang.NullPointerException if the given media file is null.
    */
   public MediaBean(File mediaFile, String mimeType) throws MediaException
   {
      if (mediaFile == null)
      {
         throw new NullPointerException();
      }

      if (!mediaFile.exists())
      {
         throw new ContentAccessException("The given file is not present or cannot be accessed.");
      }

      this.tempFile = mediaFile;
      this.name = mediaFile.getName();

      if (mimeType != null)
      {
         this.mimeType = mimeType;
      }
      else
      {
         try
         {
            // Lookup a mime type given the file extension
            MediaFormatRegistry.SINGLETON.lookup(getFileExtension(name));
         }
         catch (FormatNotFoundException e)
         {
            this.mimeType = Media.MIME_TYPE_UNKNOWN;
         }
      }
   }

   /**
    * @see javax.emb.Media#getContent()
    */
   public byte[] getContent() throws MediaException
   {
      long size = getSize();

      if (size > Integer.MAX_VALUE)
      {
         throw new ContentTooLargeException("Content exceeds maximum Java array size.");
      }

      return getContent(0, (int) size);
   }

   /**
    * @see javax.emb.Media#getFormat()
    */
   public MediaFormat getFormat() throws MediaException
   {
      String fileExtension = getFileExtension(name);
      return MediaFormatRegistry.SINGLETON.lookup(fileExtension);
   }

   /**
    * @see javax.emb.Media#getHeader()
    */
   public MediaHeader getHeader() throws MediaException
   {
      return getFormat().extractHeader(getContentStream());
   }

   /**
    * @see javax.emb.Media#getMimeType()
    */
   public String getMimeType() throws MediaException
   {
      return mimeType;
   }

   /**
    * @see javax.emb.Media#getName()
    */
   public String getName() throws MediaException
   {
      return name;
   }

   /**
    * @see javax.emb.Media#getProxy()
    */
   public Media getProxy() throws MediaException
   {
      return getFormat().extractProxy(getContentStream());
   }

   /**
    * @see javax.emb.Media#getSize()
    */
   public long getSize() throws MediaException
   {
      return tempFile.length();
   }

   /**
    * @see javax.emb.Media#readContent(long, byte[])
    */
   public int readContent(long position, byte[] buffer) throws MediaException
   {
      return this.readContent(position, buffer, 0, buffer.length);
   }

   /**
    * @see javax.emb.Media#readContent(long, byte[], int, int)
    */
   public int readContent(long position, byte[] buffer, int offset, int length)
      throws MediaException
   {
      if (position < 0 || position > getSize())
      {
         throw new IndexOutOfBoundsException();
      }

      if (position < 0)
      {
         throw new NegativeArraySizeException();
      }

      if (buffer == null)
      {
         throw new NullPointerException();
      }

      // TODO: Calculate offset

      int bytesRead = 0;

      try
      {
         InputStream contentStream = new FileInputStream(tempFile);
         long contentPosition = contentStream.skip(position);
         bytesRead = contentStream.read(buffer, offset, length);
      }
      catch (IOException e)
      {
         throw new ContentAccessException(e.getMessage());
      }

      return bytesRead;
   }

   private byte[] getContent(long position, int length) throws MediaException
   {
      if (position < 0 || position > getSize())
      {
         throw new IndexOutOfBoundsException();
      }

      int contentLength;

      if ((getSize() - position) < (long) length)
      {
         contentLength = (int) (getSize() - position);
      }
      else
      {
         contentLength = length;
      }

      RandomAccessFile randomAccessFile = null;

      try
      {
         randomAccessFile = new RandomAccessFile(tempFile, "r");

         byte[] content = new byte[contentLength];

         randomAccessFile.seek(position);
         randomAccessFile.read(content);

         return content;
      }
      catch (FileNotFoundException e)
      {
         throw new ContentAccessException(e.getMessage());
      }
      catch (IOException e)
      {
         throw new ContentAccessException(e.getMessage());
      }
      finally
      {
         if (randomAccessFile != null)
         {
            try
            {
               randomAccessFile.close();
            }
            catch (IOException ignore)
            {
            }
         }
      }
   }

   /**
    * Returns an input stream on the receiver's content that can be utilized to
    * read said content in a stream I/O fashion.
    * 
    * <p>This method can be used instead of the generic block access to
    * content as defined in the <code>Media</code> interface to reduce memory
    * fragmentation in order to reduce garbage collection.
    * 
    * @return content stream.
    * @throws javax.emb.ContentAccessException if the content cannot be
    *         accessed.
    */
   private InputStream getContentStream() throws MediaException
   {
      try
      {
         return new FileInputStream(tempFile);
      }
      catch (FileNotFoundException e)
      {
         throw new ContentAccessException(e.getMessage());
      }
   }

   private static String getFileName(String name)
   {
      int lastDotPosition = name.lastIndexOf('.');

      if (lastDotPosition == -1)
      {
         return name;
      }
      else
      {
         return name.substring(0, lastDotPosition);
      }
   }

   private static String getFileExtension(String name)
   {
      int lastDotPosition = name.lastIndexOf('.');

      if (lastDotPosition == -1)
      {
         return null;
      }
      else
      {
         return name.substring(lastDotPosition + 1);
      }
   }
}