/**
 * @file lllfsthread.cpp
 * @brief LLLFSThread base class
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "lllfsthread.h"
#include "llstl.h"
#include "llapr.h"

//============================================================================

/*static*/ LLLFSThread* LLLFSThread::sLocal = NULL;

//============================================================================
// Run on MAIN thread
//static
void LLLFSThread::initClass(bool local_is_threaded)
{
    llassert(sLocal == NULL);
    sLocal = new LLLFSThread(local_is_threaded);
}

//static
S32 LLLFSThread::updateClass(U32 ms_elapsed)
{
    return static_cast<S32>(sLocal->update((F32)ms_elapsed));
}

//static
void LLLFSThread::cleanupClass()
{
    llassert(sLocal != NULL);
    sLocal->setQuitting();
    while (sLocal->getPending())
    {
        sLocal->update(0);
    }
    sLocal->shutdown();
    delete sLocal;
    sLocal = NULL;
}

//----------------------------------------------------------------------------

LLLFSThread::LLLFSThread(bool threaded) :
    LLQueuedThread("LFS", threaded)
{
    if(!mLocalAPRFilePoolp)
    {
        mLocalAPRFilePoolp = new LLVolatileAPRPool() ;
    }
}

LLLFSThread::~LLLFSThread()
{
    // mLocalAPRFilePoolp cleanup in LLThread
    // ~LLQueuedThread() will be called here
}

//----------------------------------------------------------------------------

LLLFSThread::handle_t LLLFSThread::read(const std::string& filename,    /* Flawfinder: ignore */
                                        U8* buffer, S32 offset, S32 numbytes,
                                        Responder* responder)
{
    LL_PROFILE_ZONE_SCOPED;
    handle_t handle = generateHandle();

    Request* req = new Request(this, handle,
                               FILE_READ, filename,
                               buffer, offset, numbytes,
                               responder);

    bool res = addRequest(req);
    if (!res)
    {
        LL_ERRS() << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << LL_ENDL;
    }

    return handle;
}

LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
                                         U8* buffer, S32 offset, S32 numbytes,
                                         Responder* responder)
{
    LL_PROFILE_ZONE_SCOPED;
    handle_t handle = generateHandle();

    Request* req = new Request(this, handle,
                               FILE_WRITE, filename,
                               buffer, offset, numbytes,
                               responder);

    bool res = addRequest(req);
    if (!res)
    {
        LL_ERRS() << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << LL_ENDL;
    }

    return handle;
}

//============================================================================

LLLFSThread::Request::Request(LLLFSThread* thread,
                              handle_t handle,
                              operation_t op, const std::string& filename,
                              U8* buffer, S32 offset, S32 numbytes,
                              Responder* responder) :
    QueuedRequest(handle, FLAG_AUTO_COMPLETE),
    mThread(thread),
    mOperation(op),
    mFileName(filename),
    mBuffer(buffer),
    mOffset(offset),
    mBytes(numbytes),
    mBytesRead(0),
    mResponder(responder)
{
    if (numbytes <= 0)
    {
        LL_WARNS() << "LLLFSThread: Request with numbytes = " << numbytes << LL_ENDL;
    }
}

LLLFSThread::Request::~Request()
{
}

// virtual, called from own thread
void LLLFSThread::Request::finishRequest(bool completed)
{
    LL_PROFILE_ZONE_SCOPED;
    if (mResponder.notNull())
    {
        mResponder->completed(completed ? mBytesRead : 0);
        mResponder = NULL;
    }
}

void LLLFSThread::Request::deleteRequest()
{
    LL_PROFILE_ZONE_SCOPED;
    if (getStatus() == STATUS_QUEUED)
    {
        LL_ERRS() << "Attempt to delete a queued LLLFSThread::Request!" << LL_ENDL;
    }
    if (mResponder.notNull())
    {
        mResponder->completed(0);
        mResponder = NULL;
    }
    LLQueuedThread::QueuedRequest::deleteRequest();
}

bool LLLFSThread::Request::processRequest()
{
    LL_PROFILE_ZONE_SCOPED;
    bool complete = false;
    if (mOperation ==  FILE_READ)
    {
        llassert(mOffset >= 0);
        LLAPRFile infile ; // auto-closes
        infile.open(mFileName, LL_APR_RB, mThread->getLocalAPRFilePool());
        if (!infile.getFileHandle())
        {
            LL_WARNS() << "LLLFS: Unable to read file: " << mFileName << LL_ENDL;
            mBytesRead = 0; // fail
            return true;
        }
        S32 off;
        if (mOffset < 0)
            off = infile.seek(APR_END, 0);
        else
            off = infile.seek(APR_SET, mOffset);
        llassert_always(off >= 0);
        mBytesRead = infile.read(mBuffer, mBytes );
        complete = true;
//      LL_INFOS() << "LLLFSThread::READ:" << mFileName << " Bytes: " << mBytesRead << LL_ENDL;
    }
    else if (mOperation ==  FILE_WRITE)
    {
        apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY;
        if (mOffset < 0)
            flags |= APR_APPEND;
        LLAPRFile outfile ; // auto-closes
        outfile.open(mFileName, flags, mThread->getLocalAPRFilePool());
        if (!outfile.getFileHandle())
        {
            LL_WARNS() << "LLLFS: Unable to write file: " << mFileName << LL_ENDL;
            mBytesRead = 0; // fail
            return true;
        }
        if (mOffset >= 0)
        {
            S32 seek = outfile.seek(APR_SET, mOffset);
            if (seek < 0)
            {
                LL_WARNS() << "LLLFS: Unable to write file (seek failed): " << mFileName << LL_ENDL;
                mBytesRead = 0; // fail
                return true;
            }
        }
        mBytesRead = outfile.write(mBuffer, mBytes );
        complete = true;
//      LL_INFOS() << "LLLFSThread::WRITE:" << mFileName << " Bytes: " << mBytesRead << "/" << mBytes << " Offset:" << mOffset << LL_ENDL;
    }
    else
    {
        LL_ERRS() << "LLLFSThread::unknown operation: " << (S32)mOperation << LL_ENDL;
    }
    return complete;
}

//============================================================================

LLLFSThread::Responder::~Responder()
{
}

//============================================================================