// Created with MonoDevelop
//
//    Copyright (C) 2009 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.Drawing;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;

using Hiperscan.Spectroscopy.Math;
using Hiperscan.Unix;


namespace Hiperscan.Spectroscopy.IO
{

    public static partial class CSV
    {
        private struct Parser
        {
            public const string DefaultSeparator = @" |/|,|;";
            public const string SpectrumTime = @"SPECTRUM_TIME";
            public const string Checksum = @"CHECKSUM";
            public const string DarkIntensity = @"DARK_INTENSITY";
            public const string DarkIntensitySigma = @"DARK_INTENSITY_SIGMA";
            public const string DarkIntensityTime = @"DARK_INTENSITY_TIME";
            public const string MaxAbsorbance = @"MAX_ABSORBANCE";
            public const string WavelengthCorrection = @"WAVELENGTH_CORRECTION";
            public const string WavelengthCorrectionTime = @"WAVELENGTH_CORRECTION_TIME";
            public const string WavelengthLimits = @"WAVELENGTH_LIMITS";
            public const string AdcCorrection = @"ADC_CORRECTION";
            public const string LinearDistortion = @"LINEAR_DISTORTION";
            public const string LinearDistortionTime = @"LINEAR_DISTORTION_TIME";
            public const string LimitBandwidth = @"LIMIT_BANDWIDTH";
            public const string _LimitWavelength = @"LIMIT_WAVELENGTH"; // obsolete, for read compatibility only
            public const string SerialNumber = @"SERIAL_NUMBER";
            public const string FirmwareVersion = @"FIRMWARE_VERSION";
            public const string FirmwareRevision = @"FIRMWARE_REVISION";
            public const string LutTimestamp = @"LUT_TIMESTAMP";
            public const string AverageCount = @"AVERAGE_COUNT";
            public const string SpectrumType = @"SPECTRUM_TYPE";
            public const string ProbeType = @"PROBE_TYPE";
            public const string Creator = @"CREATOR";
            public const string Label = @"LABEL";
            public const string LineColor = @"LINE_COLOR";
            public const string LineStyle = @"LINE_STYLE";
            public const string LineWidth = @"LINE_WIDTH";
            public const string Comment = @"COMMENT";
            public const string AddData = @"ADD_DATA";
            public const string DateTimeFormat = @"yyyy-MM-dd_HH:mm:ss";
            public const string Culture = @"CULTURE";
            public const string HardwareVersionInfo = @"HARDWARE_INFO";
            public const string SpectralResolution = @"SPECTRAL_RESOLUTION";
            public const string Metadata = @"%%%METADATA%%%";

            public const int MaxHeaderLines = 100;
        }

        private static object ParseLine(string line, string regexp, CultureInfo culture)
        {
            string[] split = Regex.Split(line, regexp + "=");

            if (split.Length > 1)
            {
                switch (regexp)
                {

                case CSV.Parser.Culture:
                    return split[1];

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

                case CSV.Parser.DarkIntensity:
                case CSV.Parser.DarkIntensitySigma:
                case CSV.Parser.SpectralResolution:
                    return double.Parse(split[1],
                                        NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
                                        culture);

                case CSV.Parser.DarkIntensityTime:
                    goto case CSV.Parser.SpectrumTime;

                case CSV.Parser.WavelengthCorrection:
                    Regex regex = new Regex(culture.TextInfo.ListSeparator);
                    string[] s = regex.Split(split[1]);
                    if (s.Length % 2 != 0)
                        throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid wavelength correction data."));
                    double[] wavelengths = new double[s.Length / 2];
                    double[] corrections = new double[s.Length / 2];
                    for (int ix = 0; ix < s.Length / 2; ++ix)
                    {
                        wavelengths[ix] = double.Parse(s[2 * ix],
                                                       NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
                                                       culture);
                        corrections[ix] = double.Parse(s[2 * ix + 1],
                                                       NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign,
                                                       culture);
                    }
                    return new WavelengthCorrection(wavelengths, corrections, true, new LevenbergMarquardt.OptimParams());

                case CSV.Parser.WavelengthLimits:
                    regex = new Regex(culture.TextInfo.ListSeparator);
                    s = regex.Split(split[1]);
                    if (s.Length != 2)
                        throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid wavelength limits data."));
                    double min = double.Parse(s[0], NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, culture);
                    double max = double.Parse(s[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, culture);
                    return new WavelengthLimits(min, max);

                case CSV.Parser.AdcCorrection:
                    regex = new Regex(culture.TextInfo.ListSeparator);
                    s = regex.Split(split[1]);
                    if (s.Length % 2 != 0)
                        throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid ADC correction data."));
                    double[] x = new double[s.Length / 2];
                    double[] y = new double[s.Length / 2];
                    for (int ix = 0; ix < s.Length / 2; ++ix)
                    {
                        x[ix] = double.Parse(s[2 * ix],
                                             NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
                                             culture);
                        y[ix] = double.Parse(s[2 * ix + 1],
                                             NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign,
                                             culture);
                    }
                    return new DataSet(x, y);

                case CSV.Parser.LinearDistortion:
                    regex = new Regex(culture.TextInfo.ListSeparator);
                    s = regex.Split(split[1]);
                    if (s.Length != 2)
                        throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid linear distortion data."));
                    double offset = double.Parse(s[0],
                                                 NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign,
                                                 culture);
                    double factor = double.Parse(s[1],
                                                 NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign,
                                                 culture);
                    return new LinearDistortion(offset, factor);

                case CSV.Parser.SerialNumber:
                case CSV.Parser.FirmwareVersion:
                case CSV.Parser.FirmwareRevision:
                case CSV.Parser.LutTimestamp:
                case CSV.Parser.Creator:
                case CSV.Parser.Label:
                    return split[1];

                case CSV.Parser.SpectrumType:
                    return Enum.Parse(typeof(SpectrumType), split[1]);

                case CSV.Parser.ProbeType:
                    return Enum.Parse(typeof(ProbeType), split[1].Replace("Inset", "Insert"));

                case CSV.Parser.AverageCount:
                    return int.Parse(split[1], culture);

                case CSV.Parser.WavelengthCorrectionTime:
                    goto case CSV.Parser.SpectrumTime;

                case CSV.Parser.LinearDistortionTime:
                    goto case CSV.Parser.SpectrumTime;

                case CSV.Parser.Comment:
                    return split[1] + "\n";

                case CSV.Parser.AddData:
                    return uint.Parse(split[1], culture);

                case CSV.Parser.LimitBandwidth:
                case CSV.Parser._LimitWavelength:
                    return bool.Parse(split[1]);

                case CSV.Parser.LineColor:
                    regex = new Regex(culture.TextInfo.ListSeparator);
                    s = regex.Split(split[1]);
                    if (s.Length != 4)
                        throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid color data."));
                    return Color.FromArgb(int.Parse(s[0]), int.Parse(s[1]), int.Parse(s[2]), int.Parse(s[3]));

                case CSV.Parser.LineStyle:
                    return Enum.Parse(typeof(LineStyle), split[1]);

                case CSV.Parser.LineWidth:
                    return float.Parse(split[1], culture);

                case CSV.Parser.HardwareVersionInfo:
                    s = Regex.Split(split[1], culture.TextInfo.ListSeparator);
                    if (s.Length < 4)
                        throw new FormatException(Catalog.GetString("Cannot parse header line: Invalid hardware version data."));
                    byte[] vers = new byte[s.Length];
                    for (int ix = 0; ix < s.Length; ++ix)
                    {
                        vers[ix] = byte.Parse(s[ix], culture);
                    }
                    return vers;


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