// Spewview: remote debug spew monitor
//
// Copyright (c) 2000 Microsoft Corp.
//
// Main dialog window
//
// 16 Mar 2000 sburns



#include "headers.hxx"
#include "MainDialog.hpp"
#include "SpewDialog.hpp"
#include "resource.h"



static const DWORD _help_map[] =
{
   IDC_CLIENT_NAME,        NO_HELP,                          
   IDC_APP_NAME,           NO_HELP,                          
   IDC_GET_FLAGS,          NO_HELP,                          
   IDC_SET_FLAGS,          NO_HELP,                          
   IDC_VIEW_SPEW,          NO_HELP,
   IDC_FLAGS_GROUP,        NO_HELP,
   IDC_OUTPUT_TO_FILE,     NO_HELP,
   IDC_OUTPUT_TO_DEBUGGER, NO_HELP,
   IDC_OUTPUT_TO_SPEWVIEW, NO_HELP,
   IDC_OUTPUT_LOGS,        NO_HELP,
   IDC_OUTPUT_HEADER,      NO_HELP,
   IDC_OUTPUT_ERRORS,      NO_HELP,
   IDC_OUTPUT_CTORS,       NO_HELP,
   IDC_OUTPUT_ADDREFS,     NO_HELP,
   IDC_OUTPUT_FUNCCALLS,   NO_HELP,
   IDC_OUTPUT_TIME_OF_DAY, NO_HELP,
   IDC_OUTPUT_RUN_TIME,    NO_HELP,
   IDC_OUTPUT_SCOPE_EXIT,  NO_HELP,
   IDC_FLAGS,              NO_HELP,
   IDC_STATUS,             NO_HELP,
   0, 0
};



static const DWORD ID_TO_FLAGMAP[] =
{
   IDC_OUTPUT_TO_FILE,     Burnslib::Log::OUTPUT_TO_FILE,    
   IDC_OUTPUT_TO_DEBUGGER, Burnslib::Log::OUTPUT_TO_DEBUGGER,
   IDC_OUTPUT_TO_SPEWVIEW, Burnslib::Log::OUTPUT_TO_SPEWVIEW,
   IDC_OUTPUT_LOGS,        Burnslib::Log::OUTPUT_LOGS,       
   IDC_OUTPUT_HEADER,      Burnslib::Log::OUTPUT_HEADER,     
   IDC_OUTPUT_ERRORS,      Burnslib::Log::OUTPUT_ERRORS,     
   IDC_OUTPUT_CTORS,       Burnslib::Log::OUTPUT_CTORS,      
   IDC_OUTPUT_ADDREFS,     Burnslib::Log::OUTPUT_ADDREFS,    
   IDC_OUTPUT_FUNCCALLS,   Burnslib::Log::OUTPUT_FUNCCALLS,   
   IDC_OUTPUT_TIME_OF_DAY, Burnslib::Log::OUTPUT_TIME_OF_DAY, 
   IDC_OUTPUT_RUN_TIME,    Burnslib::Log::OUTPUT_RUN_TIME,
   IDC_OUTPUT_SCOPE_EXIT,  Burnslib::Log::OUTPUT_SCOPE_EXIT,
   0, 0
};



MainDialog::MainDialog()
   :
   Dialog(IDD_MAIN, _help_map),
   spewviewer(0),
   setFlagsOnStart(false)
{
   LOG_CTOR(MainDialog);
}
   


MainDialog::~MainDialog()
{
   LOG_DTOR(MainDialog);

   SaveUiHistory();

   delete spewviewer;
   spewviewer = 0;
}



void
MainDialog::SetStatusText(const String& text)
{
   LOG_FUNCTION2(MainDialog::SetStatusText, text);

   Win::SetDlgItemText(hwnd, IDC_STATUS, text);
}



