/*
* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

package com.nokia.mj.impl.fileutils;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;

import com.nokia.mj.impl.rt.support.ApplicationInfo;
import com.nokia.mj.impl.rt.support.ApplicationUtils;
import com.nokia.mj.impl.rt.support.Finalizer;
import com.nokia.mj.impl.rt.support.Jvm;

/**
 * FileStreamHandler helps in creation of InputStream and OutputStream and
 * provides data to them from the native side.
 */
class FileStreamHandler
{
    protected int iNativeHandle;

    protected FileInputStream iInputStream;

    protected FileOutputStream iOutputStream;

    protected DataInputStream iDataInputStream;

    protected DataOutputStream iDataOutputStream;

    // Synchronizes close and open operations.
    protected final Object iCloseOperation;

    protected boolean iIsClosed;

    private Finalizer iFinalizer;

    private FileStreamHandler()
    {
        iCloseOperation = new Object();
    }

    protected FileStreamHandler(String aFileName)
    {
        iNativeHandle = createNativePeer(aFileName);
        iCloseOperation = new Object();
        iIsClosed = false;
        iFinalizer = registerFinalize();
    }

    protected int createNativePeer(String aFileName)
    {
        return _createNativePeer(aFileName);
    }

    /**
     * Registers with Finalizer to call a method when the object gets collected
     * by GC
     *
     * @return Finalizer object that will be notified when GC happens
     */
    private Finalizer registerFinalize()
    {
        return new Finalizer()
        {
            public void finalizeImpl()
            {
                doFinalize();
            }
        };
    }

    private synchronized void doFinalize()
    {
        _dispose(iNativeHandle);
    }

    protected void stopRead()
    {
        synchronized (iCloseOperation)
        {
            _stopReading(iNativeHandle);
            closeInputStreams();
        }
    }

    protected void stopWrite()
    {
        synchronized (iCloseOperation)
        {
            closeOutputStreams();
            _stopWriting(iNativeHandle);
        }
    }

    protected InputStream openInputStream() throws IOException
    {
        synchronized (iCloseOperation)
        {
            if (null == iInputStream)
            {
                iInputStream = new FileInputStream(this);
            }
            else
            {
                throw new IOException("InputStream already open");
            }
        }
        return iInputStream;
    }

    protected DataInputStream openDataInputStream() throws IOException
    {
        synchronized (iCloseOperation)
        {
            if (null == iDataInputStream)
            {
                iDataInputStream = new DataInputStream(openInputStream());
            }
            else
            { // TCK2 compliance
                throw new IOException("InputStream already open");
            }
        }
        return iDataInputStream;
    }

    /**
     * This method is called by InputStreamBase whenever it needs to fill its
     * internal buffer with the next block of data.
     *
     * @param aBuffer
     *            is the buffer that needs to be filled with data.
     * @return the number of bytes read
     * @throws InterruptedIOException
     *             in case connection was closed while read was in progress.
     * @throws IOException
     *             in case where there were problems during read.
     */
    protected synchronized int readBytes(byte[] aBuffer, int aOffset,
                                         int aLength) throws InterruptedIOException, IOException
    {
        return _readData(iNativeHandle, aBuffer, aOffset, aLength);
    }

    /**
     * Closes all input streams
     *
     */
    protected void closeInputStreams()
    {
        synchronized (iCloseOperation)
        {
            try
            {
                if (iInputStream != null)
                {
                    iInputStream.close();
                    iInputStream = null;
                }

                if (iDataInputStream != null)
                {
                    iDataInputStream.close();
                    iDataInputStream = null;
                }
            }
            catch (IOException ex)
            {
                // Do nothing. Deleting Streams anyhow
            }
        }
    }

    protected OutputStream openOutputStream() throws IOException
    {
        synchronized (iCloseOperation)
        {
            if (null == iOutputStream)
            {
                iOutputStream = new FileOutputStream(this);
            }
            else
            { // TCK2 compliance
                throw new IOException("OutputStream already open");
            }
        }
        return iOutputStream;
    }

    protected DataOutputStream openDataOutputStream() throws IOException
    {
        synchronized (iCloseOperation)
        {
            if (null == iDataOutputStream)
            {
                iDataOutputStream = new DataOutputStream(openOutputStream());
            }
            else
            {
                throw new IOException("OutputStream already open");
            }
        }
        return iDataOutputStream;
    }

    /**
     * This method is called by OutputStreamBase whenever its internal buffer
     * overruns or when flush is called or when output stream is closed.
     *
     * @param aBytes
     *            the buffer that has to be written to native.
     * @param aOffset
     *            offset from which data needs to be written in aBytes.
     * @param aLength
     *            the length of data from offset that has to be written.
     * @throws IOException
     *             in case write operation fails.
     */
    protected synchronized void writeBytes(byte[] aBytes, int aOffset,
                                           int aLength) throws IOException
    {
        _writeData(iNativeHandle, aBytes, aOffset, aLength);
    }

    /**
     * Closes all output streams.
     *
     */
    protected void closeOutputStreams()
    {
        synchronized (iCloseOperation)
        {
            try
            {
                if (iOutputStream != null)
                {
                    iOutputStream.close();
                    iOutputStream = null;
                }

                if (iDataOutputStream != null)
                {
                    iDataOutputStream.close();
                    iDataOutputStream = null;
                }
            }
            catch (IOException ex)
            {
                // Do nothing. Deleting Streams anyhow
            }
        }
    }

    /**
     * Checks the validity of the parameters passed.
     *
     * @param aBytes
     *            byte array with data
     * @param aOffset
     *            offset from which operations have to start
     * @param aLength
     *            length of data from offset
     */
    static void checkIOParams(byte[] aBytes, int aOffset, int aLength)
    {
        if (aOffset < 0 || aLength < 0 || (aOffset + aLength) > aBytes.length)
        {
            throw new IndexOutOfBoundsException();
        }
    }

    protected void closeFileStream()
    {
        _closeFileStream(iNativeHandle);
    }

    protected void openFileForReading()
    {
        _openFileForReading(iNativeHandle);
    }

    protected void openFileForWriting(long aOffset)
    {
        _openFileForWriting(iNativeHandle, aOffset);
    }

    /**
     * Moves the current read position on the native side forward and back.
     * NOTE: Method needs to be called by InputStream only. Native only sets
     * read position forward or backward.
     *
     * @param aOffset
     *            relative offset to the current native position. Positive moves
     *            the offset forward and negative moves it back.
     * @return actual number of bytes that can be skipped.
     */
    protected long skip(long aOffset)
    {
        return _skip(iNativeHandle, aOffset);
    }

    protected long available()
    {
        return _available(iNativeHandle);
    }

    private native long _available(int aNativePeer);

    private native long _skip(int aNativePeer, long aOffset);

    private native int _readData(int aNativePeer, byte[] aBuffer, int aOffset,
                                 int aLength);

    private native void _writeData(int aNativePeer, byte[] aBytes, int aOffset,
                                   int aLength);

    private native void _closeFileStream(int aHandle);

    private native void _openFileForReading(int aHandle);

    private native void _openFileForWriting(int aHandle, long aOffset);

    private native void _dispose(int aNativeHandle);

    private native void _stopReading(int iNativeHandle);

    private native void _stopWriting(int iNativeHandle);

    private native int _createNativePeer(String aFileName);

}
