// Created with MonoDevelop
//
//    Copyright (C) 2010 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.



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

using Hiperscan.SGS;
using Hiperscan.Unix;


namespace Hiperscan.FileIO
{

	public class LUT
	{
		
		private struct ParserHFormat
		{
			public const string LutOffset   = @"const long lut_offset ="; 
			public const string DateTime    = @"#define LUT_VERSION ";
		}
		
		private struct Parser
		{
			public const string Culture           = @"CULTURE";
			public const string Timestamp         = @"TIMESTAMP";
			public const string AcquisitionOffset = @"ACQUISITION_OFFSET";
			public const string LutOffset         = @"LUT_OFFSET";
            public const string MSAFactor         = @"MSA_FACTOR";
			public const string FirmwareVersion   = @"FIRMWARE_VERSION";
			public const string ScanEngineVersion = @"SCAN_ENGINE_VERSION";
			public const string DateTimeFormat    = @"yyyy-MM-dd_HH:mm:ss";
		}
		
		private static CultureInfo CultureInfo = new CultureInfo("en-US");

		
		public static void Write(Device.LUT lut, Uri uri, CultureInfo culture)
		{
			LUT.Write(lut, Uri.UnescapeDataString(uri.AbsolutePath), culture);
		}
		
		public static void Write(Device.LUT lut, string fname, CultureInfo culture)
		{
			if (!fname.ToLower().EndsWith(".lut"))
				fname += ".lut";
			                                   
			using (StreamWriter stream = new StreamWriter(fname, false))
			{
				stream.WriteLine("# {0}={1}", Parser.Culture, culture.ToString());
				stream.WriteLine("# {0}={1}", Parser.Timestamp, lut.DateTime.ToString(Parser.DateTimeFormat));
				stream.WriteLine("# {0}={1}", Parser.LutOffset, lut.Offset);
				stream.WriteLine("# {0}={1}", Parser.AcquisitionOffset, lut.AcquisitionOffset);
                stream.WriteLine("# {0}={1}", Parser.MSAFactor, lut.MSAFactor);
				stream.WriteLine("# {0}={1}.{2}", Parser.FirmwareVersion, lut.FirmwareVersion.Major, lut.FirmwareVersion.Minor);
				stream.WriteLine("# {0}={1}.{2}", Parser.ScanEngineVersion, lut.ScanEngineVersion.Major, lut.ScanEngineVersion.Minor);
				
				for (int ix=0; ix < lut.Data.Length; ++ix)
				{
					stream.WriteLine(lut.Data[ix].ToString(culture));
				}
			}
		}
		
		public static Device.LUT Read(Uri uri)
		{
			return LUT.Read(Uri.UnescapeDataString(uri.AbsolutePath));
		}

