using System;
using System.Globalization;
using System.IO;
using System.Threading;
using ThreadSafe;
using XMLRoutines;

namespace OBD
{
	[Flags]
	public enum ChangeStateFlag
	{
		NoChange = 0,
		NewAnswerData = 1<<0,
		NewFlags = 1<<1,
		NewErrors = 1<<2,
		NewEngineState = 1<<3
	}

	public enum HOBDState
	{
		Disconnected,
		Connecting,
		Connected
	}

	public enum ReadMode
	{
		All,
		Fast_60h,
		Custom
	}

	public enum RPMCalcMethod
	{
		Obd0,
		Obd1,
		Obd2a,
		Other
	}

	public enum O2CalcMethod
	{
		Oxygen,
		ARF
	}

	public enum FuelInjFlowRate
	{
		FR180cc = 180,
		FR235cc = 235,
		FR240cc = 240,
		FR275cc = 270,
		FR325cc = 325
	}

	#region Statistics Routines
	public class StatItemsList
	{
		public ThreadSafeList<OBDAnswerItem> list = new ThreadSafeList<OBDAnswerItem>();
		
		public static StatItemsList LoadItems(string FName)
		{
			StatItemsList res = new StatItemsList();
			if(File.Exists(FName))
			{
				try
				{
					using (StreamReader sr = new StreamReader(FName))
					{
						string s;
						while ( (s = sr.ReadLine()) != null)
						{
							OBDAnswerItem itm = OBDAnswerItem.FromLine(s);
							if (itm != null)
								res.list.Add(itm);
						}
					}
				}catch
				{}
			}
			return res;
		}

		public bool SaveItems(string FName)
		{
			if(list != null)
			{
				try
				{
					using(StreamWriter sw = new StreamWriter(FName))
					{
						for(int i=0; i<list.Count; i++)
						{
							sw.WriteLine(list[i].ToLine());
						}
					}
				}
				catch
				{
					return false;
				}
			}
			return true;
		}

	}

	public class CommandsAnswersList
	{
		public ThreadSafeList<OBDCommand> list = new ThreadSafeList<OBDCommand>();

		public static CommandsAnswersList LoadItems(string FName)
		{
			CommandsAnswersList res = new CommandsAnswersList();
			if (File.Exists(FName))
			{
				try
				{
					using (StreamReader sr = new StreamReader(FName))
					{
						string s;
						while ((s = sr.ReadLine()) != null)
						{
							OBDCommand itm = OBDCommand.FromLine(s);
							if (itm != null)
								res.list.Add(itm);
						}
					}
				}
				catch
				{ }
			}
			return res;
		}

		public bool SaveItems(string FName)
		{
			if (list != null)
			{
				try
				{
					using (StreamWriter sw = new StreamWriter(FName))
					{
						for (int i = 0; i < list.Count; i++)
						{
							sw.WriteLine(list[i].ToLine());
						}
					}
				}
				catch
				{
					return false;
				}
			}
			return true;
		}

	}
	#endregion

	public class SensorsState
	{
		public double NewRPM = double.NaN;
		public double NewO2 = double.NaN;
		public double NewECT = double.NaN;
		public double NewIAT = double.NaN;
		public double NewBat = double.NaN;
		public double NewCorrLT = double.NaN;
		public double NewCorrCT = double.NaN;
		public double NewIAC = double.NaN;
		public double NewInjTime = double.NaN;
		public double NewIngFAngle = double.NaN;
		public double NewIngAdv2 = double.NaN;
		public double NewKnock = double.NaN;
		public double NewMAP = double.NaN;
		public double NewBaro = double.NaN;
		public double NewTPS = double.NaN;
		public double NewTPSVolt = double.NaN;
		public double NewVss = double.NaN;
		public double NewDutyCycle = double.NaN;

