#if SQLSERVER
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Sdaleo.Systems.SQLServer
{
    /// <summary>
    /// Summary description for SQLInfoEnumerator.
    /// This class Enumerates a network for SQL Server instances and returns a list.
    /// For a given SQL Server instance a list of all available databases is returned
    /// For more information see
    /// </summary>
    /// <see cref="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcsqlbrowseconnect.asp"/>
    /// <seealso cref="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp09192002.asp"/>
    internal class SQLInfoEnumerator
    {
        #region ODBC32 external function definitions

        [DllImport("odbc32.dll")]
        private static extern short SQLAllocHandle(short handleType, IntPtr inputHandle, out IntPtr outputHandlePtr);

        [DllImport("odbc32.dll")]
        private static extern short SQLSetEnvAttr(IntPtr environmentHandle, int attribute, IntPtr valuePtr, int stringLength);

        [DllImport("odbc32.dll")]
        private static extern short SQLFreeHandle(short hType, IntPtr Handle);

        [DllImport("odbc32.dll", CharSet = CharSet.Ansi)]
        private static extern short SQLBrowseConnect(IntPtr handleConnection, StringBuilder inConnection, short stringLength, StringBuilder outConnection, short bufferLength, out short stringLength2Ptr);

        #endregion

        #region Constants

        private const string SQL_DRIVER_STR = "DRIVER={SQL SERVER};";
        private const short SQL_SUCCESS = 0;
        private const short SQL_SUCCESS_WITH_INFO = 1;
        private const short SQL_HANDLE_ENV = 1;
        private const short SQL_HANDLE_DBC = 2;
        private const int SQL_ATTR_ODBC_VERSION = 200;
        private const int SQL_OV_ODBC3 = 3;
        private const short SQL_NEED_DATA = 99;
        private const short DEFAULT_RESULT_SIZE = 1024;
        private const string START_STR = "{";
        private const string END_STR = "}";

        #endregion

        /// <summary>
        /// Enumerate the SQL Servers returning a list (if any exist)
        /// </summary>
        /// <returns></returns>
        internal static string[] EnumerateSQLServers()
        {
            return RetrieveInformation(SQL_DRIVER_STR);
        }

        /// <summary>
        /// Enumerate the specified SQL server returning a list of databases (if any exist)
        /// </summary>
        /// <returns></returns>
        internal static string[] EnumerateSQLServersDatabases(string SQLServerNInstance)
        {
            return RetrieveInformation(SQL_DRIVER_STR + "SERVER=" + SQLServerNInstance + ";");
        }

        /// <summary>
        /// Enumerate the specified SQL server returning a list of databases (if any exist)
        /// </summary>
        /// <returns></returns>
        internal static string[] EnumerateSQLServersDatabases(string SQLServerNInstance, string Username, string Password)
        {
            return RetrieveInformation(SQL_DRIVER_STR + "SERVER=" + SQLServerNInstance + ";" + "UID=" + Username + ";" + "PWD=" + Password + ";");
        }

        /// <summary>
        /// Enumerate for SQLServer/Databases based on the passed information it the string
        /// The more information provided to SQLBrowseConnect the more granular it gets so
        /// if only DRIVER=SQL SERVER passed then a list of all SQL Servers is returned
        /// If DRIVER=SQL SERVER;Server=ServerName is passed then a list of all Databases on the
        /// servers is returned etc
        /// </summary>
        /// <param name="InputParam">A valid string to query for</param>
        /// <returns></returns>
        private static string[] RetrieveInformation(string InputParam)
        {
            IntPtr m_environmentHandle = IntPtr.Zero;
            IntPtr m_connectionHandle = IntPtr.Zero;

            StringBuilder inConnection = new StringBuilder(InputParam);
            short stringLength = (short)inConnection.Length;

            StringBuilder outConnection = new StringBuilder(DEFAULT_RESULT_SIZE);

            short stringLength2Ptr = 0;
            bool bConnectSuccessful = false;

            try
            {
                // Prepare the SQL Environment Handles
                if ((IsSQLSuccess(SQLAllocHandle(SQL_HANDLE_ENV, m_environmentHandle, out m_environmentHandle))) &&
                    (IsSQLSuccess(SQLSetEnvAttr(m_environmentHandle, SQL_ATTR_ODBC_VERSION, (IntPtr)SQL_OV_ODBC3, 0))) &&
                    (IsSQLSuccess(SQLAllocHandle(SQL_HANDLE_DBC, m_environmentHandle, out m_connectionHandle))))
                {

                    // Fetch the Data * First get the size of the buffer, then call it to get the buffer *
                    if (IsSQLDataSuccess(SQLBrowseConnect(m_connectionHandle, inConnection, stringLength, null, 0, out stringLength2Ptr)) &&
                        IsSQLDataSuccess(SQLBrowseConnect(m_connectionHandle, inConnection, stringLength, outConnection, stringLength2Ptr, out stringLength2Ptr))
                        )
                    {
                        bConnectSuccessful = true;
                    }
                }
            }
            catch { /* ignore */ }
            finally
            {
                FreeConnection(m_connectionHandle);
                FreeConnection(m_environmentHandle);
            }

            if (bConnectSuccessful && outConnection.ToString() != "")
            {
                return ParseSQLOutConnection(outConnection.ToString());
            }
            else
            {
                return null;
            }
        }

        #region Helper Functions

        /// <summary>
        /// For quick SQL Return value evaluation
        /// </summary>
        private static bool IsSQLSuccess(short SQL_VALUE)
        {
            if (SQL_VALUE == SQL_SUCCESS || SQL_VALUE == SQL_SUCCESS_WITH_INFO)
                return true;
            else
                return false;
        }

        /// <summary>
        /// For quick SQL Return value evaluation
        /// </summary>
        private static bool IsSQLDataSuccess(short SQL_VALUE)
        {
            if (SQL_VALUE == SQL_SUCCESS || SQL_VALUE == SQL_SUCCESS_WITH_INFO || SQL_VALUE == SQL_NEED_DATA)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Parse an outConnection string returned from SQLBrowseConnect
        /// </summary>
        /// <param name="outConnection">string to parse</param>
        /// <returns>Parsed Server names, or Empty Array if none found</returns>
        private static string[] ParseSQLOutConnection(string outConnection)
        {
            int m_Start = outConnection.IndexOf(START_STR) + 1;
            int m_lenString = outConnection.IndexOf(END_STR) - m_Start;
            if ((m_Start > 0) && (m_lenString > 0))
            {
                outConnection = outConnection.Substring(m_Start, m_lenString);
            }
            else
            {
                outConnection = string.Empty;
            }
            return outConnection.Split(",".ToCharArray());
        }

        /// <summary>
        /// Call this to Free a SQL handle
        /// </summary>
        private static void FreeConnection(IntPtr handleToFree)
        {
            if (handleToFree != IntPtr.Zero)
                SQLFreeHandle(SQL_HANDLE_DBC, handleToFree);
        }

        #endregion
    }
}
#endif