#region <<< Using Directives >>>
using System;
using System.Collections.Specialized;
using System.IO;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;
#endregion

namespace Volpe.Cafe.IO.InputParsers
{
    /// <summary>
    /// Provides an input parser for loading and parsing Excel input files.
    /// </summary>
    public class XlParser : Input
    {

        #region <<< Ctors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="XlParser"/> class.  The constructor also invokes the
        /// <see cref="ParseInternal"/> method (which is wrapped by a try-catch block) to begin the actual parsing.  All exception
        /// handling is done by this class internally.
        /// </summary>
        /// <param name="path">The path of an Excel input file to load.</param>
        /// <param name="postException">Specifies whether to throw an <see cref="IOException"/> if any parsing errors occur.</param>
        protected XlParser(string path, bool postException = true) : base(path)
        {
            this._path = path;
            this._errorList = new StringCollection();
            this._postException = postException;
            //
            // try parsing the input file
            try                  { this.ParseInternal(); }
            catch (Exception ex) { this.LogError("Errors were encountered while parsing file: " + ex.Message + "\n" + ex.StackTrace); }

            // check for errors
            if (this._errorList.Count > 0)
            {
                string err = $"Problem Reading File: {this._path}";
                int maxErrCount = Math.Min(10000, this._errorList.Count);
                err += "\n\nThe input file contains " + this._errorList.Count + " missing or invalid entries.";
                if (this._errorList.Count > maxErrCount) { err += "  (First " + maxErrCount + " errors are shown.)"; }
                err += "\n";
                for (int i = 0; i < maxErrCount; i++)
                {
                    err += ("\n" + this._errorList[i]);
                }
                if (this._postException) { throw new IOException(err); }
            }
        }

        #endregion

        #region <<< Methods >>>

        /// <summary>
        /// Parses an Excel input file, of the specified type, from the specified path.
        /// </summary>
        /// <param name="fileType">The type of input file to load.</param>
        /// <param name="path">The path of an Excel Market-Data file to load.</param>
        /// <returns>An object parsed from the input file, corresponding to the specified input file type.</returns>
        /// <exception cref="ArgumentOutOfRangeException">If the specified input file type is not valid.</exception>
        public static object ParseInputFile(InputFileType fileType, string path)
        {
            switch (fileType)
            {
                case InputFileType.MarketData: return ParseMarketData(path);
                case InputFileType.Technologies: return ParseTechnologies(path);
                case InputFileType.Parameters: return ParseParameters(path);
                case InputFileType.Scenarios: return ParseScenarios(path);
                default: throw new ArgumentOutOfRangeException("fileType", "The specified input file type is not valid.");
            }
        }
        /// <summary>
        /// Parses an Excel Market-Data file from the specified path.
        /// </summary>
        /// <param name="path">The path of an Excel Market-Data file to load.</param>
        /// <returns>An <see cref="IIndustry"/> object parsed from the input file.</returns>
        public static IIndustry ParseMarketData(string path)
        {
            return new XlMarketDataParser(path)._ind;
        }
        /// <summary>
        /// Parses an Excel Technologies file from the specified path.
        /// </summary>
        /// <param name="path">The path of an Excel Technologies file to load.</param>
        /// <returns>A <see cref="Technologies"/> object parsed from the input file.</returns>
        public static Technologies ParseTechnologies(string path)
        {
            return new XlTechnologiesParser(path)._techs;
        }
        /// <summary>
        /// Parses an Excel Parameters file from the specified path.
        /// </summary>
        /// <param name="path">The path of an Excel Parameters file to load.</param>
        /// <returns>A <see cref="Parameters"/> object parsed from the input file.</returns>
        public static Parameters ParseParameters(string path)
        {
            return new XlParametersParser(path)._value;
        }
        /// <summary>
        /// Parses an Excel Scenarios file from the specified path.
        /// </summary>
        /// <param name="path">The path of an Excel Scenarios file to load.</param>
        /// <returns>A <see cref="Scenarios"/> object parsed from the input file.</returns>
        public static Scenarios ParseScenarios(string path)
        {
            return new XlScenariosParser(path)._scens;
        }

