/*
* 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:  Handler File IO Operations
 *
*/

#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <unistd.h>
#include <string>

#include "javacommonutils.h"
#include "jniarrayutils.h"
#include "logger.h"

#include "nativefileiohandler.h"

using namespace std;
using namespace java::util;
using namespace java::fileutils;

NativeFileIOHandler::NativeFileIOHandler(const std::wstring aName)
{
    // Set the current locale to suit unicode for entire process
    setlocale(LC_ALL, "");
    mFileName = aName;
    mFileDescriptor = 0;
    mClosedTemporarily = false;

    // Setting to -1 implies reading is not being done.
    // On call to openForReading, set this to 0 and increment on subsequent reads
    mReadPosition = -1;

    // Setting to -1 implies writing is not being done.
    // On call to openForWriting, set this to 0 and increment on subsequent writes
    mWritePosition = -1;
}

NativeFileIOHandler::~NativeFileIOHandler()
{
    // Set the current locale to suit unicode for entire process
    if (mFileDescriptor)
    {
        close(mFileDescriptor);
    }
}

long NativeFileIOHandler::skip(const long aOffset)
{
    JELOG2(EJavaFile);

    handleReopenCase();

    // lseek can set the offset beyond end of file.
    // but there will be no scenario where read position goes beyond end of file.

    // First set file offset to read position.
    int newOffset = lseek(mFileDescriptor, mReadPosition, SEEK_SET);

    if (newOffset < 0)
    {
        WLOG1(EJavaFile, "NativeFileIOHandler::skip: Seek failure. %d", errno);
        return 0;
    }
    else
    {
        // Now set the file offset in relation to the current read position.
        newOffset = lseek(mFileDescriptor, aOffset, SEEK_CUR);
        if (newOffset < 0)
        {
            WLOG1(EJavaFile, "NativeFileIOHandler::skip: Seek failure. %d",
                  errno);
            return 0;
        }
    }

    long retVal = newOffset - mReadPosition;
    mReadPosition = newOffset;
    return retVal;
}

int NativeFileIOHandler::readBytes(char* aBuffer, int aLength)
{
    JELOG2(EJavaFile);

    // Throws exception in case of errors.
    handleReopenCase();

    // lseek can set the offset beyond end of file.
    // but there will be no scenario where read position goes beyond end of file.
    int error = lseek(mFileDescriptor, mReadPosition, SEEK_SET);

    if (error < 0)
    {
        WLOG2(
            EJavaFile,
            "NativeFileIOHandler::readBytes:Seek failure. Read aborted:%d : Seek to: %d",
            errno, mReadPosition);
        // Errno cannot be thrown in S60. Causes random crashes.
        // So copy and throw.
        int err = errno;
        throw err;
    }

    int bytesRead = read(mFileDescriptor, aBuffer, aLength);
    if (bytesRead < 0)
    {
        LOG1(EJavaFile, EInfo,
             "NativeFileIOHandler::readBytes: Read failure: %d", errno);
        int err = errno;
        throw err;
    }

    // Adding 0 does no harm ;-)
    mReadPosition += bytesRead;
    return bytesRead;
}

int NativeFileIOHandler::writeBytes(char* aBuffer, int aLength)
{
    JELOG2(EJavaFile);

    // Throws exception in case of error
    handleReopenCase();

    // lseek can set the offset beyond end of file.
    // but there will be no scenario where read position goes beyond end of file.
    int error = lseek(mFileDescriptor, mWritePosition, SEEK_SET);
    if (error < 0)
    {
        WLOG1(
            EJavaFile,
            "NativeFileIOHandler::writeBytes: Seek failure. Write aborted: %d",
            errno);
        // Errno cannot be thrown in S60. Causes random crashes.
        // So copy and throw.
        int err = errno;
        throw err;
    }

    int bytesWritten = write(mFileDescriptor, aBuffer, aLength);
    if (bytesWritten <= 0)
    {
        LOG1(EJavaFile, EInfo,
             "NativeFileIOHandler::writeBytes: Write failure: %d", errno);
        int err = errno;
        throw err;
    }

    mWritePosition += bytesWritten;
    return bytesWritten;
}

void NativeFileIOHandler::stopReading()
{
    JELOG2(EJavaFile);
    mReadPosition = -1;
    closeStream();
}

void NativeFileIOHandler::stopWriting()
{
    JELOG2(EJavaFile);
    mWritePosition = -1;
    closeStream();
}

void NativeFileIOHandler::closeStream()
{
    JELOG2(EJavaFile);
    if ((-1 == mReadPosition) && (-1 == mWritePosition))
    {
        close(mFileDescriptor);
        mFileDescriptor = 0;
    }
}

