PHP_FUNCTION(grapheme_extract)
{
	unsigned char *str, *pstr;
	UChar *ustr;
	int str_len, ustr_len;
	long size; /* maximum number of grapheme clusters, bytes, or characters (based on extract_type) to return */
	long lstart = 0; /* starting position in str in bytes */
	int32_t start = 0;
	long extract_type = GRAPHEME_EXTRACT_TYPE_COUNT;
	UErrorCode status;
	unsigned char u_break_iterator_buffer[U_BRK_SAFECLONE_BUFFERSIZE];
	UBreakIterator* bi = NULL;
	int ret_pos;
	zval *next = NULL; /* return offset of next part of the string */

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|llz", (char **)&str, &str_len, &size, &extract_type, &lstart, &next) == FAILURE) {

		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
			 "grapheme_extract: unable to parse input param", 0 TSRMLS_CC );

		RETURN_FALSE;
	}

	if ( NULL != next ) {
		if ( !PZVAL_IS_REF(next) ) {
			intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
				 "grapheme_extract: 'next' was not passed by reference", 0 TSRMLS_CC );

			RETURN_FALSE;
		}
		else {
			/* initialize next */
			zval_dtor(next);
            ZVAL_LONG(next, lstart);
		}
	}

	if ( extract_type < GRAPHEME_EXTRACT_TYPE_MIN || extract_type > GRAPHEME_EXTRACT_TYPE_MAX ) {

		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
			 "grapheme_extract: unknown extract type param", 0 TSRMLS_CC );

		RETURN_FALSE;
	}

	if ( lstart > INT32_MAX || lstart < 0 || lstart >= str_len ) {
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_extract: start not contained in string", 0 TSRMLS_CC );
		RETURN_FALSE;
	}

	if ( size > INT32_MAX || size < 0) {
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_extract: size is invalid", 0 TSRMLS_CC );
		RETURN_FALSE;
	}
	if (size == 0) {
		RETURN_EMPTY_STRING();
	}

	/* we checked that it will fit: */
	start = (int32_t) lstart;

	pstr = str + start;

	/* just in case pstr points in the middle of a character, move forward to the start of the next char */
	if ( !UTF8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) {
		unsigned char *str_end = str + str_len;

		while ( !UTF8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) {
			pstr++;
			if ( pstr >= str_end ) {
				intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
								"grapheme_extract: invalid input string", 0 TSRMLS_CC );

				RETURN_FALSE;
			}
		}
	}

	str_len -= (pstr - str);

	/* if the string is all ASCII up to size+1 - or str_len whichever is first - then we are done.
		(size + 1 because the size-th character might be the beginning of a grapheme cluster)
	 */

	if ( -1 != grapheme_ascii_check(pstr, size + 1 < str_len ? size + 1 : str_len ) ) {
        long nsize = ( size < str_len ? size : str_len );
		if ( NULL != next ) {
			ZVAL_LONG(next, start+nsize);
		}
		RETURN_STRINGL(((char *)pstr), nsize, 1);
	}

	/* convert the strings to UTF-16. */
	ustr = NULL;
	ustr_len = 0;
	status = U_ZERO_ERROR;
	intl_convert_utf8_to_utf16(&ustr, &ustr_len, (char *)pstr, str_len, &status );

	if ( U_FAILURE( status ) ) {
		/* Set global error code. */
		intl_error_set_code( NULL, status TSRMLS_CC );

		/* Set error messages. */
		intl_error_set_custom_msg( NULL, "Error converting input string to UTF-16", 0 TSRMLS_CC );

		if ( NULL != ustr )
			efree( ustr );

		RETURN_FALSE;
	}

	bi = NULL;
	status = U_ZERO_ERROR;
	bi = grapheme_get_break_iterator(u_break_iterator_buffer, &status TSRMLS_CC );

	ubrk_setText(bi, ustr, ustr_len, &status);

	/* if the caller put us in the middle of a grapheme, we can't detect it in all cases since we
		can't back up. So, we will not do anything. */

	/* now we need to find the end of the chunk the user wants us to return */

	ret_pos = (*grapheme_extract_iters[extract_type])(bi, size, pstr, str_len);

	if (ustr) {
		efree(ustr);
	}
	ubrk_close(bi);

	if ( NULL != next ) {
		ZVAL_LONG(next, start+ret_pos);
	}

	RETURN_STRINGL(((char *)pstr), ret_pos, 1);
}
PHP_FUNCTION(grapheme_extract)
{
	unsigned char *str, *pstr;
	UChar *ustr;
	int str_len, ustr_len;
	long size;  
	long lstart = 0;  
	int32_t start = 0;
	long extract_type = GRAPHEME_EXTRACT_TYPE_COUNT;
	UErrorCode status;
	unsigned char u_break_iterator_buffer[U_BRK_SAFECLONE_BUFFERSIZE];
	UBreakIterator* bi = NULL;
	int ret_pos;
	zval *next = NULL;  

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|llz", (char **)&str, &str_len, &size, &extract_type, &lstart, &next) == FAILURE) {

		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
			 "grapheme_extract: unable to parse input param", 0 TSRMLS_CC );

		RETURN_FALSE;
	}

	if ( NULL != next ) {
		if ( !PZVAL_IS_REF(next) ) {
			intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
				 "grapheme_extract: 'next' was not passed by reference", 0 TSRMLS_CC );

			RETURN_FALSE;
		}
		else {
			 
			zval_dtor(next);
            ZVAL_LONG(next, lstart);
		}
	}

	if ( extract_type < GRAPHEME_EXTRACT_TYPE_MIN || extract_type > GRAPHEME_EXTRACT_TYPE_MAX ) {

		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
			 "grapheme_extract: unknown extract type param", 0 TSRMLS_CC );

		RETURN_FALSE;
	}

	if ( lstart > INT32_MAX || lstart < 0 || lstart >= str_len ) {
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_extract: start not contained in string", 0 TSRMLS_CC );
		RETURN_FALSE;
	}

	if ( size > INT32_MAX || size < 0) {
		intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_extract: size is invalid", 0 TSRMLS_CC );
		RETURN_FALSE;
	}
	if (size == 0) {
		RETURN_EMPTY_STRING();
	}

	 
	start = (int32_t) lstart;

	pstr = str + start;

	 
	if ( !UTF8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) {
		unsigned char *str_end = str + str_len;

		while ( !UTF8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) {
			pstr++;
			if ( pstr >= str_end ) {
				intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
								"grapheme_extract: invalid input string", 0 TSRMLS_CC );

				RETURN_FALSE;
			}
		}
	}

	str_len -= (pstr - str);

	 

	if ( -1 != grapheme_ascii_check(pstr, size + 1 < str_len ? size + 1 : str_len ) ) {
        long nsize = ( size < str_len ? size : str_len );
		if ( NULL != next ) {
			ZVAL_LONG(next, start+nsize);
		}
		RETURN_STRINGL(((char *)pstr), nsize, 1);
	}

	 
	ustr = NULL;
	ustr_len = 0;
	status = U_ZERO_ERROR;
	intl_convert_utf8_to_utf16(&ustr, &ustr_len, (char *)pstr, str_len, &status );

	if ( U_FAILURE( status ) ) {
		 
		intl_error_set_code( NULL, status TSRMLS_CC );

		 
		intl_error_set_custom_msg( NULL, "Error converting input string to UTF-16", 0 TSRMLS_CC );

		if ( NULL != ustr )
			efree( ustr );

		RETURN_FALSE;
	}

	bi = NULL;
	status = U_ZERO_ERROR;
	bi = grapheme_get_break_iterator(u_break_iterator_buffer, &status TSRMLS_CC );

	ubrk_setText(bi, ustr, ustr_len, &status);

	 

	 

	ret_pos = (*grapheme_extract_iters[extract_type])(bi, size, pstr, str_len);

	if (ustr) {
		efree(ustr);
	}
	ubrk_close(bi);

	if ( NULL != next ) {
		ZVAL_LONG(next, start+ret_pos);
	}

	RETURN_STRINGL(((char *)pstr), ret_pos, 1);
}