public IEnumerable<WeightedLocation> GetWeightedLocations(LogLine logLine)
        {
            var baseRpmIndex = RpmIndex(logLine.Rpm.Value);
            var baseKpaIndex = KpaIndex(logLine.Map.Value);

            var locations = new List<WeightedLocation>();

            for (int kpaI = SafeIndex(baseKpaIndex - 2); kpaI <= SafeIndex(baseKpaIndex + 2); kpaI++)
            {
                for (int rpmI = SafeIndex(baseRpmIndex - 2); rpmI <= SafeIndex(baseRpmIndex + 2); rpmI++)
                {
                    var location = new WeightedLocation
                    {
                        KpaIndex = kpaI,
                        RpmIndex = rpmI,
                        ProximityIndex = CalcProximityIndex(kpaI, rpmI, logLine)
                    };
                    if (location.ProximityIndex > 0.05)
                    {
                        locations.Add(location);    
                    }
                }
            }

            if (locations.Count == 1)
            {
                locations[0].ProximityIndex = 1;
            }

            return locations;
        }
        public void TestIndexing()
        {
            var coords = new MapCoordinates();

            var logline = new LogLine(){Rpm = new Rpm(){Value = 1888}, Map = new Map(){Value = 45}};
            Assert.AreEqual(2.7777, coords.ExactRpmIndex(logline));
            Assert.AreEqual(2.6, coords.ExactKpaIndex(logline));
        }
        public void TestImportance(float rpm, float kpa)
        {
            var service = new MapCoordinates();
            var line = new LogLine()
            {
                Map = new Map { Value = kpa },
                Rpm = new Rpm { Value = rpm }
            };

            var location = service.GetWeightedLocations(line);

            Assert.GreaterOrEqual(location.Count(), 1);
            Assert.Greater(location.Sum(l => l.ProximityIndex), 0.85);
            Assert.Less(location.Sum(l => l.ProximityIndex), 1.18);

        }
        private LogLine GetLogLine(byte[] line)
        {
            var logLine = new LogLine()
            {
                Map = new Map() { Bytes = line[Index["Map"]]},
                AccEnr = new AccEnr() { Bytes = line[Index["AccEnr"]] },
                AfrCorrection = new AfrCorrection() { Bytes = line[Index["AfrCorrection"]] },
                AfrWideband = new AfrWideband() { Bytes = line[Index["AfrWideband"]] },
                Coolant = new Coolant() { Bytes = line[Index["Coolant"]] },
                Iat = new Iat() { Bytes = line[Index["Iat"]] },
                Tps = new Tps() { Bytes = line[Index["Tps"]] },

                Rpm = new Rpm() { Bytes = BitConverter.ToUInt16(line, Index["Rpm"]) },
            };
            return logLine;
        }
        public LogLine[] ReadLog(byte[] fileContent)
        {
            var howManyLines = (fileContent.Length - Header.Length) / LineLength;
            var result = new LogLine[howManyLines];

            var copyArray = new byte[40];

            for (var lineNo = 0; lineNo < howManyLines; lineNo += 1)
            {
                var skip = Header.Length + lineNo * LineLength;
                //if slow, change to array.Copy()

                Array.Copy(fileContent, skip, copyArray, 0, 40);
                //var lineBytes = fileContent.Skip(skip).Take(LineLength).ToArray();
                var line = GetLogLine(copyArray);
                result[lineNo] = line;
            }
            return result;

        }
        public IEnumerable<LogLine>[,] BuildMap(LogLine[] log)
        {
            var map = new List<LogLine>[16,16];

            for (int i = 0; i < log.Length - 1; i++)
            {
                var logLine = log[i];

                //cool engine
                if (logLine.Coolant.Value < 70) continue;

                // acceleration enrichment
                if (logLine.AccEnr.Value > 5)
                {
                    continue;
                }

                //fuel cut
                if (logLine.Map.Value < 20 && logLine.Tps.Value < 2 && logLine.Rpm.Value > 1650) continue;

                //engine stop
                if (logLine.Rpm.Value < 3) continue;

                //fuel cut
                if(Math.Abs(logLine.AfrCorrection.Value) < 0.1 && logLine.AfrWideband.Value > 22) continue;

                if (i + AfrCorrDelay < log.Length)
                {
                    logLine.AfrCorrection.Bytes = log[i + AfrCorrDelay].AfrCorrection.Bytes;
                }

                var rpmIndex = _coord.RpmIndex(logLine.Rpm.Value);
                var lambdaDelay = LambdaDelay[rpmIndex];
                //var lambdaDelay = 0;
                //lambda delay (cca 100ms)
                if (i + lambdaDelay < log.Length)
                {
                    //Console.WriteLine("Lambda diff: {0}", logLine.AfrWideband.Value - log[i + LambdaDelay].AfrWideband.Value);
                    logLine.AfrWideband.Bytes = log[i + lambdaDelay].AfrWideband.Bytes;
                }


                var weightedLocations = _coord.GetWeightedLocations(logLine);

                var topOne = weightedLocations.OrderByDescending(w => w.ProximityIndex).FirstOrDefault();
                //if (topOne != null)
                //{
                //    topOne.ProximityIndex = 1;    
                //}
                

                foreach (var weightedLocation in weightedLocations)
                {
                    if (map[weightedLocation.RpmIndex, weightedLocation.KpaIndex] == null)
                    {
                        map[weightedLocation.RpmIndex, weightedLocation.KpaIndex] = new List<LogLine>();
                    }

                    logLine.ProximityIndex = weightedLocation.ProximityIndex;
                    map[weightedLocation.RpmIndex, weightedLocation.KpaIndex].Add(logLine);          
                }

                
                
            }

            return map;
        }
 public float ExactKpaIndex(LogLine logLine)
 {
     return (logLine.Map.Value - _realKpaOffset) / (KpaMax - KpaMin) * 16 - 1;
 }
 public float ExactRpmIndex(LogLine logLine)
 {
     return ((logLine.Rpm.Value + _realRpmOffset) / RpmMax) * 16 - 1;
 }
        public float CalcProximityIndex(int kpaI, int rpmI, LogLine logLine)
        {
            var exactRpmIndex = ExactRpmIndex(logLine);
            var exactKpaIndex = ExactKpaIndex(logLine);

            var kpaDiff = Math.Abs(kpaI - exactKpaIndex + 0.5);
            var rpmDiff = Math.Abs(rpmI - exactRpmIndex + 0.5);

            var distance = Math.Sqrt(Math.Pow(kpaDiff, 2) + Math.Pow(rpmDiff, 2));

            //var index = (float)Math.Log(1 / distance, 10);
            //return Math.Min(1, Math.Max(0, index));

            if (distance < 1)
            {
                return (float) (1 - distance);
            }

            return 0;
        }
        public float[,] CalculateAfrDelay(LogLine[] log)
        {
            var map = new List<int>[16, 16];
            var fuelCut = false;
            for (int i = 0; i < log.Length; i++)
            {
                var logLine = log[i];
                int delay;

                var kpaIndex = _coord.KpaIndex(logLine.Map.Value);
                var rpmIndex = _coord.RpmIndex(logLine.Rpm.Value);

                if (map[rpmIndex, kpaIndex] == null)
                {
                    map[rpmIndex, kpaIndex] = new List<int>();
                }

                
                // fuel cut start
                if (!fuelCut && logLine.Map.Value < 26 && logLine.Tps.Value < 1 && logLine.Rpm.Value > 1650)
                {
                    fuelCut = true;
                    //fuel cut start
                    if (log[i + 1].Map.Value > 26 && log[i + 1].Tps.Value > 1 && log[i + 1].Rpm.Value > 1650)
                    {
                        for (int j = i; j < i + 30; j++)
                        {
                            if (log[j].AfrWideband.Value > 18)
                            {
                                map[rpmIndex, kpaIndex].Add(i - j);
                                break;
                            }
                            
                        }
                        //check how long it took
                    }
                }
                // fuel cut end
                else if (fuelCut && logLine.Map.Value > 26 && logLine.Tps.Value > 1 && logLine.Rpm.Value > 1650)
                {
                    fuelCut = false;
                    //fuel cut start
                    if (log[i + 1].Map.Value > 26 && log[i + 1].Tps.Value > 1 && log[i + 1].Rpm.Value > 1650)
                    {
                        for (int j = i; j < i + 30; j++)
                        {
                            if (log[j].AfrWideband.Value < 16)
                            {
                                map[rpmIndex, kpaIndex].Add(i-j);
                                break;
                            }

                        }
                        //check how long it took
                    }
                }
                else
                {
                    fuelCut = false;
                    continue;
                }


            }

            return Averange(map);
        }