private static void _processData(Series ThisSeries, double DataStartS, double DataEndS, double ExecutionTime, string StartTime, CswColorGenerator ColorGenerator)
        {
            if (ThisSeries.data.Count > 0 && DataStartS - ThisSeries.data.Last()[0] <= 3)  //if pts are only up to 3 seconds apart, combine them
            {
                ThisSeries.data.Last()[0] = DataEndS;
            }
            else
            {
                if (String.IsNullOrEmpty(ThisSeries.color))
                {
                    ThisSeries.color = ColorGenerator.GetNextColor();
                }
                DataPoint point = new DataPoint()
                {
                    Start         = DataStartS,
                    End           = ThisSeries.SeriesNo,
                    ExecutionTime = ExecutionTime,
                    StartDate     = StartTime,
                };
                DataPoint point2 = new DataPoint()
                {
                    Start         = DataEndS,
                    End           = ThisSeries.SeriesNo,
                    ExecutionTime = ExecutionTime,
                    StartDate     = StartTime,
                };
                ThisSeries.DataPoints.Add(point);
                ThisSeries.DataPoints.Add(point2);
                ThisSeries.DataPoints.Add(null);

                if (ThisSeries.data.Count > 0 && null != ThisSeries.data.Last())
                {
                    ThisSeries.data.Add(null);
                    if (ThisSeries.label.Contains("Error"))
                    {
                        ThisSeries.data.Add(null);
                        ThisSeries.color = "#ff0000";
                    }
                }

                Collection <double> thisStartPt = new Collection <double>();
                thisStartPt.Add(DataStartS);
                thisStartPt.Add(ThisSeries.SeriesNo);
                ThisSeries.data.Add(thisStartPt);

                if (double.MinValue < DataEndS)
                {
                    Collection <double> thisEndPt = new Collection <double>();
                    thisEndPt.Add(DataEndS);
                    thisEndPt.Add(ThisSeries.SeriesNo);
                    ThisSeries.data.Add(thisEndPt);
                }
            }
        }
        public static void getTimelines(ICswResources CswResources, CswNbtSchedServiceTimeLineReturn Return, CswNbtSchedServiceTimeLineRequest Request)
        {
            CswNbtResources NbtResources = (CswNbtResources)CswResources;

            DateTime StartDate = new DateTime();
            int      counter   = 0;

            CswColorGenerator ColorGenerator = new CswColorGenerator();

            int SeriesNo = 30;

            Dictionary <string, Series> TimeLineData = new Dictionary <string, Series>();
            HashSet <string>            seen         = new HashSet <string>();

            string LogFileLocation = NbtResources.SetupVbls[CswEnumSetupVariableNames.LogFileLocation];

            _getLogFiles(NbtResources, Return, LogFileLocation);   //Order the log files by last modified date

            //Case 30403 - if the current user is not ChemSW_Admin, scope timeline to the users schema
            if (NbtResources.CurrentNbtUser.Username != CswAuthenticator.ChemSWAdminUsername)
            {
                Request.FilterSchemaTo = NbtResources.AccessId;
            }

            if (Return.Data.FilterData.LogFiles.Count > 0)
            {
                //If no log file is selected, default to the last log file modified
                string       selectedFile = String.IsNullOrEmpty(Request.SelectedLogFile) ? Return.Data.FilterData.LogFiles[0] : Request.SelectedLogFile;
                StreamReader file         = new StreamReader(LogFileLocation + @"\" + selectedFile);
                string       line;
                while ((line = file.ReadLine()) != null)
                {
                    line = line.Replace("\"", "");
                    string[] splitLine = line.Split(',');
                    string   MsgType   = splitLine[0];

                    if ((MsgType.Equals("PerOp") || MsgType.Equals("Error")))
                    {
                        string Schema        = splitLine[1];
                        string StartTime     = splitLine[20];
                        string OpName        = splitLine[23].Split(':')[0]; //this is something like "GenNode: Execution" and all we want is "GenNode"
                        double ExecutionTime = CswConvert.ToDouble(splitLine[28]);
                        string ErrMsg        = splitLine[9];

                        if (MsgType.Equals("Error"))
                        {
                            OpName        = "Error";
                            ExecutionTime = double.MinValue;
                        }
                        string LegendName = Schema + " " + OpName;

                        _populateFilterData(NbtResources, Return, OpName, Schema, seen);

                        DateTime thisStartDate = CswConvert.ToDateTime(StartTime);

                        DateTime FilterDateStart = CswConvert.ToDateTime(Request.FilterStartTimeTo);
                        DateTime FilterDateEnd   = CswConvert.ToDateTime(Request.FilterEndTimeTo);
                        CswCommaDelimitedString FilterSchemas = new CswCommaDelimitedString();
                        FilterSchemas.FromString(Request.FilterSchemaTo);
                        CswCommaDelimitedString FilterOps = new CswCommaDelimitedString();
                        FilterOps.FromString(Request.FilterOpTo);

                        if (((thisStartDate >= FilterDateStart && thisStartDate <= FilterDateEnd) || (DateTime.MinValue == FilterDateStart && DateTime.MinValue == FilterDateEnd)) &&
                            (FilterSchemas.Contains(Schema) || String.IsNullOrEmpty(Request.FilterSchemaTo)) &&
                            (FilterOps.Contains(OpName) || String.IsNullOrEmpty(Request.FilterOpTo)))
                        {
                            if (FilterSchemas.IsEmpty || FilterOps.IsEmpty)  //If no schema filter is set we want to generate a timeline of each schema + all the rules that ran
                            {
                                if (FilterOps.IsEmpty)
                                {
                                    if (MsgType.Equals("Error"))
                                    {
                                        //If we're mashing all schema errors together, we do not show what the msg or schema was was since there are probably many, just that an error occured.
                                        LegendName = "Error";
                                        OpName     = "";
                                        ErrMsg     = "";
                                        Schema     = "";
                                    }
                                    else
                                    {
                                        //If we're mashing all schema together, we don't care what op ran since there are many, just show that this schema was running something
                                        LegendName = Schema;
                                        OpName     = "";
                                    }
                                }
                                else
                                {
                                    //If no schema is selected, but there are Op filters, we mash the Ops on each Schema together
                                    LegendName = OpName;
                                    Schema     = "";
                                }
                            }

                            if (0 == counter)
                            {
                                StartDate = CswConvert.ToDateTime(StartTime);
                            }
                            counter++;

                            double DataStartS = (thisStartDate - StartDate).TotalMilliseconds / 1000;
                            double DataEndS   = double.MinValue;
                            if (MsgType.Equals("PerOp"))
                            {
                                DataEndS = DataStartS + ExecutionTime / 1000;
                            }

                            Series ThisSeries;
                            if (TimeLineData.ContainsKey(LegendName))
                            {
                                ThisSeries = TimeLineData[LegendName];
                            }
                            else
                            {
                                ThisSeries = new Series()
                                {
                                    label      = LegendName,
                                    SchemaName = Schema,
                                    OpName     = OpName,
                                    SeriesNo   = SeriesNo,
                                    ErrorMsg   = ErrMsg
                                };
                                TimeLineData.Add(LegendName, ThisSeries);
                                SeriesNo += 90;
                            }
                            _processData(ThisSeries, DataStartS, DataEndS, ExecutionTime, thisStartDate.ToString(), ColorGenerator);
                        }
                    } //if( splitLine.Length >= 28 && splitLine[0].Equals( "PerOp" ) )
                }     // while( ( line = file.ReadLine() ) != null && counter <= maxLines )

                foreach (Series series in TimeLineData.Values)
                {
                    Return.Data.Series.Add(series);
                }
            }
        }//getTimelines()