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

namespace DotPrograms
{
    public class Context
    {
        public List<Operation> Operations = new List<Operation>();
        public Dictionary<string, object> LocalVariables = new Dictionary<string, object>();
        protected Dictionary<string, bool> Stack = new Dictionary<string, bool>();
        protected Dictionary<byte, string> StackPosition = new Dictionary<byte, string>();

        public Dictionary<string, OperationConfiguration> DotOperations { get; set; }

        private static Context defaultContext;
        public static Context Default
        {
            get
            {
                if (defaultContext == null)
                    defaultContext = new Context();
                return defaultContext;
            }
        }

        // list of functions currently in scope during an evaluation
        // note that this typically is NOT thread safe.

        // contains a list of variables that is in scope. Scope is used only for DynamicFunctions (for now)
        private List<Variables> inScope;

        //public Dictionary<string, Function> Functions { get; private set; }
        public Variables Globals { get; private set; }
        //public CommandPrompt Console { get; set; }
        /// <summary>
        /// check current stacksize
        /// is used for debugging purposes and error handling
        /// to prevent stackoverflows
        /// </summary>
        public int CurrentStackSize
        {
            get
            {
                return inScope.Count;
            }
        }

        public Variables CurrentScope
        {
            get {
                if (inScope.Count <= 0)
                    return null;

                return inScope[inScope.Count-1];
            }
        }


        private const byte SOURCE_VERSION = 0x00;
        private const byte SOURCE_LEN = 0x01;
        private const byte PCOUNTER = 0x03;
        private const byte SCAN_TIME = 0x04;
        private const byte RUN_STOP_STATUS = 0x06;  // 10 STOP - 20 RUN
        private const byte DIGITAL_OUTPUT = 0x07;  // Digital input area (1 DO - 1 byte)
        private const byte DIGITAL_INPUT = 0x08;  // Digital output area (7 DI - bit0=D1 .. bit6=D7)
        private const byte ANALOG_INPUT_0 = 0x09;  // Analog input 0 area (1 AI - 2 byte)
        private const byte ANALOG_INPUT_1 = 0x0B;  // Analog input 1 area (1 AI - 2 byte)
        private const byte IVB_INIT_POINTER = 0x0D;  // First byte of Internal Variables Binary
        private const byte IVR_INIT_POINTER = 0x12;  // First byte of Internal Variables Real
        private const byte IVTB_INIT_POINTER = 0x36;  // First byte of Internal Variables Binary (Transmitted)
        private const byte IVTR_INIT_POINTER = 0x38;  // First byte of Internal Variables Real (Transmitted)
        private const byte IS_INIT_POINTER = 0x40;  // First byte of Stack Variables 
        private const byte SOURCE_INIT_POINTER = 0x48;  // First byte of DOT_PLC program area (max long 200bytes)

        //private const int IVB_COUNT = 5; //Count of internal variables binary
        //private const int IVR_COUNT = 8; //Count of internal variables real
        //private const int IVB_COUNT = 8; //Count of internal variables
        //private const int IVTB_COUNT = 7; //Count of internal variables binary transmitted
        //private const int IVTR_COUNT = 2; //Count of internal variables real trasmitted
        //private const byte IS_COUNT = 8;  //Count of stack variables

        private int ovbCount = DIGITAL_INPUT - DIGITAL_OUTPUT;
        private int vbCount = ANALOG_INPUT_0 - DIGITAL_INPUT;
        private int vrCount = (IVB_INIT_POINTER - ANALOG_INPUT_0) / 2;
        private int ivbCount = IVR_INIT_POINTER - IVB_INIT_POINTER;
        private int ivrCount = (IVTB_INIT_POINTER - IVR_INIT_POINTER) / 4;
        private int ivtbCount = IVTR_INIT_POINTER - IVTB_INIT_POINTER;
        private int ivtrCount = (IS_INIT_POINTER - IVTR_INIT_POINTER) / 4;
        private int isCount = SOURCE_INIT_POINTER - IS_INIT_POINTER;

        public void PushScope(Variables vars)
        {
            inScope.Add(vars);
        }

        public Variables PopScope()
        {
            if (inScope.Count <= 0)
                return null;
            
            Variables vars = inScope[inScope.Count-1];
            inScope.RemoveAt(inScope.Count - 1);
            return vars;
        }

        public Context()
        {
            Reset();
        }

