/*
* Copyright (c) 2007-2007 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:
*
*/

#include <dirent.h>
#include <memory>
#include <string.h> // Needed for strcmp
#include <iostream>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <e32err.h>
#include <f32file.h>
#include <hal.h>
#include <flogger.h>
#include "logger.h"
#include "javaoslayer.h"
#include "javasymbianoslayer.h"
#include "libraryloaderexception.h"
#include "dynamiclibloader.h"
#include "javacommonutils.h"
#include "s60commonutils.h"

using namespace java::util;

const char * const BOOT_CLASSPATH_MIDP_FILE = "midpodclist";
const char * const BOOT_CLASSPATH_INSTALLER_FILE = "installerodclist";
const char * const BOOT_CLASSPATH_TCKRUNNER_FILE = "tckrunnerodclist";

_LIT(KStartUpTraceDir, "java\\full");
_LIT(KStartUpTraceFile, "startup.txt");
_LIT(KTraceSpaceExtra, "                                         ");

OS_EXPORT void JavaOsLayer::startUpTrace(const std::string& header,
        int freeMem, int totalMem)
{
    if (header.length() == 0)
    {
        _LIT(KDash, "-");
        RFileLogger::Write(KStartUpTraceDir, KStartUpTraceFile,
                           EFileLoggingModeAppend, KDash);
    }
    else
    {
        TTime time;
        time.HomeTime();
        const int maxStrLen = 41;
        TBuf<maxStrLen>  str;
        _LIT(KFormatStr, "%-B%:0%J%:1%T%:2%S%.%*C2%:3%+B");
        TRAP_IGNORE(time.FormatL(str, KFormatStr));
        int mem = 0;
        int err = HAL::Get(HALData::EMemoryRAMFree, mem);
        if (err != KErrNone)
        {
            mem = err;
        }
        HBufC* buf = stringToDes(header.c_str());
        const int maxLen = 41;
        int headerLen = Min(buf->Length(), maxLen);
        TPtrC headerPtr = buf->Left(headerLen);
        TPtrC spacePtr = KTraceSpaceExtra().Right(maxLen - headerLen);

        int heapUsageTotal;
        User::AllocSize(heapUsageTotal);

        _LIT(KOutStr, "%S%S Time(%S), SysMem(%d), pUsedHeap(%d), free(%d), total(%d)");
        RFileLogger::WriteFormat(KStartUpTraceDir, KStartUpTraceFile,
                                 EFileLoggingModeAppend,
                                 KOutStr,
                                 &headerPtr, &spacePtr, &str, mem, heapUsageTotal, freeMem, totalMem);
        delete buf;
    }
}


OS_EXPORT void JavaOsLayer::getOsSpecificLibName(std::string& result,
        const char* libName)
{
//    JELOG2(EUtils);
    if (libName)
    {
        result = libName;
    }
    else
    {
        ELOG(EUtils, "JavaOsLayer::getOsSpecificLibName() libname was null");
    }
}

OS_EXPORT void JavaOsLayer::getOsSpecificJavaRootPath(std::string& path)
{
//    JELOG2(EUtils);
    path = "c:\\java";
}

OS_EXPORT std::string& JavaOsLayer::getBinRoot(std::string& path, bool append)
{
//    JELOG2(EUtils);
    const char* start = ":\\sys\\bin\\";
    if (append)
    {
        path += start;
    }
    else
    {
        path = start+1;
    }
    return path;
}

OS_EXPORT std::string& JavaOsLayer::getResRoot(std::string& path, bool append)
{
//    JELOG2(EUtils);
    const char* start = ":\\resource\\java\\";
    if (append)
    {
        path += start;
    }
    else
    {
        path = start+1;
    }
    return path;
}

OS_EXPORT std::string& JavaOsLayer::getMidpRoot(std::string& path, bool append)
{
//    JELOG2(EUtils);
    const char* start = ":\\private\\102033E6\\";
//    const char* start = ":\\private\\200211D9\\";
    if (append)
    {
        path += start;
    }
    else
    {
        path = start+1;
    }
    /*
        TUint32 uid = RProcess().SecureId().iId;
        std::ostringstream oss;
        oss<<std::hex<<uid;
        path += oss.str();
        path += "]\\";
    */
    return path;
}

