package org.luaj.vm2;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import junit.framework.TestCase;

import org.luaj.vm2.libs.jse.JsePlatform;

public class StringTest extends TestCase {

	protected void setUp() throws Exception {
		JsePlatform.standardGlobals();
	}

	public void testToInputStream() throws IOException {
		LuaString str = LuaString.valueOf("Hello");
		
		InputStream is = str.toInputStream();
		
		assertEquals( 'H', is.read() );
		assertEquals( 'e', is.read() );
		assertEquals( 2, is.skip( 2 ) );
		assertEquals( 'o', is.read() );
		assertEquals( -1, is.read() );
		
		assertTrue( is.markSupported() );
		
		is.reset();
		
		assertEquals( 'H', is.read() );
		is.mark( 4 );
		
		assertEquals( 'e', is.read() );
		is.reset();
		assertEquals( 'e', is.read() );
		
		LuaString substr = str.substring( 1, 4 );
		assertEquals( 3, substr.length() );
		
		is.close();
		is = substr.toInputStream();
		
		assertEquals( 'e', is.read() );
		assertEquals( 'l', is.read() );
		assertEquals( 'l', is.read() );
		assertEquals( -1, is.read() );
		
		is = substr.toInputStream();
		is.reset();
		
		assertEquals( 'e', is.read() );
	}
	
	
	private static final String userFriendly( String s ) {
		StringBuffer sb = new StringBuffer();
		for ( int i=0, n=s.length(); i<n; i++ ) {
			int c = s.charAt(i);
			if ( c < ' ' || c >= 0x80 ) { 
				sb.append( "\\u"+Integer.toHexString(0x10000+c).substring(1) );
			} else {
				sb.append( (char) c );
			}
		}
		return sb.toString();
	}

	public void testUtf820482051() throws UnsupportedEncodingException {
		int i = 2048;
		char[] c = { (char) (i+0), (char) (i+1), (char) (i+2), (char) (i+3) };
		String before = new String(c)+" "+i+"-"+(i+4);
		LuaString ls = LuaString.valueOf(before);
		String after = ls.tojstring();
		assertEquals( userFriendly( before ), userFriendly( after ) );
	}
	
	public void testUtf8() {		
		for ( int i=4; i<0xffff; i+=4 ) {
			char[] c = { (char) (i+0), (char) (i+1), (char) (i+2), (char) (i+3) };
			String before = new String(c)+" "+i+"-"+(i+4);
			LuaString ls = LuaString.valueOf(before);
			String after = ls.tojstring();
			assertEquals( userFriendly( before ), userFriendly( after ) );
		}
		char[] c = { (char) (1), (char) (2), (char) (3) };
		String before = new String(c)+" 1-3";
		LuaString ls = LuaString.valueOf(before);
		String after = ls.tojstring();
		assertEquals( userFriendly( before ), userFriendly( after ) );
	}

	public void testSpotCheckUtf8() throws UnsupportedEncodingException {
		byte[] bytes = {(byte)194,(byte)160,(byte)194,(byte)161,(byte)194,(byte)162,(byte)194,(byte)163,(byte)194,(byte)164};
		String expected = new String(bytes, "UTF8");
		String actual = LuaString.valueOf(bytes).tojstring();
		char[] d = actual.toCharArray();
		assertEquals(160, d[0]);
		assertEquals(161, d[1]);
		assertEquals(162, d[2]);
		assertEquals(163, d[3]);
		assertEquals(164, d[4]);
		assertEquals(expected, actual);
	}
	
	public void testNullTerminated() {		
		char[] c = { 'a', 'b', 'c', '\0', 'd', 'e', 'f' };
		String before = new String(c);
		LuaString ls = LuaString.valueOf(before);
		String after = ls.tojstring();
		assertEquals( userFriendly( "abc\0def" ), userFriendly( after ) );
	}

	public void testRecentStringsCacheDifferentHashcodes() {
		final byte[] abc = {'a', 'b', 'c' };
		final byte[] xyz = {'x', 'y', 'z' };
		final LuaString abc1 = LuaString.valueOf(abc);
		final LuaString xyz1 = LuaString.valueOf(xyz);
		final LuaString abc2 = LuaString.valueOf(abc);
		final LuaString xyz2 = LuaString.valueOf(xyz);
		final int mod = LuaString.RECENT_STRINGS_CACHE_SIZE;
		assertTrue(abc1.hashCode() % mod != xyz1.hashCode() % mod);
		assertSame(abc1, abc2);
		assertSame(xyz1, xyz2);
	}

