using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace Sdaleo.Internal
{
    /// <summary>
    /// Interface of Versioning, to check if the passed in version is supported by the Versioning Object
    /// </summary>
    internal interface IVersionSupported
    {
        bool IsSupported(string Version);
        bool IsSupported(Versioning Version);
    }

    /// <summary>
    /// Allows us to easily wrap Versioning Functionallity,
    /// into Objects.
    /// [Major version].[Minor version].[Build number]
    /// ---------------------------------------------------
    /// Major version can be any valid uint as well as *
    /// Minor version can be any valid uint as well as *
    /// Build number can be any valid uint as well as *
    /// </summary>
    internal class Versioning : ICloneable, IComparable, IVersionSupported
    {
        #region Internal consts

        internal const int STAR_ALL = -1;

        #endregion

        #region Private Members

        private int _MajorVersion = STAR_ALL;
        private int _MinorVersion = STAR_ALL;
        private int _BuildNumber = STAR_ALL;

        #endregion

        #region Construction

        /// <summary>
        /// Use this to initialize the class with a version string, can not contain multiple versions seperated by ";"
        /// </summary>
        /// <param name="strVersion">any version string in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.*</param>
        internal Versioning(string strVersion)
        {
            if (!IsValidVersionStr(strVersion) || strVersion.Contains(";"))
                throw new ArgumentException("Invalid Version String");
            ParseVersion(strVersion, out this._MajorVersion, out this._MinorVersion, out this._BuildNumber);
        }

        /// <summary>
        /// Initialize Versioning with a single MajorVersion and MinorVersion.
        /// </summary>
        /// <param name="MajorVersion">Major Version</param>
        /// <param name="MinorVersion">Minor Version</param>
        internal Versioning(int MajorVersion, int MinorVersion)
        {
            if (MajorVersion >= STAR_ALL && MinorVersion >= STAR_ALL)
            {
                _MajorVersion = MajorVersion;
                _MinorVersion = MinorVersion;
            }
            else
                throw new ArgumentException("MajorVersion or MinorVersion is invalid");
        }

        /// <summary>
        /// Initialize Versioning with a single MajorVersion,MinorVersion, and BuildNumber.
        /// </summary>
        /// <param name="MajorVersion">Major Version</param>
        /// <param name="MinorVersion">Minor Version</param>
        /// <param name="BuildNumber">Build Number</param>
        internal Versioning(int MajorVersion, int MinorVersion, int BuildNumber)
        {
            if (MajorVersion >= STAR_ALL && MinorVersion >= STAR_ALL && _BuildNumber >= STAR_ALL)
            {
                _MajorVersion = MajorVersion;
                _MinorVersion = MinorVersion;
                _BuildNumber = BuildNumber;
            }
            else
                throw new ArgumentException("MajorVersion,MinorVersion, or BuildNumber is invalid");
        }

        #endregion

        #region Versioning Getter/Setter

        /// <summary>
        /// Set/Retrieve Version 
        /// </summary>
        internal String Version
        {
            get
            {
                return VersionToVersionString(_MajorVersion, _MinorVersion, _BuildNumber);
            }
            set
            {
                if (IsValidVersionStr(value) && !value.Contains(";"))
                    ParseVersion(value, out this._MajorVersion, out this._MinorVersion, out this._BuildNumber);
                else
                    throw new ArgumentException("Invalid Version String");
            }
        }

        #endregion

        #region internal Static Helpers

        /// <summary>
        /// Gets a Versioning object for the Version Information of a File
        /// </summary>
        /// <param name="FileNameNPath">Full File Name and Path</param>
        /// <returns>returns a new Versioning Object for a File, or null if error occured</returns>
        internal static Versioning GetFileVersioning(string FileNameNPath)
        {
            if (!string.IsNullOrEmpty(FileNameNPath) && File.Exists(FileNameNPath))
            {
                try
                {
                    FileVersionInfo info = FileVersionInfo.GetVersionInfo(FileNameNPath);
                    return new Versioning(info.FileMajorPart, info.FileMinorPart, info.FileBuildPart);
                }
                catch (Exception) { /* ignore */ }
            }
            return null;
        }

        /// <summary>
        /// Parses out a single Version from a string
        /// </summary>
        /// <param name="strVersion">pass in a Version format [Major].[Minor].[Build], like 5.3.3 or 5.4.*</param>
        /// <param name="MajorVersion">returns an int or STAR_ALL, or UNINITIALIZED, if error occured</param>
        /// <param name="MinorVersion">returns an int or STAR_ALL, or UNINITIALIZED, if error occured</param>
        /// <param name="BuildNumber">returns an int or STAR_ALL, or UNINITIALIZED, if error occured</param>
        internal static void ParseVersion(string strVersion, out int MajorVersion, out int MinorVersion, out int BuildNumber)
        {
            if (!IsValidVersionStr(strVersion) || strVersion.Contains(";"))
                throw new ArgumentException("Invalid Version String");

            MajorVersion = STAR_ALL;
            MinorVersion = STAR_ALL;
            BuildNumber = STAR_ALL;
            string[] MajorMinorPossBuildVersion = strVersion.Split('.');
            try
            {
                for (int i = 0; i < MajorMinorPossBuildVersion.Length; ++i)
                {
                    if (i == 0)
                    {
                        if (MajorMinorPossBuildVersion[0] != "*")
                            MajorVersion = int.Parse(MajorMinorPossBuildVersion[0]);
                    }
                    else if (i == 1)
                    {
                        if (MajorMinorPossBuildVersion[1] != "*")
                            MinorVersion = int.Parse(MajorMinorPossBuildVersion[1]);
                    }
                    else if (i == 2)
                    {
                        if (MajorMinorPossBuildVersion[2] != "*")
                            BuildNumber = int.Parse(MajorMinorPossBuildVersion[2]);
                    }
                }

            }
            catch (Exception) { /* Ignore */ }
        }

        /// <summary>
        /// Turns Version Numbers into a Version String
        /// </summary>
        /// <param name="MajorVersion">Major Version</param>
        /// <param name="MinorVersion">Minor Version, can be *</param>
        /// <param name="BuildNumber">Build Number, can be * or UNINITIALIZED</param>
        /// <returns>the Version String</returns>
        internal static string VersionToVersionString(int MajorVersion, int MinorVersion = STAR_ALL, int BuildNumber = STAR_ALL)
        {
            string strRetVal = String.Empty;
            if (MajorVersion >= STAR_ALL)
            {
                // Major Version
                if (MajorVersion == STAR_ALL)
                    strRetVal += "*";
                else
                    strRetVal += MajorVersion.ToString();

                // Minor Version
                if (MinorVersion >= STAR_ALL)
                {
                    strRetVal += ".";
                    if (MinorVersion == STAR_ALL)
                        strRetVal += "*";
                    else
                        strRetVal += MinorVersion.ToString();
                }

                // Build Number
                if (BuildNumber >= STAR_ALL)
                {
                    strRetVal += ".";
                    if (BuildNumber == STAR_ALL)
                        strRetVal += "*";
                    else
                        strRetVal += BuildNumber.ToString();
                }
            }
            return strRetVal;
        }

        #endregion

        #region Validation

        private const string CharSet_AllowedNumeric = "0123456789";
        private const string CharSet_AllowedVersionChars = CharSet_AllowedNumeric + "*;.";

        /// <summary>
        /// Generic Function to use with Allowed Character Sets above 
        /// </summary>
        /// <param name="TextToEvaluate">string to evaluate</param>
        /// <param name="TextToEvaluateWith">Pass in one of the legal character consts declared above</param>
        /// <returns>true if valid, false otherwise</returns>
        private static bool ContainsOnlyLegalChars(string TextToEvaluate, string TextToEvaluateWith)
        {
            foreach (char c in TextToEvaluate.ToCharArray())
            {
                bool bFound = (TextToEvaluateWith.IndexOf(c) >= 0);
                if (!bFound)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Used to Validate a Version string of format 12.1.12;12.2.*;12.2.1
        /// </summary>
        /// <param name="VersionStr">a Version String</param>
        /// <returns>true if valid, false otherwise</returns>
        public static bool IsValidVersionStr(string VersionStr)
        {
            if (String.IsNullOrEmpty(VersionStr))
            {
                return false;
            }
            else if (VersionStr.Length < 1 && VersionStr.Length > 50) // restrice Version String Length
            {
                return false;
            }
            else if (!ContainsOnlyLegalChars(VersionStr, CharSet_AllowedVersionChars))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        #endregion

        #region ICloneable Members
        public object Clone()
        {
            Versioning versioning = new Versioning(this._MajorVersion, this._MinorVersion, this._BuildNumber);
            return versioning;
        }
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            if (obj is Versioning)
            {
                Versioning v = (Versioning)obj;
                //*.*.*
                //*.2.*
                //1.*.*
                //1.1.*
                //1.1.1
                //2.2.2
                //2.2.*
                int nCompare = 0;
                nCompare = this._MajorVersion.CompareTo(v._MajorVersion);
                if (nCompare == 0)
                    nCompare = this._MinorVersion.CompareTo(v._MinorVersion);
                if (nCompare == 0)
                    nCompare = this._BuildNumber.CompareTo(v._BuildNumber);
                return nCompare;
            }
            else
            {
                throw new ArgumentException("object is not a Versioning");
            }
        }

        #endregion

        #region IVersionSupported Members

        /// <summary>
        /// Returns true if the Version String passed in is supported/matches Versioning for this object
        /// </summary>
        /// <param name="Version">a Version string to validate against</param>
        /// <returns>true if supported, false otherwise</returns>
        public bool IsSupported(string Version)
        {
            Versioning version = new Versioning(Version);
            return IsSupported(version);
        }

        /// <summary>
        /// Returns true if the Version passed in is supported/matches Versioning for this object
        /// </summary>
        /// <param name="Version">a Version Object to validate against</param>
        /// <returns>true if supported, false otherwise</returns>
        public bool IsSupported(Versioning Version)
        {
            if (Version != null)
            {
                int nCompare = this.CompareTo(Version);
                if (nCompare == 0)
                {
                    return true;
                }
                else if (nCompare == 1)
                {
                    return false;
                }
                else if (nCompare == -1)
                {
                    if (((this._MajorVersion == STAR_ALL) || (this._MajorVersion == Version._MajorVersion)) &&
                        ((this._MinorVersion == STAR_ALL) || (this._MinorVersion == Version._MinorVersion)) &&
                        ((this._BuildNumber == STAR_ALL) || (this._BuildNumber == Version._BuildNumber))
                      )
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            return false;
        }

        #endregion
    }

    /// <summary>
    /// Allows us to easily parse/sort/search multiple versioning classes
    /// </summary>
    internal static class Versionings
    {
        /// <summary>
        /// Used to parse multiple ";" seperated versions from a string
        /// </summary>
        /// <param name="strVersions">single/multiple version strings, seperated by ";", in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.*</param>
        /// <returns>a sorted array of versionings or null if error occured</returns>
        internal static Versioning[] ParseVersions(string strVersions)
        {
            if (!Versioning.IsValidVersionStr(strVersions))
                return null;

            List<Versioning> RetValueVersions = new List<Versioning>();
            string[] Versions = strVersions.Split(';');
            foreach (string Version in Versions)
                RetValueVersions.Add(new Versioning(Version));

            RetValueVersions.Sort();
            return RetValueVersions.ToArray();
        }

        #region IsSupportedFile

        /// <summary>
        /// Use this to find out if a File Version is supported by the passed in Versions string
        /// </summary>
        /// <param name="strVersions">single/multiple version strings, seperated by ";", in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.*</param>
        /// <param name="FileNameNPath">Full File Name and Path</param>
        /// <returns>true if the File Version is supported by the Versions string</returns>
        internal static bool IsSupportedFile(string strVersions, string FileNameNPath)
        {
            return IsSupported(ParseVersions(strVersions), Versioning.GetFileVersioning(FileNameNPath));
        }

        /// <summary>
        /// Use this to find out if a File Version is supported by the passed in Versions []
        /// </summary>
        /// <param name="Versions">single/multiple versioning objects</param>
        /// <param name="FileNameNPath">Full File Name and Path</param>
        /// <returns>true if the File Version is supported by the Versions string</returns>
        internal static bool IsSupportedFile(Versioning[] Versions, string FileNameNPath)
        {
            return IsSupported(Versions, Versioning.GetFileVersioning(FileNameNPath));
        }

        #endregion

        #region IsSupported

        /// <summary>
        /// Pass in a Versions string, and a Version to check against. Returns true, if the Version is IVersionSupported by
        /// the passed in Versions string.
        /// </summary>
        /// <param name="strVersions">single/multiple version strings, seperated by ";", in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.*</param>
        /// <param name="strVersion">any version string in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.*</param>
        /// <returns>true, if a Versioning Object in the Versions returns true for IVersionSupported, false otherwise</returns>
        internal static bool IsSupported(string strVersions, string strVersion)
        {
            return IsSupported(ParseVersions(strVersions), new Versioning(strVersion));
        }

        /// <summary>
        /// Pass in a single/multipe Versions object, and a Version object to check against. Returns true, if the Version is IVersionSupported by
        /// the passed in Versions Objects.
        /// </summary>
        /// <param name="Versions">single/multiple versioning objects</param>
        /// <param name="Version">single versioning object</param>
        /// <returns>true, if a Versioning Object in the Versions returns true for IVersionSupported, false otherwise</returns>
        internal static bool IsSupported(Versioning[] Versions, Versioning Version)
        {
            // Let IVersionSupported do all the work
            foreach (Versioning _version in Versions)
            {
                if (_version.IsSupported(Version))
                    return true;
            }
            return false;
        }

        #endregion
    }
}