void
MainDialog::EnableControls()
{
   LOG_FUNCTION(MainDialog::EnableControls);

   String c = Win::GetTrimmedDlgItemText(hwnd, IDC_CLIENT_NAME);
   String a = Win::GetTrimmedDlgItemText(hwnd, IDC_APP_NAME);

   bool enableButtons =
         !Win::GetTrimmedDlgItemText(hwnd, IDC_CLIENT_NAME).empty()
      && !Win::GetTrimmedDlgItemText(hwnd, IDC_APP_NAME).empty();

   Win::EnableWindow(Win::GetDlgItem(hwnd, IDC_GET_FLAGS),   enableButtons);
   Win::EnableWindow(Win::GetDlgItem(hwnd, IDC_SET_FLAGS),   enableButtons);
   Win::EnableWindow(Win::GetDlgItem(hwnd, IDC_VIEW_SPEW),   enableButtons);
   Win::EnableWindow(Win::GetDlgItem(hwnd, IDC_FLAGS_GROUP), enableButtons);


   for (int i = 0; ID_TO_FLAGMAP[i]; i += 2)
   {
      Win::EnableWindow(
         Win::GetDlgItem(hwnd, ID_TO_FLAGMAP[i]),
         enableButtons);
   }
}



void
MainDialog::AddToUiHistory(const String& clientName, const String& appName)
{
   LOG_FUNCTION(MainDialog::AddToUiHistory);
   ASSERT(!clientName.empty());
   ASSERT(!appName.empty());

   push_back_unique(clientNameHistory, clientName);
   push_back_unique(appNameHistory,    appName);

   lastClientNameUsed = clientName;
   lastAppNameUsed    = appName;   
}



// Caller must call Win::RegCloseKey(remoteHKLM) 

HRESULT
MainDialog::ConnectToClientRegistry(
   HKEY&   remoteHKLM,
   String& clientName,
   String& appName)   
{
   LOG_FUNCTION(MainDialog::ConnectToClientRegistry);
   ASSERT(!remoteHKLM);

   remoteHKLM = 0;
   clientName = Win::GetTrimmedDlgItemText(hwnd, IDC_CLIENT_NAME);
   appName    = Win::GetTrimmedDlgItemText(hwnd, IDC_APP_NAME);   

   if (clientName.empty() or appName.empty())
   {
      popup.Error(
         hwnd,
         L"You need to specify a client machine and application");
      return E_INVALIDARG;
   }

   HRESULT hr = S_OK;
   do
   {
      SetStatusText(
         String::format(
            L"Attempting to attach to machine %1",
            clientName.c_str()));

      Computer comp(clientName);

      hr = comp.Refresh();
      if (FAILED(hr))
      {
         String msg =
            String::format(
               L"Can't attach to client machine %1",
               clientName.c_str());
 
         SetStatusText(msg);

         popup.Error(hwnd, hr, msg);
         break;
      }

      // connect to the client machine's registry

      hr =
         Win::RegConnectRegistry(
            comp.IsLocal() ? String() : L"\\\\" + comp.GetNetbiosName(),
            HKEY_LOCAL_MACHINE,
            remoteHKLM);
      if (FAILED(hr))
      {
         String msg =
            String::format(
               L"Can't connect to registry of client machine %1",
               clientName.c_str());

         SetStatusText(msg);

         popup.Error(hwnd, hr, msg);            
         break;
      }
   }
   while (0);

#ifdef DBG
   if (SUCCEEDED(hr))
   {
      ASSERT(remoteHKLM);
   }
#endif

   return hr;
}



DWORD
CollectFlags(HWND dialogParent)
{
   LOG_FUNCTION(CollectFlags);
   ASSERT(Win::IsWindow(dialogParent));

   DWORD outputFlags = 0;

   for (int i = 0; ID_TO_FLAGMAP[i]; i += 2)
   {
      if (Win::IsDlgButtonChecked(dialogParent, ID_TO_FLAGMAP[i]))
      {
         outputFlags |= ID_TO_FLAGMAP[i + 1];
      }
   }

   return outputFlags;
}



