#if SQLSERVER
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Net;
using System.ServiceProcess;

namespace Sdaleo.Systems.SQLServer
{
    #region SQLServer General Enums
    
    /// <summary>
    /// SQL Server Versions as specified by SQL Server
    /// </summary>
    public enum SQLServerVersion
    {
        SQL_SERVER_2000 = 80,
        SQL_SERVER_2005 = 90,
        SQL_SERVER_2008 = 100,
    }

    #endregion

    /// <summary>
    /// General SQL Server Functions
    /// </summary>
    public class SQLServerGeneral
    {
        #region SQL Version Functions

        /// <summary>
        /// Retrieves the SQL Server Version Information from the specified SQL Server Instance
        /// (* Prefered Function to use *)
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="version">we pass out the SQL Server Specific Version</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError GetSQLServerVersion(IConnectDb credential, out SQLServerVersion version)
        {
            version = SQLServerVersion.SQL_SERVER_2000; // Default behavior
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            int nVersion = (int)SQLServerVersion.SQL_SERVER_2000;
            DBError dberror = GetSQLServerVersion(credential, out nVersion);
            if (dberror.ErrorOccured)
                return dberror;

            // SQLServerVersion numbers are multiple of 10's
            nVersion = nVersion * 10;
            try
            {
                version = (SQLServerVersion)Enum.Parse(typeof(SQLServerVersion), nVersion.ToString());
            }
            catch (Exception e)
            {
                dberror = DBError.Create(e.Message);
            }
            return dberror;
        }

        /// <summary>
        /// Retrieves the SQL Server Version String Information from the specified SQL Server Instance
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="strVersion">we pass out the SQL Server Specific Version String</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError GetSQLServerVersion(IConnectDb credential, out string strVersion)
        {            
            strVersion = string.Empty;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            // Execute the Query
            string sql = "EXEC sp_server_info @attribute_id = 2";
            DB db = DB.Create(credential.DBMS.WithoutDatabase());
            DBRetVal retVal = db.FillDataTable(sql);            
            if (retVal.IsValid)
            {
                strVersion = retVal.GetDataTableRetVal().Rows[0]["attribute_value"].ToString();
                strVersion.Replace("\t", ""); // remove tabs
            }
            return retVal;
        }

        /// <summary>
        /// Retrieves the SQL Server Version Integer Information from the specified SQL Server Instance
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="nVersion">we pass out the SQL Server Specific Version Int</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError GetSQLServerVersion(IConnectDb credential, out int nVersion)
        {
            nVersion = 0;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            // Execute the Query            
            string sql = "SELECT CONVERT(char(20), SERVERPROPERTY('ProductVersion'))";
            DB db = DB.Create(credential.DBMS.WithoutDatabase());
            DBRetVal retVal = db.ExecuteScalar(sql);
            if(retVal.IsValid)
            {
                string strVersion = retVal.GetScalarRetVal();
                String[] strVersionSplit = strVersion.Split('.');
                int.TryParse(strVersionSplit[0], out nVersion);
            }
            return retVal;
        }

        #endregion

        #region SQL File N' Disk functions

        /// <summary>
        /// Retrieves the default path for the SQL Server Instance or for the Database on the SQL Server.
        /// Leave Database blank in the credential to find the default path for the Server.
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="DefaultPath">default path found or "" if not</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError SQLServerDefaultPath(IConnectDb credential, out string DefaultPath)
        {
            DefaultPath = string.Empty;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;
                                 
            // Get the Path for the default Path for the Database if Specified
            string sql = String.Empty;
            if (credential.DBMS.IsDatabaseSetAndNonSystem)
                sql = String.Format("SELECT filename FROM [{0}]..sysfiles", credential.DBMS.Database);
            else
                sql = "SELECT filename FROM master..sysfiles where [name]='master'";

            // Execute the Query
            DB db = DB.Create(credential.DBMS.WithoutDatabase());
            DBRetVal retVal = db.ExecuteScalar(sql);
            if ((retVal.IsValid) && !String.IsNullOrEmpty(retVal.GetScalarRetVal()))
            {
                string strResult = retVal.GetScalarRetVal();
                int indx = strResult.LastIndexOf('\\');
                if (indx >= 0)
                    strResult = strResult.Remove(indx);
                DefaultPath = strResult;
            }
            return retVal;
        }

