#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Tests.TestObjects;
#if NETFX_CORE
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
#elif ASPNETCORE50
using Xunit;
using Test = Xunit.FactAttribute;
using Assert = Newtonsoft.Json.Tests.XUnitAssert;
#else
using NUnit.Framework;
#endif

namespace Newtonsoft.Json.Tests.Serialization
{
    [TestFixture]
    public class ExtensionDataTests : TestFixtureBase
    {
        public class CustomDictionary : IDictionary<string, object>
        {
            private readonly IDictionary<string, object> _inner = new Dictionary<string, object>();
 
            public void Add(string key, object value)
            {
                _inner.Add(key, value);
            }

            public bool ContainsKey(string key)
            {
                return _inner.ContainsKey(key);
            }

            public ICollection<string> Keys
            {
                get { return _inner.Keys; }
            }

            public bool Remove(string key)
            {
                return _inner.Remove(key);
            }

            public bool TryGetValue(string key, out object value)
            {
                return _inner.TryGetValue(key, out value);
            }

            public ICollection<object> Values
            {
                get { return _inner.Values; }
            }

            public object this[string key]
            {
                get { return _inner[key]; }
                set { _inner[key] = value; }
            }

            public void Add(KeyValuePair<string, object> item)
            {
                _inner.Add(item);
            }

            public void Clear()
            {
                _inner.Clear();
            }

            public bool Contains(KeyValuePair<string, object> item)
            {
                return _inner.Contains(item);
            }

            public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
            {
                _inner.CopyTo(array, arrayIndex);
            }

            public int Count
            {
                get { return _inner.Count; }
            }

            public bool IsReadOnly
            {
                get { return _inner.IsReadOnly; }
            }

            public bool Remove(KeyValuePair<string, object> item)
            {
                return _inner.Remove(item);
            }

            public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
            {
                return _inner.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return _inner.GetEnumerator();
            }
        }

        public class Example
        {
            public Example()
            {
                Data = new CustomDictionary();
            }

            [JsonExtensionData]
            public IDictionary<string, object> Data { get; private set; }
        }

        [Test]
        public void DataBagDoesNotInheritFromDictionaryClass()
        {
            Example e = new Example();
            e.Data.Add("extensionData1", new int[] { 1, 2, 3 });

            string json = JsonConvert.SerializeObject(e, Formatting.Indented);

            Console.WriteLine(json);

            Example e2 = JsonConvert.DeserializeObject<Example>(json);

            JArray o1 = (JArray)e2.Data["extensionData1"];

            Assert.AreEqual(JTokenType.Array, o1.Type);
            Assert.AreEqual(3, o1.Count);
            Assert.AreEqual(1, (int)o1[0]);
            Assert.AreEqual(2, (int)o1[1]);
            Assert.AreEqual(3, (int)o1[2]);
        }

        public class ExtensionDataDeserializeWithNonDefaultConstructor
        {
            public ExtensionDataDeserializeWithNonDefaultConstructor(string name)
            {
                Name = name;
            }

            [JsonExtensionData]
            public IDictionary<string, JToken> _extensionData;

            public string Name { get; set; }
        }

        [Test]
        public void ExtensionDataDeserializeWithNonDefaultConstructorTest()
        {
            ExtensionDataDeserializeWithNonDefaultConstructor c = new ExtensionDataDeserializeWithNonDefaultConstructor("Name!");
            c._extensionData = new Dictionary<string, JToken>
            {
                { "Key!", "Value!" }
            };

            string json = JsonConvert.SerializeObject(c, Formatting.Indented);

            StringAssert.AreEqual(@"{
  ""Name"": ""Name!"",
  ""Key!"": ""Value!""
}", json);

            var c2 = JsonConvert.DeserializeObject<ExtensionDataDeserializeWithNonDefaultConstructor>(json);

            Assert.AreEqual("Name!", c2.Name);
            Assert.IsNotNull(c2._extensionData);
            Assert.AreEqual(1, c2._extensionData.Count);
            Assert.AreEqual("Value!", (string)c2._extensionData["Key!"]);
        }

        [Test]
        public void ExtensionDataWithNull()
        {
            string json = @"{
              'TaxRate': 0.125,
              'a':null
            }";

            var invoice = JsonConvert.DeserializeObject<ExtendedObject>(json);

