using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
 

namespace LabelerCore.Data
{
    //Stores labeled samples
    //using (.frq/.jpg/.csv)filenames without extension as keys 
    //(example: BF-ZH-W2_20160525T185418362+0200)
    public class TrainingData : ISamplesStorage, IXmlSerializable
    {
        protected Dictionary<string, Sample> _samples;         // key - sample id
        protected Dictionary<string, int> _counts;            // keys - classes, values - class counts

        public string fileName;
        public string FRQFolderPath;
        public bool deepLoad = true;

        public delegate void SampleReadHandler(int curCount, int totalCount);
        public event SampleReadHandler SampleRead;
        private int _totalSamplesCountToRead = 0;

        public Sample[] Samples { get { return _samples.Values.ToArray(); } }

        public TrainingData()
        {
            _samples = new Dictionary<string, Sample>();
            _counts = new Dictionary<string, int>();
        }

        public TrainingData(string fileName, string FRQFolderPath, bool deepLoad = true)
        {
            _samples = new Dictionary<string, Sample>();
            _counts = new Dictionary<string, int>();

            this.FRQFolderPath = FRQFolderPath;
            this.deepLoad = deepLoad;
            this.fileName = fileName;
            if (Path.GetExtension(fileName) == ".csv")
                LoadFromCsv(fileName);
            else if (Path.GetExtension(fileName) == ".xml")
                LoadFromXml(fileName);
            else
                throw new ArgumentException("filename should have .csv or .xml extension");
        }

        #region ISamplesStorage_IMPLEMENTATION
        public IEnumerator GetEnumerator()
        {
            foreach (Sample s in _samples.Values)
                yield return s;
        }

        public List<string> Classes
        {
            get
            {
                List<string> res = _counts.Keys.ToList();
                res.Sort();
                return res;
            }
        }

        public int getClassCount(string vehicleClass)
        {
            if (!_counts.ContainsKey(vehicleClass))
                return 0;
            else
                return _counts[vehicleClass];
        }

        public bool AddSample(Sample s)
        {
            if (_samples.ContainsKey(s.ID))
                return false;
            else
            {
                _samples.Add(s.ID, s);
                if (_counts.ContainsKey(s.vehicleClass))
                    _counts[s.vehicleClass] += 1;
                else
                    _counts.Add(s.vehicleClass, 1);
                return true;
            }
        }

        public bool InsertSample(Sample s)
        {
            if (!_samples.ContainsKey(s.ID))
                return false;
            else
            {
                RemoveSample(s.ID);
                AddSample(s);
                return true;
            }
        }

        public bool RemoveSample(string id)
        {
            if (!_samples.ContainsKey(id))
                return false;
            else
            {
                if (_counts[_samples[id].vehicleClass] == 1)
                    _counts.Remove(_samples[id].vehicleClass);
                else
                    _counts[_samples[id].vehicleClass] -= 1;
                _samples.Remove(id);
                return true;
            }
        }

        public bool Exists(Sample s)
        {
            return _samples.ContainsKey(s.ID);
        }

        public bool Exists(string id)
        {
            return _samples.ContainsKey(id);
        }

        public Sample getSample(string id)
        {
            return _samples[id];
        }

        public int Count { get { return _samples.Count; } }

        #endregion ISamplesStorage_IMPLEMENTATION

        public bool Save()
        {
            if (fileName == null)
                return false;
            if (Path.GetExtension(fileName) == ".csv")
                SaveToCSV(fileName);
            else
                SaveToXml(fileName);
            return true;
        }

        public void Load(string fileName)
        {
            if (Path.GetExtension(fileName) == ".csv")
                LoadFromCsv(fileName);
            else
                LoadFromXml(fileName);
        }

        public void LoadFromCsv(string fileName)
        {
            this.fileName = fileName;
            //_samples = new SortedDictionary<string, Sample>(new FileNameComparer());
            _samples = new Dictionary<string, Sample>();
            _counts = new Dictionary<string, int>();
            _totalSamplesCountToRead = File.ReadLines(fileName).Count() - 1;
            string[][] rows = File.ReadAllLines(fileName).Select(x => x.Split(';')).ToArray();
            string[] header = rows[0];
            int vClassIdx = header.ToList().IndexOf(Sample.VehicleClassColName);
            int filenameIdx = header.ToList().IndexOf(Sample.FilenameColName);
            foreach (var row in rows.Skip(1)) //skip header
            {
                Dictionary<string, string> addInfo = new Dictionary<string, string>();
                for (int i = 1; i < header.Length; i++) //skip header
                    if (i != vClassIdx)
                        addInfo[header[i]] = row[i];
                Sample s = new Sample(fullFileName: Path.Combine(FRQFolderPath, row[filenameIdx]),
                                      vehicleClass: row[vClassIdx],
                                      loadDataOnTheSpot: deepLoad,
                                      additionalInfo:addInfo);
                AddSample(s);
                if (SampleRead != null)
                    SampleRead(Count, _totalSamplesCountToRead);
            }
        }


