/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbCore module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include "hbstring_p.h"
#include "hbstringdata_p.h"
#include "hbsmartoffset_p.h"

#include <QLatin1String>
#include <QChar>
#include <QDebug>

static const int shared_null_offset = -2;
static HbStringData *shared_null = 0;

HbStringData::HbStringData() :
    mStartOffset(-1),
    mLength(0),
    mCapacity(0),
    mRef(1)
{
}

HbStringData *getStringData(HbMemoryManager::MemoryType type, int offset, bool shared = false)
{
    if (offset == shared_null_offset) {
        return shared_null;
    } else {
        return getAddress<HbStringData>(type, offset, shared);
    }
}

/*
    Constructs a new HbString with \a MemoryType (SharedMemory/HeapMemory).
*/
HbString::HbString(HbMemoryManager::MemoryType type)
    : mMemoryType(type), mShared(false), mDataOffset(-1)
{
    if (type == HbMemoryManager::HeapMemory) {
        // Set offset to point to default NULL instance of HbStringData to
        // avoid unnecessary heap allocation when constructing HbString instances.
        mDataOffset = shared_null_offset;
        if (!shared_null) {
            shared_null = new HbStringData;
        }
        shared_null->mRef.ref();
        return;
    }

    GET_MEMORY_MANAGER(type)
    RETURN_IF_READONLY(manager)

    mDataOffset = manager->alloc(sizeof(HbStringData));
    HbStringData* data = HbMemoryUtils::getAddress<HbStringData>(mMemoryType, mDataOffset);
    new(data) HbStringData();
}

/*
    Constructs a new HbString with \a QString and \a MemoryType (SharedMemory/HeapMemory).
*/
HbString::HbString(const QString& str, HbMemoryManager::MemoryType type)
    : mMemoryType(type), mShared(false), mDataOffset(-1)
 {   
    GET_MEMORY_MANAGER(type)
    RETURN_IF_READONLY(manager)
    HbSmartOffset dataOffset(manager->alloc(sizeof(HbStringData)), type);

    int length = str.length();
    HbStringData* data = getStringData(mMemoryType, dataOffset.get());
    new(data) HbStringData(); 
    if (length) {
        data->mStartOffset = manager->alloc(length*sizeof(QChar));
        ::memcpy((char*) manager->base() + data->mStartOffset, str.constData(), length*sizeof(QChar));
    } else {
        data->mStartOffset = -1;
    }
    data->mLength = length;
    data->mCapacity = length;
    mDataOffset = dataOffset.release();
}

/*
    Constructs a new HbString with \a HbString.
*/
HbString::HbString(const HbString& other)
{
    mMemoryType = other.mMemoryType;
    GET_MEMORY_MANAGER(other.mMemoryType)

    HbStringData* data = getStringData(mMemoryType, other.mDataOffset);
    mDataOffset = other.mDataOffset;

    if(!manager->isWritable() || other.mShared == true) {
       mShared = true;
       mMemoryType = HbMemoryManager::HeapMemory;
    } else {
        mShared = false;
        data->mRef.ref();
    }
}

/*
  Destructor for the HbString.
*/
HbString::~HbString()
{
    GET_MEMORY_MANAGER(mMemoryType);
    // if the memory where the string is not writable it means it's client process, so do nothing
    if (!manager->isWritable() || mDataOffset == shared_null_offset) 
       return;

    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);

    if (mShared != true && !data->mRef.deref()) {
        clear();
        data->~HbStringData();
        HbMemoryUtils::freeMemory(mMemoryType, mDataOffset);
    }
}

