#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;
#if !(NET20 || NET35 || NET40 || PORTABLE || PORTABLE40 || DNXCORE50) || NETSTANDARD2_0 || NET6_0_OR_GREATER
using System.Buffers;
#endif
using System.Collections.Generic;
#if !(PORTABLE || DNXCORE50 || PORTABLE40) || NETSTANDARD2_0 || NET6_0_OR_GREATER
using System.Data;
#endif
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Serialization;
#if !(NET20 || NET35 || NET40)
using System.Threading.Tasks;
#endif
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
using Assert = Newtonsoft.Json.Tests.XUnitAssert;
#else
using NUnit.Framework;

#endif

namespace Newtonsoft.Json.Tests
{
    [TestFixture]
    public class DemoTests : TestFixtureBase
    {
        public class HtmlColor
        {
            public int Red { get; set; }
            public int Green { get; set; }
            public int Blue { get; set; }
        }

        [Test]
        public void JsonConverter()
        {
            HtmlColor red = new HtmlColor
            {
                Red = 255,
                Green = 0,
                Blue = 0
            };

            string json = JsonConvert.SerializeObject(red, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented
            });
            // {
            //   "Red": 255,
            //   "Green": 0,
            //   "Blue": 0
            // }

            json = JsonConvert.SerializeObject(red, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                Converters = { new HtmlColorConverter() }
            });
            // "#FF0000"

