/**
 *
 */
package org.jboss.resteasy.plugins.providers;

import org.jboss.resteasy.plugins.server.servlet.Cleanable;
import org.jboss.resteasy.plugins.server.servlet.Cleanables;
import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.util.NoContent;

import javax.xml.transform.stream.StreamSource;

import java.io.ByteArrayInputStream;
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.io.SequenceInputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.activation.DataSource;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

/**
 * @author <a href="mailto:ryan@damnhandy.com">Ryan J. McDonough</a>
 * @version $Revision:$
 */
@Provider
@Consumes("*/*")
@Produces("*/*")
public class DataSourceProvider extends AbstractEntityProvider<DataSource>
{

   protected static class SequencedDataSource implements DataSource
   {
      private final byte[] byteBuffer;
      private final int byteBufferOffset;
      private final int byteBufferLength;
      private final File tempFile;
      private final String type;

      protected SequencedDataSource(byte[] byteBuffer, int byteBufferOffset,
                                 int byteBufferLength, File tempFile, String type)
      {
         super();
         this.byteBuffer = byteBuffer;
         this.byteBufferOffset = byteBufferOffset;
         this.byteBufferLength = byteBufferLength;
         this.tempFile = tempFile;
         this.type = type;
      }

      @Override
      public String getContentType()
      {
         return type;
      }

      @Override
      public InputStream getInputStream() throws IOException
      {
         InputStream bis = new ByteArrayInputStream(byteBuffer, byteBufferOffset, byteBufferLength);
         if (tempFile == null)
            return bis;
         InputStream fis = new FileInputStream(tempFile);
         CleanableSequenceInputStream csis = new CleanableSequenceInputStream(bis, fis, tempFile);
         Cleanables cleanables = ResteasyProviderFactory.getContextData(Cleanables.class);
         if (cleanables != null)
         {
             cleanables.addCleanable(csis);
         }
         return csis;
      }

      @Override
      public String getName()
      {
         return "";
      }

      @Override
      public OutputStream getOutputStream() throws IOException
      {
         throw new IOException(Messages.MESSAGES.noOutputStreamAllowed());
      }

   }


   /**
    * @param in
    * @param mediaType
    * @return
    * @throws IOException
    */
   public static DataSource readDataSource(final InputStream in, final MediaType mediaType) throws IOException
   {
      byte[] memoryBuffer = new byte[4096];
      int readCount = in.read(memoryBuffer, 0, memoryBuffer.length);

      File tempFile = null;
      if (readCount > 0)
      {
         byte[] buffer = new byte[4096];
         int count = in.read(buffer, 0, buffer.length);
         if (count > -1) {
             tempFile = File.createTempFile("resteasy-provider-datasource", null);
             tempFile.deleteOnExit();
             FileOutputStream fos = new FileOutputStream(tempFile);
             fos.write(buffer, 0, count);
             try
             {
                ProviderHelper.writeTo(in, fos);
             }
             finally
             {
                fos.close();
             }
         }
      }

      if (readCount == -1)
         readCount = 0;

      return new SequencedDataSource(memoryBuffer, 0, readCount, tempFile, mediaType.toString());
   }

   /**
    * FIXME Comment this
    *
    * @param type
    * @param genericType
    * @param annotations
    * @return
    * @see javax.ws.rs.ext.MessageBodyReader
    */
   @Override
   public boolean isReadable(Class<?> type,
                             Type genericType,
                             Annotation[] annotations, MediaType mediaType)
   {
      return DataSource.class.isAssignableFrom(type);
   }


   /**
    * FIXME Comment this
    *
    * @param type
    * @param genericType
    * @param annotations
    * @param mediaType
    * @param httpHeaders
    * @param entityStream
    * @return
    * @throws IOException
    * @throws WebApplicationException
    * @see @see javax.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.InputStream)
    */
   @Override
   public DataSource readFrom(Class<DataSource> type,
                              Type genericType,
                              Annotation[] annotations,
                              MediaType mediaType,
                              MultivaluedMap<String, String> httpHeaders,
                              InputStream entityStream) throws IOException
   {
      if (NoContent.isContentLengthZero(httpHeaders)) return readDataSource(new ByteArrayInputStream(new byte[0]), mediaType);
      return readDataSource(entityStream, mediaType);
   }


   /**
    * FIXME Comment this
    *
    * @param type
    * @param genericType
    * @param annotations
    * @return
    * @see @see javax.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[])
    */
   @Override
   public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
   {
      return DataSource.class.isAssignableFrom(type);
   }

   /**
    * FIXME Comment this
    *
    * @param dataSource
    * @param type
    * @param genericType
    * @param annotations
    * @param mediaType
    * @param httpHeaders
    * @param entityStream
    * @throws IOException
    * @throws WebApplicationException
    * @see @see javax.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.OutputStream)
    */
   @Override
   public void writeTo(DataSource dataSource,
                       Class<?> type,
                       Type genericType,
                       Annotation[] annotations,
                       MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException
   {
      InputStream in = dataSource.getInputStream();
      try
      {
         ProviderHelper.writeTo(in, entityStream);
      }
      finally
      {
         in.close();
      }

   }

   private static class CleanableSequenceInputStream extends SequenceInputStream implements Cleanable
   {
	   private File tempFile;
	   public CleanableSequenceInputStream(InputStream is1, InputStream is2, File tempFile)
	   {
		   super(is1, is2);
		   this.tempFile = tempFile;
	   }

	   @Override
	   public void clean() throws Exception
	   {
		   deleteTempFile();
	   }

	   private void deleteTempFile()
	   {
		   if(tempFile.exists())
		   {
			   tempFile.delete();
		   }
	   }
   }
}
