#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Security.Cryptography;


namespace UnityTwine.Editor
{
    public class TweeParser
    {
		public static Dictionary<string, TweeMacroParser> MacroParsers = new Dictionary<string, TweeMacroParser>(StringComparer.OrdinalIgnoreCase);

		static Regex rx_Passages = new Regex(@"^::\s(?<name>[^\]\|\n]+)(\s+\[(?<tags>[^\]]+)\])?\n(?<content>.*?)(?=\n::|\Z)",
            RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.ExplicitCapture);

		static Regex rx_Tags = new Regex(@"([^\s]+)");

        static Regex rx_String;
        static Regex rx_Vars;
        static Regex rx_Link;
        static Regex rx_Macro;

		static MD5 _md5 = MD5.Create();

		class PassageData
		{
			public string Name;
			public string Tags;
			public string Code;
		}

        static TweeParser()
        {
            rx_String = new Regex(
				@"(?!(<<|\[\[))(?<=(>>|\]\]|^))(?<bol>^)?(?<text>.*?)(?<eol>$)?(?=(<<|\[\[|$))(?!(>>|\]\]))",
				RegexOptions.Singleline |RegexOptions.Multiline|RegexOptions.ExplicitCapture|RegexOptions.IgnoreCase
            );
            rx_Vars = new Regex(
                @"\$([a-zA-Z_][a-zA-Z0-9_]*)",
                RegexOptions.Singleline|RegexOptions.Multiline
            );
            rx_Link  = new Regex(
				@"\[\[(?:(?:(?<name>[^|]+?)\s*=\s*)?(?<text>.*?)\|)?(?<passage>.+?)\](?:\[(?<setters>.*?)\])?\]",
                RegexOptions.Singleline|RegexOptions.Multiline|RegexOptions.ExplicitCapture
            );

			rx_Macro = new Regex(
				@"<<\s*(?<macro>[a-z_]+[a-z0-9_]*)(\s+(?<argument>.*?))?\s*>>",
				RegexOptions.Singleline | RegexOptions.Multiline| RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase
			);

			MacroParsers["display"] = TweeBuiltinMacroParsers.Display;
			MacroParsers["set"] = TweeBuiltinMacroParsers.Set;
			MacroParsers["print"] = TweeBuiltinMacroParsers.Print;
			MacroParsers["if"] = TweeBuiltinMacroParsers.IfElse;
			MacroParsers["elseif"] = TweeBuiltinMacroParsers.IfElse;
			MacroParsers["else"] = TweeBuiltinMacroParsers.IfElse;
			MacroParsers["endif"] = TweeBuiltinMacroParsers.IfElse;
        }


		// Instance vars
		Dictionary<string, string> _vars;
		PassageData[] _passages;

