/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS WinSock 2 API
 * FILE:        dll/win32/ws2_32/src/nsquery.c
 * PURPOSE:     Namespace Query Object
 * PROGRAMMER:  Alex Ionescu (alex@relsoft.net)
 */

/* INCLUDES ******************************************************************/

#include <ws2_32.h>

/* DATA **********************************************************************/

#define WsNqLock()          EnterCriticalSection(&NsQuery->Lock)
#define WsNqUnlock()        LeaveCriticalSection(&NsQuery->Lock)

/* FUNCTIONS *****************************************************************/

PNSQUERY
WSAAPI
WsNqAllocate(VOID)
{
    PNSQUERY NsQuery;

    /* Allocate the object */
    NsQuery = HeapAlloc(WsSockHeap, HEAP_ZERO_MEMORY, sizeof(*NsQuery));
    if (NsQuery)
    {
        /* Set non-zero fields */
        NsQuery->Signature = ~0xBEADFACE;
        InitializeListHead(&NsQuery->ProviderList);
        NsQuery->TryAgain = TRUE;
    }

    /* Return it */
    return NsQuery;
}

DWORD
WSAAPI
WsNqInitialize(IN PNSQUERY Query)
{
    /* Initialize the lock */
    InitializeCriticalSection(&Query->Lock);

    /* Set initial reference count and signature */
    Query->RefCount = 1;
    Query->Signature = 0xBEADFACE;

    /* Return success */
    return ERROR_SUCCESS;
}

BOOL
WSAAPI
WsNqValidateAndReference(IN PNSQUERY Query)
{
    /* Check the signature first */
    if (Query->Signature != 0xBEADFACE) return FALSE;

    /* Validate the reference count */
    if (!Query->RefCount) return FALSE;

    /* Increase reference count */
    InterlockedIncrement(&Query->RefCount);

    /* Return success */
    return TRUE;
}

VOID
WSAAPI
WsNqDelete(IN PNSQUERY NsQuery)
{
    PNSQUERY_PROVIDER Provider;
    PLIST_ENTRY Entry;

    /* Make sure that we got initialized */
    if (!NsQuery->ProviderList.Flink) return;

    /* Loop the provider list */
    while (!IsListEmpty(&NsQuery->ProviderList))
    {
        /* Remove the entry */
        Entry = RemoveHeadList(&NsQuery->ProviderList);

        /* Get the provider */
        Provider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);

        /* Delete it */
        WsNqProvDelete(Provider);
    }

    /* Remove the signature and delete the lock */
    NsQuery->Signature = ~0xBEADFACE;
    DeleteCriticalSection(&NsQuery->Lock);

    /* Free us */
    HeapFree(WsSockHeap, 0, NsQuery);
}

VOID
WSAAPI
WsNqDereference(IN PNSQUERY Query)
{
    /* Decrease the reference count and check if it's zero */
    if (!InterlockedDecrement(&Query->RefCount))
    {
        /* Delete us*/
        WsNqDelete(Query);
    }
}

BOOL
WSAAPI
WsNqBeginEnumerationProc(PVOID Context,
                         PNSCATALOG_ENTRY Entry)
{
    PNS_PROVIDER Provider;
    BOOLEAN GoOn = TRUE;
    PENUM_CONTEXT EnumContext = (PENUM_CONTEXT)Context;
    PNSQUERY NsQuery = EnumContext->NsQuery;
    DWORD NamespaceId = Entry->NamespaceId;

    /* Match the namespace ID, protocols and make sure it's enabled */
    if ((((EnumContext->lpqsRestrictions->dwNameSpace == NamespaceId) ||
          (EnumContext->lpqsRestrictions->dwNameSpace == NS_ALL)) &&
         (!(EnumContext->lpqsRestrictions->dwNumberOfProtocols) ||
           (WsNcMatchProtocols(NamespaceId,
                               Entry->AddressFamily,
                               EnumContext->lpqsRestrictions)))) &&
        (Entry->Enabled))
    {
        /* Get the provider */
        if (!(Provider = Entry->Provider))
        {
            /* None was loaded, load it */
            if (WsNcLoadProvider(EnumContext->Catalog, Entry) != ERROR_SUCCESS)
            {
                /* Return TRUE to continue enumerating */
                return TRUE;
            }

            /* Set the provider */
            Provider = Entry->Provider;
        }

        /* Add it to the query */
        if (!WsNqAddProvider(NsQuery, Provider))
        {
            /* We failed */
            EnumContext->ErrorCode = WSASYSCALLFAILURE;
            GoOn = FALSE;
        }
    }

    /* Return to caller */
    return GoOn;
}

