/*
 * JsonParser.cs
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2013-2020 Apple Inc. and the FoundationDB project authors
 *
 * 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.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Linq;

namespace Magnesium
{
	public static class JsonParser
	{
		static Random r = new Random();

		// dummy parameter nonFatalParseError to match xml
		public static IEnumerable<Event> Parse(System.IO.Stream stream, string file,
			bool keepOriginalElement = false, double startTime = -1, double endTime = Double.MaxValue,
			double samplingFactor = 1.0, Action<string> nonFatalErrorMessage = null)
		{
			using (var reader = new System.IO.StreamReader(stream))
			{
				string line;
				while((line = reader.ReadLine()) != null)
				{
					XElement root = XElement.Load(JsonReaderWriterFactory.CreateJsonReader(new MemoryStream(Encoding.UTF8.GetBytes(line)), new XmlDictionaryReaderQuotas()));
					Event ev = null;
					try
					{
						ev = ParseEvent(root, file, keepOriginalElement, startTime, endTime, samplingFactor);
					}
					catch (Exception e)
					{
						throw new Exception(string.Format("Failed to parse JSON {0}", root), e);
					}
					if (ev != null) yield return ev;
				}
			}
		}

		private static Event ParseEvent(XElement xEvent, string file, bool keepOriginalElement, double startTime, double endTime, double samplingFactor)
		{
			if (samplingFactor != 1.0 && r.NextDouble() > samplingFactor)
				return null;

			XElement trackLatestElement = xEvent.XPathSelectElement("//TrackLatestType");
			bool rolledEvent = trackLatestElement != null && trackLatestElement.Value.Equals("Rolled");
			String timeElement = (rolledEvent) ? "OriginalTime" : "Time";
			double eventTime = double.Parse(xEvent.XPathSelectElement("//" + timeElement).Value);
			
			if (eventTime < startTime || eventTime > endTime)
				return null;

			return new Event {
				Severity = (Severity)int.Parse(xEvent.XPathSelectElement("//Severity").ValueOrDefault("40")),
				Type = string.Intern(xEvent.XPathSelectElement("//Type").Value),
				Time = eventTime,
				Machine = string.Intern(xEvent.XPathSelectElement("//Machine").Value),
				ID = string.Intern(xEvent.XPathSelectElement("//ID").ValueOrDefault("0")),
				TraceFile = file,
				DDetails = xEvent.Elements()
					.Where(a=>a.Name != "Type" && a.Name != "Time" && a.Name != "Machine" && a.Name != "ID" && a.Name != "Severity" && (!rolledEvent || a.Name != "OriginalTime"))
					.ToDictionary(a=>string.Intern(a.Name.LocalName), a=>(object)a.Value),
				original = keepOriginalElement ? xEvent : null
			};
		}

		private static string ValueOrDefault( this XElement attr, string def ) {
			if (attr == null) return def;
			else return attr.Value;
		}
	}
}