        /// <summary>
        /// resets the context to its defaults
        /// </summary> 
        public void Reset()
        {
            inScope = new List<Variables>();

            //Functions = new Dictionary<string, Function>();

            Globals = new Variables();
            //Globals["Pi"] = 3.1415926535897932384626433832795; // Math.Pi is not very precise
            //Globals["E"] = 2.7182818284590452353602874713527;  // Math.E is not very precise either

            this.DotOperations = new Dictionary<string, OperationConfiguration>();


            int startStackPos;
            int varNumber;
            string varToken;
            int bytePerVar;
            int varCount;


            //Global stack variables
            startStackPos = IS_INIT_POINTER;
            varNumber = 1;
            varCount = this.isCount;
            bytePerVar = 1;

            for (int i = 0, len = varCount * bytePerVar; i < len; i += bytePerVar)
            {
                varToken = "S" + (varNumber++);

                byte stack = (byte)(i + startStackPos);

                Globals[varToken] = new GlobalVariable
                {
                    StackPosition = stack,
                    Token = varToken,
                };

                Stack.Add(varToken, false);
                this.StackPosition.Add(stack, varToken);
            }

            //Global analog internal variables
            startStackPos = IVR_INIT_POINTER;
            varNumber = 1;
            varCount = this.ivrCount;
            bytePerVar = 4;

            for (int i = 0, len = varCount * bytePerVar; i < len; i += bytePerVar)
            {
                varToken = "A" + (varNumber++);

                Globals[varToken] = new GlobalVariable
                {
                    StackPosition = (byte)(i + startStackPos),
                    Token = varToken,
                };
            }

            //Global digital internal variables
            startStackPos = IVB_INIT_POINTER;
            varNumber = 1;
            varCount = this.ivbCount;
            bytePerVar = 1;

            for (int i = 0, len = varCount * bytePerVar; i < len; i += bytePerVar)
            {
                varToken = "D" + (varNumber++);

                Globals[varToken] = new GlobalVariable
                {
                    StackPosition = (byte)(i + startStackPos),
                    Token = varToken,
                };
            }

            //Global digital input variables
            startStackPos = DIGITAL_INPUT;
            varNumber = 1;
            varCount = this.vbCount;
            //bytePerVar = 1;

            for (int i = 0, len = varCount * bytePerVar; i < len; i += bytePerVar)
            {
                varToken = "DI" + (varNumber++);

                Globals[varToken] = new GlobalVariable
                {
                    StackPosition = (byte)(i + startStackPos),
                    Token = varToken,
                };
            }

            //Global analog input variables
            startStackPos = ANALOG_INPUT_0;
            varNumber = 1;
            varCount = this.vrCount;
            bytePerVar = 2;

            for (int i = 0, len = varCount * bytePerVar; i < len; i += bytePerVar)
            {
                varToken = "AI" + (varNumber++);

                Globals[varToken] = new GlobalVariable
                {
                    StackPosition = (byte)(i + startStackPos),
                    Token = varToken,
                };
            }

            //Global digital output variables
            startStackPos = DIGITAL_OUTPUT;
            varNumber = 1;
            varCount = this.ovbCount;
            bytePerVar = 1;

            for (int i = 0, len = varCount * bytePerVar; i < len; i += bytePerVar)
            {
                varToken = "DO" + (varNumber++);

                Globals[varToken] = new GlobalVariable
                {
                    StackPosition = (byte)(i + startStackPos),
                    Token = varToken,
                };
            }
        }