static bool memEquals(const quint16 *a, const quint16 *b, int length)
{
    // Benchmarking indicates that doing memcmp is much slower than
    // executing the comparison ourselves.
    // To make it even faster, we do a 32-bit comparison, comparing
    // twice the amount of data as a normal word-by-word comparison.
    //
    // Benchmarking results on a 2.33 GHz Core2 Duo, with a 64-QChar
    // block of data, with 4194304 iterations (per iteration):
    //    operation             usec            cpu ticks
    //     memcmp                330               710
    //     16-bit                 79             167-171
    //  32-bit aligned            49             105-109
    //
    // Testing also indicates that unaligned 32-bit loads are as
    // performant as 32-bit aligned.
    if (a == b || !length)
        return true;

    register union {
        const quint16 *w;
        const quint32 *d;
        quintptr value;
    } sa, sb;
    sa.w = a;
    sb.w = b;

    // check alignment
    if ((sa.value & 2) == (sb.value & 2)) {
        // both addresses have the same alignment
        if (sa.value & 2) {
            // both addresses are not aligned to 4-bytes boundaries
            // compare the first character
            if (*sa.w != *sb.w)
                return false;
            --length;
            ++sa.w;
            ++sb.w;

            // now both addresses are 4-bytes aligned
        }

        // both addresses are 4-bytes aligned
        // do a fast 32-bit comparison
        register const quint32 *e = sa.d + (length >> 1);
        for ( ; sa.d != e; ++sa.d, ++sb.d) {
            if (*sa.d != *sb.d)
                return false;
        }

        // do we have a tail?
        return (length & 1) ? *sa.w == *sb.w : true;
    } else {
        // one of the addresses isn't 4-byte aligned but the other is
        register const quint16 *e = sa.w + length;
        for ( ; sa.w != e; ++sa.w, ++sb.w) {
            if (*sa.w != *sb.w)
                return false;
        }
    }
    return true;
}

static int compare_helper(const QChar *data1, int length1, const QLatin1String &s2)
{
    const ushort *uc = reinterpret_cast<const ushort *>(data1);
    const ushort *e = uc + length1;
    const uchar *c = (uchar *)s2.latin1();

    if (!c) {
        return length1;
    }

    // Case sensitive comparison
    while (uc != e && *c && *uc == *c) {
        uc++, c++;
    }

    if (uc == e) {
        // If both strings reached end, consider them equal
        if (!*c) {
            return 0;
        }
        // If this string reached end and other didn't, this string is considered "less" than the other
        else {
            return -1;
        }
    }

    return *uc - *c;
}



/*
    Private Utility function that copies the const char* to the HbString
*/
void HbString::copyString(const QChar *arr, int size)
{
    GET_MEMORY_MANAGER(mMemoryType)
    RETURN_IF_READONLY(manager)
    
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    // If source string is empty, simply make the destination string's length
    // as zero and keep the capacity as same for future use.
    if (size == 0){
        data->mLength = 0;
        return;
    }
    if (data->mStartOffset != -1) {
        //current size is more than the Capacity, realloc with the new size
        if (size > data->mCapacity ) {
            data->mStartOffset = manager->realloc(data->mStartOffset, size*sizeof(QChar));
            data->mCapacity = size;
        }
        // copies the string
        ::memcpy(getAddress<char>(mMemoryType, data->mStartOffset, mShared), arr, size*sizeof(QChar)); 
        data->mLength = size;
    } else {
        data->mStartOffset = manager->alloc(size*sizeof(QChar));

        // copies the string
        ::memcpy(getAddress<char>(mMemoryType, data->mStartOffset, mShared), arr, size*sizeof(QChar));
        data->mLength = size;
        data->mCapacity = size;
    }
}

/*
    Private Utility function that compares the two strings and returns boolean value
*/
bool HbString::compareString(const QChar *rhs, int len) const
{
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    
    if (len != data->mLength) {
        return false;
    } else if( data->mStartOffset == -1 && (rhs == 0 || len == 0)) { // Empty strings
        return true;
    } else if ( data->mStartOffset == -1 || len == 0 ) {
        return false;
    }
    
    QChar *ptr = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
    return memEquals((quint16 *)ptr, (quint16 *)rhs, len);
}    

/*
    This function creates a copy of data based on the size
*/
void HbString::detach(int size)
{    
    
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    if(data->mRef > 1 || mShared == true) {
        if(data->mRef > 1) {
            data->mRef.deref();
        }
        GET_MEMORY_MANAGER(mMemoryType);
        HbSmartOffset dataOffset(manager->alloc(sizeof(HbStringData)), mMemoryType);

        HbStringData* newData = 0;
        newData = getStringData(mMemoryType,dataOffset.get());
        new(newData) HbStringData();
		
		// Allocate new string buffer if given size is greater than zero
		if (size > 0) {
			newData->mStartOffset = manager->alloc(size*sizeof(QChar));
			newData->mLength = qMin(data->mLength, size);
			// copy old string data contents in the new string data
			::memcpy(HbMemoryUtils::getAddress<char>(mMemoryType, newData->mStartOffset),
                getAddress<char>(mMemoryType, data->mStartOffset, mShared),
				newData->mLength * sizeof(QChar));
		} 

        newData->mCapacity = size;
        mShared = false;
        mDataOffset = dataOffset.release();
    } else if (size > data->mCapacity) { // no need to detach, but make sure there is capacity for the new size
        GET_MEMORY_MANAGER(mMemoryType);
        data->mStartOffset = manager->realloc(data->mStartOffset, size*sizeof(QChar));
        data->mCapacity = size;
    }
}