OS_EXPORT std::string& JavaOsLayer::getJavaCaptainRoot(std::string& path, bool append)
{
//    JELOG2(EUtils);
    const char* start = ":\\private\\200211DC\\";
    if (append)
    {
        path += start;
    }
    else
    {
        path = start+1;
    }
    return path;
}

OS_EXPORT DriveId JavaOsLayer::getMidpDrive()
{
//    JELOG2(EUtils);
    int driveNum = 1;
    TUint16 driveLetter = RProcess().FileName()[0];
    RFs::CharToDrive(driveLetter, driveNum);
    char* drive = new char[2];
    drive[0] = *((char*)&driveLetter);
    drive[1] = 0;
    return DriveId(drive, driveNum);
}


OS_EXPORT void* JavaOsLayer::dlopen(const char* libName)
{
    JELOG2(EUtils);
    RLibrary* libAccess = 0;
    if (libName)
    {
        libAccess = new RLibrary();
        if (libAccess)
        {
            HBufC* buf = stringToDes(libName);
            if (buf)
            {
                int status = libAccess->Load(*buf);
                LOG2(EUtils, EInfo, "JavaOsLayer::dlopen(%s), Handle: %X",
                     libName, libAccess);
                delete buf;
                if (status != KErrNone)
                {
                    delete libAccess;
                    libAccess = 0;
                    throw LibraryLoaderException(OPENING_LIBRARY_FAILED, status,
                                                 "Error opening Symbian lib.",
                                                 __FILE__, __FUNCTION__, __LINE__);

                }
                else
                {
                    //The code below makes RLibrary accessible from any thread.
                    // copies the handle without doing Open()
                    RLibrary duplicate=*libAccess;
                    // overwrite the original
                    status = libAccess->Duplicate(RThread());
                    duplicate.Close(); // close the original
                }
            }
        }
    }
    else
    {
        ELOG(EUtils, "JavaOsLayer::dlopen() libname was null");
    }
    return libAccess;
}

OS_EXPORT void* JavaOsLayer::dlsym(void* handle, const char* name,
                                   bool containsByteCode)
{
    JELOG2(EUtils);
    void* func = 0;
    if (handle && name)
    {
        RLibrary*  libAccess = reinterpret_cast<RLibrary*>(handle);
        LookupFunc lookup = (LookupFunc)libAccess->Lookup(containsByteCode?2:1);
        if (lookup)
        {
            func = (void*) lookup(name);
            LOG2(EUtils, EInfo, "JavaOsLayer::dlsym() fuction name: %s, "
                 "fPtr: %X", name, func);
            if (func == 0)
            {
                ELOG1(EUtils, "dlsym failed for %s.", name);
            }
        }
        else
        {
            ELOG1(EUtils,
                  "No lookup method in ordinal 1 when looking for %s.", name);
        }
    }
    else
    {
        ELOG2(EUtils, "JavaOsLayer::dlsym() null argument handle: %X, name %X",
              handle, name);
    }
    return func;
}

