using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace CPF.Json.Serializer
{
    internal class ExpressionMembers
    {
        //- handler;
        internal static ParameterExpression HandlerArg = Expression.Parameter(typeof(JsonSerializerHandler), "handler");
        //- return(label);
        internal static readonly LabelTarget ReturnLable = Expression.Label(typeof(void));

        //- handler.WriteString
        internal static readonly MemberExpression WriteString = Expression.MakeMemberAccess(HandlerArg, JsonSerializerHandler._WriteString);

        //- handler.option;
        internal static readonly MemberExpression JOptionArg = Expression.MakeMemberAccess(HandlerArg, JsonSerializerHandler._Option);
        //- handler.serializeStacks
        internal static readonly MemberExpression SerializeStacksArg = Expression.MakeMemberAccess(HandlerArg, JsonSerializerHandler._SerializeStacks);

        //- handler.option.GlobalKeyFormat(delegate)
        internal static readonly MemberExpression GlobalKeyFormat = Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._GlobalKeyFormat);
        //- handler.option.GlobalValueFormat(delegate)
        internal static readonly MemberExpression GlobalValueFormat = Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._GlobalValueFormat);

        //- afterValueFormat(string)
        internal static readonly ParameterExpression AfterValueFormat = Expression.Variable(typeof(string), "afterValueFormat");
        //- isValueFormat(bool)
        internal static readonly ParameterExpression IsValueFormat = Expression.Variable(typeof(bool), "isValueFormat");

        //- handler.option.ignoreKeys(List<string>)
        internal static readonly MemberExpression IgnoreKeys = Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._IgnoreKeys);
        //- handler.option.ignoreKeys == null
        internal static readonly BinaryExpression IgnoreKeysIsNull = Expression.Equal(IgnoreKeys, Expression.Constant(null));
        //- handler.option.ignoreKeys != null
        internal static readonly BinaryExpression IgnoreKeysIsNotNull = Expression.NotEqual(IgnoreKeys, Expression.Constant(null));
        //- handler.option.ignoreKeys.count() == 0
        internal static readonly BinaryExpression IgnoreKeysCountIsZero = Expression.Equal(Expression.MakeMemberAccess(IgnoreKeys, typeof(List<string>).GetProperty("Count")), Expression.Constant(0));

        //- handler.option.jsonCharacterRead(Enum)
        internal static readonly MemberExpression JsonCharacterRead = Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._JsonCharacterRead);

        //- handler.option.isEnumNum(bool)
        internal static readonly MemberExpression IsEnumNum = Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._IsEnumNum);

        //- handler.option.isIgnoreValueNull(bool)
        internal static readonly UnaryExpression IgnoreValueNullIsTrue = Expression.IsTrue(Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._IsIgnoreValueNull));

        //- handler.option.referenceLoopHandling(Enum)
        internal static readonly MemberExpression ReferenceLoopHandling = Expression.MakeMemberAccess(JOptionArg, JsonSerializerOption._ReferenceLoopHandling);
        //- handler.option.referenceLoopHandling != Enum.None
        internal static readonly BinaryExpression IsReferenceLoopHandlingNotEqualsNone = Expression.NotEqual(ReferenceLoopHandling, Expression.Constant(JsonReferenceHandlingEnum.None));
        //- handler.option.referenceLoopHandling == Enum.Remove
        internal static readonly BinaryExpression IsReferenceLoopHandlingEqualsRemove = Expression.Equal(ReferenceLoopHandling, Expression.Constant(JsonReferenceHandlingEnum.Remove));
        //- handler.option.referenceLoopHandling == Enum.Empty
        internal static readonly BinaryExpression IsReferenceLoopHandlingEqualsEmpty = Expression.Equal(ReferenceLoopHandling, Expression.Constant(JsonReferenceHandlingEnum.Empty));
        //- handler.option.referenceLoopHandling == Enum.Null
        internal static readonly BinaryExpression IsReferenceLoopHandlingEqualsNull = Expression.Equal(ReferenceLoopHandling, Expression.Constant(JsonReferenceHandlingEnum.Null));

        //- handler.commaIndexLists
        internal static readonly MemberExpression ListCommaIndexs = Expression.MakeMemberAccess(HandlerArg, JsonSerializerHandler._CommaIndexLists);

      
        

        //if ( referenceLoopHandling == Enum.Remove ) ==> DeleteComma()
        internal static readonly ConditionalExpression IsReferenceLoopHandlingIsRemoveDeleteComma = Expression.IfThen(
            IsReferenceLoopHandlingEqualsRemove,
            DeleteComma()
            );

        //if ( referenceLoopHandling == Enum.None ) ==> serializeStacks.Pop()
        internal static readonly ConditionalExpression IsReferenceLoopHandlingIsNoneSerializeStacksArgPop = Expression.IfThen(
            IsReferenceLoopHandlingNotEqualsNone,
            Expression.Call(SerializeStacksArg, typeof(Stack<object>).GetMethod("Pop"))
          );

        //call
        internal static readonly MethodCallExpression WriteLeft = Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, Expression.Constant("{"));
        internal static readonly MethodCallExpression WriteRight = Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, Expression.Constant("}"));
        internal static readonly MethodCallExpression WriteComma = Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, Expression.Constant(","));

        internal enum RefernceByEmptyType
        {
            Array,
            Dictionary,
            CustomType
        }
        internal static Expression WriteKeyValuePairKey(Type keyType, Expression key)
        {
            List<Expression> calls = new List<Expression>();
            if (keyType == typeof(string) || keyType == typeof(Guid) || keyType == typeof(DateTime))
            {
                calls.Add(GetMethodCall(keyType, key));
                calls.Add(Append(":"));
            }
            else if (keyType.IsPrimitive)
            {
                calls.Add(Append("\""));
                calls.Add(Expression.Call(SerializerBootTable.Table.DefaultSameTypes[keyType], key, HandlerArg));
                calls.Add(Append("\":"));
            }
            else if (keyType.IsEnum)//The default is tostring
            {
                calls.Add(Append("\""));
                calls.Add(Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, Expression.Call(key, typeof(Enum).GetMethod("ToString", new Type[0]))));
                calls.Add(Append("\":"));
            }

            return Expression.Block(calls);
        }
        internal static Expression IsIgnoreSelfRefernce(ParameterExpression instanceArg, RefernceByEmptyType emptyType)
        {
            MethodCallExpression contains = Expression.Call(SerializeStacksArg, typeof(Stack<object>).GetMethod("Contains", new[] { typeof(object) }), Expression.Convert(instanceArg, typeof(object)));

            MethodCallExpression call;
            if (emptyType == RefernceByEmptyType.Array)
                call = RemoveArrayItem();
            else if (emptyType == RefernceByEmptyType.Dictionary)
                call = RemoveDictionaryKey();
            else
                call = RemoveKeyAndAddIndex();

            BlockExpression containsBody = Expression.Block(
            Expression.IfThenElse(
                IsReferenceLoopHandlingEqualsNull, Append("null"),
                Expression.IfThenElse(
                    IsReferenceLoopHandlingEqualsEmpty, Append(emptyType == RefernceByEmptyType.Array ? "[]" : "{}"),
                      call)
                    ),
                    Expression.Return(ReturnLable)
            );

            ConditionalExpression ifContainsBlock = Expression.IfThen(contains, containsBody);

            // handler.serializeStacks.Push(value);
            MethodCallExpression push = Expression.Call(SerializeStacksArg, typeof(Stack<object>).GetMethod("Push", new[] { typeof(object) }), Expression.Convert(instanceArg, typeof(object)));

            ConditionalExpression isIgnoreSelfRefernce = Expression.IfThen(IsReferenceLoopHandlingNotEqualsNone,
                Expression.Block(
                 ifContainsBlock, push
                    ));

            return isIgnoreSelfRefernce;
        }
        internal static MethodCallExpression GetMethodCall(Type type, Expression member)
        {
            return Expression.Call(typeof(FormattingProvider<>).MakeGenericType(type).GetMethod("Convert", BindingFlags.NonPublic | BindingFlags.Static), member, HandlerArg);
        }

        internal static MethodCallExpression AppendKey(string str)
        {
            return Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, Expression.Constant("\"" + str + "\":"));
        }
        internal static MethodCallExpression Append(string str)
        {
            return Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, Expression.Constant(str));
        }
        internal static MethodCallExpression Append(Expression strExpression)
        {
            return Expression.Call(WriteString, JsonSerializerHandler._WriteStringInvoke, strExpression);
        }
        internal static MethodCallExpression RemoveDictionaryKey()
        {
            return Expression.Call(RemoveWriterHelper._RemoveDictionaryKey, HandlerArg);
        }
        internal static MethodCallExpression RemoveArrayItem()
        {
            return Expression.Call(RemoveWriterHelper._RemoveArrayItem, HandlerArg);
        }
        internal static MethodCallExpression RemoveKeyAndAddIndex()
        {
            return Expression.Call(RemoveWriterHelper._RemoveKeyAndAddIndex, HandlerArg, ListCommaIndexs);
        }
        internal static MethodCallExpression RemoveLastComma()
        {
            return Expression.Call(RemoveWriterHelper._RemoveLastComma, HandlerArg);
        }
        internal static MethodCallExpression DeleteComma()
        {
            return Expression.Call(RemoveWriterHelper._DeleteComma, HandlerArg, ListCommaIndexs);
        }
    }
}
