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

package org.jboss.media.entity;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Vector;

import javax.ejb.CreateException;
import javax.emb.ContentAccessException;
import javax.emb.ContentUnmutableException;
import javax.emb.ConversionException;
import javax.emb.FormatNotFoundException;
import javax.emb.GenericMediaFormat;
import javax.emb.Media;
import javax.emb.MediaBean;
import javax.emb.MediaConverter;
import javax.emb.MediaConverterSpec;
import javax.emb.MediaEntityLocal;
import javax.emb.MediaException;
import javax.emb.MediaFormat;
import javax.emb.MediaFormatRegistry;
import javax.emb.MediaHeader;
import javax.emb.MediaListener;
import javax.emb.MetaDataEntityLocal;
import javax.emb.ProtocolConstraints;

import org.jboss.logging.Logger;

/**
 * Abstract class to make the implementation of the Media Entity Bean cleaner. 
 * 
 * <p>It implements most of the <code>javax.emb.Media</code>,
 * <code>javax.emb.MediaEntityLocal</code> and 
 * <code>javax.emb.MediaEntityLocalHome</code> required methods, and leaves
 * the persistance methods to the <code>MediaEntityBean</code> subclass.
 * 
 * @version <tt>$Revision: 1.1 $</tt>
 * @author <a href="mailto:ricardoarguello@users.sourceforge.net">Ricardo Argüello</a>
 */
public abstract class MediaEntity
{
   /** Logger. */
   private Logger log = Logger.getLogger(MediaEntity.class);

   // Container Managed Persistance (CMP): ------------------------------------

   public abstract String getManagedIdentity();
   public abstract void setManagedIdentity(String identity);

   public abstract byte[] getManagedContent();
   public abstract void setManagedContent(byte[] content);

   public abstract String getManagedLocation();
   public abstract void setManagedLocation(String location);

   public abstract String getManagedDescription();
   public abstract void setManagedDescription(String description);

   public abstract String getManagedName();
   public abstract void setManagedName(String name);

   public abstract String getManagedMimeType();
   public abstract void setManagedMimeType(String mimeType);

   public abstract long getManagedLastModified();
   public abstract void setManagedLastModified(long lastModified);

   public abstract Vector getManagedListeners();
   public abstract void setManagedListeners(Vector listeners);

   // Container Managed Relationships (CMR): ----------------------------------

   public abstract MediaEntityLocal getManagedProxy();
   public abstract void setManagedProxy(MediaEntityLocal proxy);

   public abstract MediaEntityLocal getManagedPreviousVersion();
   public abstract void setManagedPreviousVersion(MediaEntityLocal previousVersion);

   public abstract MediaEntityLocal getManagedNextVersion();
   public abstract void setManagedNextVersion(MediaEntityLocal nextVersion);

   public abstract Collection getManagedParents();
   public abstract void setManagedParents(Collection parents);

   public abstract Collection getManagedChildren();
   public abstract void setManagedChildren(Collection children);

   public abstract Collection getManagedMetaDatas();
   public abstract void setManagedMetaDatas(Collection metadatas);

   // javax.emb.Media and javax.emb.MediaEntityLocal interfaces: --------------