        /// <summary>
        /// Function retries an array of the Fixed HardDrives/Disks that are available on the Server
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="fixedDisks">an array of all the fixed disk's drive letters (i.e. C), empty list, if error occured</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError SQLServerFixedDisks(IConnectDb credential, out string[] fixedDisks)
        {
            fixedDisks = null;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            // Execute the Query
            string sql = "EXEC master..xp_fixeddrives";
            DB db = DB.Create(credential.DBMS.WithoutDatabase());
            DBRetVal retVal = db.FillDataTable(sql);
            List<String> retList = new List<String>();
            if (retVal.IsValid)
            {
                foreach (DataRow row in retVal.GetDataTableRetVal().Rows)
                         retList.Add(row["drive"].ToString());
            }
            fixedDisks = retList.ToArray();
            return retVal;
        }

        /// <summary>
        /// Function retries an array of directories for the specified Path on the specified SQL Server
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="Path">a valid path that exists on the server, i.e. C:\Program Files</param>
        /// <param name="Directories">an array of all directories found in the specified path, empty list if no directories found or if error occured</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError SQLServerGetDirectoriesInPath(IConnectDb credential, string Path, out string[] Directories)
        {            
            Directories = null;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            // Execute the Query
            string sql = String.Format("EXEC master..xp_dirtree '{0}', 1, 1", Path);
            DB db = DB.Create(credential.DBMS.WithoutDatabase());
            DBRetVal retVal = db.FillDataTable(sql);            
            List<String> retList = new List<String>();
            if (retVal.IsValid)
            {
                foreach (DataRow row in retVal.GetDataTableRetVal().Rows)
                {
                    bool bIsDirectory = (row["file"].ToString() == "0");
                    if (bIsDirectory)
                        retList.Add(row["subdirectory"].ToString());
                }
            }
            Directories = retList.ToArray();
            return retVal;
        }

        /// <summary>
        /// Function retries an array of files for the specified Path on the specified SQL Server
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>
        /// <param name="Path">a valid path that exists on the server, i.e. C:\Program Files</param>
        /// <param name="Files">an array of all files found in the specified path, empty list if no files found or if error occured</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError SQLServerGetFilesInPath(IConnectDb credential, string Path, out string[] Files)
        {            
            Files = null;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            // Execute the Query
            string sql = String.Format("EXEC master..xp_dirtree '{0}', 1, 1", Path);
            DB db = DB.Create(credential.DBMS.WithoutDatabase());
            DBRetVal retVal = db.FillDataTable(sql);
            List<String> retList = new List<String>();
            if (retVal.IsValid)
            {
                foreach (DataRow row in retVal.GetDataTableRetVal().Rows)
                {
                    bool bIsFile = (row["file"].ToString() == "1");
                    if (bIsFile)
                        retList.Add(row["subdirectory"].ToString());
                }
            }
            Files = retList.ToArray();
            return retVal;
        }

        #endregion

        #region SQL Connection Checkers

        /// <summary>
        /// Quick Check function to see if the passed in credential allows us to connect to the server
        /// If Credential contains a Database, then it will verify the Database's Existence
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>        
        /// <param name="nConnectionTimeout">Specify the ConnectionTimout</param>
        /// <param name="CanConnect">Returns true if successfull in connecting to the server</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError SQLServerCheckConnection(IConnectDb credential, uint nConnectionTimeout, out bool CanConnect)
        {
            // the SQL Server should at least have a master db, so this list should never be empty,
            // if it is, an error must have occured, either with the credential or with the server            
            CanConnect = false;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;            

            // Execute Query
            string[] Databases = null;
            DBError dberror = SQLServerDatabase.DatabasesListAll(credential.DBMS.WithoutDatabase().Timeouts.WithConnectionTimeout(nConnectionTimeout), out Databases);                        
            if (dberror.ErrorOccured || (Databases == null) || (Databases.Length == 0))
                CanConnect = false;
            else
                CanConnect = true;

            // Check Database Existence, if specified
            if (CanConnect)
            {
                dbError = Validation.IsValidDatabaseName(credential.DBMS.Database);
                if (!dberror.ErrorOccured)
                {
                    // Verify that the Database exists
                    bool bFound = false;
                    foreach (string db in Databases)
                    {
                        if (db.ToUpper() == credential.DBMS.Database.ToUpper())
                        {
                            bFound = true;
                            break;
                        }
                    }

                    // Let the Caller know if they can connect to the Database
                    CanConnect = bFound;
                }                                
            }
            return dberror;
        }

