using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows.Forms;
using NU.OJL.MPRTOS.TLV.Base;
using NU.OJL.MPRTOS.TLV.Base.Controls;
using NU.OJL.MPRTOS.TLV.Core.Controls;
using NU.OJL.MPRTOS.TLV.Core.Controls.Forms;

namespace NU.OJL.MPRTOS.TLV.Core
{
	public class EnvironmentFileContext : IFileContext
	{
		string[] visualizeRuleFilePaths = Directory.GetFiles(ApplicationData.Setting.VisualizeRulesDirectoryPath, "*." + /*とりあえず*/"viz");
		private IEnvironment m_Environment;
		private string m_Path;
		private TraceLogVisualizerData m_cftl;
		private TraceLogVisualizerData m_FileContextData;
		private ResourceData m_ResourceData;
		private SettingData m_SettingData;
		private VisualizeData m_VisualizeData;
		private Dictionary<string, LogData> m_LastLogs;
		private TraceLogData m_TraceLogData;
		private VisualizeShapeData m_VisualizeShapeData;
		private bool m_IsOpened;
		private bool m_IsSaved;
		private BackGroundWorkForm m_PrepareBw;
		private BackGroundWorkForm m_ConvertBw;

		public EnvironmentFileContext(IEnvironment environment)
		{
			m_Environment = environment;
			m_Environment.Terminated += new EventHandler(UnitExit);
			m_cftl = new TraceLogVisualizerData();

			string[] names = m_Environment.GetToolStripInfos();

			ApplicationFactory.AddToolStripButtons(names, m_Environment.CreateToolStripButtons);
		}

		public IEnvironment Environment { get { return m_Environment; } }

		public void Prepare()
		{
			m_PrepareBw = new BackGroundWorkForm();
			m_PrepareBw.Text = "共通形式トレースログへ変換中";
			m_PrepareBw.StartPosition = FormStartPosition.CenterParent;
			m_PrepareBw.DoWork += new DoWorkEventHandler(PrepareBw_DoWork);
			m_PrepareBw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(PrepareBw_RunWorkerCompleted);
			m_PrepareBw.RunWorkerAsync();
		}

		private void PrepareBw_DoWork(object sender, DoWorkEventArgs e)
		{
			GenerateData(
				delegate() { m_Environment.Initialize(); },
				10,
				"シミュレーション環境を初期化中",
				"シミュレーション環境の初期化に失敗しました。");

			GenerateData(
				delegate() { m_ResourceData = GetResourceData(); },
				30,
				"リソースデータを生成中",
				"リソースデータの生成に失敗しました。\nリソースファイルの記述に誤りがある可能性があります。");

			GenerateData(
				delegate() { m_SettingData = new SettingData(m_cftl); },
				50,
				"設定データを生成中",
				"設定データの生成に失敗しました。");

			GenerateData(
				delegate() { m_VisualizeData = GetVisualizeData(visualizeRuleFilePaths); },
				70,
				"可視化データを生成中",
				"可視化データの生成に失敗しました。\n可視化ルールファイルの記述に誤りがある可能性があります。");

			GenerateData(
				delegate() { MakeRule(); },
				90,
				"変換ルールを生成中",
				"変換ルールの生成に失敗しました。");

			GenerateData(
				delegate() { m_PrepareBw.Invoke(new MethodInvoker(delegate() { CreateInitalViews(); })); },
				100,
				"シミュレーション環境用画面を生成中",
				"シミュレーション環境用画面を生成に失敗しました。");

			ConstructProgressReport(m_PrepareBw, 100, "完了");
		}

		void PrepareBw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			m_IsOpened = true;
			if (IsOpenedChanged != null)
				IsOpenedChanged(this, new GeneralEventArgs<bool>(true));

			if (Opened != null)
				Opened(this, EventArgs.Empty);
		}