            HtmlColor r2 = JsonConvert.DeserializeObject<HtmlColor>(json, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                Converters = { new HtmlColorConverter() }
            });
            Assert.AreEqual(255, r2.Red);
            Assert.AreEqual(0, r2.Green);
            Assert.AreEqual(0, r2.Blue);

            Assert.AreEqual(@"""#FF0000""", json);
        }

        public class PersonDemo
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public string Job { get; set; }
        }

        public class Session
        {
            public string Name { get; set; }
            public DateTime Date { get; set; }
        }

        public class HtmlColorConverter : JsonConverter
        {
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                // create hex string from value
                HtmlColor color = (HtmlColor)value;
                string hexString = color.Red.ToString("X2")
                                   + color.Green.ToString("X2")
                                   + color.Blue.ToString("X2");

                // write value to json
                writer.WriteValue("#" + hexString);
            }

            //public override object ReadJson(JsonReader reader, Type objectType,
            //    object existingValue, JsonSerializer serializer)
            //{
            //    throw new NotImplementedException();
            //}

            public override object ReadJson(JsonReader reader, Type objectType,
                object existingValue, JsonSerializer serializer)
            {
                // get hex string
                string hexString = (string)reader.Value;
                hexString = hexString.TrimStart('#');

                // build html color from hex
                return new HtmlColor
                {
                    Red = Convert.ToInt32(hexString.Substring(0, 2), 16),
                    Green = Convert.ToInt32(hexString.Substring(2, 2), 16),
                    Blue = Convert.ToInt32(hexString.Substring(4, 2), 16)
                };
            }

            public override bool CanConvert(Type objectType)
            {
                return objectType == typeof(HtmlColor);
            }
        }

        [Test]
        public void SerializationGuide()
        {
            IList<string> roles = new List<string>
            {
                "User",
                "Admin"
            };

            string roleJson = JsonConvert.SerializeObject(roles, Formatting.Indented);
            // [
            //   "User",
            //   "Admin"
            // ]

            IDictionary<DateTime, int> dailyRegistrations = new Dictionary<DateTime, int>
            {
                { new DateTime(2014, 6, 1), 23 },
                { new DateTime(2014, 6, 2), 50 }
            };

            string regJson = JsonConvert.SerializeObject(dailyRegistrations, Formatting.Indented);
            // {
            //   "2014-06-01T00:00:00": 23,
            //   "2014-06-02T00:00:00": 50
            // }

            City c = new City { Name = "Oslo", Population = 650000 };

            string cityJson = JsonConvert.SerializeObject(c, Formatting.Indented);
            // {
            //   "Name": "Oslo",
            //   "Population": 650000
            // }
        }

        [Test]
        public void SerializationBasics()
        {
            IList<string> roles = new List<string>
            {
                "User",
                "Admin"
            };

            MemoryTraceWriter traceWriter = new MemoryTraceWriter();

            string j = JsonConvert.SerializeObject(roles, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                TraceWriter = traceWriter
            });

            string trace = traceWriter.ToString();
            // Started serializing System.Collections.Generic.List`1[System.String].
            // Finished serializing System.Collections.Generic.List`1[System.String].
            // Verbose Serialized JSON: 
            // [
            //   "User",
            //   "Admin"
            // ]
        }

        [Test]
        public void SerializationBasics2()
        {
            var s = new Session
            {
                Name = "Serialize All The Things",
                Date = new DateTime(2014, 6, 4, 0, 0, 0, DateTimeKind.Utc)
            };

            string j = JsonConvert.SerializeObject(s, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                Converters = { new JavaScriptDateTimeConverter() }
            });
            // {
            //   "Name": "Serialize All The Things",
            //   "Date": new Date(1401796800000)
            // }

            StringAssert.AreEqual(@"{
  ""Name"": ""Serialize All The Things"",
  ""Date"": new Date(
    1401840000000
  )
}", j);
        }

        [Test]
        public void DeserializationBasics1()
        {
            string j = @"{
              'Name': 'Serialize All The Things',
              'Date': new Date(1401796800000)
            }";

            var s = JsonConvert.DeserializeObject<Session>(j, new JsonSerializerSettings
            {
                Converters = { new JavaScriptDateTimeConverter() }
            });
            // Name = Serialize All The Things
            // Date = Tuesday, 3 June 2014

            Assert.AreEqual("Serialize All The Things", s.Name);
        }

        [Test]
        public void DeserializationBasics2()
        {
            Session s = new Session();
            s.Date = new DateTime(2014, 6, 4);

            string j = @"{
              'Name': 'Serialize All The Things'
            }";

            JsonConvert.PopulateObject(j, s);
            // Name = Serialize All The Things
            // Date = Tuesday, 3 June 2014
        }

        public class City
        {
            public string Name { get; set; }
            public int Population { get; set; }
        }

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

        public class Manager : Employee
        {
            public IList<Employee> Reportees { get; set; }
        }

        [Test]
        public void SerializeReferencesByValue()
        {
            Employee arnie = new Employee { Name = "Arnie Admin" };
            Manager mike = new Manager { Name = "Mike Manager" };
            Manager susan = new Manager { Name = "Susan Supervisor" };

            mike.Reportees = new[] { arnie, susan };
            susan.Reportees = new[] { arnie };

            string json = JsonConvert.SerializeObject(mike, Formatting.Indented);
            // {
            //   "Reportees": [
            //     { 
            //       "Name": "Arnie Admin"
            //     },
            //     {
            //       "Reportees": [
            //         {
            //           "Name": "Arnie Admin"
            //         }
            //       ],
            //       "Name": "Susan Supervisor"
            //     }
            //   ],
            //   "Name": "Mike Manager"
            // }

            StringAssert.AreEqual(@"{
  ""Reportees"": [
    {
      ""Name"": ""Arnie Admin""
    },
    {
      ""Reportees"": [
        {
          ""Name"": ""Arnie Admin""
        }
      ],
      ""Name"": ""Susan Supervisor""
    }
  ],
  ""Name"": ""Mike Manager""
}", json);
        }

        [Test]
        public void SerializeReferencesWithMetadata()
        {
            Employee arnie = new Employee { Name = "Arnie Admin" };
            Manager mike = new Manager { Name = "Mike Manager" };
            Manager susan = new Manager { Name = "Susan Supervisor" };

            mike.Reportees = new[] { arnie, susan };
            susan.Reportees = new[] { arnie };

            string json = JsonConvert.SerializeObject(mike, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                TypeNameHandling = TypeNameHandling.Objects,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            });
            // {
            //   "$id": "1",
            //   "$type": "YourNamespace.Manager, YourAssembly",
            //   "Name": "Mike Manager",
            //   "Reportees": [
            //     {
            //       "$id": "2",
            //       "$type": "YourNamespace.Employee, YourAssembly",
            //       "Name": "Arnie Admin"
            //     },
            //     {
            //       "$id": "3",
            //       "$type": "YourNamespace.Manager, YourAssembly",
            //       "Name": "Susan Supervisor",
            //       "Reportees": [
            //         {
            //           "$ref": "2"
            //         }
            //       ]
            //     }
            //   ]
            // }

            StringAssert.AreEqual(@"{
  ""$id"": ""1"",
  ""$type"": ""Newtonsoft.Json.Tests.DemoTests+Manager, Newtonsoft.Json.Tests"",
  ""Reportees"": [
    {
      ""$id"": ""2"",
      ""$type"": ""Newtonsoft.Json.Tests.DemoTests+Employee, Newtonsoft.Json.Tests"",
      ""Name"": ""Arnie Admin""
    },
    {
      ""$id"": ""3"",
      ""$type"": ""Newtonsoft.Json.Tests.DemoTests+Manager, Newtonsoft.Json.Tests"",
      ""Reportees"": [
        {
          ""$ref"": ""2""
        }
      ],
      ""Name"": ""Susan Supervisor""
    }
  ],
  ""Name"": ""Mike Manager""
}", json);
        }

        [Test]
        public void RoundtripTypesAndReferences()
        {
            string json = @"{
  '$id': '1',
  '$type': 'Newtonsoft.Json.Tests.DemoTests+Manager, Newtonsoft.Json.Tests',
  'Reportees': [
    {
      '$id': '2',
      '$type': 'Newtonsoft.Json.Tests.DemoTests+Employee, Newtonsoft.Json.Tests',
      'Name': 'Arnie Admin'
    },
    {
      '$id': '3',
      '$type': 'Newtonsoft.Json.Tests.DemoTests+Manager, Newtonsoft.Json.Tests',
      'Reportees': [
        {
          '$ref': '2'
        }
      ],
      'Name': 'Susan Supervisor'
    }
  ],
  'Name': 'Mike Manager'
}";

            var e = JsonConvert.DeserializeObject<Employee>(json, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            });
            // Name = Mike Manager
            // Reportees = Arnie Admin, Susan Supervisor

            Manager mike = (Manager)e;
            Manager susan = (Manager)mike.Reportees[1];

            Object.ReferenceEquals(mike.Reportees[0], susan.Reportees[0]);
            // true

            Assert.IsTrue(ReferenceEquals(mike.Reportees[0], susan.Reportees[0]));
        }

        public class House
        {
            public string StreetAddress { get; set; }
            public DateTime BuildDate { get; set; }
            public int Bedrooms { get; set; }
            public decimal FloorArea { get; set; }
        }

        public class House1
        {
            public string StreetAddress { get; set; }

            [JsonIgnore]
            public int Bedrooms { get; set; }

            [JsonIgnore]
            public decimal FloorArea { get; set; }

            [JsonIgnore]
            public DateTime BuildDate { get; set; }
        }

        [JsonObject(MemberSerialization.OptIn)]
        public class House3
        {
            [JsonProperty]
            public string StreetAddress { get; set; }

            public int Bedrooms { get; set; }
            public decimal FloorArea { get; set; }
            public DateTime BuildDate { get; set; }
        }

        [JsonObject(MemberSerialization.OptIn)]
        public class House2
        {
            [JsonProperty("address")]
            public string StreetAddress { get; set; }

            public int Bedrooms { get; set; }
            public decimal FloorArea { get; set; }
            public DateTime BuildDate { get; set; }
        }

        [JsonObject(MemberSerialization.OptIn)]
        public class House4
        {
            [JsonProperty("address", Order = 2)]
            public string StreetAddress { get; set; }

            public int Bedrooms { get; set; }
            public decimal FloorArea { get; set; }

            [JsonProperty("buildDate", Order = 1)]
            public DateTime BuildDate { get; set; }
        }

        [JsonObject(MemberSerialization.OptIn)]
        public class House5
        {
            [JsonProperty("address", Order = 2)]
            public string StreetAddress { get; set; }

            public int Bedrooms { get; set; }
            public decimal FloorArea { get; set; }

            [JsonProperty("buildDate", Order = 1)]
            [JsonConverter(typeof(JavaScriptDateTimeConverter))]
            public DateTime BuildDate { get; set; }
        }

        [Test]
        public void SerializeAttributes()
        {
            var house = new House3();
            house.StreetAddress = "221B Baker Street";
            house.Bedrooms = 2;
            house.FloorArea = 100m;
            house.BuildDate = new DateTime(1890, 1, 1);

            string json = JsonConvert.SerializeObject(house, Formatting.Indented);
            // {
            //   "StreetAddress": "221B Baker Street",
            //   "Bedrooms": 2,
            //   "FloorArea": 100.0,
            //   "BuildDate": "1890-01-01T00:00:00"
            // }

            // {
            //   "StreetAddress": "221B Baker Street"
            // }

            // {
            //   "address": "221B Baker Street"
            // }

            // {
            //   "buildDate": "1890-01-01T00:00:00",
            //   "address": "221B Baker Street"
            // }

            // {
            //   "buildDate": new Date(-2524568400000),
            //   "address": "221B Baker Street"
            // }
        }

        [Test]
        public void MergeJson()
        {
            JObject o1 = JObject.Parse(@"{
              'FirstName': 'John',
              'LastName': 'Smith',
              'Enabled': false,
              'Roles': [ 'User' ]
            }");
            JObject o2 = JObject.Parse(@"{
              'Enabled': true,
              'Roles': [ 'User', 'Admin' ]
            }");

            o1.Merge(o2, new JsonMergeSettings
            {
                // union arrays together to avoid duplicates
                MergeArrayHandling = MergeArrayHandling.Union
            });

            string json = o1.ToString();
            // {
            //   "FirstName": "John",
            //   "LastName": "Smith",
            //   "Enabled": true,
            //   "Roles": [
            //     "User",
            //     "Admin"
            //   ]
            // }

            StringAssert.AreEqual(@"{
  ""FirstName"": ""John"",
  ""LastName"": ""Smith"",
  ""Enabled"": true,
  ""Roles"": [
    ""User"",
    ""Admin""
  ]
}", json);
        }

