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

namespace iSTP_Embedded
{
    public class ISTPConfiguration
    {
        private string DumpFileName;
        private const string HealthDataSensorName = "HealthData";
        private List<string> Headers4HealthData = new List<string>();

        public struct Sensor_t
        {
            public Board_t ConfigurationStatus;
            public bool Active;
            public UInt32 Axis;
            public UInt32 BoardIndex;
            public UInt32 SensorNumber;
            public string SensorName;
            public string[] Quantities;
            public int[] QuantityIndexes;
            public string Comment;
            public string LongName 
            {
                get 
                {
                    return (String.Format("{0}_{1}{2}_{3}",
                                SensorName,
                                GetAxisStr(Axis),
                                BoardIndex + 1,
                                SensorNumber + 1));
                } 
            }
            public string ShortName 
            {
                get
                {
                    return (String.Format("{0}_{1}{2}",
                        SensorName,
                        GetAxisStr(Axis),
                        (BoardIndex + 1)));
                }
            }
        }

        public struct Board_t
        {
            public string Name;
            public Parameter_t[] Parameters;
        }

        public struct Parameter_t
        {
            public String Name;
            public string[] Options;
            public int CurrentOption;
            public Parameter_t(string name, string[] options, int current)
            {
                Name = name;
                Options = options;
                CurrentOption = current;
            }
        }

        public enum ConfigurationStatus
        { 
            Empty,
            Fetching,
            Complete,
            Error
        }

        public Sensor_t[] Sensors = new Sensor_t[MaxSensors]; // 3+1 axis, 6 boards, 15 + 1 sensors (board sensors + board MPU), 1Platform MPU
        public int[, ,] QuickSensorIndex = new int[MaxAxes, MaxBoardsPerAxis, MaxSensorsPerBoard];// 3 + 1 axis, 6 boards, 16 sensors (Platform MPU is on the 4th 'axis' )
        public Board_t[,] Boards = new Board_t[MaxAxes, MaxBoardsPerAxis];
        private ConfigurationStatus ConfigStatus;
        private int LastConfiguredSensorIndex; // Overall progress - index for the Sensors array
        private int LastConfiguredQuantityIndex;
        private uint LastPacketNumber;
        private List<string> HealthDataSensorsName = new List<string>();

        public ConfigurationStatus GetConfigurationStatus { get { return this.ConfigStatus; } }

        public ISTPConfiguration(string dumpFileName)
        {
            LastPacketNumber = 0;
            for (int i = 0; i < MaxSensors; i++)
                Sensors[i].Active = false;

            ConfigStatus = ConfigurationStatus.Empty;
            LastConfiguredSensorIndex = -1;
            LastConfiguredQuantityIndex = 0;
            for (int i = 0; i < MaxAxes; i++)
                for (int j = 0; j < MaxBoardsPerAxis; j++)
                    for (int k = 0; k < MaxSensorsPerBoard; k++)
                        QuickSensorIndex[i, j, k] = -1;
            DumpFileName = dumpFileName;
            FWInfo = "";
            FWVersion = "Unknown";
        }

        public string[] GetHealthDataHeaders { get { return Headers4HealthData.ToArray(); } }

        /// <summary>
        /// Cleans up configuration
        /// </summary>
        public void ResetConfiguration()
        {
            for (int i = 0; i < Sensors.Length; i++)
            {
                Sensors[i].Active = false;
                Sensors[i].Axis = 0;
                Sensors[i].BoardIndex = 0;
                Sensors[i].SensorNumber = 0;
            }

            LastConfiguredSensorIndex = -1;
            LastConfiguredQuantityIndex = 0;
            ConfigStatus = ConfigurationStatus.Empty;

            for (int i = 0; i < MaxAxes; i++)
                for (int j = 0; j < MaxBoardsPerAxis; j++)
                {
                    Boards[i, j].Name = null;
                    Boards[i, j].Parameters = null;
                    for (int k = 0; k < MaxSensorsPerBoard; k++)
                        QuickSensorIndex[i, j, k] = -1;
                }

            FWInfo = "";
            FWVersion = "Unknown";
        }

        public bool CheckCompletness(EmbeddedPacket packet)
        {
            if ((packet.MsgNumber - LastPacketNumber) > 1)
            {
                ConfigStatus = ConfigurationStatus.Error;
                DebugConsole.WriteLine("Missing configuration packets");
            }
            LastPacketNumber = packet.MsgNumber;
            return ConfigStatus != ConfigurationStatus.Error;
        }