void
UpdateFlagsEdit(HWND dialogParent)
{
   LOG_FUNCTION(UpdateFlagsEdit);
   ASSERT(Win::IsWindow(dialogParent));

   DWORD flags = CollectFlags(dialogParent);

   Win::SetDlgItemText(
      dialogParent,
      IDC_FLAGS,
      String::format(L"%1!08X!", flags));
}
   


// Set the logging options
//
// remoteHKLM - already opened registry handle.  Not closed by this
// function.

HRESULT
SetLoggingOptions(
   HWND           dialogParent,
   HKEY           remoteHKLM,
   const String&  clientName,
   const String&  appName)   
{
   LOG_FUNCTION(SetLoggingOptions);
   ASSERT(Win::IsWindow(dialogParent));
   ASSERT(remoteHKLM);
   ASSERT(!clientName.empty());
   ASSERT(!appName.empty());

   HRESULT hr = S_OK;

   do
   {
      String logKey = String(REG_ADMIN_RUNTIME_OPTIONS) + appName;
      RegistryKey key;
               
      hr = key.Create(remoteHKLM, logKey);
      if (FAILED(hr))
      {
         popup.Error(
            dialogParent,
            hr,
            String::format(
               L"Can't create logging registry key %1 on client machine %2",
               logKey.c_str(),
               clientName.c_str()));
         break;
      }

      DWORD outputFlags = CollectFlags(dialogParent);

      hr = key.SetValue(L"LogFlags", outputFlags);
      if (FAILED(hr))
      {
         popup.Error(
            dialogParent,
            hr,
            String::format(
               L"Can't set logging registry value on client machine %1",
               clientName.c_str()));
         break;
      }
   }
   while (0);

   return hr;
}



HRESULT
MainDialog::SetFlags()
{
   LOG_FUNCTION(MainDialog::SetFlags);

   HRESULT hr = S_OK;
   HKEY    remoteHKLM = 0;   
   String  clientName;
   String  appName;

   do
   {
      hr = ConnectToClientRegistry(remoteHKLM, clientName, appName);

      // if that failed, the connect function will have griped to the user
      // already, so just bail out here

      BREAK_ON_FAILED_HRESULT(hr);

      hr = SetLoggingOptions(hwnd, remoteHKLM, clientName, appName);

      // ditto about griping here

      BREAK_ON_FAILED_HRESULT(hr);
   }
   while (0);
   Win::RegCloseKey(remoteHKLM);

   if (SUCCEEDED(hr))
   {
      // Since we could successfully perform the operation, save the
      // client name and app name in the ui history

      AddToUiHistory(clientName, appName);
   }

   return hr;
}



void
MainDialog::OnSetFlagsButton()
{
   LOG_FUNCTION(MainDialog::OnSetFlagsButton);

   if (SUCCEEDED(SetFlags()))
   {
      // refresh the flags

      HRESULT hr = GetFlags();
      ASSERT(SUCCEEDED(hr));

      SetStatusText(L"Flags set successfully.");
   }
}



void
MainDialog::OnGetFlagsButton()
{
   LOG_FUNCTION(MainDialog::OnGetFlagsButton);

   if (SUCCEEDED(GetFlags()))
   {
      SetStatusText(L"Flags read successfully.");
   }
}



void
MainDialog::UpdateCheckboxen(DWORD flags)
{
   LOG_FUNCTION(MainDialog::UpdateCheckboxen);
   
   for (int i = 0; ID_TO_FLAGMAP[i]; i += 2)
   {
      Win::CheckDlgButton(
         hwnd,
         ID_TO_FLAGMAP[i],
            (flags & ID_TO_FLAGMAP[i + 1])
         ?  BST_CHECKED
         :  BST_UNCHECKED);
   }
}



void
MainDialog::ResetFlagsDisplay()
{
   LOG_FUNCTION(MainDialog::ResetFlagsDisplay);

   Win::SetDlgItemText(hwnd, IDC_FLAGS, L"");
   Win::UpdateWindow(Win::GetDlgItem(hwnd, IDC_FLAGS));

   // clear all the checkboxes

   UpdateCheckboxen(0);
}