/*
    This function returns the QString form of HbString
*/
HbString::operator QString() const
{
    HbStringData * data = getStringData(mMemoryType, mDataOffset, mShared);
    const QChar *ptr = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
    if(ptr)
        return QString(ptr, data->mLength);
    else {
        // TODO: this should return QString() but currently it causes wierd behaviour.
        // Seems some clients assume it returns "".
        return QString("");
    }
}

/*
    Overloaded assignment operator that takes HbString as argument 
*/
HbString& HbString::operator=(const HbString &other)
{
    // self assignment check
    if (this != &other){
        GET_MEMORY_MANAGER(mMemoryType)

        if(!manager->isWritable())
            Q_ASSERT(HbMemoryManager::HeapMemory == mMemoryType);

        HbStringData* otherData = 0;
		otherData = getStringData(other.mMemoryType, other.mDataOffset, other.mShared);
        HbStringData* data = 0;
        data = getStringData(mMemoryType, mDataOffset, mShared);
    
        if(other.mMemoryType != mMemoryType || other.mShared == true) {
            if(mShared != true && data->mRef == 1) {                
                if(data->mRef == 1) {
                    clear();
                    data->~HbStringData();
                    HbMemoryUtils::freeMemory(mMemoryType,mDataOffset);
                } else {
                    data->mRef.deref();
                }
            }
            mShared = true;
            mMemoryType = HbMemoryManager::HeapMemory;
        } else {            
            otherData->mRef.ref();

            if(mShared != true && !data->mRef.deref() ) {
                clear(); 
                data->~HbStringData();
                HbMemoryUtils::freeMemory(mMemoryType,mDataOffset);
            }                 
            mMemoryType = other.mMemoryType;
            mShared = other.mShared;
        }
        mDataOffset = other.mDataOffset;
    }
    return *this;
}

/*
    Overloaded assignment operator that takes QLatin1String as argument 
*/
HbString& HbString::operator=(const QLatin1String &str)
{
    bool result = false;    
    
    // This creates a temp string from the latin1 string.
    // It causes one extra mem alloc but it does the conversion from latin1 that is needed.
    QString tempStr(str);
    int len = tempStr.length();

    // Compares both strings
    result = compareString(tempStr.constData(), len);

    // If both strings are not equal, then assigns QLatin1String to HbString
    if (!result) {
        detach(len);
        copyString(tempStr.constData(), len);
    }

    return *this;
}

/*
    Overloaded assignment operator that takes QString as argument 
*/
HbString& HbString::operator=(const QString& str)
{
    int len = 0;
    const QChar *ptr = str.constData();
    if(ptr) {
        len = str.length();
        // Compares both strings.
        // If both strings are not equal, then assigns QString to HbString
        if(!compareString(ptr, len)) {
            detach(len);
            copyString(ptr, len);
        }
    }
    return *this;
}

/*
    Overloaded "==" operator that takes HbString as argument
    and returns boolean value
*/
bool HbString::operator==(const HbString &other) const
{
    if(mDataOffset == other.mDataOffset) {
        return true;
    }

    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    HbStringData* otherData = getStringData(other.mMemoryType, other.mDataOffset, other.mShared);;

    if (data->mLength != otherData->mLength) {
        return false;
    }

    QChar *ptr = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
	QChar *otherPtr = getAddress<QChar>(other.mMemoryType, otherData->mStartOffset, other.mShared);

    return memEquals((quint16 *)ptr, (quint16 *)otherPtr, data->mLength);
}

/*
    Overloaded "==" operator that takes QLatin1String as 
    argument and returns boolean value.
*/
bool HbString::operator==(const QLatin1String& str) const
{

    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    const ushort *uc = (const ushort *)getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
    const ushort *e = uc + data->mLength;
    const uchar *c = (uchar *)str.latin1();

    if (!c)
        return !data->mLength;

    while (*c) {
        if (uc == e || *uc != *c)
            return false;
        ++uc;
        ++c;
    }
    return (uc == e);
}

/*
    Overloaded "==" operator that takes QString as 
    argument and returns boolean value.
*/
bool HbString::operator==(const QString &str) const
{
    return compareString(str.constData(), str.length());
}

