/*
 * Copyright (c) 2008-2014 MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// JSONTest.java

package com.mongodb.util;

import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import org.bson.BSON;
import org.bson.BasicBSONObject;
import org.bson.types.BSONTimestamp;
import org.bson.types.Code;
import org.bson.types.CodeWScope;
import org.bson.types.ObjectId;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
import java.util.UUID;
import java.util.regex.Pattern;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class JSONTest extends com.mongodb.util.TestCase {

    @Test
    public void testSerializationMethods(){
        
        // basic test of each of JSON class' serialization methods
        String json = "{ \"x\" : \"basic test\"}";
        StringBuilder buf = new StringBuilder();
        Object obj = JSON.parse(json);

        assertEquals(JSON.serialize(obj), json);
    }
    
    @Test
    public void testNumbers(){
        assertEquals(JSON.serialize(JSON.parse("{'x' : 5 }")), "{ \"x\" : 5}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 5.0 }")), "{ \"x\" : 5.0}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 0 }")), "{ \"x\" : 0}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 0.0 }")), "{ \"x\" : 0.0}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 500 }")), "{ \"x\" : 500}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 500.0 }")), "{ \"x\" : 500.0}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 0.500 }")), "{ \"x\" : 0.5}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 5. }")), "{ \"x\" : 5.0}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 5.0e+1 }")), "{ \"x\" : 50.0}");
        assertEquals(JSON.serialize(JSON.parse("{'x' : 5.0E-1 }")), "{ \"x\" : 0.5}");
    }

    @Test
    public void testLongValues() {
        Long bigVal = Integer.MAX_VALUE + 1L;
        String test = String.format("{ \"x\" : %d}", bigVal);
        assertEquals(JSON.serialize(JSON.parse(test)), test);

        Long smallVal = Integer.MIN_VALUE - 1L;
        String test2 = String.format("{ \"x\" : %d}", smallVal);
        assertEquals(JSON.serialize(JSON.parse(test2)), test2);
        
        try{
        	JSON.parse("{\"ReallyBigNumber\": 10000000000000000000 }");
        	fail("JSONParseException should have been thrown");
        }catch(JSONParseException e) {
            // fall through
        }
    }

    @Test
    public void testSimple() {
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : true}")), "{ \"csdf\" : true}");
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : false}")), "{ \"csdf\" : false}");
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : null}")), "{ \"csdf\" :  null }");
    }
    
    @Test
    public void testString() {
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : \"foo\"}")), "{ \"csdf\" : \"foo\"}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : \'foo\'}")), "{ \"csdf\" : \"foo\"}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : \"a\\\"b\"}")), "{ \"csdf\" : \"a\\\"b\"}");
        assertEquals(JSON.serialize(JSON.parse("{\n\t\"id\":\"1689c12eb234c54a84ebd100\",\n}")),
                     "{ \"id\" : \"1689c12eb234c54a84ebd100\"}");
    }
    
    @Test
    public void testArray() {
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : [\"foo\"]}")), "{ \"csdf\" : [ \"foo\"]}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : [3, 5, \'foo\', null]}")), "{ \"csdf\" : [ 3 , 5 , \"foo\" ,  null ]}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : [3.0, 5.0, \'foo\', null]}")), "{ \"csdf\" : [ 3.0 , 5.0 , \"foo\" ,  null ]}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : [[],[[]],false]}")), "{ \"csdf\" : [ [ ] , [ [ ]] , false]}");
    }

    @Test
    public void testObject() {
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : {}}")), "{ \"csdf\" : { }}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : {\"foo\":\"bar\"}}")), "{ \"csdf\" : { \"foo\" : \"bar\"}}") ;
        assertEquals(JSON.serialize(JSON.parse("{'csdf' : {\'hi\':{\'hi\':[{}]}}}")), "{ \"csdf\" : { \"hi\" : { \"hi\" : [ { }]}}}");
    }

    @Test
    public void testMulti() {
        assertEquals(JSON.serialize(JSON.parse("{\'\' : \"\", \"34\" : -52.5}")), "{ \"\" : \"\" , \"34\" : -52.5}") ;
    }    

    @Test
    public void testUnicode() {
        assertEquals(JSON.serialize(JSON.parse("{'x' : \"hi\\u0020\"}")),"{ \"x\" : \"hi \"}") ;
        assertEquals(JSON.serialize(JSON.parse("{ \"x\" : \"\\u0E01\\u2702\\uF900\"}")), "{ \"x\" : \"\u0E01\u2702\uF900\"}");
        assertEquals(JSON.serialize(JSON.parse("{ \"x\" : \"foo\\u0020bar\"}")), "{ \"x\" : \"foo bar\"}");
    }

    @Test
    public void testBin() {
        byte b[] = {'a', 'b', 0, 'd'};
        DBObject obj = BasicDBObjectBuilder.start().add("b", b).get();
        assertEquals(JSON.serialize(obj), "{ \"b\" : <Binary Data>}");
    }


    @Test
    public void testErrors(){
        boolean threw = false;
        try {
            JSON.parse("{\"x\" : \"");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;
        try {
            JSON.parse("{\"x\" : \"\\");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;
        try {
            JSON.parse("{\"x\" : 5.2");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;
        try {
            JSON.parse("{\"x\" : 5");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;
        try {
            JSON.parse("{\"x\" : 5,");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;
    }

    @Test
    public void testBasic(){
        assertEquals( JSON.serialize(JSON.parse("{}")), "{ }");
        assertEquals( JSON.parse(""), null );
        assertEquals( JSON.parse("     "), null);
        assertEquals( JSON.parse(null), null);

        boolean threw = false;
        try {
            JSON.parse("{");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;

        try {
            JSON.parse("}");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;

        try {
            JSON.parse("{{}");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, true);
        threw = false;

        try {
            JSON.parse("4");
        }
        catch(JSONParseException e) {
            threw = true;
        }
        assertEquals(threw, false);
        threw = false;
        
        assertEquals( 4 , JSON.parse( "4" ) );
    }

    @Test
    public void testNumbers2(){
        DBObject x = new BasicDBObject( "x" , 123 );
        assertEquals( x , JSON.parse( x.toString() ) );

        x = new BasicDBObject( "x" , 123123123123L );
        assertEquals( x , JSON.parse( x.toString() ) );

        x = new BasicDBObject( "x" , 123123123 );
        assertEquals( x , JSON.parse( x.toString() ) );
    }

    void _escapeChar( String s ){
        String thingy = "va" + s + "lue";
        DBObject x = new BasicDBObject( "name" , thingy );
        x = (DBObject)JSON.parse( x.toString() );
        assertEquals( thingy , x.get( "name" ) );

        thingy = "va" + s + s + s + "lue" + s;
        x = new BasicDBObject( "name" , thingy );
        x = (DBObject)JSON.parse( x.toString() );
        assertEquals( thingy , x.get( "name" ) );
    }



    @Test
    public void testEscape1(){
        String raw = "a\nb";

        DBObject x = new BasicDBObject( "x" , raw );
        assertEquals( "\"a\\nb\"" , JSON.serialize( raw ) );
        assertEquals( x , JSON.parse( x.toString() ) );
        assertEquals( raw , ((DBObject)JSON.parse( x.toString() ) ).get( "x" ) );

        x = new BasicDBObject( "x" , "a\nb\bc\td\re" );
        assertEquals( x , JSON.parse( x.toString() ) );

        
        String thingy = "va\"lue";
        x = new BasicDBObject( "name" , thingy );
        x = (DBObject)JSON.parse( x.toString() );
        assertEquals( thingy , x.get( "name" ) );

        thingy = "va\\lue";
        x = new BasicDBObject( "name" , thingy );
        x = (DBObject)JSON.parse( x.toString() );
        assertEquals( thingy , x.get( "name" ) );

        assertEquals( "va/lue" , (String)JSON.parse("\"va\\/lue\"") );
        assertEquals( "value" , (String)JSON.parse("\"va\\lue\"") );
        assertEquals( "va\\lue" , (String)JSON.parse("\"va\\\\lue\"") );

        _escapeChar( "\t" );
        _escapeChar( "\b" );
        _escapeChar( "\n" );
        _escapeChar( "\r" );
        _escapeChar( "\'" );
        _escapeChar( "\"" );
        _escapeChar( "\\" );
    }

   @Test
   public void testPattern() {
       String x = "^Hello$";
       String serializedPattern = 
	   "{ \"$regex\" : \"" + x + "\" , \"$options\" : \"" + "i\"}";
       
       Pattern pattern = Pattern.compile( x , Pattern.CASE_INSENSITIVE);
       assertEquals( serializedPattern, JSON.serialize(pattern));

       BasicDBObject a = new BasicDBObject( "x" , pattern );
       assertEquals( "{ \"x\" : " + serializedPattern + "}" , a.toString() );

       DBObject b = (DBObject)JSON.parse( a.toString() );
       assertEquals( b.get("x").getClass(), Pattern.class );
       assertEquals( a.toString() , b.toString() );
   }

   @Test
   public void testRegexNoOptions() {
       String x = "^Hello$";
       String serializedPattern =
       "{ \"$regex\" : \"" + x + "\"}";

       Pattern pattern = Pattern.compile( x );
       assertEquals( serializedPattern, JSON.serialize(pattern));

       BasicDBObject a = new BasicDBObject( "x" , pattern );
       assertEquals( "{ \"x\" : " + serializedPattern + "}" , a.toString() );

       DBObject b = (DBObject)JSON.parse( a.toString() );
       assertEquals( b.get("x").getClass(), Pattern.class );
       assertEquals( a.toString() , b.toString() );
   }

   @Test
   public void testObjectId() {
       ObjectId oid = new ObjectId(new Date());

       String serialized = JSON.serialize(oid);
       assertEquals("{ \"$oid\" : \"" + oid + "\"}", serialized);
       
       ObjectId oid2 = (ObjectId)JSON.parse(serialized);
       assertEquals(oid, oid2);
   }

   @Test
   public void testDate() {
       Date d = new Date();
       SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
       format.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT")));
       String formattedDate = format.format(d);

       String serialized = JSON.serialize(d);
       assertEquals("{ \"$date\" : \"" + formattedDate + "\"}", serialized);

       Date d2 = (Date)JSON.parse(serialized);
       assertEquals(d.toString(), d2.toString());
       assertTrue(d.equals(d2));
   }

    @Test
    public void testJSONEncoding() throws ParseException {
        String json = "{ 'str' : 'asdfasd' , 'long' : 123123123123 , 'int' : 5 , 'float' : 0.4 , 'bool' : false , 'date' : { '$date' : '2011-05-18T18:56:00Z'} , 'pat' : { '$regex' : '.*' , '$options' : ''} , 'oid' : { '$oid' : '4d83ab3ea39562db9c1ae2ae'} , 'ref' : { '$ref' : 'test.test' , '$id' : { '$oid' : '4d83ab59a39562db9c1ae2af'}} , 'code' : { '$code' : 'asdfdsa'} , 'codews' : { '$code' : 'ggggg' , '$scope' : { }} , 'ts' : { '$ts' : 1300474885 , '$inc' : 10} , 'null' :  null, 'uuid' : { '$uuid' : '60f65152-6d4a-4f11-9c9b-590b575da7b5' }}";
        BasicDBObject a = (BasicDBObject) JSON.parse(json);
        assert (a.get("str").equals("asdfasd"));
        assert (a.get("int").equals(5));
        assert (a.get("long").equals(123123123123L));
        assert (a.get("float").equals(0.4d));
        assert (a.get("bool").equals(false));
        SimpleDateFormat format =
                new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        format.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT")));
        assert (a.get("date").equals(format.parse("2011-05-18T18:56:00Z")));
        Pattern pat = (Pattern) a.get("pat");
        Pattern pat2 = Pattern.compile(".*", BSON.regexFlags(""));
        assert (pat.pattern().equals(pat2.pattern()));
        assert (pat.flags() == (pat2.flags()));
        ObjectId oid = (ObjectId) a.get("oid");
        assert (oid.equals(new ObjectId("4d83ab3ea39562db9c1ae2ae")));
        DBRef ref = (DBRef) a.get("ref");
        assert (ref.equals(new DBRef(null, "test.test", new ObjectId("4d83ab59a39562db9c1ae2af"))));
        assert (a.get("code").equals(new Code("asdfdsa")));
        assert (a.get("codews").equals(new CodeWScope("ggggg", new BasicBSONObject())));
        assert (a.get("ts").equals(new BSONTimestamp(1300474885, 10)));
        assert (a.get("uuid").equals(UUID.fromString("60f65152-6d4a-4f11-9c9b-590b575da7b5")));
        String json2 = JSON.serialize(a);
        BasicDBObject b = (BasicDBObject) JSON.parse(json2);
        a.equals(b);
        assert (a.equals(b));
    }
}