HRESULT
MainDialog::GetFlags()
{
   LOG_FUNCTION(MainDialog::GetFlags);

   HRESULT hr = S_OK;
   HKEY    remoteHKLM = 0;   
   String  clientName;
   String  appName;

   Win::WaitCursor wait;

   do
   {
      ResetFlagsDisplay();

      hr = ConnectToClientRegistry(remoteHKLM, clientName, appName);

      // if that failed, the connect function will have griped to the user
      // already, so just bail out here

      BREAK_ON_FAILED_HRESULT(hr);   

      // Set the logging options

      String logKey = String(REG_ADMIN_RUNTIME_OPTIONS) + appName;
      RegistryKey key;
               
      hr = key.Open(remoteHKLM, logKey);
      if (FAILED(hr))
      {
         String msg =
            String::format(
               L"Can't open logging registry key %1 on client machine %2",
               logKey.c_str(),
               clientName.c_str());
            
         SetStatusText(msg);
               
         popup.Error(hwnd, hr, msg);
         break;
      }

      DWORD outputFlags = 0;
      hr = key.GetValue(L"LogFlags", outputFlags);
      if (FAILED(hr))
      {
         String msg =
            String::format(
               L"Can't get logging registry value on client machine %1",
               clientName.c_str());
            
         SetStatusText(msg);
               
         popup.Error(hwnd, hr, msg);
         break;
      }

      // here, we've got the flags, so update the checkboxen

      UpdateCheckboxen(outputFlags);
      UpdateFlagsEdit(hwnd);
   }
   while (0);

   Win::RegCloseKey(remoteHKLM);

   if (SUCCEEDED(hr))
   {
      // Since we could successfully perform the operation, save the
      // client name and app name in the ui history

      AddToUiHistory(clientName, appName);
   }

   return hr;
}



HRESULT
MainDialog::SetClientConfiguration()
{
   LOG_FUNCTION(MainDialog::SetClientConfiguration);

   HRESULT hr = S_OK;
   HKEY    remoteHKLM = 0;   
   String  clientName;
   String  appName;

   Win::WaitCursor wait;

   do
   {
      hr = ConnectToClientRegistry(remoteHKLM, clientName, appName);

      // if that failed, the connect function will have griped to the user
      // already, so just bail out here

      BREAK_ON_FAILED_HRESULT(hr);   

      // create the spewview key with the name of the server (this machine)

      RegistryKey key;

      hr =
         key.Create(
            remoteHKLM,
            SPEWVIEW_KEY_NAME + appName,
            REG_OPTION_VOLATILE);
      if (FAILED(hr))
      {
         popup.Error(
            hwnd,
            hr,
            String::format(
               L"Can't create spewview registry key on client machine %1",
               clientName.c_str()));
         break;
      }

      hr =
         key.SetValue(
            L"Server",
            Win::GetComputerNameEx(ComputerNameNetBIOS));
      if (FAILED(hr))
      {
         popup.Error(
            hwnd,
            hr,
            String::format(
               L"Can't set spewview server registry value on client machine %1",
               clientName.c_str()));
         break;
      }

      if (setFlagsOnStart)
      {
         hr = SetLoggingOptions(hwnd, remoteHKLM, clientName, appName);

         // if that failed, the function will have griped to the user already,
         // so just bail out here

         BREAK_ON_FAILED_HRESULT(hr);
      }
   }
   while (0);

   Win::RegCloseKey(remoteHKLM);

   if (SUCCEEDED(hr))
   {
      // Since we could successfully perform the operation, save the
      // client name and app name in the ui history

      AddToUiHistory(clientName, appName);
   }

   return hr;
}