        /// <summary>
        /// Quick Check function to see if the passed in credential allows us to connect to the
        /// server, read some data, as well as check SysAdmin Rights for the passed in Credential
        /// If Credential contains a Database, then it will verify the Database's Existence
        /// </summary>
        /// <param name="credential">SQL Server Credentials</param>        
        /// <param name="nConnectionTimeout">Specify the ConnectionTimout</param>
        /// <param name="IsValid">true, if passed in credential connects and is a sysadmin, false otherwise</param>
        /// <returns>DBError Object with ErrorOccured, if error Occured</returns>
        public static DBError SQLServerCheckConnectionNCredential(IConnectDb credential, uint nConnectionTimeout, out bool IsValid)
        {
            IsValid = false;
            DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER);
            if (dbError.ErrorOccured)
                return dbError;

            DBError dberror = SQLServerCheckConnection(credential, nConnectionTimeout, out IsValid);
            if (dberror.ErrorOccured || !IsValid)
                return dberror;

            // Now also make sure that the credential is a SysAdmin
            dberror = SQLServerSecurity.LoginIsSysAdmin(credential, credential.User, out IsValid);
            return dberror;
        }

        #endregion

        #region SQL Browse

        /// <summary>
        /// Returns a array of any found valid SQL Server intances * Using the SQLBrowse Functionallity *
        /// If SQLBrowse fails, will look in the Registry
        /// This Function can take a long time, depending on the Network
        /// </summary>
        /// <returns>list of valid SQLServers or null if error occured</returns>
        public static string[] SQLServerNamesAndInstaces()
        {
            // Call the SQL Browse Function
            List<String> validSQLServerInstances = new List<string>();
            string[] SQLServersRaw = SQLInfoEnumerator.EnumerateSQLServers();
            foreach (string SQLServerRaw in SQLServersRaw)
            {
                // Target name is empty - so add any valid instances found
                if (Validation.IsValidServerNameAndInstanceName(SQLServerRaw))
                    validSQLServerInstances.Add(SQLServerRaw);
            }

            // Last thing to try if EnumerateSQLServers() failed is to query the registry
            // for all possible SQL Server Installations there * and return those * 
            if (validSQLServerInstances.Count == 0)
            {
                string strInstalledInstances = RegKey.GetKey<string>(HKEYRoot.LocalMachine, "Microsoft\\Microsoft SQL Server", "InstalledInstances", String.Empty);
                if (!String.IsNullOrEmpty(strInstalledInstances))
                {
                    string MachineName = Dns.GetHostName();
                    // Add any Registry Found Instances
                    string[] Instances = strInstalledInstances.Split(' ');
                    foreach (string Instance in Instances)
                        validSQLServerInstances.Add(MachineName + "\\" + Instance);
                }
            }
            return validSQLServerInstances.ToArray();
        }

        #endregion

        #region SQL Service Functions