#if !(NET20 || NET35 || NET40 || PORTABLE || PORTABLE40 || DNXCORE50) || NETSTANDARD2_0 || NET6_0_OR_GREATER
        [Test]
        public void ArrayPooling()
        {
            IList<int> value;

            JsonSerializer serializer = new JsonSerializer();
            using (JsonTextReader reader = new JsonTextReader(new StringReader(@"[1,2,3,4]")))
            {
                reader.ArrayPool = JsonArrayPool.Instance;

                value = serializer.Deserialize<IList<int>>(reader);
            }

            Assert.AreEqual(4, value.Count);
        }
#endif

#if !(PORTABLE || DNXCORE50 || PORTABLE40) || NETSTANDARD2_0 || NET6_0_OR_GREATER
        [Test]
        public void SerializeDataTable()
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("PackageId", typeof(string));
            dt.Columns.Add("Version", typeof(string));
            dt.Columns.Add("ReleaseDate", typeof(DateTime));

            dt.Rows.Add("Newtonsoft.Json", "11.0.1", new DateTime(2018, 2, 17));
            dt.Rows.Add("Newtonsoft.Json", "10.0.3", new DateTime(2017, 6, 18));

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

            Console.WriteLine(json);
            // [
            //   {
            //     "PackageId": "Newtonsoft.Json",
            //     "Version": "11.0.1",
            //     "ReleaseDate": "2018-02-17T00:00:00"
            //   },
            //   {
            //     "PackageId": "Newtonsoft.Json",
            //     "Version": "10.0.3",
            //     "ReleaseDate": "2017-06-18T00:00:00"
            //   }
            // ]

            StringAssert.AreEqual(@"[
  {
    ""PackageId"": ""Newtonsoft.Json"",
    ""Version"": ""11.0.1"",
    ""ReleaseDate"": ""2018-02-17T00:00:00""
  },
  {
    ""PackageId"": ""Newtonsoft.Json"",
    ""Version"": ""10.0.3"",
    ""ReleaseDate"": ""2017-06-18T00:00:00""
  }
]", json);
        }