            Assert.AreEqual(JTokenType.Null, invoice._additionalData["a"].Type);
            Assert.AreEqual(typeof(double), ((JValue)invoice._additionalData["TaxRate"]).Value.GetType());

            string result = JsonConvert.SerializeObject(invoice);

            Assert.AreEqual(@"{""TaxRate"":0.125,""a"":null}", result);
        }

        [Test]
        public void ExtensionDataFloatParseHandling()
        {
            string json = @"{
              'TaxRate': 0.125,
              'a':null
            }";

            var invoice = JsonConvert.DeserializeObject<ExtendedObject>(json, new JsonSerializerSettings
            {
                FloatParseHandling = FloatParseHandling.Decimal
            });

            Assert.AreEqual(typeof(decimal), ((JValue)invoice._additionalData["TaxRate"]).Value.GetType());
        }

#pragma warning disable 649
        class ExtendedObject
        {
            [JsonExtensionData]
            internal IDictionary<string, JToken> _additionalData;
        }
#pragma warning restore 649

#pragma warning disable 169
        public class CustomerInvoice
        {
            // we're only modifing the tax rate
            public decimal TaxRate { get; set; }

            // everything else gets stored here
            [JsonExtensionData]
            private IDictionary<string, JToken> _additionalData;
        }
#pragma warning restore 169

        [Test]
        public void ExtensionDataExample()
        {
            string json = @"{
              'HourlyRate': 150,
              'Hours': 40,
              'TaxRate': 0.125
            }";

            var invoice = JsonConvert.DeserializeObject<CustomerInvoice>(json);

            // increase tax to 15%
            invoice.TaxRate = 0.15m;

            string result = JsonConvert.SerializeObject(invoice);
            // {
            //   'TaxRate': 0.15,
            //   'HourlyRate': 150,
            //   'Hours': 40
            // }

            Assert.AreEqual(@"{""TaxRate"":0.15,""HourlyRate"":150,""Hours"":40}", result);
        }

        public class ExtensionDataTestClass
        {
            public string Name { get; set; }

            [JsonProperty("custom_name")]
            public string CustomName { get; set; }

            [JsonIgnore]
            public IList<int> Ignored { get; set; }

            public bool GetPrivate { get; internal set; }

            public bool GetOnly
            {
                get { return true; }
            }

            public readonly string Readonly = "Readonly";
            public IList<int> Ints { get; set; }

            [JsonExtensionData]
            internal IDictionary<string, JToken> ExtensionData { get; set; }

            public ExtensionDataTestClass()
            {
                Ints = new List<int> { 0 };
            }
        }

        public class JObjectExtensionDataTestClass
        {
            public string Name { get; set; }

            [JsonExtensionData]
            public JObject ExtensionData { get; set; }
        }

        [Test]
        public void RoundTripJObjectExtensionData()
        {
            JObjectExtensionDataTestClass c = new JObjectExtensionDataTestClass();
            c.Name = "Name!";
            c.ExtensionData = new JObject
            {
                { "one", 1 },
                { "two", "II" },
                { "three", new JArray(1, 1, 1) }
            };

            string json = JsonConvert.SerializeObject(c, Formatting.Indented);

            JObjectExtensionDataTestClass c2 = JsonConvert.DeserializeObject<JObjectExtensionDataTestClass>(json);

            Assert.AreEqual("Name!", c2.Name);
            Assert.IsTrue(JToken.DeepEquals(c.ExtensionData, c2.ExtensionData));
        }

        [Test]
        public void ExtensionDataTest()
        {
            string json = @"{
  ""Ints"": [1,2,3],
  ""Ignored"": [1,2,3],
  ""Readonly"": ""Readonly"",
  ""Name"": ""Actually set!"",
  ""CustomName"": ""Wrong name!"",
  ""GetPrivate"": true,
  ""GetOnly"": true,
  ""NewValueSimple"": true,
  ""NewValueComplex"": [1,2,3]
}";

            ExtensionDataTestClass c = JsonConvert.DeserializeObject<ExtensionDataTestClass>(json);

            Assert.AreEqual("Actually set!", c.Name);
            Assert.AreEqual(4, c.Ints.Count);

            Assert.AreEqual("Readonly", (string)c.ExtensionData["Readonly"]);
            Assert.AreEqual("Wrong name!", (string)c.ExtensionData["CustomName"]);
            Assert.AreEqual(true, (bool)c.ExtensionData["GetPrivate"]);
            Assert.AreEqual(true, (bool)c.ExtensionData["GetOnly"]);
            Assert.AreEqual(true, (bool)c.ExtensionData["NewValueSimple"]);
            Assert.IsTrue(JToken.DeepEquals(new JArray(1, 2, 3), c.ExtensionData["NewValueComplex"]));
            Assert.IsTrue(JToken.DeepEquals(new JArray(1, 2, 3), c.ExtensionData["Ignored"]));

            Assert.AreEqual(7, c.ExtensionData.Count);
        }

        public class MultipleExtensionDataAttributesTestClass
        {
            public string Name { get; set; }

            [JsonExtensionData]
            internal IDictionary<string, JToken> ExtensionData1 { get; set; }

            [JsonExtensionData]
            internal IDictionary<string, JToken> ExtensionData2 { get; set; }
        }

        public class ExtensionDataAttributesInheritanceTestClass : MultipleExtensionDataAttributesTestClass
        {
            [JsonExtensionData]
            internal IDictionary<string, JToken> ExtensionData0 { get; set; }
        }

        public class FieldExtensionDataAttributeTestClass
        {
            [JsonExtensionData]
            internal IDictionary<object, object> ExtensionData;
        }

        public class PublicExtensionDataAttributeTestClass
        {
            public string Name { get; set; }

            [JsonExtensionData]
            public IDictionary<object, object> ExtensionData;
        }

        public class PublicExtensionDataAttributeTestClassWithNonDefaultConstructor
        {
            public string Name { get; set; }

            public PublicExtensionDataAttributeTestClassWithNonDefaultConstructor(string name)
            {
                Name = name;
            }

            [JsonExtensionData]
            public IDictionary<object, object> ExtensionData;
        }

        public class PublicNoReadExtensionDataAttributeTestClass
        {
            public string Name { get; set; }

            [JsonExtensionData(ReadData = false)]
            public IDictionary<object, object> ExtensionData;
        }

        public class PublicNoWriteExtensionDataAttributeTestClass
        {
            public string Name { get; set; }

            [JsonExtensionData(WriteData = false)]
            public IDictionary<object, object> ExtensionData;
        }

        public class PublicJTokenExtensionDataAttributeTestClass
        {
            public string Name { get; set; }

            [JsonExtensionData]
            public IDictionary<string, JToken> ExtensionData;
        }

        [Test]
        public void DeserializeDirectoryAccount()
        {
            string json = @"{'DisplayName':'John Smith', 'SAMAccountName':'contoso\\johns'}";

            DirectoryAccount account = JsonConvert.DeserializeObject<DirectoryAccount>(json);

            Assert.AreEqual("John Smith", account.DisplayName);
            Assert.AreEqual("contoso", account.Domain);
            Assert.AreEqual("johns", account.UserName);
        }

        [Test]
        public void SerializePublicExtensionData()
        {
            string json = JsonConvert.SerializeObject(new PublicExtensionDataAttributeTestClass
            {
                Name = "Name!",
                ExtensionData = new Dictionary<object, object>
                {
                    { "Test", 1 }
                }
            });

            Assert.AreEqual(@"{""Name"":""Name!"",""Test"":1}", json);
        }

        [Test]
        public void SerializePublicExtensionDataNull()
        {
            string json = JsonConvert.SerializeObject(new PublicExtensionDataAttributeTestClass
            {
                Name = "Name!"
            });

            Assert.AreEqual(@"{""Name"":""Name!""}", json);
        }

        [Test]
        public void SerializePublicNoWriteExtensionData()
        {
            string json = JsonConvert.SerializeObject(new PublicNoWriteExtensionDataAttributeTestClass
            {
                Name = "Name!",
                ExtensionData = new Dictionary<object, object>
                {
                    { "Test", 1 }
                }
            });

            Assert.AreEqual(@"{""Name"":""Name!""}", json);
        }

        [Test]
        public void DeserializeNoReadPublicExtensionData()
        {
            PublicNoReadExtensionDataAttributeTestClass c = JsonConvert.DeserializeObject<PublicNoReadExtensionDataAttributeTestClass>(@"{""Name"":""Name!"",""Test"":1}");

            Assert.AreEqual(null, c.ExtensionData);
        }

        [Test]
        public void SerializePublicExtensionDataCircularReference()
        {
            var c = new PublicExtensionDataAttributeTestClass
            {
                Name = "Name!",
                ExtensionData = new Dictionary<object, object>
                {
                    { "Test", 1 }
                }
            };
            c.ExtensionData["Self"] = c;

            string json = JsonConvert.SerializeObject(c, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.All,
                Formatting = Formatting.Indented
            });

            StringAssert.AreEqual(@"{
  ""$id"": ""1"",
  ""Name"": ""Name!"",
  ""Test"": 1,
  ""Self"": {
    ""$ref"": ""1""
  }
}", json);

            var c2 = JsonConvert.DeserializeObject<PublicExtensionDataAttributeTestClass>(json, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.All
            });

            Assert.AreEqual("Name!", c2.Name);

            PublicExtensionDataAttributeTestClass bizzaroC2 = (PublicExtensionDataAttributeTestClass)c2.ExtensionData["Self"];

            Assert.AreEqual(c2, bizzaroC2);
            Assert.AreEqual(1, (long)bizzaroC2.ExtensionData["Test"]);
        }

        [Test]
        public void DeserializePublicJTokenExtensionDataCircularReference()
        {
            string json = @"{
  ""$id"": ""1"",
  ""Name"": ""Name!"",
  ""Test"": 1,
  ""Self"": {
    ""$ref"": ""1""
  }
}";

            var c2 = JsonConvert.DeserializeObject<PublicJTokenExtensionDataAttributeTestClass>(json, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.All
            });

            Assert.AreEqual("Name!", c2.Name);

            JObject bizzaroC2 = (JObject)c2.ExtensionData["Self"];

            Assert.AreEqual("Name!", (string)bizzaroC2["Name"]);
            Assert.AreEqual(1, (int)bizzaroC2["Test"]);

            Assert.AreEqual(1, (int)c2.ExtensionData["Test"]);
        }

        [Test]
        public void DeserializePublicExtensionDataTypeNamdHandling()
        {
            string json = @"{
  ""$id"": ""1"",
  ""Name"": ""Name!"",
  ""Test"": 1,
  ""Self"": {
    ""$type"": ""Newtonsoft.Json.Tests.TestObjects.WagePerson, Newtonsoft.Json.Tests"",
    ""HourlyWage"": 2.0,
    ""Name"": null,
    ""BirthDate"": ""0001-01-01T00:00:00"",
    ""LastModified"": ""0001-01-01T00:00:00""
  }
}";

            PublicExtensionDataAttributeTestClass c2 = JsonConvert.DeserializeObject<PublicExtensionDataAttributeTestClass>(json, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects
            });

            Assert.AreEqual("Name!", c2.Name);

            WagePerson bizzaroC2 = (WagePerson)c2.ExtensionData["Self"];

            Assert.AreEqual(2m, bizzaroC2.HourlyWage);
        }

        [Test]
        public void DeserializePublicExtensionDataTypeNamdHandlingNonDefaultConstructor()
        {
            string json = @"{
  ""$id"": ""1"",
  ""Name"": ""Name!"",
  ""Test"": 1,
  ""Self"": {
    ""$type"": ""Newtonsoft.Json.Tests.TestObjects.WagePerson, Newtonsoft.Json.Tests"",
    ""HourlyWage"": 2.0,
    ""Name"": null,
    ""BirthDate"": ""0001-01-01T00:00:00"",
    ""LastModified"": ""0001-01-01T00:00:00""
  }
}";

            PublicExtensionDataAttributeTestClassWithNonDefaultConstructor c2 = JsonConvert.DeserializeObject<PublicExtensionDataAttributeTestClassWithNonDefaultConstructor>(json, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects
            });

            Assert.AreEqual("Name!", c2.Name);

            WagePerson bizzaroC2 = (WagePerson)c2.ExtensionData["Self"];

            Assert.AreEqual(2m, bizzaroC2.HourlyWage);
        }

        [Test]
        public void SerializePublicExtensionDataTypeNamdHandling()
        {
            PublicExtensionDataAttributeTestClass c = new PublicExtensionDataAttributeTestClass
            {
                Name = "Name!",
                ExtensionData = new Dictionary<object, object>
                {
                    {
                        "Test", new WagePerson
                        {
                            HourlyWage = 2.1m
                        }
                    }
                }
            };

            string json = JsonConvert.SerializeObject(c, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects,
                Formatting = Formatting.Indented
            });

            StringAssert.AreEqual(@"{
  ""$type"": ""Newtonsoft.Json.Tests.Serialization.ExtensionDataTests+PublicExtensionDataAttributeTestClass, Newtonsoft.Json.Tests"",
  ""Name"": ""Name!"",
  ""Test"": {
    ""$type"": ""Newtonsoft.Json.Tests.TestObjects.WagePerson, Newtonsoft.Json.Tests"",
    ""HourlyWage"": 2.1,
    ""Name"": null,
    ""BirthDate"": ""0001-01-01T00:00:00"",
    ""LastModified"": ""0001-01-01T00:00:00""
  }
}", json);
        }

        [Test]
        public void DeserializePublicExtensionData()
        {
            string json = @"{
  'Name':'Name!',
  'NoMatch':'NoMatch!',
  'ExtensionData':{'HAI':true}
}";

            var c = JsonConvert.DeserializeObject<PublicExtensionDataAttributeTestClass>(json);

            Assert.AreEqual("Name!", c.Name);
            Assert.AreEqual(2, c.ExtensionData.Count);

            Assert.AreEqual("NoMatch!", (string)c.ExtensionData["NoMatch"]);

            // the ExtensionData property is put into the extension data
            // inception
            var o = (JObject)c.ExtensionData["ExtensionData"];
            Assert.AreEqual(1, o.Count);
            Assert.IsTrue(JToken.DeepEquals(new JObject { { "HAI", true } }, o));
        }

        [Test]
        public void FieldExtensionDataAttributeTest_Serialize()
        {
            FieldExtensionDataAttributeTestClass c = new FieldExtensionDataAttributeTestClass
            {
                ExtensionData = new Dictionary<object, object>()
            };

            string json = JsonConvert.SerializeObject(c);

            Assert.AreEqual("{}", json);
        }

        [Test]
        public void FieldExtensionDataAttributeTest_Deserialize()
        {
            var c = JsonConvert.DeserializeObject<FieldExtensionDataAttributeTestClass>("{'first':1,'second':2}");

            Assert.AreEqual(2, c.ExtensionData.Count);
            Assert.AreEqual(1, (long)c.ExtensionData["first"]);
            Assert.AreEqual(2, (long)c.ExtensionData["second"]);
        }

        [Test]
        public void MultipleExtensionDataAttributesTest()
        {
            var c = JsonConvert.DeserializeObject<MultipleExtensionDataAttributesTestClass>("{'first':[1],'second':[2]}");

            Assert.AreEqual(null, c.ExtensionData1);
            Assert.AreEqual(2, c.ExtensionData2.Count);
            Assert.AreEqual(1, (int)((JArray)c.ExtensionData2["first"])[0]);
            Assert.AreEqual(2, (int)((JArray)c.ExtensionData2["second"])[0]);
        }

        [Test]
        public void ExtensionDataAttributesInheritanceTest()
        {
            var c = JsonConvert.DeserializeObject<ExtensionDataAttributesInheritanceTestClass>("{'first':1,'second':2}");

            Assert.AreEqual(null, c.ExtensionData1);
            Assert.AreEqual(null, c.ExtensionData2);
            Assert.AreEqual(2, c.ExtensionData0.Count);
            Assert.AreEqual(1, (int)c.ExtensionData0["first"]);
            Assert.AreEqual(2, (int)c.ExtensionData0["second"]);
        }

        public class TestClass
        {
            [JsonProperty("LastActivityDate")]
            public DateTime? LastActivityDate { get; set; }

            [JsonExtensionData]
            public Dictionary<string, object> CustomFields { get; set; }
        }

        [Test]
        public void DeserializeNullableProperty()
        {
            string json = @"{ ""LastActivityDate"":null, ""CustomField1"":""Testing"" }";

            var c = JsonConvert.DeserializeObject<TestClass>(json);

            Assert.AreEqual(null, c.LastActivityDate);
            Assert.AreEqual(1, c.CustomFields.Count);
            Assert.AreEqual("Testing", (string)c.CustomFields["CustomField1"]);
        }
    }
}