		public double RPM = double.NaN;
		public double O2 = double.NaN;
		public double ECT = double.NaN;
		public double IAT = double.NaN;
		public double Bat = double.NaN;
		public double CorrLT = double.NaN;
		public double CorrCT = double.NaN;
		public double IAC = double.NaN;
		public double InjTime = double.NaN;
		public double IngFAngle = double.NaN;
		public double IngAdv2 = double.NaN;
		public double Knock = double.NaN;
		public double MAP = double.NaN;
		public double Baro = double.NaN;
		public double TPS = double.NaN;
		public double TPSVolt = double.NaN;
		public double Vss = double.NaN;
		public double DutyCycle = double.NaN;

		public void ApplyNew()
		{
			RPM = NewRPM;
			O2 = NewO2;
			ECT = NewECT;
			IAT = NewIAT;
			Bat = NewBat;
			CorrLT = NewCorrLT;
			CorrCT = NewCorrCT;
			IAC = NewIAC;
			InjTime = NewInjTime;
			IngFAngle = NewIngFAngle;
			IngAdv2 = NewIngAdv2;
			Knock = NewKnock;
			MAP = NewMAP;
			Baro = NewBaro;
			TPS = NewTPS;
			TPSVolt = NewTPSVolt;
			Vss = NewVss;
			DutyCycle = NewDutyCycle;
			NewRPM = double.NaN;
			NewO2 = double.NaN;
			NewECT = double.NaN;
			NewIAT = double.NaN;
			NewBat = double.NaN;
			NewCorrLT = double.NaN;
			NewCorrCT = double.NaN;
			NewIAC = double.NaN;
			NewInjTime = double.NaN;
			NewIngFAngle = double.NaN;
			NewIngAdv2 = double.NaN;
			NewKnock = double.NaN;
			NewMAP = double.NaN;
			NewBaro = double.NaN;
			NewTPS = double.NaN;
			NewVss = double.NaN;
			NewDutyCycle = double.NaN;
		}
		public void ResetToZero()
		{
			NewRPM = 0;
			NewO2 = 0;
			NewECT = 0;
			NewIAT = 0;
			NewBat = 0;
			NewCorrLT = 0;
			NewCorrCT = 0;
			NewIAC = 0;
			NewInjTime = 0;
			NewIngFAngle = 0;
			NewIngAdv2 = 0;
			NewKnock = 0;
			NewMAP = 0;
			NewBaro = 0;
			NewTPS = 0;
			NewTPSVolt = 0;
			NewVss = 0;
			NewDutyCycle = 0;
		}
	}

	public class EngineState
	{
		public int CylindersCount = 4;
		public FuelInjFlowRate InjectorFlowRate = FuelInjFlowRate.FR240cc;

		public double CorrectionLiters = 0;
		public double CorrectionVSS = 0;

		private int CurrVSS;
		private int CurrRPM;
		private double CurrInjTime;
		
		private double TotalInjTime = 0;
		private double TotalLengthPath = 0;
		private double fMomentumLiters100 = 0;
		private double fModentumLitersHour = 0;

		private double MomentumInjTime = 0;
		private double MomentumTimeSpan = 0;


		private int LastTimeUpdateVSS;
		private int LastTimeUpdateRPM;
		private int LastTimeUpdateInjTime;		
		private int LastCalcTime;
		private int LastCalcRealTime; // для таймаутов моментальных величин

		#region Total Values without corrections Use only for Load Save
		internal void SetTotalValues(double injTime, double PathLen)
		{
			TotalInjTime = injTime;
			TotalLengthPath = PathLen;
		}
		internal void GetTotalValues(out double injTime, out double PathLen)
		{
			injTime = TotalInjTime;
			PathLen = TotalLengthPath;
		}
		#endregion

		public double MomentumLiters100
		{
			get
			{
				if (Environment.TickCount - LastCalcRealTime > 1000)
					return double.NaN;
				return fMomentumLiters100;
			}
		}
		public double MomentumLitersHour
		{
			get
			{
				if (Environment.TickCount - LastCalcRealTime > 1000)
					return double.NaN;
				return fModentumLitersHour;
			}
		}

		public double GetTotalLengthPath()
		{
			return TotalLengthPath + (TotalLengthPath * CorrectionVSS);
		}
		public double GetTotalLiters()
		{
			double res = 0;
			if (TotalInjTime > 0)
				res = CalcLiters(TotalInjTime);
			return res;
		}

