// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "csmcmd.h"

// Forward declarations
int EnumRoots();
int EnumRules();
int AddRoots(PCWSTR pszURL);
int RemoveRoots(PCWSTR pszURL);
int AddRule(BOOL fDefault, BOOL fInclude, BOOL fOverride, PCWSTR pszURL);
int RemoveRule(BOOL fDefault, PCWSTR pszURL);
int Revert();
int Reset();
int Reindex();

// Global objects used for command line parsing.
CExclFlagParam g_IncludeParam(L"include", L"exclude");
CExclFlagParam g_DefaultParam(L"default", L"user");
CFlagParam g_OverrideParam(L"override_children");
CFlagParam g_EnumRootsParam(L"enumerate_roots");
CFlagParam g_EnumRulesParam(L"enumerate_rules");
CSetValueParam g_AddRootParam(L"add_root");
CSetValueParam g_RemoveRootParam(L"remove_root");
CSetValueParam g_AddRuleParam(L"add_rule");
CSetValueParam g_RemoveRuleParam(L"remove_rule");
CFlagParam g_RevertParam(L"revert");
CFlagParam g_Reset(L"reset");
CFlagParam g_Reindex(L"reindex");
CFlagParam g_HelpParam(L"help");
CFlagParam g_AltHelpParam(L"?");

// List of all supported command line options.
CParamBase* Params[] =
{
    &g_IncludeParam,
    &g_DefaultParam,
    &g_EnumRootsParam,
    &g_EnumRulesParam,
    &g_AddRootParam,
    &g_RemoveRootParam,
    &g_AddRuleParam,
    &g_RemoveRuleParam,
    &g_RevertParam,
    &g_Reset,
    &g_Reindex,
    &g_HelpParam,
    &g_AltHelpParam
};

// List of alternative options corresponding to CSM operations.
CParamBase* ExclusiveParams[] =
{
    &g_EnumRootsParam,
    &g_EnumRulesParam,
    &g_AddRootParam,
    &g_RemoveRootParam,
    &g_AddRuleParam,
    &g_RemoveRuleParam,
    &g_RevertParam,
    &g_Reset,
    &g_Reindex,
    &g_HelpParam,
    &g_AltHelpParam,
};

// Command line options help text
const PCWSTR rgParamsHelp[] =
{
    L"/enumerate_roots",
    L"/enumerate_rules",
    L"/add_root <new root path>",
    L"/remove_root <root path to remove>",
    L"/add_rule <rule URL> /[DEFAULT|USER] /[INCLUDE|EXCLUDE]",
    L"/remove_rule <rule URL> /[DEFAULT|USER]",
    L"/revert",
    L"/reset",
    L"/reindex",
    L"/help or /? "
};

int PrintHelp()
{
    wcout << L"NOTE: you must run this tool as an admin to perform functions" << endl;
    wcout << L"      that change the state of the index" << endl;

    wcout << L"List of availible options:" << endl;

    for (DWORD i = 0; i < ARRAYSIZE(rgParamsHelp); i++)
    {
        wcout << L"\t" << rgParamsHelp[i] << endl;
    }
    return ERROR_SUCCESS;
}

// CheckExcusiveParameters function validates that only one option
// from ExclusiveParams list is present in argument list
int CheckExcusiveParameters()
{
    int iRes = ERROR_SUCCESS;
    DWORD dwCount = 0;
    for (int i = 0; i < ARRAYSIZE(ExclusiveParams); i++)
    {
        if (ExclusiveParams[i]->Exists())
        {
            dwCount++;
        }
    }

    if (0 == dwCount)
    {
        wcout << L"Error: CSM operation parameter is expected!" << endl;
        iRes = ERROR_INVALID_PARAMETER;
    }
    else if (1 < dwCount)
    {
        wcout << L"Error: Duplicated CSM operation parameters!" << endl;
        iRes = ERROR_INVALID_PARAMETER;
    }

    return iRes;
}