        private bool InDatabase(string name, uint axis, uint board)
        {
            bool _res = false;

            foreach (Sensor_t element in Sensors)
            {
                if (element.SensorName == name && element.Axis == axis && element.BoardIndex == board)
                {
                    _res = true;
                    break;
                }
            }

            return _res;
        }

        /// <summary>
        /// parses configuration packets from the iSTP and builts up a sensor database
        /// <param name="confPacket"></param>
        /// </summary>
        public void DigestConfPacket(EmbeddedPacket confPacket)
        {
            List<string> quantities = new List<string>();
            List<int> quantityIndexes = new List<int>();
            string currentSensor = null;
            bool previousEmpty = true;
            int activeSensorIndex = 0;

            CheckCompletness(confPacket);

            uint axis = (confPacket.MsgID & 0xFF000000) >> 24;
            uint board = (confPacket.MsgID & 0x00FF0000) >> 16;
            uint activeSensors = confPacket.MsgID & 0x0000FFFF;
            uint[] sensorsOnBoard = GetSensorsOnBoard(activeSensors);

            //convert byte[] to string
            string confString = System.Text.Encoding.ASCII.GetString(confPacket.MsgData, 0, confPacket.MsgLength).Trim('\0');
            string[] configSubstrings = confString.Split('%');
            foreach (string substring in configSubstrings)
            {
                if (substring != "")
                {
                    if (previousEmpty)
                        currentSensor = substring;
                    else
                    {
                        quantities.Add(substring);
                        quantityIndexes.Add(LastConfiguredQuantityIndex++);
                    }
                    previousEmpty = false;
                }
                else
                {
                    if (currentSensor != null && !currentSensor.Contains(HealthDataSensorName) && !InDatabase(currentSensor, axis, board))
                    {
                        LastConfiguredSensorIndex++;
                        Sensors[LastConfiguredSensorIndex].Active = true;
                        Sensors[LastConfiguredSensorIndex].Axis = axis;
                        Sensors[LastConfiguredSensorIndex].BoardIndex = board;
                        Sensors[LastConfiguredSensorIndex].SensorName = currentSensor;
                        Sensors[LastConfiguredSensorIndex].SensorNumber = sensorsOnBoard[activeSensorIndex];
                        Sensors[LastConfiguredSensorIndex].Quantities = quantities.ToArray();
                        Sensors[LastConfiguredSensorIndex].QuantityIndexes = quantityIndexes.ToArray();
                        int boardNameIdx = currentSensor.LastIndexOf('-');
                        string[] _sensname = currentSensor.Split('-');
                        if (_sensname.Length >= 3)
                        {
                            boardNameIdx = 0;
                            for (int i = 0; i < _sensname.Length - 2; i++)
                            {
                                boardNameIdx += _sensname[i].Length + 1;
                            }
                            boardNameIdx -= 1;
                        }
                        Boards[axis,board].Name = (boardNameIdx > 0 ? currentSensor.Substring(0, boardNameIdx): currentSensor);
                        currentSensor = null;
                        quantities.Clear();
                        quantityIndexes.Clear();
                        QuickSensorIndex[axis, board, sensorsOnBoard[activeSensorIndex]] = LastConfiguredSensorIndex;
                        activeSensorIndex++;
                        previousEmpty = true;
                    }
                    else if (currentSensor != null && currentSensor.Contains(HealthDataSensorName) && !HealthDataSensorsName.Contains(currentSensor))
                    {
                        HealthDataSensorsName.Add(currentSensor);
                        foreach (string s in quantities)
                            Headers4HealthData.Add(s);

                        currentSensor = null;
                        quantities.Clear();
                        quantityIndexes.Clear();
                        previousEmpty = true;
                    }
                }
            }
        }

        public bool DigestCommentPacket(EmbeddedPacket commentPacket)
        {
            CheckCompletness(commentPacket);
            bool retVal = false;
            if ((commentPacket.MsgID & 0x0000FFFF) != 0)
            {
                retVal = DigestComment(commentPacket);
            }
            else
            {
                retVal = DigestParams(commentPacket);
            }
            return retVal;
        }

