/*
 * $Id$
 *
 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials
 *      provided with the distribution.
 *    * Neither the name of the jCoderZ.org Project nor the names of
 *      its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written
 *      permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.jcoderz.commons.util;

import java.io.BufferedInputStream;
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.io.RandomAccessFile;
import java.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.Random;

import junit.framework.TestCase;


/**
 * Unit Tests for the class {@link org.jcoderz.commons.util.IoUtil}.
 *
 * @author Michael Griffel
 */
public class IoUtilTest
      extends TestCase
{
   private static final Random RANDOM_GENERATOR = new Random();
   
   private static final int GAP_SIZE = 10 * 1024;

   /**
    * Tests the method {@link IoUtil#readFully(InputStream, int)}.
    * @throws IOException in case of an unexpected I/O error.
    */
   public void testReadFully ()
         throws IOException
   {
      read1kBytes();
      read0Bytes();
      readAhead();
      underread();
   }

   /**
    * Tests the method {@link IoUtil#close(InputStream)}.
    */
   public void testSafeCloseInputStream ()
   {
      final InputStream in = new ByteArrayInputStream(new byte[0])
         {
            public void close ()
                  throws IOException
            {
               throw new IOException();
            }
         };

      IoUtil.close(in);
      IoUtil.close(in);
      IoUtil.close((InputStream) null);
   }

   /**
    * Tests the method {@link IoUtil#close(OutputStream)}.
    */
   public void testSafeCloseOutputStream ()
   {
      final OutputStream out = new ByteArrayOutputStream()
         {
            public void close ()
                  throws IOException
            {
               throw new IOException();
            }
         };

      IoUtil.close(out);
      IoUtil.close(out);
      IoUtil.close((OutputStream) null);
   }

   /**
    * Tests the method {@link IoUtil#close(Reader)}.
    */
   public void testSafeCloseReader ()
   {
      final Reader reader = new Reader()
         {
            public void close ()
                  throws IOException
            {
               throw new IOException();
            }

            public int read (char[] cbuf, int off, int len)
            {
               return 0;
            }
         };

      IoUtil.close(reader);
      IoUtil.close(reader);
      IoUtil.close((Reader) null);
   }

   /**
    * Tests the method {@link IoUtil#close(Writer)}.
    */
   public void testSafeCloseWriter ()
   {
      final Writer writer = new Writer()
         {
            public void close ()
                  throws IOException
            {
               throw new IOException();
            }

            public void flush ()
            {
                // NOP
            }

            public void write (char[] cbuf, int off, int len)
            {
                // NOP
            }
         };

      IoUtil.close(writer);
      IoUtil.close(writer);
      IoUtil.close((Writer) null);
   }

   /**
    * Tests the method {@link IoUtil#close(RandomAccessFile)}.
    * @throws Exception in case of an unexpected error.
    */
   public void testSafeCloseRandomAccessFile ()
         throws Exception
   {
      final File tmp = File.createTempFile("tmp", "tmp");
      tmp.deleteOnExit();
      final RandomAccessFile file = new RandomAccessFile(tmp, "r")
         {
            public void close ()
                  throws IOException
            {
               throw new IOException();
            }
         };

      IoUtil.close(file);
      IoUtil.close(file);
      IoUtil.close((RandomAccessFile) null);
   }

   /**
    * Tests the method {@link IoUtil#close(Channel)}.
    * @throws Exception in case of an unexpected error.
    */
   public void testSafeCloseChannel ()
         throws Exception
   {
      final Channel channel = new ChannelImpl();
      IoUtil.close(channel);
      IoUtil.close(channel);
      IoUtil.close((Channel) null);
   }

   
   public void testSkip() throws Exception {
       final File tmp = File.createTempFile("fawkez", "tmp");
       FileOutputStream out = new FileOutputStream(tmp);
       out.write(1);
       out.write(new byte[GAP_SIZE]);
       out.write(2);
       out.close();
       final InputStream in = new BufferedInputStream(new FileInputStream((tmp)));
       int first = in.read();
       IoUtil.skip(in, GAP_SIZE); // instead of in.skip(GAP_SIZE);
       int second = in.read();
       assertEquals(3, first + second);
       
   }

   private void read1kBytes ()
         throws IOException
   {
      final byte[] inData = new byte[Constants.BYTES_PER_KILO_BYTE];
      final InputStream in = createRandomInputStream(inData);
      final byte[] outData = IoUtil.readFully(in, inData.length);
      assertTrue("bytes arrays should be equal",
            Arrays.equals(inData, outData));
   }

   private void read0Bytes ()
         throws IOException
   {
      final byte[] inData = new byte[0];
      final InputStream in = createRandomInputStream(inData);
      final byte[] outData = IoUtil.readFully(in, inData.length);
      assertTrue("bytes arrays should be equal",
            Arrays.equals(inData, outData));
   }

   private void readAhead ()
   {
      final byte[] inData = new byte[Constants.BYTES_PER_KILO_BYTE];
      final InputStream in = createRandomInputStream(inData);
      try
      {
         IoUtil.readFully(in, inData.length + 1);
         fail("Expected IOException for reading more bytes than available");
      }
      catch (IOException expected)
      {
         assertNotNull("Expected a real exception.", expected);
      }
   }
   private void underread ()
         throws IOException
   {
      final byte[] inData = new byte[Constants.BYTES_PER_KILO_BYTE];
      final InputStream in = createRandomInputStream(inData);
      final byte[] outData = IoUtil.readFully(in, inData.length - 1);
      final byte[] strippedInData = new byte[inData.length - 1];
      System.arraycopy(inData, 0, strippedInData, 0, inData.length - 1);
      assertTrue("bytes arrays should be equal except the last one",
            Arrays.equals(strippedInData, outData));
   }

   private InputStream createRandomInputStream (byte[] inData)
   {
      RANDOM_GENERATOR.nextBytes(inData);
      final InputStream in = new ByteArrayInputStream(inData);
      return in;
   }

   private static final class ChannelImpl
         implements ReadableByteChannel
   {
      private boolean mIsOpen = true;
      
      /** {@inheritDoc} */
      public int read (ByteBuffer dst)
      {
         return 0;
      }
      
      /** {@inheritDoc} */
      public void close ()
            throws IOException
      {
         if (mIsOpen)
         {
            mIsOpen = false;
         }
         else
         {
            throw new IOException();
         }
      }

      /** {@inheritDoc} */
      public boolean isOpen ()
      {
         return mIsOpen;
      }
   }


}