		private void GenerateData(Action action, int progress, string message, string exceptionMessage)
		{
			ConstructProgressReport(m_PrepareBw, progress, message);
			try
			{
				action();
			}
			catch (ConvertException e)
			{
				throw e;
			}
			catch (Exception e)
			{
				foreach (System.Collections.DictionaryEntry de in e.Data)
				{
					exceptionMessage += "\n" + de.Value;
				}
				throw new Exception(exceptionMessage + "\n" + e.Message);
			}
		}

		private ResourceData GetResourceData()
		{
			ResourceData resData = ApplicationFactory.JsonSerializer.Deserialize<ResourceData>(m_Environment.TraceLogResource);

			// 未記述の属性についてデフォルト値で追加する
			foreach (ResourceType resType in resData.ResourceHeaders.ResourceTypes)
			{
				foreach (Resource res in Where<Resource>(resData.Resources, delegate(Resource r) { return r.Type == resType.Name; }))
				{
					foreach (AttributeType attrType in resType.Attributes)
					{
						if (!res.Attributes.ContainsKey(attrType.Name))
						{
							res.Attributes.Add(attrType.Name, attrType.Default);
						}
					}
				}
			}
			return resData;
		}

		private VisualizeData GetVisualizeData(string[] visualizeRuleFilePaths)
		{
			VisualizeData visualizeData = new VisualizeData();
			visualizeData.VisualizeRules = new GeneralNamedCollection<VisualizeRule>();
			visualizeData.Shapes = new GeneralNamedCollection<Shapes>();

			string[] target = m_ResourceData.VisualizeRules.ToArray();

			// ファイルが複数ある場合を想定している
			foreach (string s in visualizeRuleFilePaths)
			{
				Json json = JsonExtension.Parse(new Json(), File.ReadAllText(s));
				foreach (KeyValuePair<string, Json> j in json.GetKeyValuePairEnumerator())
				{
					if (Contains<string>(target, j.Key))
					{
						string d = ApplicationFactory.JsonSerializer.Serialize(j.Value);
						VisualizeData vizData = ApplicationFactory.JsonSerializer.Deserialize<VisualizeData>(d);

						if (vizData.VisualizeRules != null)
							foreach (VisualizeRule vizRule in vizData.VisualizeRules)
							{
								visualizeData.VisualizeRules.Add(vizRule);
							}

						if (vizData.Shapes != null)
							foreach (Shapes sp in vizData.Shapes)
							{
								visualizeData.Shapes.Add(sp);
							}
					}
				}
			}

			return visualizeData;
		}

		private EventShapes GenerateByNewRule(VisualizeRule rule)
		{
			ProcessStartInfo psi;
			if (rule.Script != null)
			{
				string path = System.IO.Path.GetTempFileName();
				StreamWriter sw = new StreamWriter(new FileStream(path, FileMode.Create));

				string script = rule.Script;
				sw.Write(script);
				sw.Close();
				psi = new ProcessStartInfo(rule.FileName, string.Format(rule.Arguments, path));
			}
			else
			{
				psi = new ProcessStartInfo(rule.FileName, rule.Arguments);
			}
			psi.UseShellExecute = false;
			psi.RedirectStandardInput = true;
			psi.RedirectStandardOutput = true;
			psi.RedirectStandardError = true;

			Process p = new Process();
			p.StartInfo = psi;
			string AppPath = System.Windows.Forms.Application.StartupPath;
			p.StartInfo.WorkingDirectory = AppPath;

			string json = "";

			p.Start();
			p.StandardInput.WriteLine(m_ResourceData.ToJson());
			p.StandardInput.WriteLine("---");
			foreach (TraceLog log in m_TraceLogData.TraceLogs)
			{
				p.StandardInput.WriteLine(log.ToString());
			}

			p.StandardInput.Close();

			while (!(p.HasExited && p.StandardOutput.EndOfStream))
			{
				json += p.StandardOutput.ReadLine();
			}

			if (p.ExitCode != 0)
			{
				string error = "";
				while (!p.StandardError.EndOfStream)
				{
					error += p.StandardError.ReadLine() + "\n";
				}
				throw new Exception(error);
			}
			p.Close();

			EventShapes es = new EventShapes();
			List<EventShape> shapes = ApplicationFactory.JsonSerializer.Deserialize<List<EventShape>>(json);

			// FIXME: 古いルールと整合性を保つためにrule.Shapesを作成する
			// Shapesに代入されたEventはNameしか使われないと仮定してる
			rule.Shapes = new GeneralNamedCollection<Event>();
			foreach (EventShape shape in shapes)
			{
				shape.Event.SetVisualizeRuleName(rule.Name);
				es.Add(shape);
				if (!Contains<string>(rule.Shapes.Keys, shape.Event.Name))
				{
					rule.Shapes.Add(shape.Event);
				}
			}
			return es;
		}