        private bool DigestComment(EmbeddedPacket commentPacket)
        {
            string commentString;
            bool retVal = false;
            uint axis = (commentPacket.MsgID & 0xFF000000) >> 24;
            uint board = (commentPacket.MsgID & 0x00FF0000) >> 16; 
            uint sensor = commentPacket.MsgID & 0x0000FFFF;

            //only one sensor should be indicated by the bit field, so the array will only have one item
            uint sensorNum = GetSensorsOnBoard(sensor)[0];
            if (commentPacket.MsgLength == 0)
                commentString = "";
            else
                commentString = System.Text.Encoding.ASCII.GetString(commentPacket.MsgData, 0, commentPacket.MsgLength).Trim('\0');
            if (QuickSensorIndex[axis, board, sensorNum] >= 0)
            {
                Sensors[QuickSensorIndex[axis, board, sensorNum]].Comment = commentString;
                retVal = true; ;
            }
            else
                retVal = false;
            return retVal;
        }

        private bool DigestParams(EmbeddedPacket commentPacket)
        {
            bool retVal = false;
            uint axis = (commentPacket.MsgID & 0xFF000000) >> 24;
            uint board = (commentPacket.MsgID & 0x00FF0000) >> 16;
            bool previousEmpty = true;
            string currentParameter = null;
            List<string> options = new List<string>();
            List<Parameter_t> parametersList = new List<Parameter_t>();

            string paramString = System.Text.Encoding.ASCII.GetString(commentPacket.MsgData, 0, commentPacket.MsgLength);
            int selectedOption = paramString.IndexOf('$') + 1;
            bool explicitOption = (paramString.IndexOf('$') != paramString.LastIndexOf('$'));
            
            paramString = paramString.Substring(0, paramString.IndexOf('$'));
            string[] paramSubstrings = paramString.Trim('\0').Split('&', '%');
            foreach (string substring in paramSubstrings)
            {
                if (substring != "")
                {
                    if (previousEmpty)
                        currentParameter = substring;
                    else
                        options.Add(substring);
                    previousEmpty = false;
                }
                else
                {
                    if (currentParameter != null)
                    {
                        parametersList.Add(new Parameter_t(currentParameter, options.ToArray(), (explicitOption ? commentPacket.MsgData[selectedOption++] : 0)));
                        currentParameter = null;
                        options.Clear();
                        previousEmpty = true;
                    }
                }
            }

            Boards[axis, board].Parameters = parametersList.ToArray();
            return retVal;
        }

        public void DigestVersionPacket(EmbeddedPacket packet)
        {
            FWVersion = String.Format("{0}.{1}.{2}.{3}", (packet.MsgID >> 24) & 0xFF,
                                                         (packet.MsgID >> 16) & 0xFF,
                                                         (packet.MsgID >>  8) & 0xFF,
                                                          packet.MsgID        & 0xFF);
            if (packet.MsgLength > 0)
                FWInfo = System.Text.Encoding.ASCII.GetString(packet.MsgData, 0, packet.MsgLength);
        }

        public void Finish(EmbeddedPacket packet)
        {
            if(CheckCompletness(packet))
                ConfigStatus = ConfigurationStatus.Complete;
            DumpConfiguration(DumpFileName);
            for (int ax = 0; ax < MaxAxes; ax++)
            {
                for (int bo = 0; bo < MaxBoardsPerAxis; bo++)
                {
                    if (Boards[ax, bo].Parameters != null)
                    {
                        for (int i = 0; i < Sensors.Length; i++)
                        {
                            if (Sensors[i].Active)
                            {
                                if (Sensors[i].Axis == ax && Sensors[i].BoardIndex == bo)
                                {
                                    Sensors[i].ConfigurationStatus = Boards[ax, bo];
                                }
                            }
                            else
                                break;
                        }
                    }
                }
            }
        }

        public static string GetEmbConfFileContent { get; private set; }

