// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// 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.

goog.provide('goog.proto2.ObjectSerializerTest');
goog.setTestOnly('goog.proto2.ObjectSerializerTest');

goog.require('goog.proto2.ObjectSerializer');
goog.require('goog.proto2.Serializer');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
goog.require('proto2.TestAllTypes');

var propertyReplacer = new goog.testing.PropertyReplacer();

function tearDown() {
  propertyReplacer.reset();
}

function testSerialization() {
  var message = new proto2.TestAllTypes();

  // Set the fields.
  // Singular.
  message.setOptionalInt32(101);
  message.setOptionalInt64('102');
  message.setOptionalUint32(103);
  message.setOptionalUint64('104');
  message.setOptionalSint32(105);
  message.setOptionalSint64('106');
  message.setOptionalFixed32(107);
  message.setOptionalFixed64('108');
  message.setOptionalSfixed32(109);
  message.setOptionalSfixed64('110');
  message.setOptionalFloat(111.5);
  message.setOptionalDouble(112.5);
  message.setOptionalBool(true);
  message.setOptionalString('test');
  message.setOptionalBytes('abcd');

  var group = new proto2.TestAllTypes.OptionalGroup();
  group.setA(111);

  message.setOptionalgroup(group);

  var nestedMessage = new proto2.TestAllTypes.NestedMessage();
  nestedMessage.setB(112);

  message.setOptionalNestedMessage(nestedMessage);

  message.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.FOO);

  // Repeated.
  message.addRepeatedInt32(201);
  message.addRepeatedInt32(202);

  // Serialize to a simplified object.
  var simplified = new goog.proto2.ObjectSerializer().serialize(message);

  // Assert that everything serialized properly.
  assertEquals(101, simplified[1]);
  assertEquals('102', simplified[2]);
  assertEquals(103, simplified[3]);
  assertEquals('104', simplified[4]);
  assertEquals(105, simplified[5]);
  assertEquals('106', simplified[6]);
  assertEquals(107, simplified[7]);
  assertEquals('108', simplified[8]);
  assertEquals(109, simplified[9]);
  assertEquals('110', simplified[10]);
  assertEquals(111.5, simplified[11]);
  assertEquals(112.5, simplified[12]);
  assertEquals(true, simplified[13]);
  assertEquals('test', simplified[14]);
  assertEquals('abcd', simplified[15]);

  assertEquals(111, simplified[16][17]);
  assertEquals(112, simplified[18][1]);
  assertEquals(proto2.TestAllTypes.NestedEnum.FOO, simplified[21]);

  assertEquals(201, simplified[31][0]);
  assertEquals(202, simplified[31][1]);

  // Serialize to a simplified object (with key as name).
  simplified =
      new goog.proto2
          .ObjectSerializer(goog.proto2.ObjectSerializer.KeyOption.NAME)
          .serialize(message);

  // Assert that everything serialized properly.
  assertEquals(101, simplified['optional_int32']);
  assertEquals('102', simplified['optional_int64']);
  assertEquals(103, simplified['optional_uint32']);
  assertEquals('104', simplified['optional_uint64']);
  assertEquals(105, simplified['optional_sint32']);
  assertEquals('106', simplified['optional_sint64']);
  assertEquals(107, simplified['optional_fixed32']);
  assertEquals('108', simplified['optional_fixed64']);
  assertEquals(109, simplified['optional_sfixed32']);
  assertEquals('110', simplified['optional_sfixed64']);
  assertEquals(111.5, simplified['optional_float']);
  assertEquals(112.5, simplified['optional_double']);
  assertEquals(true, simplified['optional_bool']);
  assertEquals('test', simplified['optional_string']);
  assertEquals('abcd', simplified['optional_bytes']);

  assertEquals(111, simplified['optionalgroup']['a']);
  assertEquals(112, simplified['optional_nested_message']['b']);

  assertEquals(
      proto2.TestAllTypes.NestedEnum.FOO, simplified['optional_nested_enum']);

  assertEquals(201, simplified['repeated_int32'][0]);
  assertEquals(202, simplified['repeated_int32'][1]);
}


function testSerializationOfUnknown() {
  var message = new proto2.TestAllTypes();

  // Set the fields.
  // Known.
  message.setOptionalInt32(101);
  message.setOptionalInt64('102');
  message.addRepeatedInt32(201);
  message.addRepeatedInt32(202);

  // Unknown.
  message.setUnknown(1000, 301);
  message.setUnknown(1001, 302);

  // Serialize.
  var simplified = new goog.proto2.ObjectSerializer().serialize(message);

  assertEquals(101, simplified['1']);
  assertEquals('102', simplified['2']);

  assertEquals(201, simplified['31'][0]);
  assertEquals(202, simplified['31'][1]);

  assertEquals(301, simplified['1000']);
  assertEquals(302, simplified['1001']);
}