	public void testRecentStringsCacheHashCollisionCacheHit() {
		final byte[] abc = {'a', 'b', 'c' };
		final byte[] lyz = {'l', 'y', 'z' };  // chosen to have hash collision with 'abc'
		final LuaString abc1 = LuaString.valueOf(abc);
		final LuaString abc2 = LuaString.valueOf(abc); // in cache: 'abc'
		final LuaString lyz1 = LuaString.valueOf(lyz);
		final LuaString lyz2 = LuaString.valueOf(lyz); // in cache: 'lyz'
		final int mod = LuaString.RECENT_STRINGS_CACHE_SIZE;
		assertEquals(abc1.hashCode() % mod, lyz1.hashCode() % mod);
		assertNotSame(abc1, lyz1);
		assertFalse(abc1.equals(lyz1));
		assertSame(abc1, abc2);
		assertSame(lyz1, lyz2);
	}

	public void testRecentStringsCacheHashCollisionCacheMiss() {
		final byte[] abc = {'a', 'b', 'c' };
		final byte[] lyz = {'l', 'y', 'z' };  // chosen to have hash collision with 'abc'
		final LuaString abc1 = LuaString.valueOf(abc);
		final LuaString lyz1 = LuaString.valueOf(lyz); // in cache: 'abc'
		final LuaString abc2 = LuaString.valueOf(abc); // in cache: 'lyz'
		final LuaString lyz2 = LuaString.valueOf(lyz); // in cache: 'abc'
		final int mod = LuaString.RECENT_STRINGS_CACHE_SIZE;
		assertEquals(abc1.hashCode() % mod, lyz1.hashCode() % mod);
		assertNotSame(abc1, lyz1);
		assertFalse(abc1.equals(lyz1));
		assertNotSame(abc1, abc2);
		assertNotSame(lyz1, lyz2);
	}

	public void testRecentStringsLongStrings() {
		byte[] abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes();
		assertTrue(abc.length > LuaString.RECENT_STRINGS_MAX_LENGTH);
		LuaString abc1 = LuaString.valueOf(abc);
		LuaString abc2 = LuaString.valueOf(abc);
		assertNotSame(abc1, abc2);
	}

	public void testRecentStringsUsingJavaStrings() {
		final String abc = "abc";
		final String lyz = "lyz";  // chosen to have hash collision with 'abc'
		final String xyz = "xyz";

		final LuaString abc1 = LuaString.valueOf(abc);
		final LuaString abc2 = LuaString.valueOf(abc);
		final LuaString lyz1 = LuaString.valueOf(lyz);
		final LuaString lyz2 = LuaString.valueOf(lyz);
		final LuaString xyz1 = LuaString.valueOf(xyz);
		final LuaString xyz2 = LuaString.valueOf(xyz);
		final int mod = LuaString.RECENT_STRINGS_CACHE_SIZE;
		assertEquals(abc1.hashCode() % mod, lyz1.hashCode() % mod);
		assertFalse(abc1.hashCode() % mod == xyz1.hashCode() % mod);
		assertSame(abc1, abc2);
		assertSame(lyz1, lyz2);
		assertSame(xyz1, xyz2);

		final LuaString abc3 = LuaString.valueOf(abc);
		final LuaString lyz3 = LuaString.valueOf(lyz);
		final LuaString xyz3 = LuaString.valueOf(xyz);

		final LuaString abc4 = LuaString.valueOf(abc);
		final LuaString lyz4 = LuaString.valueOf(lyz);
		final LuaString xyz4 = LuaString.valueOf(xyz);
		assertNotSame(abc3, abc4);  // because of hash collision
		assertNotSame(lyz3, lyz4);  // because of hash collision
		assertSame(xyz3, xyz4);  // because hashes do not collide
	}
	
	public void testLongSubstringGetsOldBacking() {
		LuaString src = LuaString.valueOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
		LuaString sub1 = src.substring(10, 40);
		assertSame(src.m_bytes, sub1.m_bytes);
		assertEquals(sub1.m_offset, 10);
		assertEquals(sub1.m_length, 30);
	}
	
