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

namespace Sdaleo
{
    /// <summary>
    /// Core Class to Parse / Generate a UDL String
    /// </summary>
    internal class UDL : ICloneable, IComparable
    {
        #region Internal ReadWrite Properties

        /// <summary>
        /// Credential Object can point to different databases
        /// </summary>
        internal string DataBase { get; set; }

        /// <summary>
        /// Default Connection Timeout 
        /// </summary>
        internal const uint DEFAULT_CONNECTION_TIMEOUT_UDL = 15;
        
        /// <summary>
        /// Allow Caller to set connection timeout
        /// </summary>
        internal uint ConnectionTimeout = DEFAULT_CONNECTION_TIMEOUT_UDL;

        /// <summary>
        /// Defaulted to 'true'. When set uses the ConnectionTimeout value, when false it won't
        /// </summary>
        internal bool UseConnectionTimeout = true;

        // * Secured Password *
        private SecuredPassword _Password = new SecuredPassword();
        
        /// <summary>
        /// Secured Password Object that stores the password in a secure manner in memory
        /// </summary>
        internal string Password
        {
            get
            {
                return _Password.Password;
            }
            private set
            {
                _Password.Password = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal string DataSource
        {
            get
            {
                if (!String.IsNullOrEmpty(ServerAddress) && !String.IsNullOrEmpty(InstanceName))
                    return ServerAddress + "\\" + InstanceName;
                else if (!String.IsNullOrEmpty(ServerAddress))
                    return ServerAddress;
                else
                    return string.Empty;
            }
            private set
            {
                if (!String.IsNullOrEmpty(value))
                {
                    string[] values = value.Split('\\');
                    if (values.Length == 2)
                    {
                        ServerAddress = values[0];
                        InstanceName = values[1];
                    }
                    else if (values.Length > 2)
                    {
                        int nIndx = value.LastIndexOf('\\');
                        ServerAddress = value.Substring(0, nIndx);
                        InstanceName = value.Substring(nIndx + 1);
                    }
                    else
                    {
                        ServerAddress = value;
                    }
                }
            }
        }

        #endregion

        #region Internal ReadOnly Properties

        #region ReadOnly String Getters

        /// <summary>
        /// 
        /// </summary>
        internal string Username { get; private set; }
        
        /// <summary>
        /// 
        /// </summary>
        internal string ServerAddress { get; private set; }
        
        /// <summary>
        /// 
        /// </summary>
        internal string InstanceName { get; private set; }
        
        /// <summary>
        /// 
        /// </summary>
        internal string IntegratedSecurity { get; private set; }
        
        /// <summary>
        /// 
        /// </summary>
        internal string AttachDBFilename { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        internal string ServerType { get; private set; }

        #endregion

        #region ReadOnly Bool Getters

        /// <summary>
        /// 
        /// </summary>
        internal bool TrustedConnection { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        internal bool PersistSecurityInfo { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        internal bool UserInstance { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        internal bool Encrypt { get; private set; }

        #endregion

        #endregion

        #region Constructors

        /// <summary>
        /// Construction (Set all UDL Settings via Connection String)
        /// </summary>
        /// <param name="ConnectionString">Pass in a valid Connection String To correctly instantiate the UDL Object</param>
        internal UDL(String ConnectionString)
        {
            Parse(ConnectionString);
        }

        #region SQL CE Constructors

        /// <summary>
        /// Construction SQL CE (Set common UDL Settings for SQL CE)
        /// </summary>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        /// <param name="Password"></param>        
        internal UDL(string FileNameNPath, string Password)
        {
            this.DataSource = FileNameNPath;
            UseConnectionTimeout = false;
            if (!String.IsNullOrEmpty(Password))
            {
                this.Password = Password;
                this.Encrypt = true;
            }
        }

        #endregion

        #region Advantage Constructors

        /// <summary>
        /// Construction Advantage (Set common UDL Settings for Advantage)
        /// </summary>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        /// <param name="Password"></param>  
        /// <param name="ServerType"></param>
        internal UDL(string FileNameNPath, string UserID, string Password, string ServerType)
        {
            UseConnectionTimeout = false;
            this.DataSource = FileNameNPath;
            this.Username = UserID;
            this.Password = Password;
            this.ServerType = ServerType;
        }

        #endregion

        #region SQL Server Constructors

        /// <summary>
        /// Construction SQL Server (Using SQL Login Credential)
        /// </summary>
        /// <param name="DataBase">Name of Database To Use</param>
        /// <param name="Username">Username to Connect With</param>
        /// <param name="Password">Password to Connect With</param>
        /// <param name="ServerAddress">ServerAddress to use as DataSource</param>
        /// <param name="InstanceName">Name of Instance to use as DataSource</param>        
        internal UDL(string DataBase, string Username, string Password, string ServerAddress, string InstanceName)
        {
            this.DataBase = DataBase.Trim();
            this.Username = Username.Trim();
            this.Password = Password;
            this.ServerAddress = (String.IsNullOrEmpty(ServerAddress)) ? "." : ServerAddress.Trim().ToUpper();
            this.InstanceName = InstanceName.Trim().ToUpper();
        }

        /// <summary>
        /// Construction SQL Server (Set common UDL Settings for SQL Server)
        /// </summary>
        /// <param name="DataBase">Name of Database To Use</param>
        /// <param name="Username">Username to Connect With</param>
        /// <param name="Password">Password to Connect With</param>
        /// <param name="ServerAddress">ServerAddress to use as DataSource</param>
        /// <param name="InstanceName">Name of Instance to use as DataSource</param>        
        /// <param name="TrustedConnection">Set to True to use Windows Authentication instead of SQL Auth</param>
        internal UDL(string DataBase, string Username, string Password, string ServerAddress, string InstanceName, bool TrustedConnection)
        {
            this.DataBase = DataBase.Trim();
            this.Username = Username.Trim();
            this.Password = Password;
            this.ServerAddress = (String.IsNullOrEmpty(ServerAddress)) ? "." : ServerAddress.Trim().ToUpper();
            this.InstanceName = InstanceName.Trim().ToUpper();
            this.TrustedConnection = TrustedConnection;
            this.IntegratedSecurity = TrustedConnection ? "SSPI" : "";
        }

        /// <summary>
        /// Construction SQL Server (Set all UDL Settings for SQL Server)
        /// </summary>
        /// <param name="DataBase">Name of Database To Use</param>
        /// <param name="Username">Username to Connect With</param>
        /// <param name="Password">Password to Connect With</param>
        /// <param name="ServerAddress">ServerAddress to use as DataSource</param>
        /// <param name="InstanceName">Name of Instance to use as DataSource</param>
        /// <param name="AttachDBFilename">Set to SQL Express .mdf file to attach to (Creates a User Instance)</param>                
        /// <param name="TrustedConnection">Set to True to use Windows Authentication instead of SQL Auth</param>
        /// <param name="PersistSecurityInfo">Legacy Setting that Persists the Security Information</param>                
        internal UDL(string DataBase, string Username, string Password, string ServerAddress, string InstanceName, 
                     string AttachDBFilename, bool TrustedConnection, bool PersistSecurityInfo) 
        {
            this.DataBase = DataBase.Trim();
            this.Username = Username.Trim();
            this.Password = Password;
            this.ServerAddress = (String.IsNullOrEmpty(ServerAddress)) ? "." : ServerAddress.Trim().ToUpper();
            this.InstanceName = InstanceName.Trim().ToUpper();
            this.AttachDBFilename = AttachDBFilename.Trim();
            this.UserInstance = !String.IsNullOrEmpty(AttachDBFilename);
            this.TrustedConnection = TrustedConnection;
            this.IntegratedSecurity = TrustedConnection? "SSPI" : "";
            this.PersistSecurityInfo = PersistSecurityInfo;
        }

        #endregion

        #endregion

        #region Connection String N' Parse

        /// <summary>
        /// Returns the Connection String formed from the UDL Properties
        /// </summary>
        /// <see cref="http://www.connectionstrings.com/sql-server-2008"/>
        /// <example>
        /// Data Source=myServerAddress;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;
        /// Server=myServerAddress;Database=myDataBase;User ID=myUsername;Password=myPassword;Trusted_Connection=False;
        /// Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI;
        /// Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;
        /// Server=myServerName\theInstanceName;Database=myDataBase;Trusted_Connection=True;
        /// Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI;User ID=myDomain\myUsername;Password=myPassword;
        /// </example>
        internal string ConnectionString
        {            
            get
            {
                string strUDL = String.Empty;

                // * Data Source Must Always be Specified *
                if (!String.IsNullOrEmpty(ServerAddress) && !String.IsNullOrEmpty(InstanceName))
                {
                    strUDL = String.Format(@"Data Source={0}\{1};", ServerAddress, InstanceName);
                }
                else if (!String.IsNullOrEmpty(ServerAddress))
                {
                    strUDL = String.Format(@"Data Source={0};", ServerAddress);
                }
                else
                    throw new Exception("Invalid DataSource Parameter");

                // Add Database?
                if (!String.IsNullOrEmpty(DataBase))
                {
                    string strAddMore = String.Format(@"Initial Catalog={0};", DataBase);
                    strUDL += strAddMore;
                }

                // Add User Information?
                if (!String.IsNullOrEmpty(Username))
                {
                    string strAddMore = String.Format(@"User ID={0};", Username);
                    strUDL += strAddMore;
                }

                // Add Password Information?
                if (!String.IsNullOrEmpty(Password))
                {
                    string strAddMore = String.Format(@"Password={0};", Password);
                    strUDL += strAddMore;
                }

                // Should we use User Instance?
                if (UserInstance && !String.IsNullOrEmpty(AttachDBFilename))
                {
                    string strAddMore = String.Format(@"User Instance={0};AttachDBFilename={1}", UserInstance.ToString(), AttachDBFilename);
                    strUDL += strAddMore;
                }

                // Should we use Integrated Security?
                if (TrustedConnection && !String.IsNullOrEmpty(IntegratedSecurity))
                {
                    string strAddMore = String.Format(@"Trusted_Connection={0};Integrated Security={1}", TrustedConnection.ToString(), IntegratedSecurity);
                    strUDL += strAddMore;
                }

                // Persist Security Info?
                if (PersistSecurityInfo)
                {
                    string strAddMore = String.Format(@"Persist Security Info={0};", PersistSecurityInfo.ToString());
                    strUDL += strAddMore;
                }

                // SQL CE Encryption 
                if (Encrypt)
                {
                    string strAddMore = "Encrypt=TRUE;Encryption Mode=platform default;";
                    strUDL += strAddMore;
                }

                // Advantage DB
                if (!String.IsNullOrEmpty(ServerType))
                {
                    string strAddMore = String.Format("ServerType={0};", ServerType);
                    strUDL += strAddMore;
                    strUDL += "TableType=ADT;";
                }

                // At the end, specifically add the connection timeout * If asked to *
                if (UseConnectionTimeout)
                {
                    string strAddMore = String.Format(@"Connection Timeout={0};", ConnectionTimeout.ToString());
                    strUDL += strAddMore;
                }
                return strUDL;
            }
        }

        /// <summary>
        /// Returns a Connection String for use with Legacy ADO
        /// </summary>
        internal string ConnectionStringADO
        {
            get
            {
                // Force Persist Security Info
                bool bPersistSecurity = PersistSecurityInfo;
                PersistSecurityInfo = true;
                string retString = "Provider=SQLOLEDB.1;OLE DB Services=-2;" + ConnectionString;
                PersistSecurityInfo = bPersistSecurity;
                return retString;
            }
        }

        /// <summary>
        /// Parses (fills in) UDL properties from the passed in Connection String
        /// </summary>
        /// <param name="strToParse"></param>
        private void Parse(String ConnectionString)
        {
            if (!String.IsNullOrEmpty(ConnectionString))
            {
                String[] tokens = ConnectionString.Split(';');
                foreach (string Pair in tokens)
                {
                    string[] KeyValuePair = Pair.Split('=');                    
                    try
                    {
                        string left = KeyValuePair[0];
                        string right = KeyValuePair[1];
                        switch (KeyValuePair[0].ToUpper().Trim())
                        {
                            case "INITIAL CATALOG":
                                DataBase = right.Trim();
                                break;

                            case "USER ID":
                                Username = right.Trim();
                                break;

                            case "PASSWORD":
                                Password = right;
                                break;

                            case "DATA SOURCE":                                
                                DataSource = right.Trim();
                                break;

                            case "ATTACHDBFILENAME":
                                AttachDBFilename = right.Trim();
                                break;

                            case "USER INSTANCE":
                                UserInstance = Boolean.Parse(right.Trim());
                                break;

                            case "CONNECTION TIMEOUT":
                                ConnectionTimeout = UInt32.Parse(right.Trim());
                                break;

                            case "TRUSTED_CONNECTION":
                                TrustedConnection = Boolean.Parse(right.Trim());
                                break;

                            case "INTEGRATED SECURITY":
                                IntegratedSecurity = KeyValuePair[1].Trim().ToUpper();
                                break;

                            case "PERSIST SECURITY INFO":
                                PersistSecurityInfo = Boolean.Parse(KeyValuePair[1].Trim());
                                break;

                            case "ENCRYPT":
                                Encrypt = true;
                                break;

                            case "ENCRYPTION MODE":
                                Encrypt = true;
                                break;

                            case "SERVERTYPE":
                                ServerType = KeyValuePair[1].Trim();
                                break;

                        }
                    }
                    catch (Exception e) { string m = e.Message; /* ignore and continue iteration */ }
                }
            }
        }

        #endregion

        #region ICloneable Members

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {            
            return new UDL(this.ConnectionString);
        }

        #endregion

        #region IComparable Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {            
            UDL otherUDL = obj as UDL;
            if (otherUDL != null)
            {
                int nCompare = String.Compare(this.ConnectionString, otherUDL.ConnectionString, true);
                return nCompare;
            }
            else
            {
                throw new ArgumentException("Object is not a UDL");
            }
        }

        #endregion      

        #region UDL File Reading / Writing

        /// <summary>
        /// Use this to Parse a UDL object from a text File
        /// </summary>
        /// <param name="FileNameNPath">Full Path to File to Read and Parse</param>
        /// <param name="bFileIsEncrypted">true if the Text file is encrypted, false otherwise</param>
        /// <returns>a valid UDL object if successful, null otherwise</returns>
        internal static UDL UDLReadInFromFile(string FileNameNPath, bool bFileIsEncrypted)
        {
            if (!File.Exists(FileNameNPath))
                return null;

            try
            {                
                // Read In the file
                string UDLFileContents = string.Empty;
                FileStream fs = new FileStream(FileNameNPath, FileMode.Open);
                StreamReader sr = new StreamReader(fs, Encoding.Unicode);
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    // UDL File is encrypted * Decrypt line by line *
                    //if (bFileIsEncrypted)
                        //line = Encryption.DecryptText(line);                        

                    if (line.StartsWith("[oledb]") ||
                        line.StartsWith(";"))
                        continue; // skip first 2 lines

                    // Read the line
                    UDLFileContents += line;
                }
                sr.Close();
                fs.Close();

                UDLFileContents = UDLFileContents.Trim();
                if (String.IsNullOrEmpty(UDLFileContents))
                    return null;

                // Parse out the UDL Info
                UDL udl = new UDL(UDLFileContents);
                return udl;
            }
            catch (Exception) {/* ignore */}
            return null;
        }

        /// <summary>
        /// Use this to Write a UDL Object to a Text File
        /// </summary>
        /// <param name="FileNameNPath">Full Path to File to Write To</param>
        /// <param name="udl">UDL Object to write out</param>
        /// <param name="bEncryptFile">True to encrypt File Contents, False Otherwise</param>
        /// <returns>true if successful, false otherwise</returns>
        internal static bool UDLWriteOutToToFile(string FileNameNPath, UDL udl, bool bEncryptFile)
        {
            try
            {
                string UDLFileContents = string.Empty;

                // Create the Directory (if it doesn't exist) * Otherwise an error occurs *
                if (!Directory.Exists(Path.GetDirectoryName(FileNameNPath)))
                     Directory.CreateDirectory(Path.GetDirectoryName(FileNameNPath));

                // Write to File
                FileStream fs = new FileStream(FileNameNPath, FileMode.Create);
                StreamWriter sw = new StreamWriter(fs, Encoding.Unicode);

                // UDL File is Encrypted Now, so we encrypte Line-by-line
                if (bEncryptFile)
                {
                    // sw.WriteLine(Encryption.EncryptText("[oledb]"));
                    // sw.WriteLine(Encryption.EncryptText("; Everything after this line is an OLE DB initstring"));
                    // write out the Connection string,
                    // sw.WriteLine(Encryption.EncryptText(udl.ConnectionStringLegacy));
                }
                else
                {
                    sw.WriteLine("[oledb]");
                    sw.WriteLine("; Everything after this line is an OLE DB initstring");
                    sw.WriteLine(udl.ConnectionString);
                }               

                sw.Close();
                fs.Close();
                return true;
            }
            catch (Exception) { /* ignore */ }
            return false;
        }

        #endregion           
    }
}