DWORD
WSAAPI
WsNqLookupServiceEnd(IN PNSQUERY NsQuery)
{
    PNSQUERY_PROVIDER Provider;
    PLIST_ENTRY Entry;

    /* Protect us from closure */
    WsNqLock();
    NsQuery->ShuttingDown = TRUE;

    /* Get the list and loop */
    Entry = NsQuery->ProviderList.Flink;
    while (Entry != &NsQuery->ProviderList)
    {
        /* Get the provider */
        Provider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);

        /* Call its routine */
        WsNqProvLookupServiceEnd(Provider);

        /* Move to the next one */
        Entry = Entry->Flink;
    }

    /* Release lock and return success */
    WsNqUnlock();
    return ERROR_SUCCESS;
}

DWORD
WSAAPI
WsNqLookupServiceNext(IN PNSQUERY NsQuery,
                      IN DWORD ControlFlags,
                      OUT PDWORD BufferLength,
                      OUT LPWSAQUERYSETW Results)
{
    INT ErrorCode = SOCKET_ERROR, OldErrorCode;
    PNSQUERY_PROVIDER Provider, NextProvider;
    PLIST_ENTRY Entry;

    /* Make sure we're not shutting down */
    if (NsQuery->ShuttingDown)
    {
        /* We are shutting down, fail */
        SetLastError(WSAECANCELLED);
        return ErrorCode;
    }

    /* Acquire query lock */
    WsNqLock();

    /* Check if we already have an active provider */
    NextProvider = NsQuery->ActiveProvider;
    if (!NextProvider)
    {
        /* Make sure we have a current provider */
        if (!NsQuery->CurrentProvider)
        {
            /* We don't; fail */
            WsNqUnlock();
            SetLastError(WSA_E_NO_MORE);
            return SOCKET_ERROR;
        }

        /* Get the last provider in the list and start looping */
        Entry = NsQuery->ProviderList.Blink;
        NextProvider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);
        while (NextProvider)
        {
            /* Check if this is a new-style provider */
            if (NextProvider->Provider->Service.NSPIoctl)
            {
                /* Remove it and re-add it on top */
                RemoveEntryList(&NextProvider->QueryLink);
                InsertHeadList(&NsQuery->ProviderList, &NextProvider->QueryLink);

                /* Set it as the active provider and exit the loop */
                NsQuery->ActiveProvider = NextProvider;
                break;
            }

            /* Get the previous provider */
            NextProvider = WsNqPreviousProvider(NsQuery, NextProvider);
        }
    }

    /* Release the lock */
    WsNqUnlock();

    /* Restart and keep looping as long as there is an active provider */
    while (NextProvider)
    {
        /* Call its routine */
        ErrorCode = WsNqProvLookupServiceNext(NextProvider,
                                              ControlFlags,
                                              BufferLength,
                                              Results);
        /* Check for error or shutdown */
        if ((ErrorCode == ERROR_SUCCESS) ||
            (GetLastError() == WSAEFAULT) || (NsQuery->ShuttingDown))
        {
            /* Get out */
            break;
        }

        /* Acquire Query Lock */
        WsNqLock();

        /* Check if we have an active provider */
        if (NsQuery->ActiveProvider)
        {
            /* Save the old provider and get the next one */
            Provider = NextProvider;
            NextProvider = WsNqNextProvider(NsQuery, NsQuery->ActiveProvider);

            /* Was the old provider our active? */
            if (Provider == NsQuery->ActiveProvider)
            {
                /* Change our active provider to the new one */
                NsQuery->ActiveProvider = NextProvider;
            }
        }
        else
        {
            /* No next provider */
            NextProvider = NULL;
        }

        /* Check if we failed and if we can try again */
        if (!(NextProvider) &&
            (ErrorCode == SOCKET_ERROR) &&
            (NsQuery->TryAgain))
        {
            /* Save the error code so RAS doesn't overwrite it */
            OldErrorCode = GetLastError();

            /* Make sure we won't try for a 3rd time */
            NsQuery->TryAgain = FALSE;

            /* Call the helper to auto-dial */
            if (WSAttemptAutodialName(NsQuery->QuerySet))
            {
                /* It succeeded, so we'll delete the current state. */
                while (!IsListEmpty(&NsQuery->ProviderList))
                {
                    /* Remove the entry and get its provider */
                    Entry = RemoveHeadList(&NsQuery->ProviderList);
                    Provider = CONTAINING_RECORD(Entry,
                                                 NSQUERY_PROVIDER,
                                                 QueryLink);

                    /* Reset it */
                    WsNqProvLookupServiceEnd(Provider);
                    WsNqProvDelete(Provider);
                }

                /* Start a new query */
                if (WsNqLookupServiceBegin(NsQuery,
                                           NsQuery->QuerySet,
                                           NsQuery->ControlFlags,
                                           NsQuery->Catalog) == ERROR_SUCCESS)
                {
                    /* New query succeeded, set active provider now */
                    NsQuery->ActiveProvider =
                        WsNqNextProvider(NsQuery, NsQuery->ActiveProvider);
                }
            }
            else
            {
                /* Reset the error code */
                SetLastError(OldErrorCode);
            }
        }

        /* Release lock */
        WsNqUnlock();
    }

    /* Return */
    return ErrorCode;
}