/*
    Overloaded "!=" operator that takes HbString as argument
    and returns boolean value
*/
bool HbString::operator!=(const HbString &str) const
{
    return !(*this == str);
}

/*
    Overloaded "!=" operator that takes QLatin1String as 
    argument and returns boolean value.
*/
bool HbString::operator!=(const QLatin1String& str) const
{
    return !(*this==str);
}

/*
    Overloaded "!=" operator that takes QString as 
    argument and returns boolean value.
*/
bool HbString::operator!=(const QString &str) const
{
    return !(*this == str);
}

/*
    This function clears the HbString by freeing the allocated memory.
*/
void HbString::clear()
{
    // No need to clear null string
    if (mDataOffset == shared_null_offset) {
        return;
    }

    detach(0); // This will update the new mDataOffset.
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);

    if (data->mStartOffset != -1) {
        GET_MEMORY_MANAGER(mMemoryType)
        RETURN_IF_READONLY(manager);

        manager->free(data->mStartOffset);
        data->mStartOffset = -1;
        data->mLength = 0;
        data->mCapacity = 0;
    }
}

/*
    This function removes the last 'n' characters from the HbString.
*/
void HbString::chop(int n)
{
    GET_MEMORY_MANAGER(mMemoryType)
    RETURN_IF_READONLY(manager);

    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);

    detach(data->mLength);
    HbStringData* newData = 0;
    newData = getStringData(mMemoryType, mDataOffset, mShared);

    if (n > 0) {
        if(newData->mStartOffset != -1 && newData->mLength > n){
            newData->mLength -= n;
        } else {
            clear();
        }
    }
}

/*
    This function removes the 'n' no. of characters from the specified 
    position from the HbString.
*/
HbString& HbString::remove(int position, int n)
{
    GET_MEMORY_MANAGER(mMemoryType)
    RETURN_OBJECT_IF_READONLY(manager, *this)
    
    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);

    detach(data->mLength);

    HbStringData* newData = getStringData(mMemoryType, mDataOffset, mShared);
    QChar *ptr = 0;
    ptr = getAddress<QChar>(mMemoryType, newData->mStartOffset, mShared);
    int i = position;
    if(ptr){
        if(n > (newData->mLength - position)){
            n = newData->mLength - position;
            chop(n);
        } else{
            while (i <= (position + n)) {
                *(ptr + i) = *(ptr + i + n);
                i++;
            }
            newData->mLength -= n;
        }
    }
    return *this;
}

/*
    Checks whether the HbString is empty and returns the boolean value.
*/
bool HbString::isEmpty() const
{ 
    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);
    return (data->mLength == 0)? true:false;
}

/*
    Checks whether the HbString starts with the specified character and returns 
    the boolean value. Also considers the case sensitivity based on the argument
    passed to it.
*/
bool HbString::startsWith ( const QLatin1Char &s, Qt::CaseSensitivity cs ) const 
{
    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);

    if (data->mStartOffset == -1) {
        return false;
    }

    char* ptr = getAddress<char>(mMemoryType, data->mStartOffset, mShared);
    char ch = s.toLatin1();

    if(cs == Qt::CaseSensitive){
        return (*ptr == ch);
    }
    else{
        return QChar(*ptr).toLower() == QChar(ch).toLower();
    }
}

/*
    Prepends the passed QString to the existing HbString
*/
HbString& HbString::prepend(const QString &str)
{
    GET_MEMORY_MANAGER(mMemoryType)
    RETURN_OBJECT_IF_READONLY(manager, *this)
   
    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);

    int size = data->mLength + str.length();

    detach(size);
    HbStringData* newData = 0;
    newData = getStringData(mMemoryType, mDataOffset, mShared);

    if(size > newData->mCapacity ) {
        newData->mStartOffset = manager->realloc(newData->mStartOffset, size*sizeof(QChar));
        newData->mCapacity = size;
    }

    if(newData->mStartOffset != -1) {
        char *dest = getAddress<char>(mMemoryType, newData->mStartOffset, mShared);
        memmove(dest + str.length() * sizeof(QChar), dest, newData->mLength*sizeof(QChar));
        memcpy(dest, str.constData(), str.length() * sizeof(QChar));
        newData->mLength = size;
    }
    return *this;
}

