using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.IO;
using System.Drawing;


namespace LabelerCore.Data {
    public class IncorrectFormatException : Exception {
        public IncorrectFormatException() { }

        public IncorrectFormatException(string message)
            : base(message) { }
    }
    public class IncorrectExtensionException : Exception {
        public IncorrectExtensionException() { }

        public IncorrectExtensionException(string message)
            : base(message) { }
    }

    public class FeaturesMissingException : Exception {
        public FeaturesMissingException() { }

        public FeaturesMissingException(string message)
            : base(message) { }
    }


    public abstract class BaseInfo {
        public readonly string fileName; //fullname!
        public readonly DateTime dt;     //datetime parsed from filename
        //stored items extracted from frq and csv file (key - parameter name, value - parameter name)
        public Dictionary<string, string> info { get; protected set; }


        public BaseInfo(string fileName) {
            this.fileName = fileName;
            info = new Dictionary<string, string>();
            try {
                dt = parseFileName(fileName);
            }
            catch (Exception) {
                dt = new DateTime();
            }
        }

        public static DateTime parseFileName(string fileName) {
            //BF-ZH-W2_20160525T185418362+0200.csv
            fileName = Path.GetFileName(fileName);
            string utc = new Regex(@"[_]\d*[T]\d*[+]").Match(fileName)
                                                      .Captures[0]
                                                      .ToString();
            return new DateTime(
                year: int.Parse(string.Join("", utc.Skip(1).Take(4))),
                month: int.Parse(string.Join("", utc.Skip(5).Take(2))),
                day: int.Parse(string.Join("", utc.Skip(7).Take(2))),
                hour: int.Parse(string.Join("", utc.Skip(10).Take(2))),
                minute: int.Parse(string.Join("", utc.Skip(12).Take(2))),
                second: int.Parse(string.Join("", utc.Skip(14).Take(2))),
                millisecond: int.Parse(string.Join("", utc.Skip(16).Take(3)))
                );
        }
    }

    public class WIMInfo : BaseInfo {
        #region DEFAULT_COLUMNS
        public static readonly string[] DefaultColumns = new string[] 
        {
            "id",
            "datetime",
            "datetime_local",
            "ms",
            "wim",
            "opposite_direction",
            "wim_detector_id",
            "detection_id",
            "detection_image_id",
            "detection_image_id_back",
            "time_distance_front_front",
            "time_distance_back_front",
            "vehicle_class",
            "vehicle_class_orig",
            "velocity",
            "length",
            "lp",
            "lp_back",
            "insert_timestamp",
            "total_weight",
            "axles_count",
            "axles_1_2_base",
            "axles_2_3_base",
            "axles_3_4_base",
            "axles_4_5_base",
            "axles_5_6_base",
            "axles_6_7_base",
            "axles_7_8_base",
            "axles_8_9_base",
            "axle_1_leftside_load",
            "axle_1_rightside_load",
            "axle_2_leftside_load",
            "axle_2_rightside_load",
            "axle_3_leftside_load",
            "axle_3_rightside_load",
            "axle_4_leftside_load",
            "axle_4_rightside_load",
            "axle_5_leftside_load",
            "axle_5_rightside_load",
            "axle_6_leftside_load",
            "axle_6_rightside_load",
            "axle_7_leftside_load",
            "axle_7_rightside_load",
            "axle_8_leftside_load",
            "axle_8_rightside_load",
            "axle_9_leftside_load",
            "axle_9_rightside_load",
            "overweighting",
            "match_datetime",
            "loop_class",
            "l_pos",
            "r_pos",
            "dual_tire",
            "validity",
            "validity_flags",
            "error_flags",
            "temp_internal",
            "temp_asphalt",
            "acceleration",
            "ax_accu_1",
            "ax_accu_2",
            "ax_accu_3",
            "ax_accu_4",
            "ax_accu_5",
            "ax_accu_6",
            "ax_accu_7",
            "ax_accu_8",
            "ax_accu_9",
            "sum_accu",
            "overweight_desc",
            "passage_type",
            "unicam_wim_class",
            "v_width",
            "width",
            "possition",
            "velocity_loop",
            "v_code",
            "vc_raised",
            "e_code",
            "ec_raised",
            "car_layout",
            "stop_go",
            "s_height",
            "s_width",
            "s_length",
            "s_separated",
            "overheight",
            "oversize",
            "oversize_desc",
            "overvelocity",
            "overvelocity_desc",
            "trailer_presence",
            "found_in_whitelist",
            "WCF_post_class"
        };
        #endregion
        public WIMInfo(string fileName)
            : base(fileName) {
            if (Path.GetExtension(fileName) != ".csv")
                throw new IncorrectExtensionException(fileName);
            using (var reader = new StreamReader(File.Open(fileName, FileMode.Open, FileAccess.Read))) {
                try {
                    string[] header = reader.ReadLine().Split(';');
                    string[] values = reader.ReadLine().Split(';');
                    info = new Dictionary<string, string>(header.Length);
                    for (int i = 0; i < header.Length; i++)
                        info.Add(header[i], values[i]);
                }
                catch (Exception) {
                    throw new IncorrectFormatException("Cant read .csv file");
                }
            }
        }
    }