		public static Device.LUT Read(string fname)
		{
			if (fname.EndsWith(".h"))
				return LUT.ReadHFormat(fname);
			
			List<float> lut_data = new List<float>();
			
			uint lut_offset = 0;
			int  acquisition_offset = 0;
            double msa_factor = double.NaN;
			DateTime timestamp = DateTime.MinValue;
			SGS.Version firmware = new SGS.Version(0);
			SGS.Version scan_engine = new SGS.Version(0);
			
			using (StreamReader stream = File.OpenText(fname))
			{
				CultureInfo culture = LUT.CultureInfo;
				string line;
				int skipped = 0;
				int line_number = 0;
				float last = float.MinValue;
				
				while ((line = stream.ReadLine()) != null)
				{
					++line_number;
					
					string s = line.Trim();
					try
					{
						float f = float.Parse(s,
						                      NumberStyles.AllowDecimalPoint|
						                      NumberStyles.AllowExponent,
						                      culture);
						if (f <= last)
							throw new InvalidDataException(Catalog.GetString("A LUT must increase monotonically."));
						
						if (float.IsNaN(f))
							throw new InvalidDataException(Catalog.GetString("A LUT must not contain NaNs"));
						
						last = f;
						lut_data.Add(f);
					}
					catch (InvalidDataException ex)
					{
						throw ex;
					}
					catch (Exception)
					{
						++skipped;
						
						try
						{
							culture = (CultureInfo)LUT.ParseLine(line, LUT.Parser.Culture, culture);
							continue;
						} 
						catch (InvalidDataException) {}
						
						try
						{
							timestamp = (DateTime)LUT.ParseLine(line, LUT.Parser.Timestamp, culture);
							continue;
						} 
						catch (InvalidDataException) {}
						
						try
						{
							lut_offset = (uint)LUT.ParseLine(line, LUT.Parser.LutOffset, culture);
							continue;
						} 
						catch (InvalidDataException) {}
						
						try
						{
							acquisition_offset = (int)LUT.ParseLine(line, LUT.Parser.AcquisitionOffset, culture);
							continue;
						} 
						catch (InvalidDataException) {}

                        try
                        {
                            msa_factor = (double)LUT.ParseLine(line, LUT.Parser.MSAFactor, culture);
                            continue;
                        }
                        catch (InvalidDataException) {}
						
						try
						{
							firmware = (SGS.Version)LUT.ParseLine(line, LUT.Parser.FirmwareVersion, culture);
							continue;
						} 
						catch (InvalidDataException) {}
						
						try
						{
							scan_engine = (SGS.Version)LUT.ParseLine(line, LUT.Parser.ScanEngineVersion, culture);
							continue;
						} 
						catch (InvalidDataException) {}
					}
				}
			}
			
			Console.WriteLine("Date time: {0}",  timestamp.ToString());
			Console.WriteLine("LUT offset: {0}", lut_offset);
			Console.WriteLine("Acquisition offset: {0}", acquisition_offset);
            Console.WriteLine("MSA factor: {0}", msa_factor);
			Console.WriteLine("Firmware version: {0}", firmware.ToString());
			Console.WriteLine("Scan engine version: {0}", scan_engine.ToString());
			Console.WriteLine("LUT length: {0}", lut_data.Count);
			
			if (lut_data.Count == 0 || lut_offset == 0)
				throw new InvalidDataException(Catalog.GetString("Cannot read LUT file: Invalid file format.")); 
			
            return new Device.LUT(lut_data.ToArray(), lut_offset, acquisition_offset, msa_factor, timestamp, firmware, scan_engine);
		}
		
		private static object ParseLine(string line, string regexp, CultureInfo culture)
		{
			Regex parser;
			string[] split;
			
			parser = new Regex(regexp + "=");
			split  = parser.Split(line);
			
			if (split.Length > 1)
			{
				switch (regexp)
				{
					
				case LUT.Parser.Culture:
					return new CultureInfo(split[1]);

				case LUT.Parser.Timestamp:
					DateTime dt;
					try
					{
						dt = DateTime.ParseExact(split[1],
						                         LUT.Parser.DateTimeFormat, 
						                         DateTimeFormatInfo.CurrentInfo);
					}
					catch
					{
						throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid time format:") + 
						                          " " + split[1]);
					}
					return dt;
					
				case LUT.Parser.LutOffset:
					return uint.Parse(split[1]);
					
				case LUT.Parser.AcquisitionOffset:
					return int.Parse(split[1]);

				case LUT.Parser.FirmwareVersion:
				case LUT.Parser.ScanEngineVersion:
					return new SGS.Version(split[1]);

                case LUT.Parser.MSAFactor:
                    return double.Parse(split[1], culture);
					
				default:
					throw new ArgumentException(Catalog.GetString("Cannot parse header line: Unknown parameter field.")); 
				}
			}

			throw new InvalidDataException();
		}

		public static void WriteHFormat(Device.LUT lut, Uri uri)
		{
			WriteHFormat(lut, Uri.UnescapeDataString(uri.AbsolutePath));
		}
		