function testDeserializationOfUnknown() {
  var simplified = {1: 101, 2: '102', 1000: 103, 1001: 104};

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);
  assertTrue(message.hasOptionalInt32());
  assertTrue(message.hasOptionalInt64());

  assertEquals(101, message.getOptionalInt32());
  assertEquals('102', message.getOptionalInt64());

  var count = 0;

  message.forEachUnknown(function(tag, value) {
    if (tag == 1000) {
      assertEquals(103, value);
    }

    if (tag == 1001) {
      assertEquals(104, value);
    }

    ++count;
  });

  assertEquals(2, count);
}

function testDeserializationRepeated() {
  var simplified = {
    31: [101, 102],
    41: [201.5, 202.5, 203.5],
    42: [],
    43: [true, false],
    44: ['he', 'llo'],
    46: [{47: [101]}, {47: [102]}],
    48: [{1: 201}, {1: 202}]
  };

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  // Ensure the fields are set as expected.
  assertTrue(message.hasRepeatedInt32());
  assertTrue(message.hasRepeatedFloat());

  assertFalse(message.hasRepeatedDouble());

  assertTrue(message.hasRepeatedBool());
  assertTrue(message.hasRepeatedgroup());
  assertTrue(message.hasRepeatedNestedMessage());

  // Ensure the counts match.
  assertEquals(2, message.repeatedInt32Count());
  assertEquals(3, message.repeatedFloatCount());

  assertEquals(0, message.repeatedDoubleCount());

  assertEquals(2, message.repeatedBoolCount());
  assertEquals(2, message.repeatedStringCount());
  assertEquals(2, message.repeatedgroupCount());
  assertEquals(2, message.repeatedNestedMessageCount());

  // Ensure the values match.
  assertEquals(101, message.getRepeatedInt32(0));
  assertEquals(102, message.getRepeatedInt32(1));

  assertEquals(201.5, message.getRepeatedFloat(0));
  assertEquals(202.5, message.getRepeatedFloat(1));
  assertEquals(203.5, message.getRepeatedFloat(2));

  assertEquals(true, message.getRepeatedBool(0));
  assertEquals(false, message.getRepeatedBool(1));

  assertEquals('he', message.getRepeatedString(0));
  assertEquals('llo', message.getRepeatedString(1));

  assertEquals(101, message.getRepeatedgroup(0).getA(0));
  assertEquals(102, message.getRepeatedgroup(1).getA(0));

  assertEquals(201, message.getRepeatedNestedMessage(0).getB());
  assertEquals(202, message.getRepeatedNestedMessage(1).getB());
}

function testDeserialization() {
  var simplified = {
    1: 101,
    2: '102',
    3: 103,
    4: '104',
    5: 105,
    6: '106',
    7: 107,
    8: '108',
    9: 109,
    10: '110',
    11: 111.5,
    12: 112.5,
    13: true,
    14: 'test',
    15: 'abcd',
    16: {17: 113},
    18: {1: 114},
    21: proto2.TestAllTypes.NestedEnum.FOO
  };

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  assertTrue(message.hasOptionalInt32());
  assertTrue(message.hasOptionalInt64());
  assertTrue(message.hasOptionalUint32());
  assertTrue(message.hasOptionalUint64());
  assertTrue(message.hasOptionalSint32());
  assertTrue(message.hasOptionalSint64());
  assertTrue(message.hasOptionalFixed32());
  assertTrue(message.hasOptionalFixed64());
  assertTrue(message.hasOptionalSfixed32());
  assertTrue(message.hasOptionalSfixed64());
  assertTrue(message.hasOptionalFloat());
  assertTrue(message.hasOptionalDouble());
  assertTrue(message.hasOptionalBool());
  assertTrue(message.hasOptionalString());
  assertTrue(message.hasOptionalBytes());
  assertTrue(message.hasOptionalgroup());
  assertTrue(message.hasOptionalNestedMessage());
  assertTrue(message.hasOptionalNestedEnum());

  assertEquals(1, message.optionalInt32Count());
  assertEquals(1, message.optionalInt64Count());
  assertEquals(1, message.optionalUint32Count());
  assertEquals(1, message.optionalUint64Count());
  assertEquals(1, message.optionalSint32Count());
  assertEquals(1, message.optionalSint64Count());
  assertEquals(1, message.optionalFixed32Count());
  assertEquals(1, message.optionalFixed64Count());
  assertEquals(1, message.optionalSfixed32Count());
  assertEquals(1, message.optionalSfixed64Count());
  assertEquals(1, message.optionalFloatCount());
  assertEquals(1, message.optionalDoubleCount());
  assertEquals(1, message.optionalBoolCount());
  assertEquals(1, message.optionalStringCount());
  assertEquals(1, message.optionalBytesCount());
  assertEquals(1, message.optionalgroupCount());
  assertEquals(1, message.optionalNestedMessageCount());
  assertEquals(1, message.optionalNestedEnumCount());

  assertEquals(101, message.getOptionalInt32());
  assertEquals('102', message.getOptionalInt64());
  assertEquals(103, message.getOptionalUint32());
  assertEquals('104', message.getOptionalUint64());
  assertEquals(105, message.getOptionalSint32());
  assertEquals('106', message.getOptionalSint64());
  assertEquals(107, message.getOptionalFixed32());
  assertEquals('108', message.getOptionalFixed64());
  assertEquals(109, message.getOptionalSfixed32());
  assertEquals('110', message.getOptionalSfixed64());
  assertEquals(111.5, message.getOptionalFloat());
  assertEquals(112.5, message.getOptionalDouble());
  assertEquals(true, message.getOptionalBool());
  assertEquals('test', message.getOptionalString());
  assertEquals('abcd', message.getOptionalBytes());
  assertEquals(113, message.getOptionalgroup().getA());
  assertEquals(114, message.getOptionalNestedMessage().getB());

  assertEquals(
      proto2.TestAllTypes.NestedEnum.FOO, message.getOptionalNestedEnum());
}

