/****************************************************************************
** $Id: qsstring_object.cpp  beta1   edited Dec 17 18:51 $
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "qsstring_object.h"
#include "qsregexp_object.h"
#include "qsoperations.h"
#include "qstypes.h"
#include "qsclass.h"
#include "qsenv.h"
#include <qregexp.h>

using namespace QS;

QSStringClass::QSStringClass( QSClass *b )
    : QSClass( b, AttributeFinal )
{
}

void QSStringClass::init()
{
    // custom property
    addMember( "length", QSMember( QSMember::Custom, 0,
				   AttributeNonWritable ) );

    // static function
    addMember( "fromCharCode", QSMember( &fromCharCode, AttributeStatic ) );

    // member functions
    addMember( "toString", QSMember( &QSStringClass::toStringScript ) );
    addMember( "valueOf", QSMember( &QSStringClass::toStringScript ) );
    addMember( "charAt", QSMember( &charAt ) );
    addMember( "charCodeAt", QSMember( &charCodeAt ) );
    addMember( "indexOf", QSMember( &indexOf ) );
    addMember( "lastIndexOf", QSMember( &lastIndexOf ) );
    addMember( "match", QSMember( &match ) );
    addMember( "search", QSMember( &search ) );
    addMember( "replace", QSMember( &replace ) );
    addMember( "split", QSMember( &split ) );
    addMember( "substr", QSMember( &substr ) );
    addMember( "substring", QSMember( &substring ) );
    addMember( "toLowerCase", QSMember( &toLowerCase ) );
    addMember( "lower", QSMember( &toLowerCase ) );
    addMember( "toUpperCase", QSMember( &toUpperCase ) );
    addMember( "upper", QSMember( &toUpperCase ) );
    addMember( "isEmpty", QSMember( &isEmpty ) );
    addMember( "left", QSMember( &left ) );
    addMember( "mid", QSMember( &mid ) );
    addMember( "right", QSMember( &right ) );
    addMember( "find", QSMember( &find ) );
    addMember( "findRev", QSMember( &findRev ) );
    addMember( "startsWith", QSMember( &startsWith ) );
    addMember( "endsWith", QSMember( &endsWith ) );
}

void QSStringClass::ref( QSObject *o ) const
{
    o->val.str->ref();
}

void QSStringClass::deref( QSObject *o ) const
{
    o->val.str->deref();
    if ( o->val.str->count == 0 )
	delete o->val.str;
}

QSObject QSStringClass::fetchValue( const QSObject *objPtr,
				    const QSMember &mem ) const
{
    if ( mem.type() == QSMember::Custom )
	if ( mem.idx == 0 ) {
	    return QSNumber( objPtr->sVal().length() );
	} else {
	    qFatal( "QSStringClass::fetchValue: unhandled member" );
	    return QSUndefined();
	}
    else
	return QSClass::fetchValue( objPtr, mem );
}

bool QSStringClass::toBoolean( const QSObject *obj ) const
{
    return !obj->sVal().isEmpty();
}

double QSStringClass::toNumber( const QSObject *obj ) const
{
    return QSString::toDouble( obj->sVal() );
}

QString QSStringClass::toString( const QSObject *obj ) const
{
    return obj->sVal();
}

QSObject QSStringClass::toPrimitive( const QSObject *obj, Type ) const
{
    return *obj;
}

QSObject QSStringClass::construct( const QSList &args ) const
{
    QString s = args.isEmpty() ? QString( "" ) : args[0].toString();

    return QSString( s );
}

/*!
  \reimp
*/
QSObject QSStringClass::cast( const QSList &args ) const
{
    return construct( args );
}

int QSStringClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    Q_ASSERT( a.isA( this ) );
    if ( b.isString() )
	return a.sVal() == b.sVal();
    else if ( b.isNumber() )
	return a.sVal() == b.toString();
    else if ( !b.isPrimitive() )
	return isEqual( a, b.toPrimitive() );
    else
	return -1;

}