   /**
   * @see javax.emb.MediaEntityLocal#addListener(javax.emb.MediaListener)
   */
   public void addListener(MediaListener listener)
   {
      if (listener == null)
      {
         throw new NullPointerException();
      }

      Vector listeners = getManagedListeners();

      if (!listeners.contains(listener))
      {
         listeners.add(listener);
         setManagedListeners(listeners);
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#addMetaData(javax.emb.MetaDataEntityLocal)
    */
   public void addMetaData(MetaDataEntityLocal metaData) throws MediaException
   {
      if (metaData == null)
      {
         throw new NullPointerException();
      }

      if (!getManagedMetaDatas().contains(metaData))
      {
         getManagedMetaDatas().add(metaData);
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#convert(javax.emb.MediaConverterSpec[])
    */
   public void convert(MediaConverterSpec[] specifications)
      throws MediaException
   {
      if (specifications == null)
      {
         throw new NullPointerException();
      }

      if (getManagedLocation() != null)
      {
         throw new ContentUnmutableException();
      }

      int specificationsLength = specifications.length;

      if (specificationsLength == 0)
      {
         // Nothing to do
         return;
      }

      // No MediaConverterSpec can be null
      for (int i = 0; i < specificationsLength; i++)
      {
         if (specifications[i] == null)
         {
            throw new ConversionException();
         }
      }

      InputStream inputStream = new ByteArrayInputStream(getContent());

      for (int i = 0; i < specificationsLength - 1; i++)
      {
         MediaConverterSpec mediaConverterSpec = specifications[i];
         MediaConverter mediaConverter = mediaConverterSpec.getConverter();
         inputStream = mediaConverter.process(inputStream);
      }

      MediaConverterSpec lastSpecification =
         specifications[specificationsLength - 1];

      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      lastSpecification.getConverter().process(inputStream, outputStream);
      byte[] convertedContent = outputStream.toByteArray();

      try
      {
         outputStream.close();
      }
      catch (IOException ignore)
      {
      }

      String convertedName =
         getFileName(getManagedName())
            + "."
            + lastSpecification.getTargetFileExtension();

      String convertedMimeType = lastSpecification.getTargetMimeType();

      setContent(convertedContent);
      setName(convertedName);
      setMimeType(convertedMimeType);
      updateLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#exportMedia(java.net.URL)
    */
   public URL exportMedia(URL targetDirectoryLocation) throws MediaException
   {
      String name = getManagedName();

      String exportedFilePrefix = getFileName(name);
      String exportedFileSuffix = getFileExtension(name);

      try
      {
         File targetDirectory = new File(targetDirectoryLocation.getPath());

         // TODO: Refactor to an MBean since we can't do file I/O inside an EJB!
         File exportedFile =
            File.createTempFile(
               exportedFilePrefix,
               exportedFileSuffix,
               targetDirectory);

         OutputStream exportedFileStream = new FileOutputStream(exportedFile);

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

            // Copy content to exportedFileStream
            while ((bytesRead = readContent(position, buffer)) != -1)
            {
               exportedFileStream.write(buffer, 0, bytesRead);
               position += bytesRead;
            }

            return exportedFile.toURL();
         }
         catch (IOException e)
         {
            // Throwed by OutputStream.write(byte[], int, int);
            throw new ContentAccessException(e.getMessage());
         }
         finally
         {
            try
            {
               exportedFileStream.close();
            }
            catch (IOException ignore)
            {
            }
         }
      }
      catch (IOException e)
      {
         // Throwed by File.createTempFile(String, String);
         throw new ContentAccessException(e.getMessage());
      }
   }

   /**
     * @see javax.emb.MediaEntityLocal#getChildren()
     */
   public MediaEntityLocal[] getChildren() throws MediaException
   {
      Collection children = getManagedChildren();
      return (MediaEntityLocal[]) children.toArray(new MediaEntityLocal[0]);
   }

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

      if (content == null)
      {
         throw new ContentAccessException();
      }
      else
      {
         return content;
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#getDescription()
    */
   public String getDescription() throws MediaException
   {
      return getManagedDescription();
   }

   /**
    * @see javax.emb.Media#getFormat()
    */
   public MediaFormat getFormat() throws MediaException
   {
      String name = getName();

      if (name == null)
      {
         throw new FormatNotFoundException();
      }

      String fileExtension = getFileExtension(name);

      MediaFormat mediaFormat =
         MediaFormatRegistry.SINGLETON.lookup(fileExtension);

      return mediaFormat;
   }

   /**
    * @see javax.emb.Media#getHeader()
    */
   public MediaHeader getHeader() throws MediaException
   {
      InputStream content = new ByteArrayInputStream(getContent());
      return getFormat().extractHeader(content);
   }

   /**
    * @see javax.emb.MediaEntityLocal#getLastModified()
    */
   public long getLastModified() throws MediaException
   {
      return getManagedLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#getListeners()
    */
   public MediaListener[] getListeners() throws MediaException
   {
      Vector listeners = getManagedListeners();
      return (MediaListener[]) listeners.toArray(new MediaListener[0]);
   }

   /**
    * @see javax.emb.MediaEntityLocal#getLocation()
    */
   public URL getLocation() throws MediaException
   {
      String location = getManagedLocation();

      try
      {
         return new URL(location);
      }
      catch (MalformedURLException e)
      {
         return null;
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#getMetaData()
    */
   public MetaDataEntityLocal[] getMetaData() throws MediaException
   {
      return (MetaDataEntityLocal[]) getManagedMetaDatas().toArray(
         new MetaDataEntityLocal[0]);
   }

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

      if (mimeType == null)
      {
         mimeType = getFormat().getDefaultMimeType();
      }

      return mimeType;
   }

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

   /**
     * @see javax.emb.MediaEntityLocal#getNextVersion()
     */
   public MediaEntityLocal getNextVersion() throws MediaException
   {
      return getManagedNextVersion();
   }

   /**
    * @see javax.emb.MediaEntityLocal#getParents()
    */
   public MediaEntityLocal[] getParents() throws MediaException
   {
      Collection parents = getManagedParents();
      return (MediaEntityLocal[]) parents.toArray(new MediaEntityLocal[0]);
   }

   /**
    * @see javax.emb.MediaEntityLocal#getPreviousVersion()
    */
   public MediaEntityLocal getPreviousVersion() throws MediaException
   {
      return getManagedPreviousVersion();
   }

   /**
    * @see javax.emb.Media#getProxy()
    */
   public Media getProxy() throws MediaException
   {
      MediaEntityLocal proxy = getManagedProxy();
      Media proxyMedia = null;

      try
      {
         if (proxy != null)
         {
            // Build a MediaBean from the proxy content
            InputStream content = new ByteArrayInputStream(proxy.getContent());
            proxyMedia =
               new MediaBean(content, proxy.getMimeType(), proxy.getName());
         }
         else
         {
            // Return a transient proxy from this format
            InputStream content = new ByteArrayInputStream(getContent());
            proxyMedia = getFormat().extractProxy(content);
         }

         if (proxyMedia != null)
         {
            return proxyMedia;
         }
         else
         {
            return new GenericMediaFormat().extractProxy(null);
         }
      }
      catch (MediaException e)
      {
         return new GenericMediaFormat().extractProxy(null);
      }
   }

   /**
    * @see javax.emb.Media#getSize()
    */
   public long getSize() throws MediaException
   {
      if (getContent() == null)
      {
         throw new ContentAccessException();
      }

      return getContent().length;
   }

   /**
    * @see javax.emb.MediaEntityLocal#importMedia(java.net.URL, java.lang.String)
    */
   public void importMedia(URL sourceLocation, String name)
      throws MediaException
   {
      if (sourceLocation == null)
      {
         throw new NullPointerException();
      }

      setName(name);

      InputStream sourceStream = null;

      try
      {
         // TODO: Refactor to an MBean since we can't do file I/O inside an EJB!
         sourceStream = new FileInputStream(sourceLocation.getPath());
         setContent(sourceStream);
      }
      catch (IOException e)
      {
         throw new ContentAccessException();
      }
      finally
      {
         if (sourceStream != null)
         {
            try
            {
               sourceStream.close();
            }
            catch (IOException ignore)
            {
            }
         }
      }
   }

   /**
    * @see javax.emb.Media#readContent(long, byte[])
    */
   public int readContent(long position, byte[] buffer) throws MediaException
   {
      return 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
   {
      byte[] content = getManagedContent();

      if (content == null)
      {
         throw new ContentAccessException();
      }

      System.arraycopy(content, (int) position, buffer, offset, length);

      return content.length;
   }

   /**
    * @see javax.emb.MediaEntityLocal#removeListener(javax.emb.MediaListener)
    */
   public void removeListener(MediaListener listener) throws MediaException
   {
      if (listener == null)
      {
         throw new NullPointerException();
      }

      Vector listeners = getManagedListeners();

      if (listeners.contains(listener))
      {
         listeners.remove(listener);
         setManagedListeners(listeners);
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#removeMetaData(javax.emb.MetaDataEntityLocal)
    */
   public void removeMetaData(MetaDataEntityLocal metaData)
      throws MediaException
   {
      if (metaData == null)
      {
         throw new NullPointerException();
      }

      getManagedMetaDatas().remove(metaData);
   }

   /**
    * @see javax.emb.MediaEntityLocal#setChildren(javax.emb.MediaEntityLocal[])
    */
   public void setChildren(MediaEntityLocal[] children) throws MediaException
   {
      if (children == null)
      {
         throw new NullPointerException();
      }

      if (getLocation() != null)
      {
         throw new ContentUnmutableException();
      }

      if (getContent() == null)
      {
         throw new ContentAccessException();
      }

      for (int i = 0; i < children.length; i++)
      {
         getManagedChildren().add(children[i]);
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#setContent(byte[])
    */
   public void setContent(byte[] content) throws MediaException
   {
      if (getLocation() != null)
      {
         throw new ContentUnmutableException();
      }

      setManagedContent(content);
      updateLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#setContent(java.io.InputStream)
    */
   public void setContent(InputStream content) throws MediaException
   {
      if (content == null)
      {
         throw new NullPointerException();
      }

      if (getLocation() != null)
      {
         throw new ContentUnmutableException();
      }

      ByteArrayOutputStream out = new ByteArrayOutputStream();

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

         while ((bytesRead = content.read(buffer)) != -1)
         {
            out.write(buffer, 0, bytesRead);
         }

         byte[] newContent = new byte[out.size()];
         newContent = out.toByteArray();

         setManagedContent(newContent);
         updateLastModified();
      }
      catch (IOException e)
      {
         throw new ContentAccessException(e.getMessage());
      }
      finally
      {
         try
         {
            out.close();
         }
         catch (IOException ignore)
         {
         }
      }
   }

   /**
    * @see javax.emb.MediaEntityLocal#setDescription(java.lang.String)
    */
   public void setDescription(String description) throws MediaException
   {
      setManagedDescription(description);
      updateLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#setLocation(java.net.URL)
    */
   public void setLocation(URL location) throws MediaException
   {
      if (getContent() != null)
      {
         throw new ContentUnmutableException();
      }

      setManagedLocation(location.toExternalForm());
      updateLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#setMimeType(java.lang.String)
    */
   public void setMimeType(String mimeType) throws MediaException
   {
      setManagedMimeType(mimeType);
      updateLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#setName(java.lang.String)
    */
   public void setName(String name) throws MediaException
   {
      setManagedName(name);
      updateLastModified();
   }

   /**
    * @see javax.emb.MediaEntityLocal#setPreviousVersion(javax.emb.MediaEntityLocal)
    */
   public void setPreviousVersion(MediaEntityLocal mediaEntity)
      throws MediaException
   {
      // TODO: Implement
      throw new UnsupportedOperationException("Not implemented yet!");
   }

   /**
    * @see javax.emb.MediaEntityLocal#setProxy(javax.emb.MediaEntityLocal)
    */
   public void setProxy(MediaEntityLocal mediaEntity) throws MediaException
   {
      setManagedProxy(mediaEntity);
   }

   // javax.emb.MediaEntityLocalHome interface: -------------------------------

   /**
    * @see javax.emb.MediaEntityLocalHome#exportMedia(javax.emb.MediaEntityLocal[], java.net.URL)
    */
   public URL[] ejbHomeExportMedia(
      MediaEntityLocal[] sourceMedia,
      URL targetDirectoryLocation)
      throws MediaException
   {
      // TODO: Implement
      throw new UnsupportedOperationException("Not implemented yet!");
   }

   /**
    * @see javax.emb.MediaEntityLocalHome#importMedia(java.net.URL[], java.lang.String[])
    */
   public MediaEntityLocal[] ejbHomeImportMedia(
      URL[] sourceLocations,
      String[] names)
      throws CreateException, MediaException
   {
      // TODO: Implement
      throw new UnsupportedOperationException("Not implemented yet!");
   }

   /**
    * @see javax.emb.MediaEntityLocalHome#publishContent(javax.emb.Media, byte, javax.emb.ProtocolConstraints)
    */
   public URL ejbHomePublishContent(
      Media mediaObject,
      byte protocol,
      ProtocolConstraints constraints)
      throws MediaException
   {
      // TODO: Implement
      throw new UnsupportedOperationException("Not implemented yet!");
   }

   /**
    * @see javax.emb.MediaEntityLocalHome#publishMedia(javax.emb.MediaEntityLocal[], byte, javax.emb.ProtocolConstraints)
    */
   public Media ejbHomePublishMedia(
      MediaEntityLocal[] playlist,
      byte transferType,
      ProtocolConstraints constraints)
      throws MediaException
   {
      // TODO: Implement
      throw new UnsupportedOperationException("Not implemented yet!");
   }

   // Protected: --------------------------------------------------------------

   protected void updateLastModified()
   {
      setManagedLastModified(System.currentTimeMillis());
   }

   // Private: ----------------------------------------------------------------

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

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

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

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