		class RuleData
		{
			public string Key;
			public Json Value;
			private Regex m_Pattern;

			public RuleData(string Key, Json Value)
			{
				this.Key = Key;
				this.Value = Value;
				m_Pattern = new Regex(Key, RegexOptions.Compiled);
			}

			public Regex Pattern { get { return m_Pattern; } }

			public bool IsMatch(string s)
			{
				return m_Pattern.IsMatch(s);
			}
		}

		LinkedList<RuleData> oldRule = new LinkedList<RuleData>();
		Json newRule = null;

		public void MakeRule()
		{
			StreamWriter writer = new StreamWriter("testOutput.txt");

			string[] target = m_ResourceData.ConvertRules.ToArray();

			string[] convertRulePaths = Directory.GetFiles(ApplicationData.Setting.ConvertRulesDirectoryPath, "*." + /*とりあえず*/"cnv");
			// トレースログ変換ファイルを開きJsonValueでデシリアライズ
			// ファイルが複数ある場合を想定している
			foreach (string s in convertRulePaths)
			{
				Json json = JsonExtension.Parse(new Json(), File.ReadAllText(s));
				foreach (KeyValuePair<string, Json> j in json.GetKeyValuePairEnumerator())
				{
					if (Contains<string>(target, j.Key))
					{
						if (j.Value.ContainsKey("$STYLE"))
						{
							if (newRule == null)
							{
								newRule = j.Value;
							}
							else
							{
								throw new Exception("複数の新形式変換ルールが存在します");
							}
						}
						else
						{
							foreach (KeyValuePair<string, Json> _j in j.Value.GetKeyValuePairEnumerator())
							{
								oldRule.AddLast(new RuleData(_j.Key, _j.Value));
								writer.WriteLine(_j.Key);
							}
						}
					}
				}
			}
			writer.Close();

			if (newRule != null && oldRule.Count > 0)
			{
				throw new Exception("新形式変換ルールと旧形式変換ルールは混在させることはできません");
			}
			else if ((oldRule.Count == 0) && (newRule == null))
			{
				throw new Exception(target.ToString() + " に一致する変換ルールが見つかりません。");
			}
		}

		private TraceLogData GetTraceLogData(int from, int to)
		{
			if (oldRule.Count > 0)
			{
				return GenerateByOldRule(oldRule, from, to);
			}
			else if (newRule != null)
			{
				return GenerateByNewRule(newRule);
			}

			return null;
		}

		private TraceLogData GenerateByNewRule(Json rule)
		{
			ProcessStartInfo psi;
			if (rule.ContainsKey("script"))
			{
				string path = System.IO.Path.GetTempFileName();
				StreamWriter sw = new StreamWriter(new FileStream(path, FileMode.Create));

				string script = rule["script"];
				sw.Write(script);
				sw.Close();
				psi = new ProcessStartInfo(rule["fileName"],
										   string.Format(rule["arguments"], path));
			}
			else
			{
				psi = new ProcessStartInfo(rule["fileName"], rule["arguments"]);
			}
			psi.UseShellExecute = false;
			psi.RedirectStandardInput = true;
			psi.RedirectStandardOutput = true;

			Process p = Process.Start(psi);

			foreach (string log in m_Environment.TraceLog)
			{
				p.StandardInput.WriteLine(log);
			}
			p.WaitForExit();

			TraceLogData t = new TraceLogData(m_ResourceData);
			while (!p.StandardOutput.EndOfStream)
			{
				t.Add(new TraceLog(p.StandardOutput.ReadLine()));
			}
			return t;
		}