		public double GetAverageLitersPer100()
		{
			double res = double.NaN;
			if (GetTotalLengthPath() > 5)
			{
				double Coeff100 = GetTotalLengthPath() / 100;
				res = CalcLiters(TotalInjTime) / Coeff100;
			}
			return res;
		}

		public void SetNewVSS(int vss, int now)
		{
			double spanSec = Math.Abs((double)(LastTimeUpdateVSS - now) / 1000);
			if(spanSec<1)
			{
				TotalLengthPath += spanSec * ((double) vss / 60 / 60);
			}
			CurrVSS = vss;
			LastTimeUpdateVSS = now;
		}
		
		public void SetNewRPM(int rpm, int now)
		{
			double spanSecRPM = Math.Abs((LastTimeUpdateRPM - now) / 1000d);
			double spanSecInjTime = Math.Abs((LastTimeUpdateInjTime - now) / 1000d);
			if (spanSecRPM < 1 && spanSecInjTime <1)
			{
				double spanSecCalc = (now - LastCalcTime) / 1000d;
				double IngjCount = spanSecCalc * (rpm / 60f) * (CylindersCount / 2f);
				double spanInjTime = CurrInjTime * IngjCount/1000d;
				TotalInjTime += spanInjTime;
				MomentumInjTime = spanInjTime;
				MomentumTimeSpan = spanSecCalc;
				fMomentumLiters100 = CalcMomentumLitersPer100(now);
				fModentumLitersHour = CalcMomentumLitersPerHour(now);
				LastCalcRealTime = Environment.TickCount;
				LastCalcTime = now;
			}
			CurrRPM = rpm;
			LastTimeUpdateRPM = now;
		}

		public void SetNewInjTime(double injTime, int now)
		{
			double spanSecRPM = Math.Abs((double)(LastTimeUpdateInjTime - now) / 1000);
			double spanSecInjTime = Math.Abs((double)(LastTimeUpdateInjTime - now) / 1000);
			if (spanSecRPM < 1 && spanSecInjTime < 1)
			{
				double spanSecCalc = (now - LastCalcTime) / 1000d;
				double IngjCount = spanSecCalc * (CurrRPM / 60f) * (CylindersCount / 2f);
				double spanInjTime = injTime * IngjCount/1000d;
				TotalInjTime += spanInjTime;
				MomentumInjTime = spanInjTime;
				MomentumTimeSpan = spanSecCalc;
				fMomentumLiters100 = CalcMomentumLitersPer100(now);
				fModentumLitersHour = CalcMomentumLitersPerHour(now);
				LastCalcRealTime = Environment.TickCount;
				LastCalcTime = now;
			}
			CurrInjTime = injTime;
			LastTimeUpdateInjTime = now;
		}

		private double CalcMomentumLitersPer100(int now)
		{
			double res = 0;
			double span = Math.Abs((double)(LastCalcTime - now) / 1000);
			if (span < 1 && CurrVSS > 5 && CurrVSS != 0)
			{
				double Speed100Coeff = 100d / (CurrVSS + CorrectionVSS * CurrVSS);
				res = CalcMomentumLitersPerHour(now) * Speed100Coeff;
			}
			return res;
		}
		private double CalcMomentumLitersPerHour(int now)
		{
			double res = 0;
			double span = Math.Abs((double)(LastCalcTime - now) / 1000);
			if (span < 1 && MomentumTimeSpan != 0)
			{
				double HourCoeff = 3600 / MomentumTimeSpan;
				res = CalcLiters(MomentumInjTime) * HourCoeff;
			}
			return res;			
		}
		private double CalcLiters(double injTime)
		{
			double tmp = injTime / 60d * (int) InjectorFlowRate / 1000d; 
			return tmp + tmp* CorrectionLiters;
		}

		public void ClearCurrState()
		{
			CurrVSS = 0;
			CurrRPM = 0;
			CurrInjTime = 0;
			LastTimeUpdateVSS = 0;
			LastTimeUpdateRPM = 0;
			LastTimeUpdateInjTime = 0;
			LastCalcTime = 0;
		}
		public void ClearTotalState()
		{
			ClearCurrState();
			TotalInjTime = 0;
			TotalLengthPath = 0;
		}
	}