OS_EXPORT int JavaOsLayer::dlclose(void* handle)
{
    JELOG2(EUtils);
    if (handle)
    {
        RLibrary*  libAccess = reinterpret_cast<RLibrary*>(handle);
        libAccess->Close();
        delete libAccess;
    }
    else
    {
        ELOG(EUtils, "dlclose failed, null handle");
    }
    return 0;
}
OS_EXPORT void JavaOsLayer::bootClassPath(std::list<std::wstring>& odcFiles,
        std::list<std::wstring>& bcpEntites,
        const int pathType)
{
    /*
     * getResRoot requires drive letter but VerifiedFileNameL adds correct one.
     * C drive used that it is checked first and if file is found that is used
     * after that z is checked.
     */
    std::string path = "c";
    JavaOsLayer::getResRoot(path, true);

    if (BOOT_CLASSPATH_MIDP == pathType)
    {
        path.append(BOOT_CLASSPATH_MIDP_FILE);
    }
    else if (BOOT_CLASSPATH_INSTALLER == pathType)
    {
        path.append(BOOT_CLASSPATH_INSTALLER_FILE);
    }
    else if (BOOT_CLASSPATH_TCKRUNNER == pathType)
    {
        path.append(BOOT_CLASSPATH_TCKRUNNER_FILE);
    }
    else
    {
        path.append(BOOT_CLASSPATH_MIDP_FILE);
    }

    std::auto_ptr<HBufC>pathDes(stringToDes(path.c_str()));
    TFileName realPathName;
    TRAPD(err, realPathName = S60CommonUtils::VerifiedFileNameL(*pathDes));

    if (KErrNone == err)
    {
        std::wstring temp((wchar_t*)realPathName.PtrZ());
        std::string odcFileName(temp.begin(), temp.end());

        std::vector<std::string> romClasspathEntries;
        std::string dir_path("Z:\\resource\\java\\jvm\\lib\\jrt");

        listDirectory(dir_path, romClasspathEntries);  // This is always needed.

        if (odcFileName[0] == 'c' || odcFileName[0] == 'C')  // IAD deployed
        {
            std::vector<std::string> updatedClasspathEntries;
            listOdcFile(path, updatedClasspathEntries);  // Read all ODC files from odc file

            std::string::size_type const delimiter = path.rfind("\\");
            path = path.substr(0, delimiter);  // IAD ODC file directory

            populateCPEntries((path + "\\jvm\\lib\\jrt\\"), updatedClasspathEntries, odcFiles, bcpEntites);

            std::vector<std::string> romDeltaEntries;

            // Add all ROM entries that are not deployed using IAD.
            deltaCPEntries(romClasspathEntries, updatedClasspathEntries, romDeltaEntries);

            // Add ROM only entries
            populateCPEntries((dir_path + "\\"), romDeltaEntries, odcFiles, bcpEntites);
        }
        else  // Classpath only at ROM so using dir listing.
        {
            populateCPEntries((dir_path + "\\"), romClasspathEntries, odcFiles, bcpEntites);
        }
    }
    else
    {
        ELOG1(EUtils, "Cannot get real path name for: '%s'", path.c_str());
    }
}

void JavaOsLayer::listDirectory(std::string& aDirPath, std::vector<std::string>& aEntries)
{
    DIR *pDIR = opendir(aDirPath.c_str());    // open directory

    if (pDIR == NULL)
    {
        std::string errorMsg("Cannot read ROM ODC files: ");
        errorMsg.append(strerror(errno));
        errorMsg.append(" : dir: ");
        errorMsg.append(aDirPath.c_str());
        ELOG(EUtils, errorMsg.c_str());
        // Suppres error if no Java at all on device ROM.
    }

    struct dirent* pDirEnt = readdir(pDIR);
    while (pDirEnt != NULL)
    {
        aEntries.push_back(std::string(pDirEnt->d_name));
        pDirEnt = readdir(pDIR);
    }

    closedir(pDIR);    // Release the open directory
}

void JavaOsLayer::listOdcFile(std::string& filePath, std::vector<std::string>& entries)
{
    std::ifstream odcFile;
    odcFile.open(filePath.c_str(), std::ifstream::in);

    if (odcFile)
    {
        std::string line;
        while (std::getline(odcFile, line))
        {
            // Trim enter if exists.
            std::string::size_type const enter = line.find_last_not_of("\n");
            entries.push_back(line.substr(0, enter));
        }
        odcFile.close();
    }
    else
    {
        ELOG1(EUtils, "Cannot read file: '%s'", filePath.c_str());
    }
}