        public void SaveToCSV(string fileName)
        {
            this.fileName = fileName;
            string[] rows = new string[_samples.Count + 1];
            //header
            string[] attr = Sample.AttributeNames;
            rows[0] = string.Join(";", Sample.AttributeNames);
            int idx = 1;
            foreach (string cl in Classes)
                foreach (Sample s in _samples.Values.Where(x => x.vehicleClass == cl))
                    rows[idx++] = s.GetCsvRow(Sample.AttributeNames);

            File.WriteAllLines(fileName, rows);
        }

        public void SaveToCSV_WithAttributes(string fileName)
        {
            SaveToCSV(fileName);
        }

        public XElement getXElement()
        {
            //compatibility
            XElement root = new XElement(XmlTags.DocXmlTag,
                               new XElement("Common",
                                  new XElement("AutoAddStrangeClasses", "",
                                     new XAttribute("Enabled", 1),
                                     new XAttribute("ThreshValue", 800))));

            //write features
            root.Add(new XElement(XmlTags.FeaturesXmlTag, "", new XAttribute("Count", Sample.Features)));
            foreach (FeatureHeader f in Sample.Features)
                (root.LastNode as XElement).Add(f.getXElement());
            //write samples
            root.Add(new XElement(XmlTags.ClassesXmlTag, ""));
            var clStor = new Dictionary<string, XElement>();
            foreach (Sample s in this)
            {
                if (clStor.ContainsKey(s.vehicleClass))
                    clStor[s.vehicleClass].Add(s.GetXElement(Sample.Features));
                else
                {
                    int classCode;
                    if (!int.TryParse(string.Join("", s.vehicleClass.Take(1)), out classCode))
                        classCode = -1;
                    XElement classHeader = new XElement(XmlTags.ClassXmlTag, "",
                                      new XAttribute("ID", Guid.NewGuid()),
                                      new XAttribute("Color", "ffff"),
                                      new XAttribute("Name", s.vehicleClass),
                                      new XAttribute("Retv", classCode));
                    clStor[s.vehicleClass] = classHeader;
                    clStor[s.vehicleClass].Add(s.GetXElement(Sample.Features));
                }
            }
            foreach (string cl in clStor.Keys)
                (root.LastNode as XElement).Add(clStor[cl]);
            return root;
        }

        public void WriteXml(XmlWriter writer)
        {
            XElement xElem = getXElement();
            xElem.WriteTo(writer);
        }

        public void ReadXml (XmlReader reader)
        {
            _samples.Clear();
            _counts.Clear();
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);
            if (FRQFolderPath == null)
                deepLoad = false;
            _totalSamplesCountToRead = doc.DocumentElement.GetElementsByTagName(XmlTags.PatternXmlTag).Count;
            _Walk(doc.DocumentElement);
        }

        public void SaveToXml(string fileName)
        {
            XmlWriterSettings writerSettings = new XmlWriterSettings()
            {
                Indent = true,
                IndentChars = "\t",
            };
            XmlWriter writer = XmlWriter.Create(fileName, writerSettings);
            WriteXml(writer);
            writer.Close();
            this.fileName = fileName;

        }

        public void LoadFromXml(string fileName)
        {
            XmlReader reader = XmlReader.Create(fileName);
            //reader.Read();
            ReadXml(reader);
            reader.Close();
            this.fileName = fileName;
        }

        private void _Walk(XmlElement root)
        {
            if (!root.HasChildNodes)
                return;

            foreach(XmlElement childNode in root.ChildNodes)
            {

                if (childNode.Name == XmlTags.PatternXmlTag)
                {
                    string fullname = Path.Combine(FRQFolderPath, childNode.GetAttribute("ID"));
                    double[] features = childNode.InnerText
                                                    .Split(';')
                                                    .Select(x => int.Parse(x))
                                                    .Select(x => x / 1000.0)
                                                    .ToArray();
                    Dictionary<string, string> addInfo = new Dictionary<string,string>();
                    for (int i = 0; i < Sample.Features.Length; i++)
                        addInfo.Add(Sample.Features[i].Name, features[i].ToString());
                    Sample s = new Sample(fullname, root.GetAttribute("Name"), false, addInfo, checkPath:false);
                    
                    AddSample(s);
  
                    if (SampleRead != null)
                        SampleRead(Count, _totalSamplesCountToRead);
                }
                else
                    _Walk(childNode);
            }
        }

        public double[] getFeatureDistribution(string featureName, string vehicleClass)
        {
            int index = 0;
            for (int i = 0; i < Sample.Features.Length; i++)
                if (Sample.Features[i].Name == featureName)
                    index = i;
            List<double> featureDistrib = new List<double>();
            foreach (Sample s in this)
                if (s.vehicleClass == vehicleClass)
                    featureDistrib.Add(s.FeatureArray[index]);
            return featureDistrib.ToArray();
        }


        public XmlSchema GetSchema()
        {
            return (null);
        }

    }
}