	public class HOBD_Status
	{
		private object QueueSync = new object();
		public readonly SensorsState SensState = new SensorsState();
		public readonly EngineState EngineState = new EngineState();
		public HOBDState State;
		private HOBDFlags fFlags;
		public HOBDFlags Flags
		{
			get { return fFlags; }
			set
			{
				if(fFlags != value)
				{
					fFlags = value;
					ChStateFlag |= ChangeStateFlag.NewFlags;
				}
			}
		}
		public ChangeStateFlag ChStateFlag = ChangeStateFlag.NoChange;
		public string IdECU = "";
		public string StateStr
		{
			get
			{
				string res = "Состояние: ";
				switch(State)
				{
					case HOBDState.Disconnected:
						res += "отключено";
						break;
					case HOBDState.Connecting:
						res += "подключение";
						break;
					case HOBDState.Connected:
						res += "подключено";
						if(LastCommandChainTimeSpan>-1)
						{
							double speed = 1000f / LastCommandChainTimeSpan;
							res += string.Format(". Скорость: {0} блоков с.", speed.ToString("0"));
						}
						break;
				}
				return res;
			}
		}

		public readonly ThreadSafeList<byte> ErrorCodes = new ThreadSafeList<byte>();

		public int LastCommandChainTimeSpan = -1;

		public void AddStatItem(OBDAnswerItem itm)
		{
				if (itm == null)
					return;
				if (!offsetSetted)
				{
					offset = -itm.AnsTime;
					LastTimeValue = 0;
					offsetSetted = true;
				}
				int Span = (itm.AnsTime + offset) - LastTimeValue;
				if (Span < 0)
				{
					offset = itm.AnsTime - LastTimeValue;
					offset += 3000;
				}
				if (Span > 3000)
				{
					offset = -itm.AnsTime + LastTimeValue;
					offset += 3000;
				}
				itm.AnsTime = itm.AnsTime + offset;

				LastTimeValue = itm.AnsTime;
				lock (QueueSync)
				{
					SensorNewStatQueue.Enqueue(itm);
				}
		}
		public OBDAnswerItem GetNextStatItem()
		{
			return SensorNewStatQueue.Dequeue();
		}
		public int CountStatItems
		{
			get { return SensorNewStatQueue.Count; }
		}
		public OBDAnswerItem[] GetAllStatItems()
		{
			OBDAnswerItem[] res;
			lock(QueueSync)
			{
				res = SensorNewStatQueue.ToArray;
				SensorNewStatQueue.Clear();
			}
			return res;
		}
		private int offset;
		public bool offsetSetted;
		private int LastTimeValue;
		private readonly ThreadSafeQueue<OBDAnswerItem> SensorNewStatQueue = new ThreadSafeQueue<OBDAnswerItem>();

		public bool FileOperationActive = false;
		public bool FileOperationNeedCancel = false;
		public bool FileOprationNeedPause = false;
		public int FileOperationPercent = 0;
		public string StringAPPTitle = "";
	}

	public class HOBD_Params : XMLSettings
	{
		public int PortNumber = 6;
		public bool AutoSaveLogDataOnExit = true;
		public bool AutoLoadLogDataOnStartApp = true;
		public ReadMode ReadMode = ReadMode.All;
		public bool ScrollChartIfSelected = false;
		public string CustomReqests = "000F";
		public RPMCalcMethod RMPMethod = RPMCalcMethod.Obd1;
		public O2CalcMethod O2Method = O2CalcMethod.Oxygen;
		public bool InverseHiLoOrder = true;
		public bool PaintCharts = true;
		public int SpeedLoadingHistory; //0-max, else realTime*value  
		public double EngineTotalInjTime = 0;
		public double EngineTotalPathLength;
		public FuelInjFlowRate InjectorFlowRate = FuelInjFlowRate.FR240cc;
		public double CorrectionLiters = 1;
		public double CorrectionVSS = 1;