void
MainDialog::OnStartButton()
{
   LOG_FUNCTION(MainDialog::OnStartButton);

   HRESULT hr = S_OK;
   do
   {
      if (spewviewer)
      {
         popup.Error(
            hwnd,
            L"Spew Viewing has already commenced.");
         break;
      }
         
      String clientName = Win::GetTrimmedDlgItemText(hwnd, IDC_CLIENT_NAME);
      String appName    = Win::GetTrimmedDlgItemText(hwnd, IDC_APP_NAME);   

      if (clientName.empty() or appName.empty())
      {
         popup.Error(
            hwnd,
            L"You need to specify a client machine and application");
         break;
      }

      if (!setFlagsOnStart)
      {
         GetFlags();
      }

      // configure the client

      hr = SetClientConfiguration();

      // if that call failed, it will have griped at the user.

      BREAK_ON_FAILED_HRESULT(hr);

      // deleted either in dtor or in WM_KILL_SPEWVIEWER handler

      spewviewer = new SpewDialog(clientName, appName);
      spewviewer->ModelessExecute(hwnd);
   }
   while (0);
}



bool
MainDialog::OnCommand(
   HWND        windowFrom,
   unsigned    controlIDFrom,
   unsigned    code)
{
   //   LOG_FUNCTION(MainDialog::OnCommand);

   switch (controlIDFrom)
   {
      case IDC_VIEW_SPEW:
      {
         if (code == BN_CLICKED)
         {
            OnStartButton();
            return true;
         }
         break;
      }
      case IDC_GET_FLAGS:
      {
         if (code == BN_CLICKED)
         {
            OnGetFlagsButton();
            return true;
         }
         break;
      }
      case IDC_SET_FLAGS:
      {
         if (code == BN_CLICKED)
         {
            OnSetFlagsButton();
            return true;
         }
         break;
      }
      case IDCANCEL:
      {
         if (code == BN_CLICKED)
         {
            // kill the spew window...

            Win::EndDialog(hwnd, 0);
            return true;
         }
         break;
      }
      case IDC_CLIENT_NAME:
      case IDC_APP_NAME:
      {
         if (code == CBN_EDITCHANGE)
         {
            EnableControls();
         }
         if (code == CBN_CLOSEUP)
         {
            // move the list box selection into the combo box edit control

            Win::SetWindowText(
               windowFrom,
               Win::ComboBox_GetCurText(windowFrom));

            EnableControls();
         }
         break;
      }
      case IDC_OUTPUT_TO_FILE:
      case IDC_OUTPUT_TO_DEBUGGER:
      case IDC_OUTPUT_TO_SPEWVIEW:
      case IDC_OUTPUT_LOGS:
      case IDC_OUTPUT_HEADER:
      case IDC_OUTPUT_ERRORS:
      case IDC_OUTPUT_CTORS:
      case IDC_OUTPUT_ADDREFS:
      case IDC_OUTPUT_FUNCCALLS:
      case IDC_OUTPUT_TIME_OF_DAY:
      case IDC_OUTPUT_RUN_TIME:
      case IDC_OUTPUT_SCOPE_EXIT:
      {
         if (code == BN_CLICKED)
         {
            UpdateFlagsEdit(hwnd);
            setFlagsOnStart = true;
         }
         break;
      }
      case IDC_FLAGS:
      {
         switch (code)
         {
            case EN_CHANGE:
            {
               setFlagsOnStart = true;

               // update the display

               String text = Win::GetWindowText(windowFrom);
               DWORD flags = 0;
               text.convert(flags, 16);

               UpdateCheckboxen(flags);
               break;
            }
            case EN_UPDATE:
            {
               
               
            }
            default:
            {
               // do nothing

               break;
            }
         break;
         }
      }
      default:
      {
         // do nothing
      }
   }

   return false;
}