void JavaOsLayer::populateCPEntries(const std::string& pathPrefix,
                                    const std::vector<std::string>& entries,
                                    std::list<std::wstring>& odcFiles,
                                    std::list<std::wstring>& bcpEntites)
{
    std::vector<std::string>::const_iterator iter;

    for (iter = entries.begin(); iter != entries.end(); ++iter)
    {
        std::string entryName((*iter));
        entryName.insert(0, pathPrefix);    // Full path needed.

        std::wstring wideEntryName = L"";

        try
        {
            wideEntryName = JavaCommonUtils::utf8ToWstring(entryName.c_str());
        }
        catch (ExceptionBase& eb)
        {
            // Suppress error to skip invalid ones.
            ELOG2(EUtils, "utf8->wstring failed on cp entry: '%s' exp: %s ",
                  entryName.c_str(), eb.toString().c_str());
        }
        size_t pos = wideEntryName.rfind(L".odc");
        bool isOdcFile = (pos != std::string::npos) &&
                         (pos == (wideEntryName.length() - 4));
        if (isOdcFile)
        {
            odcFiles.push_back(wideEntryName);
        }
        else
        {
            bcpEntites.push_back(wideEntryName);
        }
    }
}

void JavaOsLayer::deltaCPEntries(const std::vector<std::string>& romEntries,
                                 const std::vector<std::string>& iadEntries,
                                 std::vector<std::string>& deltaEntries)
{
    std::vector<std::string>::const_iterator iter;  // ROM CP iter
    std::vector<std::string>::const_iterator matchIter; // IAD CP iter
    std::vector<std::string> romDeltaEntries;

    // Iterate through all rom entries
    for (iter = romEntries.begin(); iter != romEntries.end(); ++iter)
    {
        bool match = false;
        // and check if they do not match to IAD ones add to romDeltaEntries.
        for (matchIter = iadEntries.begin(); matchIter != iadEntries.end(); ++matchIter)
        {
            if ((*matchIter) == (*iter))
            {
                match = true;
                break;
            }
        }

        if (!match)
        {
            deltaEntries.push_back((*iter));
        }
    }
}

OS_EXPORT FuncPtr findMethod(const char* funcName,
                             const FuncTable funcTable[],
                             int tableSize)
{
//    JELOG2(EUtils);
    int       res = 0;
    int       mid = 0;
    int       top = 0;
    int       bottom = tableSize-1;

    if (funcName == 0)
    {
        ELOG(EUtils, "findMethod failed, null funcName");
        return 0;
    }
    if (funcTable == 0)
    {
        ELOG(EUtils, "findMethod failed, null funcTable");
        return 0;
    }

    // Loop while the number of the items left in the list is greater
    // than 2.  Each iteration will split the number of items left to search
    // in half
    while ((bottom - top) > 1)
    {
        // This case handles the normal serach case where the number of
        // items left to search is greater than 2
        mid = (top + bottom) / 2;
        res = strcmp(funcName, funcTable[mid].mFuncName);
        if (res == 0)
        {
            return((FuncPtr) funcTable[mid].mFuncAddr);
        }
        if (res > 0)
        {
            top = mid;
        }
        else
        {
            bottom = mid;
        }
    }

    // If there are two items left in the list then the bottom item should be
    // checked for a match
    if (bottom != top)
    {
        // Check the bottom item to see if it is a match
        res=strcmp(funcName, funcTable[bottom].mFuncName);
        if (res == 0)
        {
            return ((FuncPtr) funcTable[bottom].mFuncAddr);
        }
    }

    // Check the top item to see if it is a match
    res=strcmp(funcName, funcTable[top].mFuncName);

    if (res == 0)
    {
        return ((FuncPtr) funcTable[top].mFuncAddr);
    }

    // Neither the top or bottom items were a match so the
    // method must not exist in the file
    return 0;
}

OS_EXPORT HBufC* stringToDes(const char* str)
{
//    JELOG2(EUtils);
    HBufC* resultBuf = 0;
    try
    {
        if (str)
        {
            int len = strlen(str);
            resultBuf = HBufC::New(len + 1);
            TPtr ptr = resultBuf->Des();
            TPtr8 ptr8((TUint8 *)str, len);
            ptr8.SetLength(len);
            ptr.Copy(ptr8);
            ptr.ZeroTerminate();
        }
    }
    catch (...)
    {
        ELOG(EUtils, "stringToDes, exception");
    }
    return resultBuf;
}