        public void ParseToStream(string name, string tweeSource, StreamWriter output)
        {
            tweeSource = Regex.Replace(tweeSource, @"either: ?\((.*)\)", @"<<print either($1)>>");
            tweeSource = Regex.Replace(tweeSource, @":: ([A-Za-z0-9]+)\[([A-Za-z0-9]+)\]", @":: $1 [<tags>$2]");
            Debug.Log(tweeSource);
			// ------------------------------------
			// Step 1: do the parsing

			MatchCollection matches = rx_Passages.Matches(tweeSource);
			_vars = new Dictionary<string, string>();
			_passages = new PassageData[matches.Count];

			for (int i = 0; i < matches.Count; i++)
			{
				Match m = matches[i];

				_passages[i] = new PassageData()
				{
					Name = m.Groups["name"].Value,
					Tags = ParseTags(m.Groups["tags"].Value),
					Code = ParseContent(m.Groups["content"].Value.Trim())
				};

				if (_passages[i].Code == null || _passages[i].Code.Trim().Length == 0)
					_passages[i].Code = "yield break;";
			}
			string[] vars = _vars.Keys.ToArray();

			// ------------------------------------
			// Step 2: generate the code

            output.Write(
@"/*
Generated by UnityTwine on {0}
https://github.com/daterre/UnityTwine
*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityTwine;

public class {1}: TwineStory
{{"
			, DateTime.Now.ToString("G"), name);

			for (int i = 0; i < vars.Length; i++)
				output.Write(@"
	public TwineVar {0};", vars[i]);

			output.Write(@"
	public override TwineVar this[string name]
	{
		get
		{
			switch(name)
			{"
			);
			for (int i = 0; i < vars.Length; i++)
				output.Write(@"
				case ""{0}"": return {0};", vars[i]);

			output.Write(@"
				default: throw new KeyNotFoundException(string.Format(""There is no variable with the name '{0}'."", name));
			}
		}
		set
		{
			switch(name)
			{"
			);
			for (int i = 0; i < vars.Length; i++)
				output.Write(@"
				case ""{0}"": {0} = value; break;", vars[i]);

			output.Write(@"
				default: throw new KeyNotFoundException(string.Format(""There is no variable with the name '{0}'."", name));
			}
		}
	}
");

			output.Write(@"

	void Awake() {
		base.Init();"
			);
            for (int i = 0; i < _passages.Length; i++)
                output.Write(@"
		passageInit_{0}();"
                    , i); 

    		output.Write(@"
	}");

			for (int i = 0; i < _passages.Length; i++)
			{
				PassageData passage = _passages[i];
				output.Write(@"
    
	// .............
	// #{0}: {1}

	void passageInit_{0}()
	{{
		this.Passages[""{1}""] = new TwinePassage(""{1}"", new string[]{{ {2} }}, passageExecute_{0});
	}}

	IEnumerable<TwineOutput> passageExecute_{0}()
	{{
		{3}	
	}}"
				, i, passage.Name, passage.Tags, passage.Code.Replace("\n", "\n\t\t"));
			}

			output.Write(@"

}"
				);
		}

		static string ParseTags(string tweeTags)
        {
			return rx_Tags.Replace(tweeTags, "\"$&\",");
        }

		string ParseContent(string tweeCode)
        {
            string output = tweeCode;

            // Regular text
            output = rx_String.Replace(output, match=>{
                string value = match.Groups["text"].Value;
				bool bol = match.Groups["bol"].Success; // beginning of line
				bool eol = match.Groups["eol"].Success; // end of line
                return
					(bol && eol) || value.Trim().Length > 0 ? 
                   
					// output the string, even empty if its a single line
					String.Format("yield return new TwineText(@\"{0}\");", value
						.Replace("\"", "\"\"")
						.Replace("\n", "\\n")
					) :

					// only beginning or end of line is whitespace, use it as C# whitespace
					value
                ;
            });

			output = rx_Macro.Replace(output, match => {
				string macro = match.Groups["macro"].Value;
				TweeMacroParser parse;
				if (!MacroParsers.TryGetValue(macro, out parse))
					parse = TweeBuiltinMacroParsers.DisplayShorthand;

				return parse( this, macro,
					match.Groups["argument"].Success ?
						match.Groups["argument"].Value :
						null
				);
			});

            // [[kiss = Kiss the girl|scene1_kiss][$girl = 'kissed';]]
            output = rx_Link.Replace(output, match => {
				string passage = match.Groups["passage"].Value;
				string text = match.Groups["text"].Success ? match.Groups["text"].Value : passage;
				string name = match.Groups["name"].Success ? match.Groups["name"].Value : text;
                string setters = match.Groups["setters"].Length > 0 ?
					string.Format("() =>{{ {0}; }}", ParseVars(match.Groups["setters"].Value)) : // stick the setter into a lambda
                    null;
				string settersHash = setters == null ? null : Convert.ToBase64String(_md5.ComputeHash(Encoding.UTF8.GetBytes(setters)));
				return string.Format("yield return new TwineLink(@\"{0}\", @\"{1}\", {2}, {3}, {4});",
					name.Replace("\"", "\"\""),
					text.Replace("\"", "\"\""),
					passage.IndexOf('(') >= 1 ? passage : string.Format("@\"{0}\"", passage.Replace("\"", "\"\"")), // if a peren is present, treat as a function
					setters == null ? "null" : setters,
					setters == null ? "null" : '"' + settersHash + '"'
				);
            });

            return output;
        }

		internal string ParseVars(string expression)
		{
			return rx_Vars.Replace(expression, varName =>
			{
				string val = varName.Groups[1].Value;
				_vars[val] = null; // null because we don't need any value here, just using a dictionary as a lookup
				return val;
			});
		}
            
    }
}
#endif