        public void Clear()
        {
            this.Operations.Clear();
            this.LocalVariables.Clear();

            this.Stack.ToList().ForEach(kvp => this.Stack[kvp.Key] = false);
        }
        public List<byte> GetBytesCode()
        {
            List<byte> bytesCode = new List<byte>();
            int startpos;


            //Fill with empty
            for (int i = 0; i < 256; i++)
                bytesCode.Add(0);


            //----------------------
            //Meta data
            //----------------------
            //Software version
            bytesCode[SOURCE_VERSION] = 1;
            //Program Counter
            bytesCode[PCOUNTER] = 0;
            //Scane time
            byte[] scantime = BitConverter.GetBytes((ushort)5000);
            bytesCode[SCAN_TIME] = scantime[0];
            bytesCode[SCAN_TIME + 1] = scantime[1];
            //Run stop/start status
            bytesCode[RUN_STOP_STATUS] = 10;

            ////Digital output
            //bytesCode[DIGITAL_OUTPUT] = Convert.ToByte(Globals["DO1"]);
            ////Analog input 0
            //bytesCode[ANALOG_INPUT_0] = Convert.ToByte(Globals["AI1"]);
            ////Anlaog input 1
            //bytesCode[ANALOG_INPUT_1] = Convert.ToByte(Globals["AI2"]);


            ////Analog internal variables
            //for (int i = IVR_INIT_POINTER; i < this.ivrCount; i++)
            //{

            //    bytesCode[i] = Convert.ToByte(Globals["A" + (i + 1)]);
            //}

            ////Digital internal variables
            //for (int i = IVB_INIT_POINTER; i < this.ivbCount; i++)
            //    bytesCode[i] = Convert.ToByte(Globals["D" + (i + 1)]);

            ////Digital input variables
            //for (int i = VB_INIT_POINTER; i < this.vbCount; i++)
            //    bytesCode[i] = Convert.ToByte(Globals["DI" + (i + 1)]);

            ////Analog input variables
            //for (int i = VR_INIT_POINTER; i < this.vrCount; i += 2)
            //    bytesCode[i] = Convert.ToByte(Globals["DI" + (i + 1)]);

            ////Digital ouptut variables
            //for (int i = 0; i < 1; i++)
            //    bytesCode.Add(0);

            ////Transmition flag
            //bytesCode.Add(0);

            ////Analog transmition
            //for (int i = 0; i < 4; i++)
            //    bytesCode.Add(0);

            ////Digital transmition
            //bytesCode.Add(0);


            //Source code
            //int maxLen = 195;
            //int rest = maxLen;
            Dictionary<string, List<byte>> jumpOperBytePositions = new Dictionary<string, List<byte>>();
            Dictionary<string, byte> labelsBytePosition = new Dictionary<string, byte>();
            startpos = SOURCE_INIT_POINTER;

            //int len = Operations.op => op.GetLength(0));
            this.Operations.ForEach(op => 
            {
                op.BytesCode.ForEach(bc => 
                {
                    //rest -= bc.Length;

                    bc.ToList().ForEach(b => bytesCode[startpos++] = b);
                    //bytesCode.AddRange(bc);
                });

                LabelOperation labelop;

                switch (op.OperationCode)
                {
                    case (byte)OperationCodes.Jump:
                    case (byte)OperationCodes.JumpIf:
                    case (byte)OperationCodes.JumpNot:
                        byte[] opBytesCode = op.BytesCode[0];
                        labelop = (LabelOperation)op;

                        //Jump operations always have the parameter of the position to jump in the 
                        //last position of the bytes code.
                        if (jumpOperBytePositions.ContainsKey(labelop.Label) == false)
                            jumpOperBytePositions.Add(labelop.Label, new List<byte> { (byte)(bytesCode.Count - 1), });
                        else
                            jumpOperBytePositions[labelop.Label].Add((byte)(bytesCode.Count - 1));

                        break;
                    case (byte)OperationCodes.Label:
                        labelop = (LabelOperation)op;

                        labelsBytePosition.Add(labelop.Label, (byte)bytesCode.Count);

                        break;
                    default:
                        break;
                }
            });


            //Set the label byte position of all jump operations.
            labelsBytePosition
                .ToList()
                .ForEach(kvp =>
                {
                    byte lblpos = kvp.Value;

                    if (jumpOperBytePositions.ContainsKey(kvp.Key) == true)
                        jumpOperBytePositions[kvp.Key].ForEach(jumpBytePos => bytesCode[jumpBytePos] = lblpos);
                });


            //Source code len
            byte[] sourcelen = BitConverter.GetBytes((ushort)(startpos - SOURCE_INIT_POINTER));
            bytesCode[SOURCE_LEN] = sourcelen[0];
            bytesCode[SOURCE_LEN + 1] = sourcelen[1];

            ////Empty source code
            //for (int i = 0; i < rest; i++)
            //    bytesCode[pos++] = 0;
            

            return bytesCode;
        }
        protected string GetAvailableStackVar()
        {
            var kvpVar = Stack.FirstOrDefault(kvp => kvp.Value == false);

            if (string.IsNullOrWhiteSpace(kvpVar.Key) == false)
            {
                return kvpVar.Key;
            }
            else
            {
                throw new Exception("No more stack variables available.");
            }
        }
        public List<string> GetFrames()
        {
            List<string> output = new List<string>();

            int max = 40;
            byte header = 2;
            byte frameCounter = 0;
            List<byte> curFrame;
            List<byte> bytesCode = this.GetBytesCode();


            curFrame = new List<byte>
            {
                header,
                frameCounter++,
            };

            for (int i = 0; i < bytesCode.Count; i++)
            {
                if (curFrame.Count == max)
                {
                    string frameOut = "";

                    curFrame.ForEach(b => frameOut += b.ToString("X2"));

                    output.Add(frameOut);

                    curFrame = new List<byte>
                    {
                        header,
                        frameCounter++,
                    };
                }

                curFrame.Add(bytesCode[i]);
            }

            return output;
        }
        public string PrintStack()
        {
            StringBuilder str = new StringBuilder();

            this.Stack.ToList().ForEach(kvp => str.AppendLine(string.Format("[{0}] = {1}", ((GlobalVariable)this.Globals[kvp.Key]).StackPosition, kvp.Value)));

            return str.ToString();
        }
        public byte ReservStackVar()
        {
            string token = this.GetAvailableStackVar();

            byte stack = ((GlobalVariable)this.Globals[token]).StackPosition;

            this.Stack[token] = true;

            return stack;
        }
        public void ReleaseStackVar(byte stack)
        {
            if (this.StackPosition.ContainsKey(stack) == true)
                this.Stack[this.StackPosition[stack]] = false;
        }
    }
}
