using System;
using System.Dynamic;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace PGFSharp
{
    /// <summary>
    /// The class for PGF grammars.
    /// </summary>
    public class PGF
    {
        private PGF() { }

        IntPtr _ptr;
        NativeGU.NativeMemoryPool pool;

        /// <summary>
        /// Reads a grammar with the specified file path.
        /// </summary>
        /// <param name="fn">The path to the file.</param>
        /// <returns>an object representing the grammar in memory.</returns>
        public static PGF ReadPGF(string fn)
        {
            var obj = new PGF();
            var exn = new NativeGU.NativeExceptionContext(new NativeGU.NativeMemoryPool());
            obj.pool = new NativeGU.NativeMemoryPool();
            obj._ptr = Native.pgf_read(fn, obj.pool.Ptr, exn.Ptr);
            if (exn.IsRaised)
            {
                throw new PGFError($"Could not read PGF from file {fn}. ({System.IO.Directory.GetCurrentDirectory()})");
            }
            return obj;
        }

        public override string ToString() => $"Grammar:{Name}, {String.Join(", ", Languages.Keys)}";

        /// <summary>
        /// Name of the abstract grammar.
        /// </summary>
        public string Name => Native.NativeString.StringFromNativeUtf8(Native.pgf_abstract_name(_ptr));

        /// <summary>
        /// Default category of the grammar.
        /// </summary>
        public Type StartCat => Type.FromPtr(Native.pgf_start_cat(_ptr, pool.Ptr), pool);

        /// <summary>
        /// All concrete grammars in the language.
        /// </summary>
        public Dictionary<string, Concr> Languages
        {
            get
            {
                var dict = new Dictionary<string, Concr>();
                Native.MapIter(Native.pgf_iter_languages, _ptr, (k, v) => dict[k] = Concr.FromPtr(this, dereference(v)));
                return dict;
            }
        }

        private IntPtr dereference(IntPtr ptr)
        {
            return (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr));
        }

        /// <summary>
        /// All categories in the abstract grammar.
        /// </summary>
        public IEnumerable<string> Categories => GetStringList(Native.pgf_iter_categories);

        /// <summary>
        /// All functions in the abstract grammar.
        /// </summary>
        public IEnumerable<string> Functions => GetStringList(Native.pgf_iter_functions);

        /// <summary>
        /// Returns a list with all functions with a given return category.
        /// </summary>
        /// <param name="catName">The name of the return category.</param>
        public IEnumerable<string> FunctionsByCat(string catName)
        {
            using (var str = new Native.NativeString(catName))
            {
                return GetStringList(new Native.IterFuncCurryName(Native.pgf_iter_functions_by_cat, str.Ptr).IterFunc);
            }
        }

        /// <summary>
        /// Returns the type of the function with the given name.
        /// </summary>
        /// <param name="funName">The name of the function.</param>
        /// <returns></returns>
        public Type FunctionType(string funName)
        {
            using (var str = new Native.NativeString(funName))
            {
                var typePtr = Native.pgf_function_type(_ptr, str.Ptr);
                if (typePtr == IntPtr.Zero) throw new NullReferenceException();
                return Type.FromPtr(typePtr, pool);
            }
        }

        /// <summary>
        /// Normalizes an expression to its normal form by using the 'def' rules in the grammar.
        /// </summary>
        /// <param name="expr">the original expression.</param>
        /// <returns>the normalized expression.</returns>
        public Expr Compute(Expr expr)
        {
            var tmp_pool = new NativeGU.NativeMemoryPool();
            var exn = new NativeGU.NativeExceptionContext(tmp_pool);
            var result_pool = new NativeGU.NativeMemoryPool();
            var newExpr = Native.pgf_compute(_ptr, expr.Ptr, exn.Ptr, pool.Ptr, result_pool.Ptr);

            if (exn.IsRaised || newExpr == IntPtr.Zero)
            {
                throw new PGFError("Could not reduce expression.");
            }
            else
            {
                return Expr.FromPtr(newExpr, result_pool);
            }
        }

        /// <summary>
        /// Returns an enumerable over the set of all expression in
        /// the given category. The expressions are enumerated in decreasing
        /// probability order.
        /// </summary>
        /// <param name="cat">the start category.</param>
        /// <returns></returns>
        public IEnumerable<Expr> GenerateAll(Type cat = null)
        {
            cat = cat ?? StartCat;
            var tmp_pool = new NativeGU.NativeMemoryPool();
            var exn = new NativeGU.NativeExceptionContext(tmp_pool);
            var result_pool = new NativeGU.NativeMemoryPool();
            IntPtr ptr = IntPtr.Zero;
            var iterator = Native.pgf_generate_all(this._ptr, cat.Ptr, exn.Ptr, tmp_pool.Ptr, result_pool.Ptr);

            return NativeGU.IteratorToIEnumerable(iterator, tmp_pool.Ptr).Select(p =>
            {
                var exprProb = Marshal.PtrToStructure<Native.PgfExprProb>(ptr);
                return Expr.FromPtr(exprProb.expr, result_pool);

            });
        }

        private IEnumerable<string> GetStringList(Native.MapIterFunc f)
        {
            var c = new List<string>();
            Native.MapIter(f, _ptr, (k, v) => c.Add(k));
            return c;
        }
        
        public dynamic Embed() {
			return new GrammarEmbedding(this);
		}

		private class GrammarEmbedding : DynamicObject {
			private PGF gr;

			public GrammarEmbedding(PGF gr) {
				this.gr = gr;
			}
		
			public override bool TryGetMember(GetMemberBinder binder, out object result)
			{
				result = null;

				using (var str = new Native.NativeString(binder.Name))
				{
					var typePtr = Native.pgf_function_type(gr._ptr, str.Ptr);
					if (typePtr == IntPtr.Zero)
						return false;
				}

				result = new ApplicationExpr(binder.Name, new Expr[0]);
				return true;
			}

			public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
				result = null;

				uint n_hypos = 0;
				using (var str = new Native.NativeString(binder.Name))
				{
					var typePtr = Native.pgf_function_type(gr._ptr, str.Ptr);
					if (typePtr == IntPtr.Zero)
						return false;
					n_hypos = NativeGU.SeqLength(Marshal.PtrToStructure<Type.PgfType>(typePtr).hypos);
				}

				if (args.Length != n_hypos)
					return false;
					
				Expr[] exprs = new Expr[args.Length];
				for (var i = 0; i < args.Length; i++) {
					exprs[i] = args[i] as Expr;
					if (exprs[i] == null)
						return false;
				}
				result = new ApplicationExpr(binder.Name, exprs);
				return true;
			}
		}
    }
}