OS_EXPORT wchar_t* desToWstring(TPtr16& aDes)
{
    return (wchar_t*) aDes.PtrZ();
}

OS_EXPORT TAppVersion wstringToAppVersion(const std::wstring& aVersionString)
{
    TAppVersion appVersion;
    char* temp = JavaCommonUtils::wstringToUtf8(aVersionString);
    std::string versionStr = temp;

    std::string::size_type idx = 0;
    idx = versionStr.find(".", idx);

    // 00.00.00 --> 00 00.00
    if (idx != std::string::npos)
    {
        versionStr.replace(idx, 1, " ");
    }

    idx = versionStr.find(".", idx + 1);

    // 00 00.00 --> 00 00 00
    if (idx != std::string::npos)
    {
        versionStr.replace(idx, 1, " ");
    }

    int major = 0;
    int minor = 0;
    int build = 0;
    int result = sscanf(
                     versionStr.c_str(), "%d %d %d", &major, &minor, &build);

    if (major > 0)
    {
        appVersion.iMajor = major;
    }

    if (minor > 0)
    {
        appVersion.iMinor = minor;
    }

    if (build > 0)
    {
        appVersion.iBuild = build;
    }
    delete[] temp;

    return appVersion;
}

OS_EXPORT HBufC* wstringToBuf(const std::wstring& aString)
{
    HBufC* stringBuf = HBufC::New(aString.size());
    if (stringBuf != 0)
    {
        TPtr16 namePtr(stringBuf->Des());
        namePtr.Append((const TUint16*)aString.c_str(), aString.size());
    }
    return stringBuf;
}

/**
 *
 */
OS_EXPORT java::util::Uid& TUidToUid(const TUid& aId,java::util::Uid& aOutUid)
{
//  JELOG2(EUtils);
    if (0 == aId.iUid)
        return aOutUid;

    std::wstringstream stream;
    stream.fill('0');
    stream << std::setw(8) << std::hex << (int)aId.iUid; // codescanner::leave

    std::wstring idAsStr;
    idAsStr.reserve(11);
    idAsStr.append(L"[");
    idAsStr.append(stream.str());
    idAsStr.append(L"]");
    Uid tmpUid(idAsStr);
    aOutUid = tmpUid;
    return aOutUid;
}

/**
 *
 */
OS_EXPORT TInt uidToTUid(const java::util::Uid& aUid,TUid& aOutId)
{
//  JELOG2(EUtils);
    long long tmpInt = 0;
    std::wstring uidAsStr = aUid.toString();
    if (0 == uidAsStr.size())
        return KErrArgument;

    JavaCommonUtils::trimWstring(uidAsStr,L'\n');
    JavaCommonUtils::trimWstring(uidAsStr,L'\t');
    JavaCommonUtils::trimWstring(uidAsStr,L' ');

    if ((10 != uidAsStr.size()) && (8 != uidAsStr.size()))
        return KErrArgument;

    if (('[' == uidAsStr.at(0)) && (']' == uidAsStr.at(uidAsStr.size()-1)))
    {
        if (10 != uidAsStr.size())
            return KErrArgument;
        std::wstring plainNumStr = uidAsStr.substr(1,uidAsStr.size()-2);
        std::wstringstream stream(plainNumStr);
        stream >> std::hex >> tmpInt; // codescanner::leave
        if ((false == stream.eof()) || (stream.fail()))
            return KErrArgument;
    }
    else
    {
        std::wstringstream stream(uidAsStr);
        stream >> std::hex >> tmpInt; // codescanner::leave
        if ((false == stream.eof()) || (stream.fail()))
            return KErrArgument;
    }

    if ((tmpInt > 0xEFFFFFFF) || (tmpInt < 0))
        return KErrArgument;
    aOutId.iUid = tmpInt;
    return KErrNone;
}