int wmain(int argc, wchar_t *argv[])
{
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        // Parsing command line
        int iRes = ParseParams(Params, ARRAYSIZE(Params), argc - 1, argv + 1);
        if (ERROR_SUCCESS == iRes)
        {
            // Check that only one CSM operation parameter was referred
            iRes = CheckExcusiveParameters();
            if (ERROR_SUCCESS == iRes)
            {
                // Default catalog name will be used if /CATALOG option doesn't specify otherwise
                if (g_HelpParam.Exists() || g_AltHelpParam.Exists())
                {
                    iRes = PrintHelp();
                }
                else if (g_EnumRootsParam.Exists())
                {
                    iRes = EnumRoots();
                }
                else if (g_EnumRulesParam.Exists())
                {
                    iRes = EnumRules();
                }
                else if (g_AddRootParam.Exists())
                {
                    iRes = AddRoots(g_AddRootParam.Get());
                }
                else if (g_RemoveRootParam.Exists())
                {
                    iRes = RemoveRoots(g_RemoveRootParam.Get());
                }
                else if (g_AddRuleParam.Exists())
                {
                    iRes = AddRule(g_DefaultParam.Exists() && g_DefaultParam.Get(),
                                           g_IncludeParam.Exists() && g_IncludeParam.Get(),
                                           g_OverrideParam.Exists(),
                                           g_AddRuleParam.Get());
                }
                else if (g_RemoveRuleParam.Exists())
                {
                    iRes = RemoveRule(g_DefaultParam.Exists() && g_DefaultParam.Get(),
                                              g_RemoveRuleParam.Get());
                }
                else if (g_RevertParam.Exists())
                {
                    iRes = Revert();
                }
                else if (g_Reset.Exists())
                {
                    iRes = Reset();
                }
                else if (g_Reindex.Exists())
                {
                    iRes = Reindex();
                }
                else
                {
                    assert(!"Required parameter is missing!");
                }
            }
            else
            {
                iRes = PrintHelp();
            }
        }

        CoUninitialize();
    }

    return 0;
}


int ReportHRESULTError(PCWSTR pszOpName, HRESULT hr)
{
    int iErr = 0;

    if (FAILED(hr))
    {
        if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
        {
            iErr = HRESULT_CODE(hr);
            void *pMsgBuf;

            ::FormatMessageW(
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                iErr,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (PWSTR) &pMsgBuf,
                0, NULL );

            wcout << endl << L"Error: " << pszOpName << L" failed with error " << iErr << L": " << (PWSTR)pMsgBuf << endl;

            LocalFree(pMsgBuf);
        }
        else
        {
            wcout << endl << L"Error: " << pszOpName << L" failed with error 0x" << setbase(16) << hr << endl;
            iErr = -1;
        }
    }

    return iErr;
}

HRESULT CreateCatalogManager(ISearchCatalogManager **ppSearchCatalogManager)
{
    *ppSearchCatalogManager = NULL;

    ISearchManager *pSearchManager;
    HRESULT hr = CoCreateInstance(CLSID_CSearchManager, NULL, CLSCTX_SERVER, IID_PPV_ARGS(&pSearchManager));
    if (SUCCEEDED(hr))
    {
        hr = pSearchManager->GetCatalog(L"SystemIndex", ppSearchCatalogManager);
        pSearchManager->Release();
    }
    return hr;
}

HRESULT CreateCrawlScopManager(ISearchCrawlScopeManager **ppSearchCrawlScopeManager)
{
    *ppSearchCrawlScopeManager = NULL;

    ISearchCatalogManager *pCatalogManager;
    HRESULT hr = CreateCatalogManager(&pCatalogManager);
    if (SUCCEEDED(hr))
    {
        // Crawl scope manager for that catalog
        hr = pCatalogManager->GetCrawlScopeManager(ppSearchCrawlScopeManager);
        pCatalogManager->Release();
    }
    return hr;
}

HRESULT DisplayRootInfo(ISearchRoot* pSearchRoot)
{
    PWSTR pszUrl = NULL;
    HRESULT hr = pSearchRoot->get_RootURL(&pszUrl);
    if (SUCCEEDED(hr))
    {
        wcout << L"\t" << pszUrl;
        BOOL fProvidesNotifications = FALSE;
        hr = pSearchRoot->get_ProvidesNotifications(&fProvidesNotifications);
        if (SUCCEEDED(hr) && fProvidesNotifications)
        {
            wcout << L" - Provides Notifications";
        }
        CoTaskMemFree(pszUrl);
    }

    wcout << endl;
    return hr;
}