	public void testShortSubstringGetsNewBacking() {
		LuaString src = LuaString.valueOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
		LuaString sub1 = src.substring(10, 20);
		LuaString sub2 = src.substring(10, 20);
		assertEquals(sub1.m_offset, 0);
		assertEquals(sub1.m_length, 10);
		assertSame(sub1, sub2);
		assertFalse(src.m_bytes == sub1.m_bytes);
	}
	
	public void testShortSubstringOfVeryLongStringGetsNewBacking() {
		LuaString src = LuaString.valueOf(
				"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +
				"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" );
		LuaString sub1 = src.substring(10, 50);
		LuaString sub2 = src.substring(10, 50);
		assertEquals(sub1.m_offset, 0);
		assertEquals(sub1.m_length, 40);
		assertFalse(sub1 == sub2);
		assertFalse(src.m_bytes == sub1.m_bytes);
	}

	public void testIndexOfByteInSubstring() {
		LuaString str = LuaString.valueOf("abcdef:ghi");
		LuaString sub = str.substring(2, 10);
		assertEquals(10, str.m_length);
		assertEquals(8, sub.m_length);
		assertEquals(0, str.m_offset);
		assertEquals(2, sub.m_offset);
		
		assertEquals(6, str.indexOf((byte) ':', 0));
		assertEquals(6, str.indexOf((byte) ':', 2));
		assertEquals(6, str.indexOf((byte) ':', 6));
		assertEquals(-1, str.indexOf((byte) ':', 7));
		assertEquals(-1, str.indexOf((byte) ':', 9));
		assertEquals(9, str.indexOf((byte) 'i', 0));
		assertEquals(9, str.indexOf((byte) 'i', 2));
		assertEquals(9, str.indexOf((byte) 'i', 9));
		assertEquals(-1, str.indexOf((byte) 'z', 0));
		assertEquals(-1, str.indexOf((byte) 'z', 2));
		assertEquals(-1, str.indexOf((byte) 'z', 9));

		assertEquals(4, sub.indexOf((byte) ':', 0));
		assertEquals(4, sub.indexOf((byte) ':', 2));
		assertEquals(4, sub.indexOf((byte) ':', 4));
		assertEquals(-1, sub.indexOf((byte) ':', 5));
		assertEquals(-1, sub.indexOf((byte) ':', 7));
		assertEquals(7, sub.indexOf((byte) 'i', 0));
		assertEquals(7, sub.indexOf((byte) 'i', 2));
		assertEquals(7, sub.indexOf((byte) 'i', 7));
		assertEquals(-1, sub.indexOf((byte) 'z', 0));
		assertEquals(-1, sub.indexOf((byte) 'z', 2));
		assertEquals(-1, sub.indexOf((byte) 'z', 7));
	}

	public void testIndexOfPatternInSubstring() {
		LuaString str = LuaString.valueOf("abcdef:ghi");
		LuaString sub = str.substring(2, 10);
		assertEquals(10, str.m_length);
		assertEquals(8, sub.m_length);
		assertEquals(0, str.m_offset);
		assertEquals(2, sub.m_offset);

		LuaString pat = LuaString.valueOf(":");
		LuaString i = LuaString.valueOf("i");
		LuaString xyz = LuaString.valueOf("xyz");
		
		assertEquals(6, str.indexOf(pat, 0));
		assertEquals(6, str.indexOf(pat, 2));
		assertEquals(6, str.indexOf(pat, 6));
		assertEquals(-1, str.indexOf(pat, 7));
		assertEquals(-1, str.indexOf(pat, 9));
		assertEquals(9, str.indexOf(i, 0));
		assertEquals(9, str.indexOf(i, 2));
		assertEquals(9, str.indexOf(i, 9));
		assertEquals(-1, str.indexOf(xyz, 0));
		assertEquals(-1, str.indexOf(xyz, 2));
		assertEquals(-1, str.indexOf(xyz, 9));

		assertEquals(4, sub.indexOf(pat, 0));
		assertEquals(4, sub.indexOf(pat, 2));
		assertEquals(4, sub.indexOf(pat, 4));
		assertEquals(-1, sub.indexOf(pat, 5));
		assertEquals(-1, sub.indexOf(pat, 7));
		assertEquals(7, sub.indexOf(i, 0));
		assertEquals(7, sub.indexOf(i, 2));
		assertEquals(7, sub.indexOf(i, 7));
		assertEquals(-1, sub.indexOf(xyz, 0));
		assertEquals(-1, sub.indexOf(xyz, 2));
		assertEquals(-1, sub.indexOf(xyz, 7));
	}