		public static void WriteHFormat(Device.LUT lut, string fname)
		{
			if (!fname.ToLower().EndsWith(".h"))
				fname += ".h";
			                                   
			using (StreamWriter stream = new StreamWriter(fname, false))
			{
				stream.WriteLine("/* LUT origin: Hiperscan Class Library (HCL) */");
				stream.WriteLine("#ifndef LUT_H");
				stream.WriteLine("#define LUT_H");
				stream.WriteLine();
				stream.WriteLine("#define LUT_VERSION \"{0}\"", lut.DateTime.ToString(LUT.CultureInfo));
				stream.WriteLine("const long lut_offset = {0};", lut.Offset);
				stream.WriteLine("const float lut[{0}] = {{", lut.Data.Length);
				
				CultureInfo culture = new CultureInfo("en-US");
				for (int ix=0; ix < lut.Data.Length; ++ix)
				{
					if (ix < lut.Data.Length-1)
						stream.WriteLine("  {0},", lut.Data[ix].ToString(culture));
					else
						stream.WriteLine("  {0} }};", lut.Data[ix].ToString(culture));
				}
				
				stream.WriteLine();
				stream.WriteLine("#endif");
			}
		}
		
		public static Device.LUT ReadHFormat(Uri uri)
		{
			return LUT.ReadHFormat(Uri.UnescapeDataString(uri.AbsolutePath));
		}

		public static Device.LUT ReadHFormat(string fname)
		{
			List<float> lut_data = new List<float>();
			
			uint lut_offset = 0;
			DateTime date_time = DateTime.MinValue;
			
			using (StreamReader stream = File.OpenText(fname))
			{
				string line;
				int skipped = 0;
				int line_number = 0;
				float max = float.MinValue;
				
				while ((line = stream.ReadLine()) != null)
				{
					++line_number;
					
					string s = line.Trim();
					if (s.EndsWith(","))
						s = s.Remove(s.Length-1);
					if (s.EndsWith("};"))
					    s = s.Remove(s.Length-2).Trim();
					try
					{
						float f = float.Parse(s,
						                      NumberStyles.AllowDecimalPoint|
						                      NumberStyles.AllowExponent,
						                      LUT.CultureInfo);
						if (f <= max)
							throw new InvalidDataException(Catalog.GetString("A LUT must increase monotonically."));
						
						if (float.IsNaN(f))
							throw new InvalidDataException(Catalog.GetString("A LUT must not contain NaNs"));
						
						max = f;
						lut_data.Add(f);
					}
					catch (InvalidDataException ex)
					{
						throw ex;
					}
					catch (Exception)
					{
						++skipped;
						
						try
						{
							lut_offset = (uint)LUT.ParseLineHFormat(line, LUT.ParserHFormat.LutOffset, LUT.CultureInfo);
							continue;
						} 
						catch (InvalidDataException) {}
						
						try
						{
							date_time = (DateTime)LUT.ParseLineHFormat(line, LUT.ParserHFormat.DateTime, LUT.CultureInfo);
							continue;
						} 
						catch (InvalidDataException) {}
					}
				}
			}
			
			Console.WriteLine("LUT offset: {0}", lut_offset);
			Console.WriteLine("Date time: {0}",  date_time.ToString());
			Console.WriteLine("LUT length: {0}", lut_data.Count);
			
			if (lut_data.Count == 0 || lut_offset == 0)
				throw new InvalidDataException(Catalog.GetString("Cannot read LUT file: Invalid file format.")); 
			
            return new Device.LUT(lut_data.ToArray(), lut_offset, 0, double.NaN, date_time, new SGS.Version(0), new SGS.Version(0));
		}
		
		private static object ParseLineHFormat(string line, string regexp, CultureInfo culture)
		{
			Regex parser;
			string[] split;
			
			parser = new Regex(regexp);
			split  = parser.Split(line);
			
			if (split.Length > 1)
			{
//				Console.WriteLine("Parse " + regexp + " as " + split[1]);
				switch (regexp)
				{
					
				case LUT.ParserHFormat.LutOffset:
					string s = split[1].Trim();
					if (s.EndsWith(";"))
						s = s.Remove(s.Length-1, 1);
					return uint.Parse(s);
					
				case LUT.ParserHFormat.DateTime:
					s = split[1].Replace('"', ' ').Trim();
					try
					{
						if (s.Contains("/"))
							return DateTime.Parse(s, new CultureInfo("en-US"));
						else
							return DateTime.Parse(s, new CultureInfo("de-DE"));
					}
					catch
					{
						return DateTime.Parse(s, new CultureInfo("en-US"));
					}

				default:
					throw new ArgumentException(Catalog.GetString("Cannot parse header line: Unknown regexp.")); 
				}
			}
			throw new InvalidDataException();
		}
	}
}