/*
    Appends the passed QString to the existing HbString
*/
HbString& HbString::append(const QString &str) 
{
    GET_MEMORY_MANAGER(mMemoryType)
    RETURN_OBJECT_IF_READONLY(manager, *this)
    
    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);

    int size = data->mLength + str.length();

    detach(size);

    HbStringData* newData = 0;
    newData = getStringData(mMemoryType, mDataOffset, mShared);

    if(size > newData->mCapacity ) {
        newData->mStartOffset = manager->realloc(newData->mStartOffset, size*sizeof(QChar));
        newData->mCapacity = size;
    }

    if(newData->mStartOffset != -1) {
        char *dest = getAddress<char>(mMemoryType, newData->mStartOffset, mShared);
        memcpy(dest + newData->mLength*sizeof(QChar), str.constData(), str.length()*sizeof(QChar));
        newData->mLength = size;
    }
    return *this;
}

/*
    Converts the HbString into lowercase and returns the converted copy of 
    the HbString
*/
HbString HbString::toLower() const
{
    GET_MEMORY_MANAGER(mMemoryType)
    HbStringData* data = 0;
    data = getStringData(mMemoryType, mDataOffset, mShared);

    // If the string is empty, a copy of it can be returned directly.
    if (!data->mLength) {
        return *this;
    }

    QChar *src = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
    
    // check whether source memory type is writable if yes, create temporary HbString using same
    // memory manager else use Heap memory
    HbMemoryManager::MemoryType copyMemoryType = HbMemoryManager::HeapMemory;
    if (manager->isWritable()) {
        copyMemoryType = mMemoryType;
    }

    HbString copy(copyMemoryType);
    // Detach allocates data with new capacity
    copy.detach(data->mLength);
    HbStringData *newData = getStringData(copy.mMemoryType, copy.mDataOffset, copy.mShared);

	QChar *dest = getAddress<QChar>(copyMemoryType, newData->mStartOffset, copy.mShared);

    int count = data->mLength;
    for (int i = 0; i<count; ++i) {
        *dest = src->toLower();
        dest++;
        src++;
    }

    // Update string length after data copy
    newData->mLength = count;
    return copy;
}

/*
    Returns QByteArray keeping latin representation of string
*/
QByteArray HbString::toLatin1 () const
{
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    QChar * ptr = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
    return QString::fromRawData(ptr, data->mLength).toLatin1();
}

/*
    Returns const pointer to string data
*/
const QChar *HbString::constData() const
{
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    return getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
}

/*
    Returns length of string
*/
int HbString::length() const
{
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    return data->mLength;
}

/*
    Test whether string contains character ch. Check is case sensitive.
*/
bool HbString::contains(const QChar &ch) const
{
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    if (data->mLength) {
        QChar *stringData = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);
        int index = 0;
        while (index < data->mLength) {
            if (*(stringData + index++) == ch) {
                return true;
            }  
        }
    }
    return false;
}

/*
    Compare the string with other QLatin1String. Comparison is Check is case sensitive.
*/
int HbString::compare(const QLatin1String &other)const
{
    HbStringData* data = getStringData(mMemoryType, mDataOffset, mShared);
    QChar *stringData = getAddress<QChar>(mMemoryType, data->mStartOffset, mShared);

    return compare_helper(stringData, data->mLength, other);
}

/*
    Reads a HbString from the QDataStream
*/
QDataStream& operator>>(QDataStream &in, HbString &str)
{
    quint32 length = 0;
    in >> length;
        
    // If stream had an empty string, clear target string.
    if (!length) {
        str.clear();
    } else {
        // Detach target string and allocate its buffer to the new length
        str.detach(length);
        GET_MEMORY_MANAGER(str.mMemoryType)
        RETURN_OBJECT_IF_READONLY(manager, in)
        HbStringData* data = getStringData(str.mMemoryType, str.mDataOffset);
        char *dest = getAddress<char>(str.mMemoryType, data->mStartOffset, str.mShared);
        in.readRawData(dest, (uint)length * sizeof(QChar));
        data->mLength = length;
    }

    return in;
}

/*
    Writes the HbString to the QDataStream
*/
QDataStream& operator<<(QDataStream &out, const HbString &str)
{
    HbStringData* data = getStringData(str.mMemoryType, str.mDataOffset);
    out << quint32(data->mLength);
    if (data->mLength > 0) {
        const char *contents = getAddress<char>(str.mMemoryType, data->mStartOffset, str.mShared);
        out.writeRawData(contents, (uint)data->mLength * sizeof(QChar));
    }
    return out;
}