void
AddLastUsedNameToCombo(
   HWND              combo,
   const StringList& historyList,
   const String&     lastNameUsed)
{
   typedef std::binder1st<String::EqualIgnoreCase> FindIfPredicate;

   if (!lastNameUsed.empty())
   {
      if (
         std::find_if(
            historyList.begin(),
            historyList.end(),
            FindIfPredicate(String::EqualIgnoreCase(), lastNameUsed))
         == historyList.end() )
      {
         // last name used not present in history list, so add it

         Win::ComboBox_AddString(combo, lastNameUsed);
      }

      Win::ComboBox_SelectString(combo, lastNameUsed);
   }
}



void
MainDialog::OnInit()
{
   LOG_FUNCTION(MainDialog::OnInit);

   LoadUiHistory();

   // Load the client and app name combo boxes with the historical values

   HWND clientCombo = Win::GetDlgItem(hwnd, IDC_CLIENT_NAME);

   Win::ComboBox_AddStrings(
      clientCombo,
      clientNameHistory.begin(),
      clientNameHistory.end());

   HWND appCombo = Win::GetDlgItem(hwnd, IDC_APP_NAME);

   Win::ComboBox_AddStrings(
      appCombo,
      appNameHistory.begin(),
      appNameHistory.end());

   AddLastUsedNameToCombo(clientCombo, clientNameHistory, lastClientNameUsed);
   AddLastUsedNameToCombo(appCombo,    appNameHistory,    lastAppNameUsed);   

   // Limit to number of hex digits in a DWORD

   Win::Edit_LimitText(Win::GetDlgItem(hwnd, IDC_FLAGS), 8);

   SetStatusText(L"");
   ResetFlagsDisplay();
   EnableControls();
}



void
MainDialog::LoadUiHistory()
{
   LOG_FUNCTION(MainDialog::LoadUiHistory);

   HRESULT hr = S_OK;

   do
   {
      RegistryKey key;

      hr = key.Open(HKEY_LOCAL_MACHINE, SPEWVIEW_KEY_NAME);
      BREAK_ON_FAILED_HRESULT(hr);

      hr =
         key.GetValue(
            L"ClientNameHistory",
            std::back_inserter(clientNameHistory));
      LOG_HRESULT(hr);

      // don't break on failure, try to read the app name history too.

      hr =
         key.GetValue(
            L"AppNameHistory",
            std::back_inserter(appNameHistory));
      LOG_HRESULT(hr);

      // don't break on failure, try to read the last names used, too.

      hr = key.GetValue(L"LastClientNameUsed", lastClientNameUsed);
      LOG_HRESULT(hr);

      hr = key.GetValue(L"LastAppNameUsed", lastAppNameUsed);
      LOG_HRESULT(hr);
   }
   while (0);
}



void
MainDialog::SaveUiHistory()
{
   LOG_FUNCTION(MainDialog::SaveUiHistory);

   HRESULT hr = S_OK;

   do
   {
      RegistryKey key;

      hr = key.Create(HKEY_LOCAL_MACHINE, SPEWVIEW_KEY_NAME);
      BREAK_ON_FAILED_HRESULT(hr);

      hr =
         key.SetValue(
            L"ClientNameHistory",
            clientNameHistory.begin(),
            clientNameHistory.end());
      LOG_HRESULT(hr);

      // don't break on failure, try to write the app name history too.

      hr =
         key.SetValue(
            L"AppNameHistory",
            appNameHistory.begin(),
            appNameHistory.end());
      LOG_HRESULT(hr);

      // don't break on failure, try to write the last names used, too.

      hr = key.SetValue(L"LastClientNameUsed", lastClientNameUsed);
      LOG_HRESULT(hr);

      hr = key.SetValue(L"LastAppNameUsed", lastAppNameUsed);
      LOG_HRESULT(hr);
   }
   while (0);
}



bool
MainDialog::OnMessage(
   UINT     message,
   WPARAM   /* wparam */ ,
   LPARAM   /* lparam */ )
{
   switch (message)
   {
      case WM_KILL_SPEWVIEWER:
      {
         delete spewviewer;
         spewviewer = 0;

         return true;
      }
      default:
      {
         // do nothing
         break;
      }
   }

   return false;
}