function testDeserializationUnknownEnumValue() {
  var simplified = {21: 1001};

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  assertEquals(1001, message.getOptionalNestedEnum());
}

function testDeserializationSymbolicEnumValue() {
  var simplified = {21: 'BAR'};

  propertyReplacer.set(goog.proto2.Serializer, 'DECODE_SYMBOLIC_ENUMS', true);

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  assertEquals(
      proto2.TestAllTypes.NestedEnum.BAR, message.getOptionalNestedEnum());
}

function testDeserializationSymbolicEnumValueTurnedOff() {
  var simplified = {21: 'BAR'};

  var serializer = new goog.proto2.ObjectSerializer();

  assertThrows(
      'Should have an assertion failure in deserialization', function() {
        serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);
      });
}

function testDeserializationUnknownSymbolicEnumValue() {
  var simplified = {21: 'BARRED'};

  var serializer = new goog.proto2.ObjectSerializer();

  assertThrows(
      'Should have an assertion failure in deserialization', function() {
        serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);
      });
}

function testDeserializationEnumValueAsNumericString() {
  var simplified = {21: '2'};

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  assertEquals(
      proto2.TestAllTypes.NestedEnum.BAR, message.getOptionalNestedEnum());
}

function testDeserializationEnumValueWithNegativeString() {
  var simplified = {21: '-2'};

  var serializer = new goog.proto2.ObjectSerializer();

  assertThrows(
      'Should have an assertion failure in deserialization', function() {
        serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);
      });
}

function testDeserializationNumbersOrStrings() {
  // 64-bit types may have been serialized as numbers or strings.
  // Deserialization should be able to handle either.

  var simplifiedWithNumbers =
      {50: 5000, 51: 5100, 52: [5200, 5201], 53: [5300, 5301]};

  var simplifiedWithStrings =
      {50: '5000', 51: '5100', 52: ['5200', '5201'], 53: ['5300', '5301']};

  var serializer = new goog.proto2.ObjectSerializer();

  var message = serializer.deserialize(
      proto2.TestAllTypes.getDescriptor(), simplifiedWithNumbers);

  assertNotNull(message);

  assertEquals(5000, message.getOptionalInt64Number());
  assertEquals('5100', message.getOptionalInt64String());
  assertEquals(5200, message.getRepeatedInt64Number(0));
  assertEquals(5201, message.getRepeatedInt64Number(1));
  assertEquals('5300', message.getRepeatedInt64String(0));
  assertEquals('5301', message.getRepeatedInt64String(1));

  assertArrayEquals([5200, 5201], message.repeatedInt64NumberArray());
  assertArrayEquals(['5300', '5301'], message.repeatedInt64StringArray());

  message = serializer.deserialize(
      proto2.TestAllTypes.getDescriptor(), simplifiedWithStrings);

  assertNotNull(message);

  assertEquals(5000, message.getOptionalInt64Number());
  assertEquals('5100', message.getOptionalInt64String());
  assertEquals(5200, message.getRepeatedInt64Number(0));
  assertEquals(5201, message.getRepeatedInt64Number(1));
  assertEquals('5300', message.getRepeatedInt64String(0));
  assertEquals('5301', message.getRepeatedInt64String(1));

  assertArrayEquals([5200, 5201], message.repeatedInt64NumberArray());
  assertArrayEquals(['5300', '5301'], message.repeatedInt64StringArray());
}