        public void DumpConfiguration(string fileName)
        {
            if (File.Exists(fileName))
                File.Delete(fileName);
            StreamWriter configFile = new StreamWriter(File.OpenWrite(fileName));

            GetEmbConfFileContent = string.Empty;

            configFile.WriteLine("== Sensors ==");
            GetEmbConfFileContent += string.Format("== Sensors =={0}",Environment.NewLine);
            for (int i = 0; i <= LastConfiguredSensorIndex; i++)
            {
                //Parameter_t[] pars = Boards[(int)(Sensors[i].Axis), (int)Sensors[i].Board_t].Parameters; 
                configFile.WriteLine("[{0}] >> {1}", Sensors[i].SensorName, Sensors[i].Comment);//, Boards[Sensors[i].Axis, Sensors[i].Board_t].Parameters.);
                GetEmbConfFileContent += string.Format("[{0}] >> {1}{2}", Sensors[i].SensorName, Sensors[i].Comment, Environment.NewLine);

                foreach (string quantity in Sensors[i].Quantities)
                {
                    configFile.WriteLine("\t{0}_{1}", Sensors[i].LongName, quantity);
                    GetEmbConfFileContent += string.Format("\t{0}_{1}{2}", Sensors[i].LongName, quantity, Environment.NewLine);
                }
            }
            configFile.WriteLine("== Board Parameters ==");
            GetEmbConfFileContent += string.Format("== Board Parameters ==",Environment.NewLine);
            for (int i = 0; i < MaxAxes; i++)
                for (int j = 0; j < MaxBoardsPerAxis; j++)
                    if(Boards[i,j].Parameters != null)
                    {
                        configFile.WriteLine(string.Format("[{0}]", Boards[i, j].Name));
                        GetEmbConfFileContent += string.Format("[{0}]{1}", Boards[i, j].Name, Environment.NewLine);
                        foreach (Parameter_t par in Boards[i, j].Parameters)
                        {
                            //configFile.WriteLine(">> {0}\r\n\t: {1}", par.Name, String.Join("\r\n\t: ", par.Options));
                            configFile.WriteLine(">> {0}", par.Name);
                            GetEmbConfFileContent += string.Format(">> {0}{1}", par.Name, Environment.NewLine);
                            for (int o = 0; o < par.Options.Length; o++)
                            {
                                configFile.WriteLine("\t:{0}{1}", (par.CurrentOption == o ? " * " : "   "), par.Options[o]);
                                GetEmbConfFileContent += string.Format("\t:{0}{1}{2}", (par.CurrentOption == o ? " * " : "   "), par.Options[o], Environment.NewLine);
                            }
                        }
                    }
            configFile.Close();
        }

        public static string GetAxisStr(uint axis)
        {
            string retval = "-";
            switch (axis)
            {
                case 0x00:
                    retval = "a"; // X
                    break;
                case 0x01:
                    retval = "b"; // Y
                    break;
                case 0x02:
                    retval = "c"; // Z
                    break;
                case 0x03:
                    retval = "P"; // main platform
                    break;
            }
            return retval;
        }

        private uint[] GetSensorsOnBoard(uint bitField)
        {
            List<uint> activeSensors = new List<uint>();
            for (uint i = 0; i < MaxSensorsPerBoard; i++)
            {
                if ((bitField & 0x01) > 0)
                {
                    activeSensors.Add(i);
                }
                bitField = bitField >> 1;
            }
            return (activeSensors.ToArray());
        }

        public bool ConfigurationComplete
        { get { return (ConfigStatus == ConfigurationStatus.Complete); } }

        public int Size // the number of configured sensors 
        { get { return (LastConfiguredSensorIndex + 1); } }

        public Sensor_t this[int Index]
        {
            get
            {
                if (Index >= 0 && Index <= LastConfiguredSensorIndex)
                    return Sensors[Index];
                else
                    return new Sensor_t();
            }
            set
            {
                if (Index > 0)
                {
                    LastConfiguredSensorIndex = Index;
                    Sensors[Index] = value;
                }
            }
        }

        public void ChangeSelectedParama(int axis, int board, int paramIndex, int optionIndex)
        {
            Boards[axis, board].Parameters[paramIndex].CurrentOption = optionIndex;

            for (int i = 0; i < Sensors.Length; i++)
            {
                if (Sensors[i].Active)
                {
                    if (Sensors[i].Axis == axis && Sensors[i].BoardIndex == board)
                    {
                        Sensors[i].ConfigurationStatus.Parameters[paramIndex].CurrentOption = optionIndex;
                    }
                }
                else
                    break;
            }
        }

        public static int MaxAxes { get { return 4; } }
        public static int MaxBoardsPerAxis { get { return 6; } }
        public static int MaxSensorsPerBoard { get { return 16; } }
        public static int MaxSensors { get { return MaxAxes * MaxBoardsPerAxis * MaxSensorsPerBoard;} }
        
        public string FWVersion { get; private set; }
        public string FWInfo { get; private set; }
        
    }
}
