using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Specialized;

namespace Icarus.Scripting
{
	/// <summary>
	/// Enumerates all the languages that can be used for scripting.
	/// </summary>
	/// <remarks>
	/// If more languages are added here, please modify ScriptingProvider.InitializeCodeCompilerFor function to get the proper compiler for the new language.
	/// </remarks>
	/// 
	public enum Language 
	{
        /// <summary>
        /// Represents the C# Programming language as being readable by the Scripting Engine
        /// </summary>
		CSharp,
        /// <summary>
        /// Represents the JScript Programming language as being readable by the Scripting Engine
        /// </summary>
		JScript,
        /// <summary>
        /// Represents the Visual Basic Programming language as being readable by the Scripting Engine
        /// </summary>
		VisualBasic
	}

	/// <summary>
	/// This exception is thrown when we get an unknown file extension, or a bad language.
	/// </summary>
	public class UnknownLanguageException : System.Exception 
	{
        /// <summary>
        /// Delegates Exception handing
        /// </summary>
        /// <param name="text">The text description of the error</param>
		public UnknownLanguageException(string text) : base(text) { }
	}

	/// <summary>
	/// The scripting provider class will handle providing scripting engines to the rest of the scripting module. It handles various code providers in such a way that they don't have to be reinitialized every time we want to use scripting.
	/// </summary>
	public class ScriptingProvider
	{
		/// <summary>
		/// Singleton Implementation.
		/// </summary>
		private static ScriptingProvider mg_Instance = new ScriptingProvider();

		/// <summary>
		/// This dictionary maps Language enum items to the code providers. 
		/// </summary>
		private IDictionary m_CodeProviders;

		/// <summary>
		/// This is a dictionary of templates to use for wrapping around the executable code compiled for a specific language.
		/// </summary>
		private IDictionary m_ExecutableCodeTemplates;

		/// <summary>
		/// Maps file extensions to language that they correspond to.
		/// </summary>
		private IDictionary m_LanguageExtensions;

		/// <summary>
		/// Singleton Implementation.
		/// </summary>
		private ScriptingProvider() 
		{
			//We are using List Dictionary here, because the documentation says that is the recommended implementation for lists that have 10 items or less. We surely support under 10 languages normally.
			m_CodeProviders = new ListDictionary();
			m_ExecutableCodeTemplates = new ListDictionary();
			InitializeLanguageExtensions();
		}
		
		/// <summary>
		/// Singleton Implementation.
		/// </summary>
		public static ScriptingProvider Instance 
		{
			get { return mg_Instance; }
		}

		/// <summary>
		/// Returns the code provider for the specified language.
		/// </summary>
		public ICodeCompiler GetCodeCompilerFor(Language lang) 
		{
			ICodeCompiler result = (ICodeCompiler) m_CodeProviders[lang];

			if (result == null) 
			{
				InitializeCodeCompilerFor(lang);
				result = (ICodeCompiler) m_CodeProviders[lang];
			}

			return result;
		}

		/// <summary>
		/// Returns a template, so that when we perform a string format on it with one parameter, the resulting code is wrapped in whatever it needs to be wrapped in for the compiler to generate an executable.
		/// </summary>
		public string GetExecutableTemplateFor(Language lang) 
		{
			string result = (string) m_ExecutableCodeTemplates[lang];

			if (result == null) 
			{
				InitializeCodeCompilerFor(lang);
				result = (string) m_ExecutableCodeTemplates[lang];
			}

			return result;
		}

		/// <summary>
		/// Given the extension of a file, returns the language that the file's script is written in.
		/// </summary>
		public Language GetLanguageForExtension(string s) 
		{
			s = s.ToLower();

			if (!m_LanguageExtensions.Contains(s)) 
			{
				throw new UnknownLanguageException(string.Format("Unknown script extension \"{0}\"", s));
			}
			else 
			{
				return (Language) m_LanguageExtensions[s];
			}
		}

		/// <summary>
		/// Generates a code provider for the specified language and registers it in our local dictionary.
		/// </summary>
		private void InitializeCodeCompilerFor(Language lang) 
		{
			CodeDomProvider provider = null;
			string template = null;

			switch (lang) 
			{
				case Language.CSharp:
					provider = new Microsoft.CSharp.CSharpCodeProvider();
					template
						= "using System;\r\n" +
						"namespace Icarus.TempScript {{\r\n" +
						"public class TemporaryClass {{\r\n" +
						"public static void Main(string[] args) {{\r\n" +
						"{0}\r\n" +
						"}}}}}}";
					break;

				case Language.JScript:
					provider = new Microsoft.JScript.JScriptCodeProvider();
					template = "import System;\r\n {0}\r\n";
					break;

				case Language.VisualBasic:
					provider = new Microsoft.VisualBasic.VBCodeProvider();
					break;
			}

			m_CodeProviders[lang] = provider.CreateCompiler();
			m_ExecutableCodeTemplates[lang] = template;
		}

		private void InitializeLanguageExtensions() 
		{
			m_LanguageExtensions = new ListDictionary();
			m_LanguageExtensions["cs"] = Language.CSharp;
			m_LanguageExtensions["js"] = Language.JScript;
			m_LanguageExtensions["vb"] = Language.VisualBasic;
		}
	}
}