#endif

        [Test]
        public void JsonPathRegex()
        {
            JArray packages = JArray.Parse(@"[
              {
                ""PackageId"": ""Newtonsoft.Json"",
                ""Version"": ""11.0.1"",
                ""ReleaseDate"": ""2018-02-17T00:00:00""
              },
              {
                ""PackageId"": ""NUnit"",
                ""Version"": ""3.9.0"",
                ""ReleaseDate"": ""2017-11-10T00:00:00""
              }
            ]");

            List<JToken> newtonsoftPackages = packages.SelectTokens(@"$.[?(@.PackageId =~ /^Newtonsoft\.(.*)$/)]").ToList();

            Console.WriteLine(newtonsoftPackages.Count);
            // 1

            Assert.AreEqual(1, newtonsoftPackages.Count);
        }

#if !(NET20 || NET35 || NET40 || PORTABLE || PORTABLE40 || DNXCORE50) || NETSTANDARD2_0 || NET6_0_OR_GREATER
        [Test]
        public async Task AsyncDemo()
        {
            JArray largeJson;

            // read asynchronously from a file
            using (TextReader textReader = new StreamReader(new FileStream(ResolvePath(@"large.json"), FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)))
            {
                largeJson = await JArray.LoadAsync(new JsonTextReader(textReader));
            }

            JToken user = largeJson.SelectToken("$[?(@.name == 'Woodard Caldwell')]");
            user["isActive"] = false;

            // write asynchronously to a file
            using (TextWriter textWriter = new StreamWriter(new FileStream(ResolvePath(@"large.json"), FileMode.Open, FileAccess.Write, FileShare.Write, 4096, true)))
            {
                await largeJson.WriteToAsync(new JsonTextWriter(textWriter));
            }
        }
#endif
    }

#if !(NET20 || NET35 || NET40 || PORTABLE || PORTABLE40 || DNXCORE50) || NETSTANDARD2_0 || NET6_0_OR_GREATER
    public class JsonArrayPool : IArrayPool<char>
    {
        public static readonly JsonArrayPool Instance = new JsonArrayPool();

        public char[] Rent(int minimumLength)
        {
            // use System.Buffers shared pool
            return ArrayPool<char>.Shared.Rent(minimumLength);
        }

        public void Return(char[] array)
        {
            // use System.Buffers shared pool
            ArrayPool<char>.Shared.Return(array);
        }
    }
#endif
}