	public void testLastIndexOfPatternInSubstring() {
		LuaString str = LuaString.valueOf("abcdef:ghi");
		LuaString sub = str.substring(2, 10);
		assertEquals(10, str.m_length);
		assertEquals(8, sub.m_length);
		assertEquals(0, str.m_offset);
		assertEquals(2, sub.m_offset);

		LuaString pat = LuaString.valueOf(":");
		LuaString i = LuaString.valueOf("i");
		LuaString xyz = LuaString.valueOf("xyz");
		
		assertEquals(6, str.lastIndexOf(pat));
		assertEquals(9, str.lastIndexOf(i));
		assertEquals(-1, str.lastIndexOf(xyz));

		assertEquals(4, sub.lastIndexOf(pat));
		assertEquals(7, sub.lastIndexOf(i));
		assertEquals(-1, sub.lastIndexOf(xyz));
	}

	public void testIndexOfAnyInSubstring() {
		LuaString str = LuaString.valueOf("abcdef:ghi");
		LuaString sub = str.substring(2, 10);
		assertEquals(10, str.m_length);
		assertEquals(8, sub.m_length);
		assertEquals(0, str.m_offset);
		assertEquals(2, sub.m_offset);

		LuaString ghi = LuaString.valueOf("ghi");
		LuaString ihg = LuaString.valueOf("ihg");
		LuaString ijk = LuaString.valueOf("ijk");
		LuaString kji= LuaString.valueOf("kji");
		LuaString xyz = LuaString.valueOf("xyz");
		LuaString ABCdEFGHIJKL = LuaString.valueOf("ABCdEFGHIJKL");
		LuaString EFGHIJKL = ABCdEFGHIJKL.substring(4, 12);
		LuaString CdEFGHIJ = ABCdEFGHIJKL.substring(2, 10);
		assertEquals(4, EFGHIJKL.m_offset);
		assertEquals(2, CdEFGHIJ.m_offset);
		
		assertEquals(7, str.indexOfAny(ghi));
		assertEquals(7, str.indexOfAny(ihg));
		assertEquals(9, str.indexOfAny(ijk));
		assertEquals(9, str.indexOfAny(kji));
		assertEquals(-1, str.indexOfAny(xyz));
		assertEquals(3, str.indexOfAny(CdEFGHIJ));
		assertEquals(-1, str.indexOfAny(EFGHIJKL));

		assertEquals(5, sub.indexOfAny(ghi));
		assertEquals(5, sub.indexOfAny(ihg));
		assertEquals(7, sub.indexOfAny(ijk));
		assertEquals(7, sub.indexOfAny(kji));
		assertEquals(-1, sub.indexOfAny(xyz));
		assertEquals(1, sub.indexOfAny(CdEFGHIJ));
		assertEquals(-1, sub.indexOfAny(EFGHIJKL));
	}
	
	public void testMatchShortPatterns() {
		LuaValue[] args = { LuaString.valueOf("%bxy") };
		LuaString empty = LuaString.valueOf("");

		LuaString a = LuaString.valueOf("a");
		LuaString ax = LuaString.valueOf("ax");
		LuaString axb = LuaString.valueOf("axb");
		LuaString axby = LuaString.valueOf("axby");
		LuaString xbya = LuaString.valueOf("xbya");
		LuaString bya = LuaString.valueOf("bya");
		LuaString xby = LuaString.valueOf("xby");
		LuaString axbya = LuaString.valueOf("axbya");
		LuaValue nil = LuaValue.NIL;
		
		assertEquals(nil, empty.invokemethod("match", args));
		assertEquals(nil, a.invokemethod("match", args));
		assertEquals(nil, ax.invokemethod("match", args));
		assertEquals(nil, axb.invokemethod("match", args));
		assertEquals(xby, axby.invokemethod("match", args));
		assertEquals(xby, xbya.invokemethod("match", args));
		assertEquals(nil, bya.invokemethod("match", args));
		assertEquals(xby, xby.invokemethod("match", args));
		assertEquals(xby, axbya.invokemethod("match", args));
		assertEquals(xby, axbya.substring(0,4).invokemethod("match", args));
		assertEquals(nil, axbya.substring(0,3).invokemethod("match", args));
		assertEquals(xby, axbya.substring(1,5).invokemethod("match", args));
		assertEquals(nil, axbya.substring(2,5).invokemethod("match", args));
	}
}