    public class FRQInfo : BaseInfo {
        #region DEFAULT_COLUMNS
        public static readonly string[] DefaultColumns = new string[]
        {
            "TsPH",
            "TsPL",
            "TsOH",
            "TsOL",
            "Velocity",
            "Shift",
            "Distance",
            "Length",
            "Curvitude",
            "Center",
            "FillF",
            "Curv1",
            "Curv2",
            "NZPD",
            "P00",
            "P01",
            "P02",
            "P03",
            "P04",
            "P05",
            "P06",
            "P07",
            "P08",
            "P09",
            "P10",
            "P11",
            "P12",
            "P13",
            "P14",
            "P15",
            "P16",
            "P17",
            "P18",
            "P19",
            "Pb",
            "Pe",
            "KlasRes",
            "KlT",
            "KlV",
            "WDir",
            "LoopWidth"
        };
        #endregion

        public readonly int numChannels;
        public readonly int frameSize;
        public readonly int totalSingleFrames;
        public readonly int infoOffset;
        public readonly int infoSize;
        public readonly int dataoffset;

        public FRQInfo(string fileName)
            : base(fileName) {
            if (Path.GetExtension(fileName) != ".frq")
                throw new IncorrectExtensionException(fileName);
            using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open, FileAccess.Read))) {
                char[] hdr = reader.ReadChars(3);
                if (!(hdr[0] == 'F' && hdr[1] == 'R' && hdr[2] == 'Q'))
                    throw new IncorrectFormatException(fileName + " is in incorrect format");
                reader.ReadChar();  // "FRQ" - cstring

                numChannels = reader.ReadInt32();
                frameSize = reader.ReadInt32();
                totalSingleFrames = reader.ReadInt32();
                infoOffset = reader.ReadInt32();
                infoSize = reader.ReadInt32();
                dataoffset = reader.ReadInt32();

                reader.BaseStream.Seek(infoOffset, SeekOrigin.Begin);

                string infoStr = new string(reader.ReadChars(infoSize));
                infoStr = infoStr.Substring(0, infoStr.IndexOf('\0'));
                info = new Dictionary<string, string>();
                foreach (string item in infoStr.Split('|')) {
                    info.Add(item.Split(':')[0], item.Split(':')[1]);
                }
            }
        }

        public List<List<Int32>> getSignals() {
            List<List<Int32>> signals = new List<List<Int32>>(numChannels);
            using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open, FileAccess.Read))) {
                reader.BaseStream.Seek(dataoffset, SeekOrigin.Begin);

                for (int i = 0; i < numChannels; i++)
                    signals.Add(new List<Int32>());

                for (int n_frame = 0; n_frame < totalSingleFrames; n_frame++)
                    for (int i = 0; i < frameSize; i++)
                        signals[n_frame % numChannels].Add(reader.ReadInt32());
            }
            return signals;
        }
    }


    public class IMGInfo : BaseInfo {
        public IMGInfo(string fileName)
            : base(fileName) {
            if (Path.GetExtension(fileName) != ".jpg")
                throw new IncorrectExtensionException(fileName);
        }

        public Image getImage() {
            return Image.FromFile(fileName);
        }

    }

    public class VehicleInfo {
        public static readonly string FilenameColName = "filename without extension";

        private WIMInfo _wimInfo;
        private FRQInfo _frqInfo;
        private IMGInfo _imgInfo;

        protected readonly Dictionary<string, string> _preloadedAttributes;
        private double[] _featureArray;

        private string _id;
        public int? cluster = null;

        public string ID { get { return _id; } }

        public readonly string path;

        public readonly static string[] AttributeNames = 
        {  
            #region ATTRIBUTE_NAMES
            "filename without extension",
            "class_name",
            "DateTime",
            //WIMCOLUMNS
            "id",
            "datetime",
            "datetime_local",
            "ms",
            "wim",
            "opposite_direction",
            "wim_detector_id",
            "detection_id",
            "detection_image_id",
            "detection_image_id_back",
            "time_distance_front_front",
            "time_distance_back_front",
            "vehicle_class",
            "vehicle_class_orig",
            "velocity",
            "length",
            "lp",
            "lp_back",
            "insert_timestamp",
            "total_weight",
            "axles_count",
            "axles_1_2_base",
            "axles_2_3_base",
            "axles_3_4_base",
            "axles_4_5_base",
            "axles_5_6_base",
            "axles_6_7_base",
            "axles_7_8_base",
            "axles_8_9_base",
            "axle_1_leftside_load",
            "axle_1_rightside_load",
            "axle_2_leftside_load",
            "axle_2_rightside_load",
            "axle_3_leftside_load",
            "axle_3_rightside_load",
            "axle_4_leftside_load",
            "axle_4_rightside_load",
            "axle_5_leftside_load",
            "axle_5_rightside_load",
            "axle_6_leftside_load",
            "axle_6_rightside_load",
            "axle_7_leftside_load",
            "axle_7_rightside_load",
            "axle_8_leftside_load",
            "axle_8_rightside_load",
            "axle_9_leftside_load",
            "axle_9_rightside_load",
            "overweighting",
            "match_datetime",
            "loop_class",
            "l_pos",
            "r_pos",
            "dual_tire",
            "validity",
            "validity_flags",
            "error_flags",
            "temp_internal",
            "temp_asphalt",
            "acceleration",
            "ax_accu_1",
            "ax_accu_2",
            "ax_accu_3",
            "ax_accu_4",
            "ax_accu_5",
            "ax_accu_6",
            "ax_accu_7",
            "ax_accu_8",
            "ax_accu_9",
            "sum_accu",
            "overweight_desc",
            "passage_type",
            "unicam_wim_class",
            "v_width",
            "width",
            "possition",
            "velocity_loop",
            "v_code",
            "vc_raised",
            "e_code",
            "ec_raised",
            "car_layout",
            "stop_go",
            "s_height",
            "s_width",
            "s_length",
            "s_separated",
            "overheight",
            "oversize",
            "oversize_desc",
            "overvelocity",
            "overvelocity_desc",
            "trailer_presence",
            "found_in_whitelist",
            "WCF_post_class",
            //FRQ Attributes
            "TsPH",
            "TsPL",
            "TsOH",
            "TsOL",
            "Velocity",
            "Shift",
            "Distance",
            "Length",
            "Curvitude",
            "Center",
            "FillF",
            "Curv1",
            "Curv2",
            "NZPD",
            "P00",
            "P01",
            "P02",
            "P03",
            "P04",
            "P05",
            "P06",
            "P07",
            "P08",
            "P09",
            "P10",
            "P11",
            "P12",
            "P13",
            "P14",
            "P15",
            "P16",
            "P17",
            "P18",
            "P19",
            "Pb",
            "Pe",
            "KlasRes",
            "KlT",
            "KlV",
            "WDir",
            "LoopWidth"
#endregion
        };

        public static FeatureHeader[] Features = new FeatureHeader[] 
        {
            #region DEFAULT_FEATURES                        
                      new FeatureHeader(0,  "Length", 5.0),
                      new FeatureHeader(1,  "Curvitude", 1.0),
                      new FeatureHeader(2,  "Center", 1.0),
                      new FeatureHeader(3,  "FillF", 1.0),
                      new FeatureHeader(4,  "Curv1", 1.0),
                      new FeatureHeader(5,  "Curv2", 1.0),
                      new FeatureHeader(6,  "NZPD", 1.0),
                      new FeatureHeader(7,  "P00", 1.0),
                      new FeatureHeader(8,  "P01", 1.0),
                      new FeatureHeader(9,  "P02", 1.0),
                      new FeatureHeader(10, "P03", 1.0),
                      new FeatureHeader(11, "P04", 1.0),
                      new FeatureHeader(12, "P05", 1.0),
                      new FeatureHeader(13, "P06", 1.0),
                      new FeatureHeader(14, "P07", 1.0),
                      new FeatureHeader(15, "P08", 1.0),
                      new FeatureHeader(16, "P09", 1.0),
                      new FeatureHeader(17, "P10", 1.0),
                      new FeatureHeader(18, "P11", 1.0),
                      new FeatureHeader(19, "P12", 1.0),
                      new FeatureHeader(20, "P13", 1.0),
                      new FeatureHeader(21, "P14", 1.0),
                      new FeatureHeader(22, "P15", 1.0),
                      new FeatureHeader(23, "P16", 1.0),
                      new FeatureHeader(24, "P17", 1.0),
                      new FeatureHeader(25, "P18", 1.0),
                      new FeatureHeader(26, "P19", 1.0)
                  //temporarily disable because of constant value
                  //new FeatureHeader(27, "LoopWidth", 1.0)
            #endregion DEFAULT_FEATURES
        };


        public double[] FeatureArray {
            get {
                if (_featureArray == null) {
                    return GetFeatureArray(Sample.Features);
                }
                else
                    return _featureArray;
            }
            set {
                if (value == null)
                    throw new ArgumentException();
                if (value.Length != Sample.Features.Length)
                    throw new ArgumentException();
                _featureArray = value;
            }
        }

        public double[] GetFeatureArray(FeatureHeader[] features) {
            _featureArray = new double[features.Length];
            for (int i = 0; i < features.Length; i++) {
                double v;
                string s = GetAttribute(features[i].Name).ToString().Replace('.', ',');
                if (!double.TryParse(s, out v))
                    v = 0.0;
                _featureArray[features[i].Index] = v;
            }
            return _featureArray;
        }

        public FRQInfo Frq {
            get {
                if (_frqInfo == null) {
                    try {
                        _frqInfo = new FRQInfo(path + ".frq");
                    }
                    catch (Exception) {  }
                }
                return _frqInfo;
            }
        }
        public IMGInfo Img {
            get {
                if (_imgInfo == null) {
                    try {
                        _imgInfo = new IMGInfo(path + ".jpg");
                    }
                    catch (Exception) {  }
                }
                return _imgInfo;
            }
        }

        public WIMInfo WIM {
            get {
                if (_wimInfo == null)
                {
                    try {
                        _wimInfo = new WIMInfo(path + ".csv");
                    }
                    catch (Exception) {  }
                }
                return _wimInfo;
            }
        }

        public VehicleInfo(string path,
                           Dictionary<string, string> preloadedAttributes = null,
                           bool checkPath = true) {
            this.path = path;
            if (checkPath) {
                //if (this.path == null)
                //    throw new ArgumentException("wrong path " + this.path);

                //if (!Directory.Exists(Path.GetDirectoryName(this.path)))
                //    throw new ArgumentException("wrong path " + this.path);

                this.path = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path));
            }

            if (preloadedAttributes != null)
                _preloadedAttributes = preloadedAttributes;
            else
                _preloadedAttributes = new Dictionary<string, string>();

            _id = Path.GetFileNameWithoutExtension(this.path);
            if (_id == null)
                _id = Guid.NewGuid().ToString();
        }

        public virtual object GetAttribute(string name) {
            if (name == "DateTime")
                return Img.dt;
            else if (name == FilenameColName)
                return string.Join("", this.path.Skip(this.path.IndexOf("FRQ")));
            else if (_preloadedAttributes.ContainsKey(name))
                return _preloadedAttributes[name];
            else if (FRQInfo.DefaultColumns.Contains(name)){
                if (Frq != null)
                    return Frq.info[name];
                else
                    return null;
            }  
            else if (WIMInfo.DefaultColumns.Contains(name)){
                if (WIM != null)
                    return WIM.info[name];
                else
                    return null;
            }
            else if (name == "Cluster")
                return cluster;
            else
                return null;
        }
    }

    public static class RecordFinder {
        private static string[] GetInfoPaths(DirectoryInfo dir) {
            //var stopwatch = new System.Diagnostics.Stopwatch();
            //stopwatch.Start();
            List<FileInfo> files = dir.GetFiles().ToList();
            var csvs = files.FindAll(x => x.Extension == ".csv")
                            .Select(x => Path.GetFileNameWithoutExtension(x.FullName));
            var frqs = files.FindAll(x => x.Extension == ".frq")
                            .Select(x => Path.GetFileNameWithoutExtension(x.FullName));
            var imgs = files.FindAll(x => x.Extension == ".jpg")
                            .Select(x => Path.GetFileNameWithoutExtension(x.FullName));

            var result = csvs.Intersect(frqs).Intersect(imgs);
            var ret = result.Select(x => Path.Combine(dir.FullName, x)).ToArray();
            //stopwatch.Stop();
            return ret;

            //List<FileInfo> files = dir.GetFiles().ToList();
            //var csvs = files.FindAll(x => x.Extension == ".csv");
            //var frqs = files.FindAll(x => x.Extension == ".frq");
            //var imgs = files.FindAll(x => x.Extension == ".jpg");

            //SortedSet<string> result = new SortedSet<string>();
            //foreach (FileInfo frq in frqs) {
            //    string frqStr = Path.GetFileNameWithoutExtension(frq.FullName);
            //    int imgIndex = imgs.FindIndex(x => Path.GetFileNameWithoutExtension(x.FullName) == frqStr);
            //    int csvIndex = csvs.FindIndex(x => Path.GetFileNameWithoutExtension(x.FullName) == frqStr);
            //    if (imgIndex > -1 && csvIndex > -1)
            //        result.Add(Path.Combine(frq.DirectoryName, frqStr));

            //}
            //return result.ToArray();
        }

        public static VehicleInfo[] GetVehiclesInfo(DirectoryInfo dir) {
            string[] paths = GetInfoPaths(dir);
            return paths.Select(x => new VehicleInfo(x)).ToArray();
        }
    }






}