		public bool Save(string FName)
		{
			return SaveSettings(FName);
		}
		public static HOBD_Params Load(string FName)
		{
			HOBD_Params res = (HOBD_Params)
				LoadSettings(FName, typeof(HOBD_Params));
			return res;
		}
	}

	public class HOBD
	{
		#region private Vars
		private LogStorage fLog;
		private readonly string ProgrammDir;
		private readonly string LogDir;
		private readonly string SettingsFName;
		private readonly string HistoryFName;
		private string fFatalError;
		private readonly Thread UpdateThread;
		private readonly ThreadSafeList<byte[]> CustomCodes= new ThreadSafeList<byte[]>();
		#endregion

		#region Public Properties
		public int LastAckTime;
		public HOBD_Params Params;
		public LogStorage Log
		{
			get
			{
				return fLog;
			}
			set
			{
				fLog = value;
				if (Linker != null)
					Linker.Log = fLog;
			}
		}
		public bool UpdateStarted;

		public HOBD_Status Status;
		public string FatalError
		{
			get
			{
				string s = Linker != null ? Linker.FatalError : "";
				if (string.IsNullOrEmpty(s))
					s = fFatalError;
				return s;
			}
		}
		public CommandsAnswersList CommandsAnswersHistory = new CommandsAnswersList();
		public bool ActivityVisible
		{
			get
			{
				return (Environment.TickCount - LastAckTime) < 250;
			}
		}
		#endregion

		protected OBDLinker Linker;


		public HOBD(string ProgDir)
		{
			ProgrammDir = ProgDir;
			if (ProgrammDir != null)
				if (!ProgrammDir.EndsWith("\\"))
					ProgrammDir += "\\";
			LogDir = ProgrammDir + "LogStorage\\";
			SettingsFName = ProgrammDir + "Settings.xml";
			HistoryFName = LogDir + "AutoSavedHistory.log";
			Status = new HOBD_Status {State = HOBDState.Disconnected};
			UpdateThread = new Thread(UpdateThreadProc) {IsBackground = true};
			UpdateStarted = true;
			UpdateThread.Start();
		}

		public void Disconnect()
		{
			Status.State = HOBDState.Disconnected;
			Status.Flags = HOBDFlags.AllOff;
			if (Linker != null)
			{
				Linker.Dispose();
				Linker = null;
			}
		}

		public void Connect()
		{
			if (Status.State != HOBDState.Disconnected)
				return;
			LastAckTime = Environment.TickCount;
			fFatalError = "";
			Thread ConnThread = new Thread(() =>
			{
				Status.StringAPPTitle = "";
				Status.State = HOBDState.Connecting;
				Linker = new OBDLinker(Params.PortNumber) {Log = Log};
				if (Linker.Connected)
				{
					Status.State = HOBDState.Connected;
					Linker.OnAnswer = OnReceiveAnswer;
					BuildCustomCodes();
					Linker.SendIdECUReq();
				}
				else
				{
					string s = FatalError;
					if (!String.IsNullOrEmpty(s))
						Log.ErrorString = FatalError;
					Disconnect();
				}
			}) {IsBackground = true};
			ConnThread.Start();
		}

		public void SendErrorCodesReq()
		{
			if(Status.State == HOBDState.Connected)
			{
				Status.ErrorCodes.Clear();
				Status.ChStateFlag |= ChangeStateFlag.NewErrors;
				Linker.SendErrorCodesReq();
			}
		}
		public void SendClearECUReq()
		{
			if (Status.State == HOBDState.Connected)
			{
				Status.ErrorCodes.Clear();
				Status.ChStateFlag |= ChangeStateFlag.NewErrors;
				Linker.SendClearECURequest();
			}			
		}