function testSerializationSpecialFloatDoubleValues() {
  // NaN, Infinity and -Infinity should get serialized as strings.
  var message = new proto2.TestAllTypes();
  message.setOptionalFloat(Infinity);
  message.setOptionalDouble(-Infinity);
  message.addRepeatedFloat(Infinity);
  message.addRepeatedFloat(-Infinity);
  message.addRepeatedFloat(NaN);
  message.addRepeatedDouble(Infinity);
  message.addRepeatedDouble(-Infinity);
  message.addRepeatedDouble(NaN);
  var simplified = new goog.proto2.ObjectSerializer().serialize(message);

  // Assert that everything serialized properly.
  assertEquals('Infinity', simplified[11]);
  assertEquals('-Infinity', simplified[12]);
  assertEquals('Infinity', simplified[41][0]);
  assertEquals('-Infinity', simplified[41][1]);
  assertEquals('NaN', simplified[41][2]);
  assertEquals('Infinity', simplified[42][0]);
  assertEquals('-Infinity', simplified[42][1]);
  assertEquals('NaN', simplified[42][2]);
}

function testDeserializationSpecialFloatDoubleValues() {
  // NaN, Infinity and -Infinity values should be de-serialized from their
  // string representation.
  var simplified = {
    41: ['Infinity', '-Infinity', 'NaN'],
    42: ['Infinity', '-Infinity', 'NaN']
  };

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  var floatArray = message.repeatedFloatArray();
  assertEquals(Infinity, floatArray[0]);
  assertEquals(-Infinity, floatArray[1]);
  assertTrue(isNaN(floatArray[2]));

  var doubleArray = message.repeatedDoubleArray();
  assertEquals(Infinity, doubleArray[0]);
  assertEquals(-Infinity, doubleArray[1]);
  assertTrue(isNaN(doubleArray[2]));
}

function testDeserializationConversionProhibited() {
  // 64-bit types may have been serialized as numbers or strings.
  // But 32-bit types must be serialized as numbers.
  // Test deserialization fails on 32-bit numbers as strings.

  var simplified = {
    1: '1000'  // optionalInt32
  };
  var serializer = new goog.proto2.ObjectSerializer();

  assertThrows(
      'Should have an assertion failure in deserialization', function() {
        serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);
      });
}

function testDefaultValueNumbersOrStrings() {
  // 64-bit types may have been serialized as numbers or strings.
  // The default values should have the correct type.

  var serializer = new goog.proto2.ObjectSerializer();
  var message = serializer.deserialize(proto2.TestAllTypes.getDescriptor(), {});

  assertNotNull(message);

  // Default when using Number is a number, and precision is lost.
  var value = message.getOptionalInt64NumberOrDefault();
  assertTrue('Expecting a number', typeof value === 'number');
  assertEquals(1000000000000000000, value);
  assertEquals(1000000000000000001, value);
  assertEquals(1000000000000000002, value);
  assertEquals('1000000000000000000', String(value));  // Value is rounded!

  // When using a String, the value is preserved.
  assertEquals(
      '1000000000000000001', message.getOptionalInt64StringOrDefault());
}

function testSerializationBooleanAsNumberFalse() {
  // Some libraries, such as GWT, can expect boolean field values as 0/1

  var message = new proto2.TestAllTypes();
  message.setOptionalBool(false);

  var serializer = new goog.proto2.ObjectSerializer(
      goog.proto2.ObjectSerializer.KeyOption.TAG,
      true /* opt_serializeBooleanAsNumber */);
  var simplified = serializer.serialize(message);

  assertEquals(0, simplified[13]);
}

function testSerializationBooleanAsNumberTrue() {
  var message = new proto2.TestAllTypes();
  message.setOptionalBool(true);

  var serializer = new goog.proto2.ObjectSerializer(
      goog.proto2.ObjectSerializer.KeyOption.TAG,
      true /* opt_serializeBooleanAsNumber */);
  var simplified = serializer.serialize(message);

  assertEquals(1, simplified[13]);
}

function testDeserializationBooleanAsNumberFalse() {
  // Some libraries, such as GWT, can serialize boolean values as 0/1

  var simplified = {13: 0};

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  assertFalse(message.getOptionalBool());
}

function testDeserializationBooleanAsNumberTrue() {
  var simplified = {13: 1};

  var serializer = new goog.proto2.ObjectSerializer();

  var message =
      serializer.deserialize(proto2.TestAllTypes.getDescriptor(), simplified);

  assertNotNull(message);

  assertTrue(message.getOptionalBool());
}
