using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Helpers.Common;
using Helpers.Log;
using log4net;

namespace DotPrograms
{
    public static class DotProgramCompiler
    {
        #region Fields

        /// <summary>
        /// A <typeparamref name="log4net.ILog"/> object.
        /// </summary>
        private static ILog logger = Helpers.Log.Logger.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.Name);

        #endregion

        #region Methods

        public static Response<CompilationResult> Compile(SourceCode sourceCode)
        {
            TransactionLogger tranLog = new TransactionLogger(logger, MethodBase.GetCurrentMethod().Name);
            Response<CompilationResult> response = new Response<CompilationResult>();
            try
            {
                tranLog.ParametersCollection = new Dictionary<string, object>()
            {
                { "SourceCode", sourceCode },
            };

                //Log begin of the transaction.
                tranLog.LogBegin();


                ContextConfiguration contextCfg = GetContextConfiguration();



                //Compile.
                CompilationResult compResult = new CompilationResult();
                Context context = new Context();

                context.DotOperations = contextCfg.Operations;

                using (StringReader reader = new StringReader(sourceCode.Code))
                {
                    string line;
                    int lineNumber = 0;

                    while ((line = reader.ReadLine()) != null)
                    {
                        try
                        {
                            bool success = true;

                            Expression exp = new Expression(line, context);

                            if ((success == true) && (exp.Errors.Count > 0))
                            {
                                compResult.Errors.Add(new CompilationError
                                {
                                    Line = lineNumber,
                                    Message = exp.Errors[0].Message,
                                });
                                success = false;
                            }

                            if (success == true)
                            {
                                object val = exp.Eval();

                                if (exp.Errors.Count > 0)
                                {
                                    compResult.Errors.Add(new CompilationError
                                    {
                                        Line = lineNumber,
                                        Message = exp.Errors[0].Message,
                                    });
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            if (ex is CompilationException)
                            {
                                compResult.Errors.Add(new CompilationError
                                {
                                    Line = lineNumber,
                                    Message = ex.Message,
                                });
                            }
                            else
                            {
                                compResult.Errors.Add(new CompilationError
                                {
                                    Line = lineNumber,
                                    Message = "Unknown error",
                                });
                            }

                            tranLog.Log(ex, "Error when evaluating expresion.");
                        }

                        lineNumber++;
                    }
                }


                //Set compilation bytes code.
                compResult.BytesCode = context.GetBytesCode();
                compResult.Frames = context.GetFrames();


                //Set success response.
                response = new Response<CompilationResult>
                {
                    Data = compResult,
                };

                //Log end of the transaction.
                tranLog.LogEnd(response);
            }
            catch (Exception ex)
            {
                string errorMsg = "Failed when trying to compile dot program.";

                //Log end of the transaction with exception.
                tranLog.LogEnd(ex, errorMsg);
            }
            return response;
        }
        private static ContextConfiguration GetContextConfiguration()
        {
            ContextConfiguration contextCfg = new ContextConfiguration();

            List<OperationConfiguration> ops = new List<OperationConfiguration>
            {
                new OperationConfiguration
                {
                    Name = "=",
                    OperationCode = (byte)OperationCodes.Set,
                },
                new OperationConfiguration
                {
                    Name = "setconstant",
                    OperationCode = (byte)OperationCodes.SetConstant,
                },
                new OperationConfiguration
                {
                    Name = "&&",
                    OperationCode = (byte)OperationCodes.And,
                },
                new OperationConfiguration
                {
                    Name = "||",
                    OperationCode = (byte)OperationCodes.Or,
                },
                new OperationConfiguration
                {
                    Name = "+",
                    OperationCode = (byte)OperationCodes.Addition,
                },
                new OperationConfiguration
                {
                    Name = "-",
                    OperationCode = (byte)OperationCodes.Substraction,
                },
                new OperationConfiguration
                {
                    Name = "jump",
                    OperationCode = (byte)OperationCodes.Jump,
                    ParametersCount = 0,
                },
                new OperationConfiguration
                {
                    Name = "jumpif",
                    OperationCode = (byte)OperationCodes.JumpIf,
                    ParametersCount = 0,
                },
                //new OperationConfiguration
                //{
                //    Name = "test",
                //    OperationCode = (byte)OperationCodes.Test,
                //    ParametersCount = 3,
                //},
            };


            ops.ForEach(opcfg => contextCfg.Operations.Add(opcfg.Name, opcfg));


            return contextCfg;
        }

        #endregion
    }
}