        /// <summary>
        /// Use this to start a SQL Service on the specified Server. You must have Windows admin/SController rights to 
        /// that machine in order for this function to work
        /// </summary>
        /// <param name="ServerOrServerNInstance">Name of the SQL Server or SQL ServerNInstance you want to start (required)</param>
        /// <param name="WaitTimeoutInSeconds">Set to Value > 0 to force a wait for the specified amount of seconds(not required)</param>
        /// <returns>true if Service was started or was already running, false if an error occured</returns>
        public static bool SQLServerServiceStart(string ServerOrServerNInstance, int WaitTimeoutInSeconds)
        {
            string Server = string.Empty;
            string Instance = string.Empty;
            if (String.IsNullOrEmpty(ServerOrServerNInstance))
                return false;

            if (!SQLServerUtilities.SplitServerOrServerNInstance(ServerOrServerNInstance, out Server, out Instance))
                return false;

            // Construct the proper service name
            string Service = string.Empty;
            if (!String.IsNullOrEmpty(Instance))
                Service = String.Format("MSSQL${0}", Instance);
            else
                Service = "MSSQLSERVER";

            string MachineName = Dns.GetHostName();
            bool bIsOnThisMachine = (MachineName.ToLower() == Server.ToLower());
            try
            {
                ServiceController service = null;
                if (bIsOnThisMachine)
                    service = new ServiceController(Service);
                else
                    service = new ServiceController(Service, Server);

                bool bStartService = (service.Status != ServiceControllerStatus.Running);
                if (bStartService)
                {
                    service.Start();
                    if (WaitTimeoutInSeconds > 0)
                    {
                        TimeSpan timeout = TimeSpan.FromSeconds(WaitTimeoutInSeconds);
                        service.WaitForStatus(ServiceControllerStatus.Running, timeout);
                        return (service.Status == ServiceControllerStatus.Running);
                    }
                }
                return true;
            }
            catch (Exception) { /* ignore */ }
            return false;
        }

        /// <summary>
        /// Use this to stop a SQL Service on the specified Server. You must have Windows admin/SController rights to 
        /// that machine in order for this function to work
        /// </summary>
        /// <param name="ServerOrServerNInstance">Name of the SQL Server or SQL ServerNInstance you want to stop (required)</param>
        /// <param name="WaitTimeoutInSeconds">Set to Value > 0 to force a wait for the specified amount of seconds(not required)</param>
        /// <returns>true if Service was stopped or was already stopped, false if an error occured</returns>
        public static bool SQLServerServiceStop(string ServerOrServerNInstance, int WaitTimeoutInSeconds)
        {
            string Server = string.Empty;
            string Instance = string.Empty;
            if (String.IsNullOrEmpty(ServerOrServerNInstance))
                return false;

            if (!SQLServerUtilities.SplitServerOrServerNInstance(ServerOrServerNInstance, out Server, out Instance))
                return false;

            // Construct the proper service name
            string Service = string.Empty;
            if (!String.IsNullOrEmpty(Instance))
                Service = String.Format("MSSQL${0}", Instance);
            else
                Service = "MSSQLSERVER";

            string MachineName = Dns.GetHostName();
            bool bIsOnThisMachine = (MachineName.ToLower() == Server.ToLower());
            try
            {
                ServiceController service = null;
                if (bIsOnThisMachine)
                    service = new ServiceController(Service);
                else
                    service = new ServiceController(Service, Server);

                bool bStopService = (service.Status != ServiceControllerStatus.Stopped);
                if (bStopService)
                {
                    service.Stop();
                    if (WaitTimeoutInSeconds > 0)
                    {
                        TimeSpan timeout = TimeSpan.FromSeconds(WaitTimeoutInSeconds);
                        service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
                        return (service.Status == ServiceControllerStatus.Stopped);
                    }
                }
                return true;
            }
            catch (Exception) { /* ignore */ }
            return false;
        }

        /// <summary>
        /// Use this to restart a SQL Service on the specified Server. You must have Windows admin/SController rights to 
        /// that machine in order for this function to work
        /// </summary>
        /// <param name="ServerOrServerNInstance">Name of the SQL Server or SQL ServerNInstance you want to restart (required)</param>
        /// <param name="WaitTimeoutInSeconds">Set to Value > 0 to force a wait for the specified amount of seconds(not required)</param>
        /// <returns>true if Service was restared successfully, false if an error occured</returns>
        public static bool SQLServerServiceRestart(string ServerOrServerNInstance, int WaitTimeoutInSeconds)
        {
            return SQLServerServiceStop(ServerOrServerNInstance, WaitTimeoutInSeconds) && SQLServerServiceStart(ServerOrServerNInstance, WaitTimeoutInSeconds);
        }

        #endregion
    }
}
#endif