		private TraceLogData GenerateByOldRule(LinkedList<RuleData> dic, int from, int to)
		{
			TraceLogData t = new TraceLogData(m_ResourceData);

			// トレースログを一行ずつ調べTraceLogクラスに変換しTraceLogListに追加していく
			float i = 1;
			float max = m_Environment.TraceLog.Count;

			using (System.Threading.Timer timer = new System.Threading.Timer(new System.Threading.TimerCallback(delegate(object sender)
				{
					ConstructProgressReport(m_ConvertBw, (int)(((i / max) * (float)(to - from)) + (float)from), "トレースログを共通形式へ変換中 " + i + "/" + max + " 行目...");
				}), this, 0, 500))
			{
				foreach (string s in m_Environment.TraceLog)
				{
					foreach (RuleData kvp in dic)
					{
						if (kvp.IsMatch(s))
						{
							AddTraceLog(s, kvp.Pattern, kvp.Value, t);
						}
					}
					i++;
				}
			}

			return t;
		}

		private void SetLastLogs()
		{
			m_LastLogs = new Dictionary<string, LogData>();
			foreach (Resource res in m_ResourceData.Resources)
			{
				for (int i = m_TraceLogData.LogDataBase.Count - 1; i >= 0; i--)
				{
					if (!res.Name.Contains("Current") && m_TraceLogData.LogDataBase[i].Object.Name.Equals(res.Name))
					{
						m_LastLogs.Add(res.Name, m_TraceLogData.LogDataBase[i]);
						break;
					}
				}
			}
		}

		/// <summary>
		/// 読み込んだログがパターンにマッチした場合に変換してログを追加する
		/// </summary>
		/// <param name="log">読み込むログ</param>
		/// <param name="pattern">パターン</param>
		/// <param name="value">変換値がValue（Jsonでいうところの）</param>
		/// <param name="traceLogManager">追加先</param>
		private void AddTraceLog(string log, Regex pattern, Json value, TraceLogData traceLogData)
		{
			if (value.IsArray)
			{
				AddTraceLogAsArray(log, pattern, value, traceLogData);
			}
			else if (value.IsObject)
			{
				AddTraceLogAsObject(log, pattern, value, traceLogData);
			}
			else
			{
				// valueがstringのときログを置換して追加
				string s = pattern.Replace(log, value);
				// 関数を適用
				s = TLVFunction.Apply(s, m_ResourceData, traceLogData);
				// ログを追加
				try
				{
					traceLogData.Add(new TraceLog(s));
				}
				catch (Exception e)
				{
					e.Data.Add("log", log.ToString());
					e.Data.Add("pattern", pattern.ToString());
					e.Data.Add("jsonvalue", value.ToString());
					throw (e);
				}
			}
		}

		/// <summary>
		/// 読み込んだログがパターンにマッチした場合に変換してログを追加する
		/// </summary>
		/// <param name="log">読み込むログ</param>
		/// <param name="pattern">パターン</param>
		/// <param name="value">変換値がArray（Jsonでいうところの）</param>
		/// <param name="traceLogManager">追加先</param>
		private void AddTraceLogAsArray(string log, Regex pattern, List<Json> value, TraceLogData traceLogData)
		{
			foreach (Json j in value)
			{
				AddTraceLog(log, pattern, j, traceLogData);
			}
		}

