/// <summary>
        /// Gets raw logic analyzer data (edges) and does complete decoding.
        /// </summary>
        /// <param name="list">Edge list</param>
        /// <param name="frequency">Communication frequency (equal to speed, 4Mbps = 4MHz for Mechatrolink-I and 10MHz for M-II)</param>
        /// <param name="littleEndian">Publicly available bits of docs are confusing on the subject of command body endianess.
        /// Experience suggests that body bytes on their own are transmitted as big-endian,
        /// though multibyte data pieces inside the body might be encoded as little-endian.</param>
        /// <returns></returns>
        public static EncoderCommunication Parse(SortedList <int, bool> list, int frequency, bool littleEndian = false)
        {
            var tempDecoded = HDLCManchesterDecoder.Decode(list, frequency, 0.25);
            //DataReporter.ReportProgress("Manchester layer decoded...");
            var packets = HDLCManchesterDecoder.SeparatePackets(tempDecoded);

            tempDecoded.Clear(); //Not needed anymore
            tempDecoded.TrimExcess();
            GC.Collect();
            DataReporter.ReportProgress("HDLC layer decoded...");
            var decoded = new EncoderPacket[packets.Length];

            for (int i = 0; i < packets.Length; i++)
            {
                byte[] temp = HDLCManchesterDecoder.PackIntoBytes(
                    HDLCManchesterDecoder.DestuffZeroes(packets[i])).Values.ToArray();
                if (temp.Length == 0)
                {
                    continue;
                }
                //DataReporter.ReportProgress(string.Format("Packet {0} out of {1} packed...", i + 1, packets.Length));
                decoded[i] = EncoderPacket.Parse(temp, packets[i].Keys.First(), littleEndian);
                //DataReporter.ReportProgress(string.Format("Packet {0} out of {1} decoded...", i + 1, packets.Length));
            }
            DataReporter.ReportProgress("Packets parsed...");
            return(new EncoderCommunication(decoded, (int)Math.Round(1E8 / frequency)));
        }
 private static void DoneReportErrors()
 {
     if (DataReporter.EnableProgressOutput && ErrorListener.Exceptions.Any())
     {
         Console.WriteLine(Environment.NewLine + "Warnings:");
         Console.WriteLine(ErrorListener.ToString());
     }
     DataReporter.ReportProgress("Done." + Environment.NewLine);
 }
        private static bool CallInstance(List <Process> running, ProcessStartInfo psi)
        {
            try
            {
                running.Add(Process.Start(psi));
                DataReporter.ReportProgress("Started processing: " + psi.Arguments);
            }
            catch (Exception e)
            {
                DataReporter.ReportProgress(string.Format(@"Failed to start process with arguments: {0}
Error details: {1}", psi.Arguments, e.ToString()));
                return(false);
            }
            return(true);
        }
        /// <summary>
        /// Invokes multiple MechatrolinkParser instances
        /// Spins multiple threads for efficiency (if UseParallelComputation is set to true)
        /// Tries to use all available cores first, then reduces thread pool size if OutOfMemory exceptions are raised
        /// Retries to parse files that failed with OutOfMemory exception (once)
        /// </summary>
        /// <param name="directory">Input directory (all inner directories are included into file search)</param>
        /// <param name="limit">Ordinary command line argument: time limit</param>
        /// <param name="freq">Ordinary command line argument: communication frequency</param>
        /// <param name="search">Limited to ? and * wildcards, for example "*.txt"</param>
        /// <param name="flags">String of command line switches that go after limit and frequency, separated with whitespaces</param>
        /// <returns>Success == true</returns>
        public static bool ProcessDirectory(string directory, int limit, int freq, string search, string flags)
        {
            bool success = true;

            //Get list of all suitable files
            string[] files = Directory.GetFiles(directory, search, SearchOption.AllDirectories);
            files = files.Where(x => x != NameModifier(x)).ToArray(); //Skip reports, in case their extensions coincide with data files
            string args = string.Format("/c \"{0} \"{{0}}\" {1} {2} {3} > \"{{1}}\"\"",
                                        string.Format("\"{0}\"", System.Reflection.Assembly.GetExecutingAssembly().Location),
                                        limit, freq, flags);
            int                     maxInstances = UseParallelComputation ? Environment.ProcessorCount : 1;
            List <Process>          running      = new List <Process>(maxInstances);
            List <ProcessStartInfo> retry        = new List <ProcessStartInfo>();

            for (int i = 0; i < files.Length; i += maxInstances)
            {
                running.Clear();
                for (int j = 0; j < maxInstances; j++)
                {
                    if (i + j >= files.Length)
                    {
                        break;
                    }
                    if (!CallInstance(running, files[i + j], args))
                    {
                        success = false;
                    }
                }
                WaitForThreadPool(running);
                foreach (var item in running)
                {
                    if (item.ExitCode != 0)
                    {
                        DataReporter.ReportProgress(
                            string.Format("Parser process returned an error {0}, arguments: {1}",
                                          item.ExitCode, item.StartInfo.Arguments));
                        if (item.ExitCode == (int)Program.ExitCodes.OutOfMemory && maxInstances > 1)
                        {
                            retry.Add(item.StartInfo);
                            if (maxInstances > 1)
                            {
                                maxInstances = (int)Math.Floor(maxInstances / 2d); //Adaptive parallelism
                            }
                            i += maxInstances;                                     //Compensate for maxInstances change
                        }
                        else
                        {
                            success = false;
                        }
                    }
                }
                DataReporter.ReportProgress("Finished processing of the file (or group of files).");
            }
            running.Clear();
            if (retry.Any())
            {
                DataReporter.ReportProgress("Retrying to parse files that caused OutOfMemory exceptions...");
            }
            foreach (var item in retry)
            {
                if (!CallInstance(running, item))
                {
                    success = false;
                }
                WaitForThreadPool(running);
                if (running.First().ExitCode != 0)
                {
                    success = false;
                }
            }
            DataReporter.ReportProgress("Finished processing of all the files.");
            return(success);
        }
        public static int Main(string[] args)
        {
            Console.OutputEncoding = Encoding.UTF8;
            if (args.Length < 1)
            {
                Console.WriteLine(@"Not enough arguments. Usage:
MechatrolinkParser.exe <Exported text file path> <Line/time limit> <Frequency, Hz> [<Options>]
Limit and frequency are optional only if <Options> is omitted, defaults: 20000 and 4000000. Use limit = 0 to disable the limit.
Options: [-e] = packet body endianess swap (default is 'big-endian'), usually not needed
[-s] = silent (switch off progress reports/warnings)
[-x] = full exception info (defaults to distinct exception output)
[-stm] = transcode the file into LogicSnifferSTM format
[-f] = filter output (exclude nonsensical commands, e.g. with Control field equal to neither 01h nor 03h)
[-b] = bulk (folder) processing, <file path> argument field becomes <""directory path|search string"">, other argument fields are not changed
[-p] = parallel computation in bulk mode
[-k] = keep console windows open (Console.ReadKey())
[-i] = invert raw data (to correct receiver polarity)
[-pq:X] = set request preamble length to X (defaults to 16)
[-ps:X] = set response preamble length to X (defaults to 16)
[-n] = encoder mode (sets suitable preamble lengths, enables inversion, changes database and reporter engine - for CC-Link/Mechatrolink encoder communications)
[-c:X] = set Kingst LA channel index (i.e. CSV column index) to X (defaults to 1)
[-o:X] = start parsing from time offset X (w.r. to 0), units are x10nS
[-imax:X] = max request-response delay (defaults to 1000x10nS)
[-imin:X] = min request-response delay (defaults to 50x10nS)
[-t] = use time limit instead of line limit
[-dt] = disable timestamp output (useful for output file comparison during analysis)

Returns:
    OK = 0,
    CommandLineError = 1,
    ParserError = 2,
    TranscoderError = 3,
    EmptyDataset = 4,
    BulkProcessingFailed = 5,
    OutOfMemory = 6

");
                Console.ReadLine();
                return((int)ExitCodes.CommandLineError);
            }

            //DataReporter.ReportProgress("Parsing command line...");
            int  limit       = 20000;
            int  freq        = 4000000;
            int  column      = 1;
            int  startOffset = int.MinValue;
            bool swap        = false;
            bool transcode   = false;
            bool invert      = false;
            bool encoder     = false;

            try
            {
                args[0] = args[0].Trim('"');
                if (args.Length > 1)
                {
                    limit = int.Parse(args[1]);
                    if (limit == 0)
                    {
                        limit = int.MaxValue;
                    }
                    if (args.Length > 2)
                    {
                        freq = int.Parse(args[2]);
                    }
                }
                DataReporter.FilterOutput                 = args.Contains("-f"); //DataReporter, BulkProcessing and ErrorListener setup goes first
                DataReporter.EnableProgressOutput         = !args.Contains("-s");
                DataReporter.DisableTimestamp             = args.Contains("-dt");
                BulkProcessing.UseParallelComputation     = args.Contains("-p");
                ErrorListener.PrintOnlyDistinctExceptions = !args.Contains("-x");
                if (args.Contains("-b"))                               //Possible bulk processing goes second
                {
                    return(BulkMain(args, limit, freq));
                }
                LogicAnalyzerData.UseTimeLimit = args.Contains("-t");
                invert    = args.Contains("-i");                       //And local vars are the last
                swap      = args.Contains("-e");
                transcode = args.Contains("-stm");
                if (args.Contains("-n")) //Mode switches override on/off switches
                {
                    //invert = true;
                    encoder = true;
                    HDLCManchesterDecoder.RequestPreambleLength  = 15;
                    HDLCManchesterDecoder.ResponsePreambleLength = 4;
                }
                //Switches with arguments override mode switches
                ArgumentHelper("-pq:", args, (int i) => { HDLCManchesterDecoder.RequestPreambleLength = i; });
                ArgumentHelper("-ps:", args, (int i) => { HDLCManchesterDecoder.ResponsePreambleLength = i; });
                ArgumentHelper("-c:", args, (int i) => { column = i; });
                ArgumentHelper("-imin:", args, (int i) => { HDLCManchesterDecoder.MinRequestResponseDelay = i; });
                ArgumentHelper("-imax:", args, (int i) => { HDLCManchesterDecoder.MaxRequestResponseDelay = i; });
                ArgumentHelper("-o:", args, (int i) => { startOffset = i; });
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to parse command line:");
                Console.WriteLine(e.ToString());
                return(ReturnHelper(ExitCodes.CommandLineError, args));
            }
            DataReporter.ReportProgress(string.Format("{3} limit: {0}, frequency: {1}, endianess swap: {2}.",
                                                      limit, freq, swap, LogicAnalyzerData.UseTimeLimit ? "Time" : "Line"));

            DataReporter.ReportProgress("Loading data...");
            LogicAnalyzerData data;

            try
            {
                if (LogicAnalyzerData.DetectFormat(args[0]) == LogicAnalyzerData.Format.LogicSnifferSTM)
                {
                    data = LogicAnalyzerData.CreateFromLogicSnifferSTM(args[0], limit);
                }
                else
                {
                    data = LogicAnalyzerData.CreateFromKingst(args[0], column, limit);
                }
                if (data.Count == 0)
                {
                    Console.WriteLine("Given current time constraints, the dataset empty!");
                    return(ReturnHelper(ExitCodes.EmptyDataset, args));
                }
                if (invert)
                {
                    data = data.Invert();
                }
                data = data.SkipUntil(startOffset);
                GC.Collect();
                DataReporter.ReportProgress("Parsing data...");
                if (encoder)
                {
                    var parsed = EncoderCommunication.Parse(data, freq, swap);
                    DoneReportErrors();
                    Console.WriteLine(DataReporter.CreateEncoderReportString(parsed));
                }
                else
                {
                    var parsed = MechatrolinkCommunication.Parse(data, freq, swap);
                    DoneReportErrors();
                    Console.WriteLine(DataReporter.CreateMechatrolinkReportString(parsed));
                }
            }
            catch (OutOfMemoryException)
            {
                return(ReturnHelper(ExitCodes.OutOfMemory, args));
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to parse the data:");
                Console.WriteLine(e.ToString());
                return(ReturnHelper(ExitCodes.ParserError, args));
            }

            if (transcode)
            {
                try
                {
                    MakeBackupCopy(args[0]);
                    LogicAnalyzerData.WriteToLogicSnifferSTM(data, args[0]);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to transcode the file:");
                    Console.WriteLine(e.ToString());
                    return(ReturnHelper(ExitCodes.TranscoderError, args));
                }
            }
            return(ReturnHelper(0, args));
        }