void NativeFileIOHandler::openForReading()
{
    JELOG2(EJavaFile);

    if (!mFileDescriptor)
    {
        // it will never come here if it is not a file.
        // so, if there is a trailing slash, remove it.
        char ch = mFileName.at(mFileName.length() - 1);
        if (ch == '\\' || ch == '/')
        {
            mFileName = mFileName.substr(0, mFileName.length() - 1);
        }

        char* utf8Name = JavaCommonUtils::wstringToUtf8(mFileName);
        if (access(utf8Name, R_OK) < 0)
        {
            // We do not have write access
            delete[] utf8Name;
            ELOG(EJavaFile,
                 "NativeFileUtils::OpenForReading: Exiting in between. no read permission");
            int error = errno;
            throw error;
        }
        mFileDescriptor = open(utf8Name, O_RDWR);

        // If open fails with Read Write mode, try read mode atleast.
        if (mFileDescriptor < 0)
        {
            mFileDescriptor = open(utf8Name, O_RDONLY);
        }

        delete[] utf8Name;
        if (mFileDescriptor < 0)
        {
            // There is some problem opening the file. Throw the cause for error.
            mFileDescriptor = 0;
            ELOG(
                EJavaFile,
                "NativeFileUtils::OpenForReading: Exiting in between unable to open in read only");
            int error = errno;
            throw error;
        }
    }

    mReadPosition = 0;
}

void NativeFileIOHandler::openForWriting(const long aOffset)
{
    JELOG2(EJavaFile);
    char* utf8Name = JavaCommonUtils::wstringToUtf8(mFileName);

    if (access(utf8Name, W_OK) < 0)
    {
        WLOG(EJavaFile,
             "NativeFileIOHandler::openForWriting: Access not allowed");
        // We do not have write access
        delete[] utf8Name;
        int error = errno;
        throw error;
    }

    if (!mFileDescriptor)
    {
        // it will never come here if it is not a file.
        // so, if there is a trailing slash, remove it.
        char ch = mFileName.at(mFileName.length() - 1);
        if (ch == '\\' || ch == '/')
        {
            mFileName = mFileName.substr(0, mFileName.length() - 1);
        }

        mFileDescriptor = open(utf8Name, O_RDWR);

        if (mFileDescriptor < 0)
        {
            delete[] utf8Name;
            mFileDescriptor = 0;
            int error = errno;
            throw error;
        }
    }

    mWritePosition = 0;
    if (aOffset > 0)
    {
        // We will need to stat the file and find out its size just to make sure
        // lseek does not set the offset to beyond end of file.
        struct stat fileStat;
        int error = lstat(utf8Name, &fileStat);

        if (error < 0)
        {
            int error = errno;
            throw error;
        }
        long fileSize = fileStat.st_size;

        if (aOffset >= fileSize)
        {
            // Move to end of file
            if (lseek(mFileDescriptor, 0, SEEK_END) < 0)
            {
                delete[] utf8Name;
                int error = errno;
                throw error;
            }
            mWritePosition = fileSize;
        }
        else
        {
            // Move to given offset
            if (lseek(mFileDescriptor, aOffset, SEEK_SET) < 0)
            {
                delete[] utf8Name;
                int error = errno;
                throw error;
            }
            mWritePosition = aOffset;
        }
    }
    delete[] utf8Name;
}

void NativeFileIOHandler::closeFileToReopen()
{
    JELOG2(EJavaFile);

    // We dont try to synchronize here. It must be done from java.
    // In case of multithreaded apps, set hidden should not be done while read
    // is in progress.
    if (mFileDescriptor)
    {
        LOG(EJavaFile, EInfo,
            "NativeFileUtils::closeFileToReopen: Closing already open Files");
        LOG1(EJavaFile, EInfo, "        Current Read Offset: %ld",
             mReadPosition);
        LOG1(EJavaFile, EInfo, "        Current Write Offset: %ld",
             mWritePosition);
        mClosedTemporarily = true;
        close(mFileDescriptor);
        mFileDescriptor = 0;
    }
}

void NativeFileIOHandler::handleReopenCase()
{
    JELOG2(EJavaFile);
    if (!mClosedTemporarily)
    {
        return;
    }

    if ((-1 == mWritePosition) && (-1 == mReadPosition))
    {
        return;
    }

    if (mWritePosition > -1)
    {
        LOG(EJavaFile, EInfo,
            "NativeFileUtils::handleReopenCase: Reopening for write");
        long curWritePos = mWritePosition;
        openForWriting(curWritePos);
    }
    else if (mReadPosition > -1)
    {
        LOG(EJavaFile, EInfo,
            "NativeFileUtils::handleReopenCase: Reopening for read");
        long curReadPos = mReadPosition;
        openForReading();
        mReadPosition = curReadPos;
    }

    LOG(EJavaFile, EInfo, "NativeFileUtils::handleReopenCase: File Open");
    LOG1(EJavaFile, EInfo, "        Setting Read Offset: %ld", mReadPosition);
    LOG1(EJavaFile, EInfo, "        Setting Write Offset: %ld", mWritePosition);

    mClosedTemporarily = false;
}

long NativeFileIOHandler::available()
{
    JELOG2(EJavaFile);
    char* utf8Name = JavaCommonUtils::wstringToUtf8(mFileName);

    struct stat fileStat;
    int error = lstat(utf8Name, &fileStat);

    if (error < 0)
    {
        int error = errno;
        throw error;
    }
    long fileSize = fileStat.st_size;

    if (mReadPosition > 0)
    {
        return (fileSize - mReadPosition);
    }
    else
    {
        return fileSize;
    }
}