DWORD
WSAAPI
WsNqLookupServiceBegin(IN PNSQUERY NsQuery,
                       IN LPWSAQUERYSETW Restrictions,
                       IN DWORD ControlFlags,
                       IN PNSCATALOG Catalog)
{
    WSASERVICECLASSINFOW ClassInfo;
    LPWSASERVICECLASSINFOW pClassInfo = &ClassInfo;
    PNSQUERY_PROVIDER Provider, NextProvider;
    PLIST_ENTRY Entry;
    INT ErrorCode;
    DWORD ClassInfoSize;
    PNSCATALOG_ENTRY CatalogEntry = NULL;
    ENUM_CONTEXT EnumContext;
    BOOLEAN TryAgain;

    /* Check for RAS Auto-dial attempt */
    if (NsQuery->TryAgain)
    {
        /* Make a copy of the query set */
        ErrorCode = CopyQuerySetW(Restrictions, &NsQuery->QuerySet);
        TryAgain = (ErrorCode == ERROR_SUCCESS);

        /* Check if we'll try again */
        if (!TryAgain)
        {
            /* We won't, fail */
            SetLastError(ErrorCode);
            ErrorCode = SOCKET_ERROR;
            NsQuery->TryAgain = FALSE;
            goto Exit;
        }

        /* Cache the information for a restart */
        NsQuery->ControlFlags = ControlFlags;
        NsQuery->Catalog = Catalog;
    }

    /* Check if we have a specific ID */
    if (Restrictions->lpNSProviderId)
    {
        /* Get the catalog entry */
        ErrorCode = WsNcGetCatalogFromProviderId(Catalog,
                                                 Restrictions->lpNSProviderId,
                                                 &CatalogEntry);
        /* Check for failure */
        if (ErrorCode != ERROR_SUCCESS)
        {
            /* Fail */
            SetLastError(WSAEINVAL);
            ErrorCode = SOCKET_ERROR;
            goto Exit;
        }
        /* We succeeded, add this provider */
        else if (!WsNqAddProvider(NsQuery, CatalogEntry->Provider))
        {
            /* Fail */
            SetLastError(WSA_NOT_ENOUGH_MEMORY);
            ErrorCode = SOCKET_ERROR;
            goto Exit;
        }
    }
    else
    {
        /* Setup the lookup context */
        EnumContext.lpqsRestrictions = Restrictions;
        EnumContext.ErrorCode = ERROR_SUCCESS;
        EnumContext.NsQuery = NsQuery;
        EnumContext.Catalog = Catalog;

        /* Do a lookup for every entry */
        WsNcEnumerateCatalogItems(Catalog,
                                  WsNqBeginEnumerationProc,
                                  &EnumContext);
        ErrorCode = EnumContext.ErrorCode;

        /* Check for success */
        if (ErrorCode != ERROR_SUCCESS)
        {
            /* Fail */
            SetLastError(WSAEINVAL);
            ErrorCode = SOCKET_ERROR;
            goto Exit;
        }
    }

    /* Get the class information */
    ClassInfo.lpServiceClassId = Restrictions->lpServiceClassId;
    ErrorCode = WsNcGetServiceClassInfo(Catalog, &ClassInfoSize, pClassInfo);

    /* Check if more buffer space is needed */
    if ((ErrorCode == SOCKET_ERROR) && (GetLastError() == WSAEFAULT))
    {
        /* FIXME: The WS 2.2 spec hasn't been finalized yet on this... */
    }
    else
    {
        /* Assume success */
        ErrorCode = ERROR_SUCCESS;
    }

    /* Check if the provider list is empty */
    if (IsListEmpty(&NsQuery->ProviderList))
    {
        /* We don't have any providers to handle this! */
        ErrorCode = SOCKET_ERROR;
        SetLastError(WSASERVICE_NOT_FOUND);
        goto Exit;
    }

    /* Get the first provider and loop */
    Entry = NsQuery->ProviderList.Flink;
    NextProvider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);
    while (NextProvider)
    {
        /* Call it */
        ErrorCode = WsNqProvLookupServiceBegin(NextProvider,
                                               Restrictions,
                                               pClassInfo,
                                               ControlFlags);
        /* Check for error */
        if (ErrorCode == SOCKET_ERROR)
        {
            /* Remove this provider, get the next one, delete the old one */
            Provider = NextProvider;
            NextProvider = WsNqNextProvider(NsQuery, NextProvider);
            RemoveEntryList(&Provider->QueryLink);
            WsNqProvDelete(Provider);
        }
        else
        {
            /* Get the next provider */
            NextProvider = WsNqNextProvider(NsQuery, NextProvider);
        }
    }

