using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//todo - be able to run out of PATH too

namespace BizHawk.Bizware.BizwareGL
{
	public class CGC
	{
		public CGC()
		{
		}

		public static string CGCBinPath;

		private static string[] Escape(IEnumerable<string> args)
		{
			return args.Select(s => s.Contains(" ") ? string.Format("\"{0}\"", s) : s).ToArray();
		}

		public class Results
		{
			public bool Succeeded;
			public string Code, Errors;
			public Dictionary<string, string> MapCodeToNative = new Dictionary<string, string>();
			public Dictionary<string, string> MapNativeToCode = new Dictionary<string, string>();
		}

		public Results Run(string code, string entry, string profile)
		{
			//version=110; GLSL generates old fashioned semantic attributes and not generic attributes
			string[] args = new[]{"-profile", profile, "-entry", entry, "-po", "version=110"};

			args = Escape(args);
			StringBuilder sbCmdline = new StringBuilder();
			for (int i = 0; i < args.Length; i++)
			{
				sbCmdline.Append(args[i]);
				if (i != args.Length - 1) sbCmdline.Append(' ');
			}

			//http://stackoverflow.com/questions/139593/processstartinfo-hanging-on-waitforexit-why
			using (Process proc = new Process())
			{
				proc.StartInfo.UseShellExecute = false;
				proc.StartInfo.CreateNoWindow = true;
				proc.StartInfo.RedirectStandardInput = true;
				proc.StartInfo.RedirectStandardOutput = true;
				proc.StartInfo.RedirectStandardError = true;
				proc.StartInfo.Arguments = sbCmdline.ToString();
				proc.StartInfo.FileName = CGCBinPath;

				StringBuilder output = new StringBuilder(), error = new StringBuilder();

				using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
				using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
				{
					proc.OutputDataReceived += (sender, e) =>
					{
						if (e.Data == null) outputWaitHandle.Set();
						else output.AppendLine(e.Data);
					};
					proc.ErrorDataReceived += (sender, e) =>
					{
						if (e.Data == null) errorWaitHandle.Set();
						else error.AppendLine(e.Data);
					};


					proc.Start();
					new Thread(() =>
					{
						proc.StandardInput.AutoFlush = true;
						proc.StandardInput.Write(code);
						proc.StandardInput.Flush();
						proc.StandardInput.Close();
					}).Start();

					proc.BeginOutputReadLine();
					proc.BeginErrorReadLine();
					proc.WaitForExit();
					outputWaitHandle.WaitOne();
					errorWaitHandle.WaitOne();
				}

				bool ok = (proc.ExitCode == 0);

				var ret = new Results()
				{
					Succeeded = ok,
					Code = output.ToString(),
					Errors = error.ToString()
				};

				if (!ok)
					Console.WriteLine(ret.Errors);

				//make variable name map
				//loop until the first line that doesnt start with a comment
				var reader = new StringReader(ret.Code);
				for(;;)
				{
					var line = reader.ReadLine();
					if (line == null) break;
					if (!line.StartsWith("//")) break;
					if (!line.StartsWith("//var")) continue;
					var parts = line.Split(':');
					var native_name = parts[0].Split(' ')[2];
					var code_name = parts[1].Trim();
					if (code_name.StartsWith("TEXUNIT")) code_name = ""; //need parsing differently
					if (code_name == "")
						code_name = parts[2].Trim();
					//remove some array indicators. example: `modelViewProj1[0], 4`
					code_name = code_name.Split(',')[0];
					code_name = code_name.Split(' ')[0];
					if (code_name != "")
					{
						ret.MapCodeToNative[code_name] = native_name;
						ret.MapNativeToCode[native_name] = code_name;
					}
				}

				return ret;
			}

		}
	}
}
