//                           UNCLASSIFIED
/*-------------------------------------------------------------------------
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
COMPANY.

THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
GOVERNMENT.

UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
-------------------------------------------------------------------------*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

namespace Raytheon.Common
{
   /// <summary>
   /// A class that reads a memory map upon construction and then serves up the fields of an entry upon request
   /// </summary>
   public class MemoryMap
   {
      #region PrivateClassMembers
      public struct MemoryMapFields
      {
         public readonly uint _page;
         public readonly uint _address;
         public readonly int _startBit;
         public readonly int _numBits;
         public readonly uint _regDefault;
         public readonly double _scaleFactor;

         public MemoryMapFields(uint page, uint address, int startBit, int numBits, uint regDefault, double scaleFactor)
         {
            _page = page;
            _address = address;
            _startBit = startBit;
            _numBits = numBits;
            _regDefault = regDefault;
            _scaleFactor = scaleFactor;
         }
      }

      private struct BitInfo
      {
         public int _start;
         public int _numOfBits;
      }

      private const string _NEWLINE = "\r\n";
      private Dictionary<string, MemoryMapFields> _mapData;
      private double _scaleFactor = 1.0;
      #endregion

      #region PrivateFuctions

      /// <summary>
      /// 
      /// </summary>
      /// <param name="fileLocation"></param>
      /// <returns></returns>
      private Dictionary<string, MemoryMapFields> CreateMemoryMapDictionary(string fileLocation)
      {
         Dictionary<string, MemoryMapFields> temp = new Dictionary<string, MemoryMapFields>();

         //Open file for processing (read-only)
         using (FileStream readStream = new FileStream(fileLocation, FileMode.Open, FileAccess.Read))
         {
            //Process the memmap
            ProcessFile(readStream, temp);
         }

         return temp;
      }

      /// <summary>
      /// Processes the memory map files into a data structure
      /// </summary>
      /// <param name="fs"></param>
      /// <param name="memoryMap"></param>
      private void ProcessFile(FileStream fs, Dictionary<string, MemoryMapFields> memoryMap)
      {
         uint page = 0;
         uint address = 0;
         uint regDefault = 0;
         char[] delimit = { ' ' };

         uint prevPage = 0;
         uint prevAddress = 0;
         uint parentRegDefault = 0;

         using (StreamReader reader = new StreamReader(fs))
         {
            while (!reader.EndOfStream)
            {
               //Read a line from the file
               string line = reader.ReadLine();

               //Parse the line by spaces (remove empty entries)
               string[] fields = line.Split(delimit, StringSplitOptions.RemoveEmptyEntries);

               //If nothing to read, stop processing
               if (fields.Length == 0)
               {
                  break;
               }

               //Get the key (Register Name)
               string key = fields[0].ToUpper();

               //Check for duplicates
               if (memoryMap.ContainsKey(key))
               {
                  throw new Exception("Duplicate item found in file: " + fs.Name + ", Item key: " + key);
               }

               //Check if the register is a parent or child
               //A child will use its parent address and gets it default value from its parent
               if (line.StartsWith(" "))
               {
                  regDefault = parentRegDefault;
                  page = prevPage;
                  address = prevAddress;
               }
               else
               {
                  //Get the page number
                  page = ProcessPageNum(fields[1]);

                  //Get register address
                  address = ProcessDecimalHex(fields[1]);

                  //Get register default values
                  regDefault = ProcessDecimalHex(fields[5]);

                  //Get scale factor
                  _scaleFactor = ProcessScaleFactor(line);
               }

               //Process bits
               BitInfo bitInfo = ProcessBitInfo(fields[3]);

               double scaleFactor = _scaleFactor;

               // parse out the default value for this child item
               if (bitInfo._numOfBits < 32)
               {
                  // clear the upper bits
                  int numBitsToClear = 32 - (bitInfo._start + bitInfo._numOfBits);
                  regDefault = regDefault << numBitsToClear;

                  // clear lower bits and place into position
                  int numBitsForPosition = bitInfo._start + numBitsToClear;
                  regDefault = regDefault >> numBitsForPosition;
               }
               else
               {
                  parentRegDefault = regDefault;
               }

               memoryMap.Add(key, new MemoryMapFields(page, address, bitInfo._start, bitInfo._numOfBits, regDefault, scaleFactor));

               // hold onto previous values for child items
               prevPage = page;
               prevAddress = address;
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="pageNumber"></param>
      /// <returns></returns>
      private uint ProcessPageNum(string pageNumber)
      {
         //Delimiter used to parse string
         char[] delimit = { '.' };

         //Array of parsed string
         string[] strPageValues = pageNumber.Split(delimit);

         //Convert first array element
         uint value = uint.Parse(strPageValues[0]);

         //Return conversion
         return value;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="address"></param>
      /// <returns></returns>
      private uint ProcessDecimalHex(string address)
      {
         //Delimiter used to parse string
         char[] delimit = { '.' };

         //Array of parsed string
         string[] strValues = address.Split(delimit);

         string strCombined = "";

         //Address = 0.XXXX.XXXX, register values = XXXX.XXXX
         if (strValues.Length == 3)
         {
            strCombined = strValues[1] + strValues[2];
         }
         else
         {
            strCombined = strValues[0] + strValues[1];
         }

         //Convert hex number to UInt
         uint value = uint.Parse(strCombined, System.Globalization.NumberStyles.HexNumber);

         //Return value
         return value;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bits"></param>
      /// <returns></returns>
      private BitInfo ProcessBitInfo(string bits)
      {
         //Structure for bit information
         BitInfo temp;

         //Delimiter used to parse string
         char[] delimit = { ':' };
         char[] trimChar = { '(', ')' };

         string trimBits = bits.Trim(trimChar);
         string[] values = trimBits.Split(delimit);

         if (values.Length == 2)
         {
            //values represents a range of bits
            int start = int.Parse(values[1]);
            int end = int.Parse(values[0]);

            //Add one for zero offset
            temp._start = start;
            temp._numOfBits = end - start + 1;
         }
         else
         {
            //Only a single bit
            temp._numOfBits = 1;
            temp._start = int.Parse(values[0]);
         }

         return temp;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="scaleFactor"></param>
      /// <returns></returns>
      private double ProcessScaleFactor(string scaleFactor)
      {
         const string PATTERN = @"[+-]?[0-9]{1,}\.[0-9]{1,}[e][+-]?[0-9]{1,}";

         //Find Scale Factor (default = 1)
         string sf = "1";

         foreach (Match match in Regex.Matches(scaleFactor, PATTERN))
         {
            sf = match.Value;
         }

         double value = double.Parse(sf);

         return value;
      }
      #endregion

      #region PublicFuctions

      /// <summary>
      /// The constructor
      /// </summary>
      /// <param name="memMapLocation">The memory map file</param>
      public MemoryMap(string memMapLocation)
      {
         _mapData = CreateMemoryMapDictionary(memMapLocation);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="registerName">The name of the register</param>
      /// <returns></returns>
      public MemoryMapFields GetRegisterInfoByString(string registerName)
      {
         if (_mapData.ContainsKey(registerName.ToUpper()) == false)
         {
            throw new Exception("The requested register: " + registerName + " is not in the memory map");
         }

         return _mapData[registerName.ToUpper()];
      }
      #endregion
   }
}