		public void BuildCustomCodes()
		{
			CustomCodes.Clear();
			string s = Params.CustomReqests;
			if(!string.IsNullOrEmpty(s))
			{
				string[] sarr = s.Split(' ');
				for(int i=0; i<sarr.Length; i++)
				{
					string code = sarr[i];
					if(code!= null && code.Length==4)
					{
						try
						{
							byte[] bb = new byte[2];
							bb[0] = byte.Parse(code.Substring(0,2), NumberStyles.AllowHexSpecifier);
							bb[1] = byte.Parse(code.Substring(2,2), NumberStyles.AllowHexSpecifier);
							CustomCodes.Add(bb);
						}
						catch
						{}
					}
				}
			}
		}

		public void ClearHistory()
		{
			if(CommandsAnswersHistory != null)
			{
				CommandsAnswersHistory.list.Clear();
			}
			Status.offsetSetted = false;
		}


		public void OnReceiveAnswer(OBDCommand cmd)
		{
			LastAckTime = Environment.TickCount;
			if (cmd != null)
			{
				if (CommandsAnswersHistory == null)
					CommandsAnswersHistory = new CommandsAnswersList();
				CommandsAnswersHistory.list.Add(cmd);
				if (cmd.AnsList.Count > 0)
				{
					for (int i = 0; i < cmd.AnsList.Count; i++)
					{
						OBDAnswerItem itm = cmd.AnsList[i];
						Status.AddStatItem(itm);
						if(itm.AnsType == OBDAnswerItemType.Flags)
						{
							ulong flags = (ulong)Status.Flags;
							int shift = (int)(itm.Offset - 8)*8;
							byte partFlag = (byte) ((flags >> shift) & 0xff);
							if(partFlag != itm.Value)
							{
								ulong mask = ~((ulong)0xFF << shift);
								flags &= mask;
								ulong FlagsValue = ((ulong)((byte)itm.Value) << shift);
								flags =flags | FlagsValue;
								Status.Flags = (HOBDFlags) flags;
							}
						}
						if(itm.AnsType == OBDAnswerItemType.IDECU)
						{
							Status.IdECU = "";
							for (int j = 0; j < itm.ValuesArray.Length; j++)
							{
								if (j != 0)
									Status.IdECU += " ";
								Status.IdECU += itm.ValuesArray[j].ToString("X2");
							}
						}
						if(itm.AnsType == OBDAnswerItemType.Errors)
						{
							for(int errInd=0; errInd<64; errInd++)
							{
								if (itm.ParsedErrorsArray[errInd])
									Status.ErrorCodes.Add((byte) errInd);
							}
							Status.ChStateFlag |= ChangeStateFlag.NewErrors;
						}
						if(itm.AnsType == OBDAnswerItemType.InjTime)
						{
							Status.EngineState.SetNewInjTime(OBDHelper.FormulaInjTimeCalc(itm.Value, Params.InverseHiLoOrder), 
								itm.AnsTime);
							Status.ChStateFlag |= ChangeStateFlag.NewEngineState;
						}
						if(itm.AnsType == OBDAnswerItemType.VSS)
						{
							Status.EngineState.SetNewVSS(itm.Value, itm.AnsTime);
							Status.ChStateFlag |= ChangeStateFlag.NewEngineState;
						}
						if(itm.AnsType == OBDAnswerItemType.RPM)
						{
							Status.EngineState.SetNewRPM(OBDHelper.FormulaRPM(itm.Value, Params.RMPMethod, 
								Params.InverseHiLoOrder),itm.AnsTime);
							Status.ChStateFlag |= ChangeStateFlag.NewEngineState;
						}
					}

				}
			}
		}


		public void UpdateThreadProc()
		{
			while (UpdateStarted)
			{
				int t = Environment.TickCount;
				try
				{
					if (Status.State != HOBDState.Disconnected && t - LastAckTime > 5000)
					{
						fFatalError = "УСТРОЙСТВО НЕ ОТВЕЧАЕТ";
					}
					if(Status.State == HOBDState.Connected)
					{
						Status.LastCommandChainTimeSpan = Linker.LastCommandChainSpan;
						//Linker.SendAllSensorsReq();
						switch(Params.ReadMode)
						{
							case ReadMode.All:
								Linker.SendExperimentalReq();
								//Linker.SendAllSensorsReq();
								break;
							case ReadMode.Fast_60h:
								Linker.SendFastSensorsReq();
								break;
							case ReadMode.Custom:
								Linker.SendCustomReq(CustomCodes);
								break;
						}
						//Linker.SendErrorCodesReq();
					}
				}
				catch (Exception ex)
				{
					Log.ErrorString = "Ошибка в потоке обновления HOBD: " + ex.Message;
				}
				Thread.Sleep(10);
			}
		}