int EnumRoots()
{
    // Crawl scope manager for that catalog
    ISearchCrawlScopeManager *pSearchCrawlScopeManager;
    HRESULT hr = CreateCrawlScopManager(&pSearchCrawlScopeManager);
    if (SUCCEEDED(hr))
    {
        // Search roots on that crawl scope
        IEnumSearchRoots *pSearchRoots;
        hr = pSearchCrawlScopeManager->EnumerateRoots(&pSearchRoots);
        ISearchRoot *pSearchRoot;
        if (SUCCEEDED(hr))
        {
            while (SUCCEEDED(hr) &&
                   S_OK == (hr = pSearchRoots->Next(1, &pSearchRoot, NULL)))
            {
                hr = DisplayRootInfo(pSearchRoot);
                pSearchRoot->Release();
            }
            pSearchRoots->Release();
        }
        pSearchCrawlScopeManager->Release();
    }

    return ReportHRESULTError(L"EnumRoots()", hr);
}

int AddRoots(PCWSTR pszURL)
{
    wcout << L"Adding new root " << pszURL << endl;

    // Crawl scope manager for that catalog
    ISearchCatalogManager *pCatalogManager;
    HRESULT hr = CreateCatalogManager(&pCatalogManager);
    if (SUCCEEDED(hr))
    {
        // Crawl scope manager for that catalog
        ISearchCrawlScopeManager *pSearchCrawlScopeManager;
        hr = pCatalogManager->GetCrawlScopeManager(&pSearchCrawlScopeManager);
        if (SUCCEEDED(hr))
        {
            ISearchRoot *pISearchRoot;
            hr = CoCreateInstance(CLSID_CSearchRoot, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pISearchRoot));
            if (SUCCEEDED(hr))
            {
                hr = pISearchRoot->put_RootURL(pszURL);
                if (SUCCEEDED(hr))
                {
                    hr = pSearchCrawlScopeManager->AddRoot(pISearchRoot);
                    if (SUCCEEDED(hr))
                    {
                        hr = pSearchCrawlScopeManager->SaveAll();
                        if (SUCCEEDED(hr))
                        {
                            hr = pCatalogManager->ReindexSearchRoot(pszURL);
                            if (SUCCEEDED(hr))
                            {
                                wcout << "Reindexing was started for root " << pszURL << endl;
                            }
                        }
                    }
                }
                pISearchRoot->Release();
            }
            pSearchCrawlScopeManager->Release();
        }
        pCatalogManager->Release();
    }

    return ReportHRESULTError(L"AddRoots()", hr);
}

int RemoveRoots(PCWSTR pszURL)
{
    wcout << L"Removing root " << pszURL << endl;

    // Crawl scope manager for that catalog
    ISearchCrawlScopeManager *pSearchCrawlScopeManager;
    HRESULT hr = CreateCrawlScopManager(&pSearchCrawlScopeManager);
    if (SUCCEEDED(hr))
    {
        hr = pSearchCrawlScopeManager->RemoveRoot(pszURL);
        if (SUCCEEDED(hr))
        {
            hr = pSearchCrawlScopeManager->SaveAll();
        }
        pSearchCrawlScopeManager->Release();
    }

    return ReportHRESULTError(L"AddRoots()", hr);
}

HRESULT DisplayRule(ISearchScopeRule* pSearchScopeRule)
{
    PWSTR pszUrl;
    HRESULT hr = pSearchScopeRule->get_PatternOrURL(&pszUrl);
    if (SUCCEEDED(hr))
    {
        wcout << L"\t" << pszUrl;
        BOOL fDefault = FALSE;
        hr = pSearchScopeRule->get_IsDefault(&fDefault);
        if (SUCCEEDED(hr))
        {
            wcout << (fDefault ? L" " : L" NOT ") << L"DEFAULT";
            BOOL fIncluded = FALSE;
            hr = pSearchScopeRule->get_IsIncluded(&fIncluded);
            if (SUCCEEDED(hr))
            {
                wcout << (fIncluded ? L" INCLUDED" : L" EXCLUDED ");
            }
        }
        CoTaskMemFree(pszUrl);
    }
    wcout << endl;

    return hr;
}