// String.fromCharCode()
QSObject QSStringClass::fromCharCode( const QSList &args )
{
    QString s;
    if ( args.size() ) {
	s.setLength( args.size() );
	QSListIterator it = args.begin();
	int i = 0;
	while ( it != args.end() ) {
	    ushort u = it->toUInt16();
	    s[i] = QChar( u );
	    it++;
	    i++;
	}
    } else {
	s = "";
    }

    return QSString( s );
}

QSObject QSStringClass::toStringScript( QSObject *o, const QSList & )
{
    return QSString( o->sVal() );
}

QSObject QSStringClass::charAt( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    int pos = args[0].toInteger();
    if ( pos < 0 || pos >= (int)s.length() )
	s = "";
    else
	s = s.mid( pos, 1 );
    return QSString( s );
}

QSObject QSStringClass::charCodeAt( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    int pos = args[0].toInteger();
    double d;
    if ( pos < 0 || pos >= (int)s.length())
	d = NaN;
    else
	d = s[pos].unicode();
    return QSNumber( d );
}

QSObject QSStringClass::indexOf( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    QString s2 = args[0].toString();
    int pos = args.size() >= 2 ? args[1].toInteger() : 0;
    if ( pos < 0 )
	pos = 0;
    return QSNumber( s.find( s2, pos ) );
}

QSObject QSStringClass::lastIndexOf( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    QString s2 = args[0].toString();
    int pos = args.size() >= 2 ? args[1].toInteger() : s.length();
    return QSNumber( s.findRev( s2, pos ) );
}

QSObject QSStringClass::match( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    if ( args[0].typeName() == "RegExp" ) {
	QString s2 = args[0].get( "source" ).toString();
	QRegExp reg( s2 );
	reg.search( s );
	o->env()->regexpClass()->lastCaptures = reg.capturedTexts();
	QString mstr = reg.cap();
	if ( mstr.isNull() )
	    return QSNull();
	// ### return an array, with the matches
	return QSString( mstr );
    } else {
	return QSUndefined();
    }
}

QSObject QSStringClass::search( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    if ( args[0].typeName() == "RegExp" ) {
	QString s2 = args[0].get("source").toString();
	QRegExp reg( s2 );
	int pos = reg.search( s );
	o->env()->regexpClass()->lastCaptures = reg.capturedTexts();
	return QSNumber( pos );
    } else {
	return QSUndefined();
    }
}

QSObject QSStringClass::replace( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    int pos, len;
    // ### only most common case
    if ( args[0].typeName() == "RegExp" ) {
	QString s2 = args[0].get("source").toString();
	QRegExp reg( s2 );
	pos = reg.search( s );
	len = reg.matchedLength();
    } else {
	QString s2 = args[0].toString();
	pos = s.find( s2 );
	len = s.length();
    }
    if ( pos == -1 )
	return QSString( s );
    QString r = s.mid( 0, pos) + args[1].toString() + s.mid( pos + len );
    return QSString( r );
}