		/// <summary>
		/// 読み込んだログがパターンにマッチした場合に変換してログを追加する
		/// </summary>
		/// <param name="log">読み込むログ</param>
		/// <param name="condition">パターン</param>
		/// <param name="value">変換値がObject（Jsonでいうところの）</param>
		/// <param name="traceLogManager">追加先</param>
		private void AddTraceLogAsObject(string log, Regex pattern, Dictionary<string, Json> value, TraceLogData traceLogData)
		{
			foreach (KeyValuePair<string, Json> kvp in value)
			{
				string condition = pattern.Replace(log, kvp.Key);

				// 条件に関数を適用
				condition = TLVFunction.Apply(condition, m_ResourceData, traceLogData);

				// 条件式を評価
				bool result;
				try
				{
					result = ConditionExpression.Result(condition);
				}
				catch (Exception e)
				{
					throw new Exception("ログ条件式が異常です。\n" + "\"" + kvp.Key + "\"\n" + e.Message);
				}

				// 条件式が真ならトレースログを追加
				if (result)
				{
					AddTraceLog(log, pattern, kvp.Value, traceLogData);
				}
			}
		}

		private VisualizeShapeData GetVisualizeShapeData(int from, int to)
		{
			VisualizeShapeData vizData = new VisualizeShapeData();
			int count = m_VisualizeData.VisualizeRules.Count;
			double current = 0.0;
			foreach (VisualizeRule rule in m_VisualizeData.VisualizeRules)
			{
				ConstructProgressReport(m_ConvertBw, (int)((current / count) * (to - from) + from), rule.DisplayName);
				current += 1.0;
				if (rule.IsBelongedTargetResourceType())
				{
					foreach (Resource res in Where<Resource>(m_ResourceData.Resources, delegate(Resource r) { return r.Type == rule.Target; }))
					{
						if (rule.Style != "Script")
						{
							foreach (Event e in rule.Shapes)
							{
								e.SetVisualizeRuleName(rule.Name);
							}
							EventShapesGenerator gen = new EventShapesGenerator(rule, res);
							gen.SetData(m_TraceLogData, m_VisualizeData, m_ResourceData, m_LastLogs);
							vizData.Add(rule, res, gen.GetEventShapes());
						}
						else
						{
							vizData.Add(rule, res, GenerateByNewRule(rule));
						}
					}
				}
				else
				{
					if (rule.Style != "Script")
					{
						foreach (Event e in rule.Shapes)
						{
							e.SetVisualizeRuleName(rule.Name);
						}
						EventShapesGenerator gen = new EventShapesGenerator(rule);
						gen.SetData(m_TraceLogData, m_VisualizeData, m_ResourceData, m_LastLogs);
						vizData.Add(rule, gen.GetEventShapes());
					}
					else
					{
						vizData.Add(rule, GenerateByNewRule(rule));
					}
				}
			}

			return vizData;
		}

		#region IFileContext メンバ

		public event EventHandler Saving;

		public void Close()
		{
			if (m_Environment.IsRunning)
				m_Environment.Terminate();
			m_IsSaved = true;
		}

		public IFileContextData Data
		{
			get { return m_FileContextData; }
			set { }
		}

		public event EventHandler<GeneralEventArgs<IFileContextData>> DataChanged;

		public bool IsOpened
		{
			get { return m_IsOpened; }
		}

		public event EventHandler<GeneralEventArgs<bool>> IsOpenedChanged;

		public bool IsSaved
		{
			get { return m_IsSaved; }
		}

		public event EventHandler<GeneralEventArgs<bool>> IsSavedChanged;

		public void Open(string path)
		{
			m_Path = path;
			m_Environment.Start();
		}

		public string Path
		{
			get { return m_Path; }
			set { m_Path = value; }
		}

		public event EventHandler<GeneralEventArgs<string>> PathChanged;

		public event EventHandler Saved;

		public event EventHandler Opened;

		public void Save()
		{
			if (!m_IsOpened)
				throw new FilePathUndefinedException("保存するデータがありません。");

			if (m_Path == string.Empty)
				throw new FilePathUndefinedException("保存先のパスが未設定です。");

			if (Saving != null)
				Saving(this, EventArgs.Empty);

			try
			{
				m_FileContextData.Serialize(Path);
			}
			catch (Exception e)
			{
				throw new Exception("ファイルの保存中にエラーが発生しました。\n" + e.Message);
			}

			m_IsSaved = true;
			if (IsSavedChanged != null)
				IsSavedChanged(this, new GeneralEventArgs<bool>(true));

			if (Saved != null)
				Saved(this, EventArgs.Empty);
		}