int EnumRules()
{
    // Crawl scope manager for that catalog
    ISearchCrawlScopeManager *pSearchCrawlScopeManager;
    HRESULT hr = CreateCrawlScopManager(&pSearchCrawlScopeManager);
    if (SUCCEEDED(hr))
    {
        // Search roots on that crawl scope
        IEnumSearchScopeRules *pScopeRules;
        hr = pSearchCrawlScopeManager->EnumerateScopeRules(&pScopeRules);
        if (SUCCEEDED(hr))
        {
            ISearchScopeRule *pSearchScopeRule;
            while (SUCCEEDED(hr) &&
                   S_OK == (hr = pScopeRules->Next(1, &pSearchScopeRule, NULL)))
            {
                hr = DisplayRule(pSearchScopeRule);
                pSearchScopeRule->Release();
            }
            pScopeRules->Release();
        }
        pSearchCrawlScopeManager->Release();
    }

    return ReportHRESULTError(L"EnumRules()", hr);
}

int AddRule(BOOL fDefault, BOOL fInclude, BOOL fOverride, PCWSTR pszURL)
{
    wcout << L"Adding new " <<
         (fDefault ? L"default " : L"user ") <<
         (fInclude ? L"inclusion " : L"exclusion ") <<
         "rule " << pszURL <<
         ((!fDefault && fOverride) ? L"overriding cildren rules" : L"" ) << endl;

    // Crawl scope manager for that catalog
    ISearchCrawlScopeManager *pSearchCrawlScopeManager;
    HRESULT hr = CreateCrawlScopManager(&pSearchCrawlScopeManager);
    if (SUCCEEDED(hr))
    {
        if (fDefault)
        {
            hr = pSearchCrawlScopeManager->AddDefaultScopeRule(pszURL, fInclude, FF_INDEXCOMPLEXURLS);
        }
        else
        {
            hr = pSearchCrawlScopeManager->AddUserScopeRule(pszURL,
                                                             fInclude,
                                                             fOverride,
                                                             FF_INDEXCOMPLEXURLS);
        }
        if (SUCCEEDED(hr))
        {
            hr = pSearchCrawlScopeManager->SaveAll();
        }
        pSearchCrawlScopeManager->Release();
    }
    return ReportHRESULTError(L"AddRules()", hr);
}

int RemoveRule(BOOL fDefault, PCWSTR pszURL)
{
    wcout << L"Removing " << (fDefault ? L"default" : L"user") << " rule " << pszURL << endl;

    // Crawl scope manager for that catalog
    ISearchCrawlScopeManager *pSearchCrawlScopeManager;
    HRESULT hr = CreateCrawlScopManager(&pSearchCrawlScopeManager);
    if (SUCCEEDED(hr))
    {
        if (fDefault)
        {
            hr = pSearchCrawlScopeManager->RemoveDefaultScopeRule(pszURL);
        }
        else
        {
            hr = pSearchCrawlScopeManager->RemoveScopeRule(pszURL);
        }
        if (SUCCEEDED(hr))
        {
            hr = pSearchCrawlScopeManager->SaveAll();
        }
        pSearchCrawlScopeManager->Release();
    }
    return ReportHRESULTError(L"AddRules()", hr);
}

int Revert()
{
    wcout << L"Reverting catalog to its default state." << endl;

    // Crawl scope manager for that catalog
    ISearchCrawlScopeManager *pSearchCrawlScopeManager;
    HRESULT hr = CreateCrawlScopManager(&pSearchCrawlScopeManager);
    if (SUCCEEDED(hr))
    {
        hr = pSearchCrawlScopeManager->RevertToDefaultScopes();
        if (SUCCEEDED(hr))
        {
            hr = pSearchCrawlScopeManager->SaveAll();
        }
        pSearchCrawlScopeManager->Release();
    }

    return ReportHRESULTError(L"Revert()", hr);
}

int Reset()
{
    wcout << L"Resetting catalog." << endl;

    ISearchCatalogManager *pCatalogManager;
    HRESULT hr = CreateCatalogManager(&pCatalogManager);
    if (SUCCEEDED(hr))
    {
        hr = pCatalogManager->Reset();
        pCatalogManager->Release();
    }
    return ReportHRESULTError(L"Reset()", hr);
}

int Reindex()
{
    wcout << L"Reindexing catalog." << endl;

    ISearchCatalogManager *pCatalogManager;
    HRESULT hr = CreateCatalogManager(&pCatalogManager);
    if (SUCCEEDED(hr))
    {
        hr = pCatalogManager->Reset();
        pCatalogManager->Release();
    }
    return ReportHRESULTError(L"Reindex()", hr);
}