        // ----- internal methods -----
        /// <summary>
        /// This method should be overriden by sub-classes to begin actual parsing.
        /// </summary>
        protected virtual void ParseInternal() { }
        /// <summary>
        /// Logs an error message to the internal errors list.
        /// </summary>
        protected void LogError(string message)
        {
            this._errorList.Add(message);
        }

        /// <summary>
        /// Verifies if the specified indexes contain valid values.  If a value at the specified index array is -1, it is
        /// assumed invalid.
        /// </summary>
        /// <param name="wsName">The name of the worksheet or workbook being examined.</param>
        /// <param name="indexes">The array of indexes to examine.</param>
        /// <param name="names">The array of section names associated with each entry in the indexes array.</param>
        /// <exception cref="System.ArgumentException">The length of the indexes array does not match the length of the names array.</exception>
        /// <returns>true, if all indexes are valid; false, if one or more indexex are invalid.</returns>
        protected bool VerifyIndexes(string wsName, int[] indexes, string[] names)
        {
            return this.VerifyIndexes(wsName, indexes, names, 0, indexes.Length);
        }
        /// <summary>
        /// Verifies if the specified indexes contain valid values.  If a value at the specified index array is -1, it is
        /// assumed invalid, unless the optional flag is also set at the same index.
        /// </summary>
        /// <param name="wsName">The name of the worksheet or workbook being examined.</param>
        /// <param name="indexes">The array of indexes to examine.</param>
        /// <param name="names">The array of section names associated with each entry in the indexes array.</param>
        /// <param name="optional">The array of flags indicating if a specific value is optional.</param>
        /// <exception cref="System.ArgumentException">The length of the indexes array does not match the length of the names array.</exception>
        /// <returns>true, if all indexes are valid; false, if one or more indexex are invalid.</returns>
        protected bool VerifyIndexes(string wsName, int[] indexes, string[] names, bool[] optional)
        {
            return this.VerifyIndexes(wsName, indexes, names, optional, 0, indexes.Length);
        }
        /// <summary>
        /// Verifies if the specified indexes contain valid values, starting at the given start index and going forward by the
        /// specified length.  If a value at the specified index array is -1, it is assumed invalid.
        /// </summary>
        /// <param name="wsName">The name of the worksheet or workbook being examined.</param>
        /// <param name="indexes">The array of indexes to examine.</param>
        /// <param name="names">The array of section names associated with each entry in the indexes array.</param>
        /// <param name="startIndex">The index at which to begin examining the indexes array.</param>
        /// <param name="length">The number of indexes to examine, staring at <i>startIndex</i>.</param>
        /// <exception cref="System.ArgumentException">The length of the indexes array does not match the length of the names array.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">The startIndex and/or length is outside the bounds of the indexes and names arrays.</exception>
        /// <returns>true, if all indexes are valid; false, if one or more indexex are invalid.</returns>
        protected bool VerifyIndexes(string wsName, int[] indexes, string[] names, int startIndex, int length)
        {
            return this.VerifyIndexes(wsName, indexes, names, null, startIndex, length);
        }
        bool VerifyIndexes(string wsName, int[] indexes, string[] names, bool[] optional, int startIndex, int length)
        {
            int endIndex = startIndex + length;
            // verify ...
            if (indexes.Length != names.Length || (optional != null && indexes.Length != optional.Length))
            {
                throw new ArgumentException("The length of the indexes array does not match the length of the names array.", "names");
            }
            if (startIndex < 0 || indexes.Length < endIndex || names.Length < endIndex)
            {
                throw new ArgumentOutOfRangeException("startIndex or length",
                    "The startIndex and/or length is outside the bounds of the indexes and names arrays.");
            }

            // examine names and indexes
            bool isValid = true;
            for (int i = startIndex; i < endIndex; i++)
            {
                if (indexes[i] == -1 && (optional == null || !optional[i]))
                {
                    this.LogError("In the " + wsName + ", " + names[i] + " section or entry not found.");
                    isValid = false;
                }
            }
            //
            return isValid;
        }

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets whether errors were encountered during parsing of the input file.</summary>
        public bool HasErrors { get => this._errorList.Count > 0; }

        #endregion

        #region <<< Variables >>>

        string _path;
        StringCollection _errorList;
        bool _postException;

        #endregion

    }
}