Exit:
    /* Check if we had an error somewhere */
    if (ErrorCode == SOCKET_ERROR)
    {
        /* Loop the list */
        while (!IsListEmpty(&NsQuery->ProviderList))
        {
            /* Remove this provider */
            Entry = RemoveHeadList(&NsQuery->ProviderList);

            /* Get the failed provider and delete it */
            Provider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);
            WsNqProvDelete(Provider);
        }
    }
    else
    {
        /* Set the active provider */
        Entry = NsQuery->ProviderList.Flink;
        NsQuery->ActiveProvider = CONTAINING_RECORD(Entry,
                                                    NSQUERY_PROVIDER,
                                                    QueryLink);
    }

    /* Return */
    return ErrorCode;
}

PNSQUERY_PROVIDER
WSAAPI
WsNqNextProvider(IN PNSQUERY Query,
                 IN PNSQUERY_PROVIDER Provider)
{
    PNSQUERY_PROVIDER NextProvider = NULL;
    PLIST_ENTRY Entry;

    /* Get the first entry and get its provider */
    Entry = Provider->QueryLink.Flink;
    if (Entry != &Query->ProviderList)
    {
        /* Get the current provider */
        NextProvider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);
    }

    /* Return it */
    return NextProvider;
}

PNSQUERY_PROVIDER
WSAAPI
WsNqPreviousProvider(IN PNSQUERY Query,
                     IN PNSQUERY_PROVIDER Provider)
{
    PNSQUERY_PROVIDER PrevProvider = NULL;
    PLIST_ENTRY Entry;

    /* Get the last entry and get its provider */
    Entry = Provider->QueryLink.Blink;
    if (Entry != &Query->ProviderList)
    {
        /* Get the current provider */
        PrevProvider = CONTAINING_RECORD(Entry, NSQUERY_PROVIDER, QueryLink);
    }

    /* Return it */
    return PrevProvider;
}

BOOL
WSAAPI
WsNqAddProvider(IN PNSQUERY Query,
                IN PNS_PROVIDER Provider)
{
    BOOL Success = TRUE;
    PNSQUERY_PROVIDER QueryProvider;

    /* Allocate a new Query Provider */
    if ((QueryProvider = WsNqProvAllocate()))
    {
        /* Initialize it */
        WsNqProvInitialize(QueryProvider, Provider);

        /* Insert it into the provider list */
        InsertTailList(&Query->ProviderList, &QueryProvider->QueryLink);
    }
    else
    {
        /* We failed */
        SetLastError(WSASYSCALLFAILURE);
        Success = FALSE;
    }

    /* Return */
    return Success;
}