		#region Load Save Routines

		private string tmpFname;
		public void LoadHistory(string FName)
		{
			if (Status.FileOperationActive)
				return;
			tmpFname = FName;
			Status.FileOperationActive = true;
			Thread LoadHistoryThread = new Thread(LoadHistoryProc) {IsBackground = true};
			LoadHistoryThread.IsBackground = true;
			LoadHistoryThread.Priority = ThreadPriority.BelowNormal;
			LoadHistoryThread.Start();
		}
		
		private void LoadHistoryProc()
		{
			try
			{
				int LastCMDTime = -1;
				int NextCMDTime = -1;
				int WaitTime = 0;
				CommandsAnswersList tmp = CommandsAnswersList.LoadItems(tmpFname);
				if (tmp.list.Count > 0)
				{
					Status.StringAPPTitle = tmpFname;
					for (int i = 0; i < tmp.list.Count; i++)
					{
						Status.FileOperationPercent = (int) ((float) i / tmp.list.Count * 100);
						if (Status.FileOperationNeedCancel)
							break;
						OBDCommand cmd = tmp.list[i];
						if (LastCMDTime == -1)
						{
							LastCMDTime = cmd.ReceiveAnswerTime;
							NextCMDTime = Environment.TickCount;
						}else
						{
							WaitTime = cmd.ReceiveAnswerTime - LastCMDTime;
							LastCMDTime = cmd.ReceiveAnswerTime;
							if(Math.Abs(WaitTime)>1000)
								WaitTime = 1000;
							if(Params.SpeedLoadingHistory > 10)
								WaitTime = 0;
							else
							{
								WaitTime = (int) ((double) WaitTime / Params.SpeedLoadingHistory);
							}
							NextCMDTime = Environment.TickCount + WaitTime; 
						}
						if (WaitTime > 0)
							Thread.Sleep(WaitTime);
						if (cmd.ParseAnswer(cmd.ReceivedDataBytes))
							OnReceiveAnswer(cmd);
						while (Status.FileOprationNeedPause && !Status.FileOperationNeedCancel)
						{
							Thread.Sleep(250);
						}
					}
				}
				tmp.list.Clear();
			}
			finally
			{
				Status.FileOperationNeedCancel = false;
				Status.FileOprationNeedPause = false;
				Status.FileOperationActive = false;
				Status.FileOperationPercent = 0;
			}
		}

		public void SaveHistory(string FName)
		{
			CommandsAnswersHistory.SaveItems(FName);
		}


		public void Load()
		{
			Params = HOBD_Params.Load(SettingsFName) ?? new HOBD_Params();
			Status.EngineState.SetTotalValues(Params.EngineTotalInjTime, Params.EngineTotalPathLength);
			Status.EngineState.CorrectionLiters = Params.CorrectionLiters;
			Status.EngineState.CorrectionVSS = Params.CorrectionVSS;
			Status.EngineState.InjectorFlowRate = Params.InjectorFlowRate;

			if (Params.AutoLoadLogDataOnStartApp)
			{
				LoadHistory(HistoryFName);
			}
		}

		public void Save()
		{
			Status.EngineState.GetTotalValues(out Params.EngineTotalInjTime,out Params.EngineTotalPathLength);
			Params.CorrectionLiters = Status.EngineState.CorrectionLiters;
			Params.CorrectionVSS = Status.EngineState.CorrectionVSS;
			Params.InjectorFlowRate = Status.EngineState.InjectorFlowRate;

			if (!Params.Save(SettingsFName))
				Log.ErrorString = "Error save PARAMS";
			if (Params.AutoSaveLogDataOnExit)
			{
				SaveHistory(HistoryFName);
			}
		}
		#endregion

	}
}