//  Copyright (c) Microsoft Corporation.  All rights reserved.

namespace Microsoft.VisualStudio.TestTools.TestCaseMigrator
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Xml;

    /// <summary>
    /// This contains the Complete Information needed acrosss the Wizard Session.
    /// It also supports the Loading/Saving of Settings File.
    /// </summary>
    internal class WizardInfo : NotifyPropertyChange, IDisposable
    {
        #region Fields

        private DataSourceType m_dataSourceType;
        private string m_mhtSource;
        private string m_outputSettingsFilePath;
        private string m_inputSettingsFilePath;
        private ProgressPart m_progressPart;

        #endregion

        #region Constants
        /// <summary>
        /// These constants are name of XML Nodes/Attributes of Settings File. These needed for Saving/Loading Settings File.
        /// </summary>
        private const string c_rootNodeTag = "TestCaseMigrator";
        private const string c_fieldsRootNodeTag = "Fields";
        private const string c_sampleFileAttributeTag = "SampleFIle";
        private const string c_fieldNodeTag = "Field";
        private const string c_stepsFieldNodeTag = "StepsField";
        private const string c_canDeleteFieldNameAttributeTag = "CanDelete";
        private const string c_nameAttributeTag = "Name";
        private const string c_columnMappingRootNodeTag = "ColumnMapping";
        private const string c_columnMappingNodeTag = "ColumnMappingRow";
        private const string c_dataMappingRootNodeTag = "DataMapping";
        private const string c_dataMappingNodeTag = "DataMappingRow";
        private const string c_dataSourceFieldNameAttributeTag = "DataSourceField";
        private const string c_wiFieldNameAttributeTag = "WorkItemField";
        private const string c_dataSourceValueAttributeTag = "PreviousValue";
        private const string c_newValueAttributeTag = "NewValue";
        private const string c_isFileNameTitleAttributeTag = "IsFileNameTitle";
        private const string c_isFirstLineTitleAttributeTag = "IsFirstLineTitle";
        private const string c_multiLineSenseNode = "MultiLineSense";
        private const string c_parameterizationNode = "Parameterization";
        private const string c_startParameterizationAttribute = "Start";
        private const string c_endParameterizationAttribute = "End";
        private const string LTChar = "<";
        private const string GTChar = ">";
        private const string LTXMLChar = "&lt;";
        private const string GTXMLChar = "&gt;";
        private const string XMLDeclarationVersion = "1.0";

        #endregion

        #region Properties

        /// <summary>
        /// The Data Source Type which is going to be parsed.
        /// </summary>
        public DataSourceType DataSourceType
        {
            get
            {
                return m_dataSourceType;
            }
            set
            {
                m_dataSourceType = value;
                NotifyPropertyChanged("DataSourceType");
            }
        }

        public string MHTSource
        {
            get
            {
                return m_mhtSource;
            }
            set
            {
                m_mhtSource = value;
                NotifyPropertyChanged("MHTSource");
            }
        }

        public IWorkItemGenerator WorkItemGenerator
        {
            get;
            set;
        }

        /// <summary>
        /// The Sample Data Source used for getting the field names and column Mapping
        /// </summary>
        public IDataSourceParser DataSourceParser
        {
            get;
            set;
        }

        public IList<IDataStorageInfo> DataStorageInfos
        {
            get;
            set;
        }

        /// <summary>
        /// File Path of Setting File Path from which settings are going to be imported
        /// </summary>
        public string InputSettingsFilePath
        {
            get
            {
                return m_inputSettingsFilePath;
            }
            set
            {
                m_inputSettingsFilePath = value;
                NotifyPropertyChanged("InputSettingsFilePath");
            }
        }

        /// <summary>
        /// File Path of Settings File Path where current Settings are going to be saved.
        /// </summary>
        public string OutputSettingsFilePath
        {
            get
            {
                return m_outputSettingsFilePath;
            }
            set
            {
                m_outputSettingsFilePath = value;
                NotifyPropertyChanged("OutputSettingsFilePath");
            }
        }

        /// <summary>
        /// Hashtable having mapping between Wizard state to Wizard Part
        /// </summary>
        public IDictionary<WizardPage, IWizardPart> WizardPageHashTable
        {
            get;
            private set;
        }

        /// <summary>
        /// Corresponding Command Line 
        /// </summary>
        public string CommandUsed
        {
            get;
            set;
        }

        public IMigrator Migrator
        {
            get;
            private set;
        }

        public IReporter Reporter
        {
            get;
            set;
        }

        public IList<SourceWorkItem> ResultWorkItems
        {
            get;
            set;
        }

        /// <summary>
        /// Overlay part to show progress for long waiting operations
        /// </summary>
        public ProgressPart ProgressPart
        {
            get
            {
                return m_progressPart;
            }
            set
            {
                m_progressPart = value;
                NotifyPropertyChanged("ProgressPart");
            }
        }

        /// <summary>
        /// Name of Sample File used to get field names
        /// </summary>
        public String SampleFileForFields
        {
            get;
            set;
        }

        #endregion

        #region Constructor

        public WizardInfo()
        {
            WizardPageHashTable = new Dictionary<WizardPage, IWizardPart>();
            Migrator = new Migrator();
        }

        #endregion

        #region public methods

        /// <summary>
        /// LoadSettings from Input Settings File Path
        /// </summary>
        public void LoadSettings(string inputSettingsFile)
        {
            ProgressPart = new ProgressPart("Loading Settings...", null);

            // Load XML Document from InputSettingsFilePath
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(inputSettingsFile);

            // get the root Node of the xml Document
            XmlElement root = (XmlElement)xmlDocument.LastChild;

            // Traverse each child node of the root node
            foreach (XmlElement node in root.ChildNodes)
            {
                if (ProgressPart == null)
                {
                    throw new TestCaseMigratorException("Loading Settings is cancelled",
                                                        "You have stopped loading of the settings",
                                                        "To load settings, choose 'Next'");
                }

                switch (node.Name)
                {
                    case c_fieldsRootNodeTag:
                        if (ProgressPart != null)
                        {
                            ProgressPart.Text = "Loading Field Names...";
                        }
                        if (DataSourceType == DataSourceType.MHT)
                        {
                            if (node.Attributes.Count == 1 &&
                                node.Attributes[0].Name == c_sampleFileAttributeTag &&
                                File.Exists(node.Attributes[0].Value))
                            {

                                DataSourceParser = new MHTParser(new MHTStorageInfo(node.Attributes[0].Value));
                            }
                            DataSourceParser.ParseDataSourceFieldNames();
                            DataSourceParser.StorageInfo.FieldNames.Clear();
                            foreach (XmlElement fieldNode in node.ChildNodes)
                            {
                                if (fieldNode.Name == c_fieldNodeTag &&
                                    fieldNode.Attributes.Count == 2 &&
                                    fieldNode.Attributes[0].Name == c_nameAttributeTag &&
                                    fieldNode.Attributes[1].Name == c_canDeleteFieldNameAttributeTag)
                                {
                                    string fieldName = fieldNode.Attributes[0].Value;
                                    bool canDelete = false;
                                    if (bool.TryParse(fieldNode.Attributes[1].Value, out canDelete))
                                    {
                                        DataSourceParser.StorageInfo.FieldNames.Add(new SourceField(fieldName, canDelete));
                                    }
                                }
                                else if (fieldNode.Name == c_stepsFieldNodeTag &&
                                    fieldNode.Attributes.Count == 1 &&
                                    fieldNode.Attributes[0].Name == c_nameAttributeTag)
                                {
                                    MHTStorageInfo info = DataSourceParser.StorageInfo as MHTStorageInfo;
                                    info.StepsField = fieldNode.Attributes[0].Value;
                                }
                            }
                        }
                        break;

                    // If it is a column mapping node then Load Field Mappings from the Settings File Path
                    case c_columnMappingRootNodeTag:
                        if (ProgressPart != null)
                        {
                            ProgressPart.Text = "Loading Field Mapping...";
                        }
                        LoadColumnMappings(node);
                        break;

                    // if it is a data mapping node then Load Data Mappings from the Settings File Path
                    case c_dataMappingRootNodeTag:
                        if (ProgressPart != null)
                        {
                            ProgressPart.Text = "Loading Data Mapping...";
                        }
                        LoadDataMappings(node);
                        break;

                    case c_parameterizationNode:
                        LoadParametrizationDelimeters(node);
                        break;

                    case c_multiLineSenseNode:
                        LoadMultiLineSense(node);
                        break;

                    // If it is any other node then it is invalid Settings File Path. Throw an error
                    default: throw new XmlException();
                }
            }
            ProgressPart = null;
        }

        /// <summary>
        ///  Save Current Settings to the OutputSettingsFilePath
        /// </summary>
        public void SaveSettings(string filePath)
        {
            // Create XML Document
            XmlDocument xmlDocument = new XmlDocument();
            XmlDeclaration dec = xmlDocument.CreateXmlDeclaration(XMLDeclarationVersion, null, null);
            xmlDocument.AppendChild(dec);

            //Creating Root Node
            XmlElement root = xmlDocument.CreateElement(c_rootNodeTag);

            // Appending the root node in the XML Documnet
            xmlDocument.AppendChild(root);


            if (DataSourceType == DataSourceType.MHT)
            {
                var fieldsRootNode = xmlDocument.CreateElement(c_fieldsRootNodeTag);
                foreach (SourceField field in DataSourceParser.StorageInfo.FieldNames)
                {
                    var fieldNode = xmlDocument.CreateElement(c_fieldNodeTag);
                    AppendAttribute(xmlDocument, fieldNode, c_nameAttributeTag, field.FieldName);
                    AppendAttribute(xmlDocument, fieldNode, c_canDeleteFieldNameAttributeTag, field.CanDelete.ToString());
                    fieldsRootNode.AppendChild(fieldNode);
                }
                MHTStorageInfo info = DataSourceParser.StorageInfo as MHTStorageInfo;
                var stepsFieldNode = xmlDocument.CreateElement(c_stepsFieldNodeTag);
                AppendAttribute(xmlDocument, stepsFieldNode, c_nameAttributeTag, info.StepsField);
                fieldsRootNode.AppendChild(stepsFieldNode);

                AppendAttribute(xmlDocument, fieldsRootNode, c_sampleFileAttributeTag, SampleFileForFields);
                root.AppendChild(fieldsRootNode);
            }

            // Creating Field Mapping node
            XmlElement columnMappingRootNode = xmlDocument.CreateElement(c_columnMappingRootNodeTag);

            if (DataSourceType == DataSourceType.MHT)
            {
                MHTStorageInfo info = DataSourceParser.StorageInfo as MHTStorageInfo;
                AppendAttribute(xmlDocument, columnMappingRootNode, c_isFileNameTitleAttributeTag, info.IsFileNameTitle.ToString());
                AppendAttribute(xmlDocument, columnMappingRootNode, c_isFirstLineTitleAttributeTag, info.IsFirstLineTitle.ToString());
            }

            // Iterating through each column mapping and creating corresponding node
            foreach (var kvp in Migrator.SourceNameToFieldMapping)
            {
                // Creating Field Mapping Row Node
                XmlNode columnMappingNode = xmlDocument.CreateElement(c_columnMappingNodeTag);

                // Appending Data Source Field Attribute in the Column mapping Row Node
                AppendAttribute(xmlDocument, columnMappingNode, c_dataSourceFieldNameAttributeTag, kvp.Key);

                // Appending TFS Workiten Field Attribute in the Column mapping Row Node
                AppendAttribute(xmlDocument, columnMappingNode, c_wiFieldNameAttributeTag, kvp.Value.TfsName);

                // Appending Field Mapping Row Node in Field Mapping Node
                columnMappingRootNode.AppendChild(columnMappingNode);
            }
            // Appending Field Mapping node in the root node
            root.AppendChild(columnMappingRootNode);


            // Creating Data Mappping Node
            XmlElement dataMappingRootNode = xmlDocument.CreateElement(c_dataMappingRootNodeTag);

            // Iterating throw Data Mappings and Adding Each Data mapping entry into the DataMapping Node
            foreach (var sourceFieldNameToField in Migrator.SourceNameToFieldMapping)
            {
                // Iteration through Each Value Mapping for a Data Source Field
                foreach (KeyValuePair<string, string> valueMapping in sourceFieldNameToField.Value.ValueMapping)
                {
                    // Creating data mapping row node
                    XmlNode dataMappingNode = xmlDocument.CreateElement(c_dataMappingNodeTag);

                    // Appending Data Source Field Attribute in the Data mapping Row Node
                    AppendAttribute(xmlDocument, dataMappingNode, c_dataSourceFieldNameAttributeTag, sourceFieldNameToField.Key);

                    // Appending Data Source Value Attribute in the Data mapping Row Node
                    AppendAttribute(xmlDocument, dataMappingNode, c_dataSourceValueAttributeTag, valueMapping.Key);

                    // Appending New Value Attribute in the Data mapping Row Node
                    AppendAttribute(xmlDocument, dataMappingNode, c_newValueAttributeTag, valueMapping.Value);

                    // Appending Data Mapping Row Node in Data Mapping Node
                    dataMappingRootNode.AppendChild(dataMappingNode);
                }
            }
            // Appending Data Mapping Node in Root Node
            root.AppendChild(dataMappingRootNode);

            XmlNode parameterizationNode = xmlDocument.CreateElement(c_parameterizationNode);

            AppendAttribute(xmlDocument, parameterizationNode, c_startParameterizationAttribute, DataSourceParser.StorageInfo.StartParameterizationDelimeter);
            AppendAttribute(xmlDocument, parameterizationNode, c_endParameterizationAttribute, DataSourceParser.StorageInfo.EndParameterizationDelimeter);

            root.AppendChild(parameterizationNode);

            XmlNode multiLineSenseNode = xmlDocument.CreateElement(c_multiLineSenseNode);
            multiLineSenseNode.InnerText = DataSourceParser.StorageInfo.IsMultilineSense.ToString();
            root.AppendChild(multiLineSenseNode);

            if (!Directory.Exists(Path.GetDirectoryName(filePath)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(OutputSettingsFilePath));
            }

            // Writing XML Document in OutputSettingsFilePath
            using (StreamWriter tr = new StreamWriter(filePath))
            {
                tr.Write(xmlDocument.OuterXml);
            }
        }

        #endregion

        #region Private methods

        ///<summary>
        ///Utility Function to append an attribute in a XML Node
        ///</summary>
        ///<param name="document">XMLDocument that contains the Node</param>
        ///<param name="node">XML Node at which attribute is going to be append</param>
        /// <param name="attributeName">The anme of the Attribute</param>
        /// <param name="value">Value of The attribute</param>
        private void AppendAttribute(XmlDocument document, XmlNode node, string attributeName, string value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            // Create Attribute
            XmlAttribute attribute = document.CreateAttribute(attributeName);

            // Assign Value
            value = value.Replace(LTChar, LTXMLChar);
            value = value.Replace(GTChar, GTXMLChar);
            attribute.Value = value;

            // Append the attribute in the node
            node.Attributes.Append(attribute);
        }

        ///<summary>
        /// Utility method to Load Data Mappings from Data Mapping node
        ///</summary>
        ///<param name="root"></param>
        private void LoadDataMappings(XmlElement root)
        {
            // Iterate through each dat mapping row node and update data mapping
            foreach (XmlElement node in root.ChildNodes)
            {
                // If node is not dat mapping row node then throw XML error
                if (node.Name != c_dataMappingNodeTag)
                {
                    throw new XmlException();
                }

                // Get fieldName, fieldValue
                string fieldName = UpdateLTGTChars(node.Attributes[c_dataSourceFieldNameAttributeTag].InnerXml);
                string fieldValue = UpdateLTGTChars(node.Attributes[c_dataSourceValueAttributeTag].InnerXml);
                string newValue = UpdateLTGTChars(node.Attributes[c_newValueAttributeTag].InnerXml);

                UpdateDataMapping(fieldName, fieldValue, newValue);
            }
        }

        private void UpdateDataMapping(string fieldName, string fieldValue, string newValue)
        {
            if (string.IsNullOrEmpty(fieldName) || string.IsNullOrEmpty(fieldValue) || string.IsNullOrEmpty(newValue))
            {
                throw new XmlException();
            }
            bool isValidFieldName = false;
            foreach (SourceField field in DataSourceParser.StorageInfo.FieldNames)
            {
                if (field.FieldName == fieldName)
                {
                    isValidFieldName = true;
                    break;
                }
            }

            if (isValidFieldName &&
                Migrator.SourceNameToFieldMapping.ContainsKey(fieldName) &&
                !Migrator.SourceNameToFieldMapping[fieldName].ValueMapping.ContainsKey(fieldValue))
            {
                Migrator.SourceNameToFieldMapping[fieldName].ValueMapping.Add(fieldValue, newValue);
            }
        }

        private void LoadColumnMappings(XmlElement root)
        {
            foreach (var kvp in WorkItemGenerator.TfsNameToFieldMapping)
            {
                kvp.Value.SourceName = string.Empty;
            }

            if (DataSourceType == DataSourceType.MHT && root.Attributes != null)
            {
                bool isFirstLineTitle = false;
                bool isFileNameTitle = false;
                foreach (XmlAttribute attribute in root.Attributes)
                {
                    if (attribute.Name == c_isFileNameTitleAttributeTag)
                    {
                        bool.TryParse(attribute.Value, out isFileNameTitle);
                    }
                    if (attribute.Name == c_isFirstLineTitleAttributeTag)
                    {
                        bool.TryParse(attribute.Value, out isFirstLineTitle);
                    }
                }
                if (isFileNameTitle ^ isFirstLineTitle)
                {
                    MHTStorageInfo info = DataSourceParser.StorageInfo as MHTStorageInfo;
                    info.TitleField = MHTParser.TestTitleDefaultTag;
                    info.IsFileNameTitle = isFileNameTitle;
                    info.IsFirstLineTitle = isFirstLineTitle;
                }
            }


            var sourceNameToFieldMapping = new Dictionary<string, IWorkItemField>();
            foreach (XmlElement node in root.ChildNodes)
            {
                if (node.Name != c_columnMappingNodeTag)
                {
                    throw new XmlException();
                }

                string dataSourceField = UpdateLTGTChars(node.Attributes[c_dataSourceFieldNameAttributeTag].InnerXml);
                string wiField = UpdateLTGTChars(node.Attributes[c_wiFieldNameAttributeTag].InnerXml);

                if (string.IsNullOrEmpty(dataSourceField) || string.IsNullOrEmpty(wiField))
                {
                    throw new XmlException();
                }

                if (WorkItemGenerator.TfsNameToFieldMapping.ContainsKey(wiField))
                {
                    WorkItemGenerator.TfsNameToFieldMapping[wiField].SourceName = dataSourceField;
                    sourceNameToFieldMapping.Add(dataSourceField, WorkItemGenerator.TfsNameToFieldMapping[wiField]);
                }
            }
            DataSourceParser.FieldNameToFields = sourceNameToFieldMapping;
            WorkItemGenerator.SourceNameToFieldMapping = sourceNameToFieldMapping;
            Migrator.SourceNameToFieldMapping = sourceNameToFieldMapping;

            int count = 0;
            if (ProgressPart != null)
            {
                ProgressPart.Text = "Parsing Source...";
            }
            IList<SourceWorkItem> sourceWorkItems = new List<SourceWorkItem>();
            IList<SourceWorkItem> rawSourceWorkItems = new List<SourceWorkItem>();
            if (DataSourceType == DataSourceType.MHT)
            {
                MHTStorageInfo sampleInfo = DataSourceParser.StorageInfo as MHTStorageInfo;

                IList<IDataStorageInfo> storageInfos = DataStorageInfos;

                foreach (IDataStorageInfo storageInfo in storageInfos)
                {
                    if (ProgressPart == null)
                    {
                        break;
                    }
                    MHTStorageInfo info = storageInfo as MHTStorageInfo;
                    info.IsFirstLineTitle = sampleInfo.IsFirstLineTitle;
                    info.IsFileNameTitle = sampleInfo.IsFileNameTitle;
                    info.TitleField = sampleInfo.TitleField;
                    info.StepsField = sampleInfo.StepsField;
                    IDataSourceParser parser = new MHTParser(info);

                    info.FieldNames = sampleInfo.FieldNames;
                    parser.FieldNameToFields = sourceNameToFieldMapping;

                    while (parser.GetNextWorkItem() != null)
                    {
                        count++;
                        if (ProgressPart != null)
                        {
                            ProgressPart.Text = "Parsing " + count + " of " + DataStorageInfos.Count + ":\n" + info.Source;
                            ProgressPart.ProgressValue = (count * 100) / DataStorageInfos.Count;
                        }
                    }

                    for (int i = 0; i < parser.ParsedSourceWorkItems.Count; i++)
                    {
                        sourceWorkItems.Add(parser.ParsedSourceWorkItems[i]);
                        rawSourceWorkItems.Add(parser.RawSourceWorkItems[i]);
                    }
                    parser.Dispose();
                }
            }
            else if (DataSourceType == DataSourceType.Excel)
            {
                var parser = DataSourceParser;
                var excelInfo = parser.StorageInfo as ExcelStorageInfo;
                while (parser.GetNextWorkItem() != null)
                {
                    if (ProgressPart == null)
                    {
                        break;
                    }
                    count++;
                    ProgressPart.ProgressValue = excelInfo.ProgressPercentage;
                    ProgressPart.Text = "Parsing test case # " + count;
                }
                for (int i = 0; i < parser.ParsedSourceWorkItems.Count; i++)
                {
                    sourceWorkItems.Add(parser.ParsedSourceWorkItems[i]);
                    rawSourceWorkItems.Add(parser.RawSourceWorkItems[i]);
                }
            }

            Migrator.RawSourceWorkItems = rawSourceWorkItems;
            Migrator.SourceWorkItems = sourceWorkItems;

            if (Reporter != null)
            {
                Reporter.Dispose();
            }
            string reportDirectory = Path.Combine(Path.GetDirectoryName(DataSourceParser.StorageInfo.Source),
                                                  "Report" + DateTime.Now.ToString("g", System.Globalization.CultureInfo.CurrentCulture).
                                                  Replace(":", "_").Replace(" ", "_").Replace("/", "_"));
            switch (DataSourceType)
            {
                case DataSourceType.Excel:
                    Reporter = new ExcelReporter(this);
                    string fileNameWithoutExtension = "Report";
                    string fileExtension = Path.GetExtension(DataSourceParser.StorageInfo.Source);
                    Reporter.ReportFile = Path.Combine(reportDirectory, fileNameWithoutExtension + fileExtension);
                    break;

                case DataSourceType.MHT:
                    Reporter = new XMLReporter(this);
                    string fileName = "Report.xml"; ;
                    Reporter.ReportFile = Path.Combine(reportDirectory, fileName);
                    break;

                default:
                    throw new InvalidEnumArgumentException("Invalid Data Source type");
            }
        }

        private void LoadMultiLineSense(XmlElement node)
        {
            DataSourceParser.StorageInfo.IsMultilineSense = bool.Parse(node.InnerXml);
        }

        private void LoadParametrizationDelimeters(XmlElement node)
        {
            DataSourceParser.StorageInfo.StartParameterizationDelimeter = UpdateLTGTChars(node.Attributes[c_startParameterizationAttribute].InnerXml);
            DataSourceParser.StorageInfo.EndParameterizationDelimeter = UpdateLTGTChars(node.Attributes[c_endParameterizationAttribute].InnerXml);
        }

        private string UpdateLTGTChars(string value)
        {
            return value.Replace(LTXMLChar, LTChar).Replace(GTXMLChar, GTChar);
        }

        #endregion

        #region IDisposable Implementation

        public void Dispose()
        {
            // Disposing Data Source
            if (DataSourceParser != null)
            {
                DataSourceParser.Dispose();
            }

            if (WorkItemGenerator != null)
            {
                WorkItemGenerator.Dispose();
            }

            if (Reporter != null)
            {
                Reporter.Dispose();
            }


        }

        #endregion
    }
}