		public void SaveAs(string path)
		{
			if (m_Path != path)
			{
				m_Path = path;
				if (PathChanged != null)
					PathChanged(this, new GeneralEventArgs<string>(path));
			}

			Save();
		}

		#endregion

		private void UnitExit(object o, EventArgs e)
		{
			m_ConvertBw = new BackGroundWorkForm();
			m_ConvertBw.Text = "共通形式トレースログへ変換中";
			m_ConvertBw.StartPosition = FormStartPosition.CenterParent;
			m_ConvertBw.DoWork += new DoWorkEventHandler(ConvertBw_DoWork);
			m_ConvertBw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ConvertBw_RunWorkerCompleted);
			ApplicationFactory.WindowManager.Parent.BeginInvoke(new MethodInvoker(delegate()
			{
				m_ConvertBw.RunWorkerAsync();
			}));
		}

		void ConvertBw_DoWork(object sender, DoWorkEventArgs e)
		{
			m_ConvertBw.Tag = e;
			try
			{
				m_IsSaved = false;
				if (IsSavedChanged != null)
					IsSavedChanged(this, new GeneralEventArgs<bool>(false));

				m_TraceLogData = GetTraceLogData(0, 60);

				SetLastLogs();

				m_VisualizeShapeData = GetVisualizeShapeData(60, 80);

				if (m_ConvertBw.CancellationPending) { e.Cancel = true; return; }
				m_ConvertBw.ReportProgress(90);
				m_ConvertBw.Invoke(new MethodInvoker(delegate() { m_ConvertBw.Message = "共通形式データを生成中"; }));

				m_cftl.Initialize(m_ResourceData, m_TraceLogData, m_VisualizeData, m_SettingData, m_VisualizeShapeData);

				if (m_ConvertBw.CancellationPending) { e.Cancel = true; return; }
				m_ConvertBw.ReportProgress(100);
				m_ConvertBw.Invoke(new MethodInvoker(delegate() { m_ConvertBw.Message = "完了"; }));

				if (m_cftl.SettingData.TraceLogDisplayPanelSetting.TimeLine == null)
					m_cftl.SettingData.TraceLogDisplayPanelSetting.TimeLine = new TimeLine(m_cftl.TraceLogData.MinTime, m_cftl.TraceLogData.MaxTime);

				m_FileContextData = m_cftl;
			}
			catch (Exception excpt)
			{
				MessageForm mbox = new MessageForm(excpt.ToString(), "変換に失敗しました。");
				mbox.ShowDialog();
				e.Cancel = true;
				return;
			}
		}

		void ConvertBw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (!e.Cancelled)
			{
				//_setDataBw.RunWorkerAsync();
				if (DataChanged != null)
					DataChanged(this, new GeneralEventArgs<IFileContextData>(m_cftl));
			}
		}

		private void ConstructProgressReport(BackGroundWorkForm bw, int p, string s)
		{
			DoWorkEventArgs e = (DoWorkEventArgs)bw.Tag;
			if (bw.CancellationPending) { e.Cancel = true; return; }
			bw.ReportProgress(p);
			bw.Invoke(new MethodInvoker(delegate() { bw.Message = s; }));
		}

		private void CreateInitalViews()
		{
			ISubWindow[] views = m_Environment.CreateInitialViews();
			if (views != null)
			{
				foreach (ISubWindow view in views)
				{
					ApplicationFactory.SetFileContext(this, view.Form);
				}
			}

			m_Environment.InitializeSettings();
		}

		private static IEnumerable<T> Where<T>(IEnumerable<T> objs, Func<T, bool> p)
		{
			List<T> result = new List<T>();
			foreach (T obj in objs)
			{
				if (p(obj))
					result.Add(obj);
			}
			return result;
		}

		private static bool Contains<T>(IEnumerable<T> objs, T p)
		{
			foreach (T obj in objs)
			{
				if (p.Equals(obj))
					return true;
			}
			return false;
		}
	}
}