QSObject QSStringClass::split( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    QSArray result;
    int i = 0, p0 = 0;
    double d = args.size() >= 2 ? args[1].toInteger() : -1; // optional max
    if ( args[0].isA( "RegExp" ) ) {
	QRegExp reg( args[0].get("source").toString() );
	if ( s.isEmpty() && reg.search( s, 0 ) >= 0) {
	    // empty string matched by regexp -> empty array
	    result.put( "length", QSNumber( 0 ) );
	    return result;
	}
	int pos = 0;
	for ( ;; ) {
	    /* TODO: back references */
	    int mpos = reg.search( s, pos );
	    if ( mpos < 0 )
		break;
	    QString mstr = reg.cap( 0 );
	    pos = mpos + ( mstr.isEmpty() ? 1 : mstr.length() );
	    if ( mpos != p0 || !mstr.isEmpty() ) {
		result.put( QString::number( i ),
			    QSString( s.mid( p0, mpos-p0 ) ));
		p0 = mpos + mstr.length();
		i++;
	    }
	}
    } else if ( args.size() >= 1 ) {
	QString u2 = args[0].toString();
	if ( u2.isEmpty() ) {
	    if ( s.isEmpty() ) {
		// empty separator matches empty string -> empty array
		result.put( "length", QSNumber( 0 ), DontDelete|ReadOnly|DontEnum );
		return result;
	    } else {
		while ( i != d && i < (int)s.length() )
		    result.put( QString::number( i++ ),
				QSString( s.mid( p0++, 1 ) ) );
	    }
	} else {
	    int pos;
	    while (i != d && (pos = s.find(u2, p0)) >= 0) {
		result.put( QString::number( i ),
			    QSString( s.mid( p0, pos-p0 ) ) );
		p0 = pos + u2.length();
		i++;
	    }
	}
    }
    // add remaining string, if any
    if ( i != d && (p0 < (int)s.length() || i == 0) )
	result.put( QSString::from( i++ ), QSString( s.mid( p0 ) ) );
    result.put( "length", i );
    return result;
}

QSObject QSStringClass::substr( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    uint len = s.length();
    int n = args[0].toInteger();
    int m = args[1].toInteger();
    double d, d2;
    if ( n >= 0 )
	d = n;
    else
	d = QMAX(len + n, 0);
    if ( args.size() < 2 )
	d2 = len - d;
    else
	d2 = min( QMAX( m, 0 ), len - d );
    return QSString( s.mid( (int)d, (int)d2 ) );
}

QSObject QSStringClass::substring( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    uint len = s.length();
    int n = args[0].toInteger();
    int m = args[1].toInteger();
    double d = QMIN( QMAX( n, 0 ), (int)len );
    double d2;
    if ( args.size() < 2 ) {
	d2 = len - d;
    } else {
	d2 = min( max( m, 0), len );
	d2 = max(d2-d, 0);
    }
    return QSString( s.mid((int)d, (int)d2 ) );
}

QSObject QSStringClass::toLowerCase( QSObject *o, const QSList & )
{
    return QSString( o->sVal().lower() );
}

QSObject QSStringClass::toUpperCase( QSObject *o, const QSList & )
{
    return QSString( o->sVal().upper() );
}

QSObject QSStringClass::isEmpty( QSObject *o, const QSList & )
{
    return QSBoolean( o->sVal().isEmpty() );
}

QSObject QSStringClass::left( QSObject *o, const QSList &args )
{
    return QSString( o->sVal().left( args[0].toInteger() ) );
}

QSObject QSStringClass::mid( QSObject *o, const QSList &args )
{
    return QSString( o->sVal().mid( args[0].toInteger(),
				    args[1].toInteger() ) );
}

QSObject QSStringClass::right( QSObject *o, const QSList &args )
{
    return QSString( o->sVal().right( args[0].toInteger() ) );
}

QSObject QSStringClass::find( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    // ### regexp overload
    int idx = args.size() >= 2 ? args[1].toInteger() : 0;
    bool cs = args.size() >= 3 ? args[2].toBoolean() : TRUE;
    return QSNumber( s.find( args[0].toString(), idx, cs ) );
}

QSObject QSStringClass::findRev( QSObject *o, const QSList &args )
{
    QString s = o->sVal();
    // ### regexp overload
    int idx = args.size() >= 2 ? args[1].toInteger() : -1;
    bool cs = args.size() >= 3 ? args[2].toBoolean() : TRUE;
    return QSNumber( s.findRev( args[0].toString(), idx, cs ) );
}

QSObject QSStringClass::startsWith( QSObject *o, const QSList &args )
{
    return QSBoolean( o->sVal().startsWith( args[0].toString() ) );
}

QSObject QSStringClass::endsWith( QSObject *o, const QSList &args )
{
    return QSBoolean( o->sVal().endsWith( args[0].toString() ) );
}
