// Copyright 2008 Google LLC
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// crash_generation_app.cpp : Defines the entry point for the application.
//

#ifdef HAVE_CONFIG_H
#include <config.h>  // Must come first
#endif

#include "client/windows/tests/crash_generation_app/crash_generation_app.h"

#include <windows.h>
#include <tchar.h>

#include "client/windows/crash_generation/client_info.h"
#include "client/windows/crash_generation/crash_generation_server.h"
#include "client/windows/handler/exception_handler.h"
#include "client/windows/common/ipc_protocol.h"

#include "client/windows/tests/crash_generation_app/abstract_class.h"

namespace google_breakpad {

const int kMaxLoadString = 100;
const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer";

const DWORD kEditBoxStyles = WS_CHILD |
                             WS_VISIBLE |
                             WS_VSCROLL |
                             ES_LEFT |
                             ES_MULTILINE |
                             ES_AUTOVSCROLL |
                             ES_READONLY;

// Maximum length of a line in the edit box.
const size_t kMaximumLineLength = 256;

// CS to access edit control in a thread safe way.
static CRITICAL_SECTION* cs_edit = NULL;

// Edit control.
static HWND client_status_edit_box;

HINSTANCE current_instance;             // Current instance.
TCHAR title[kMaxLoadString];            // Title bar text.
TCHAR window_class[kMaxLoadString];     // Main window class name.

ATOM MyRegisterClass(HINSTANCE instance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);

static size_t kCustomInfoCount = 2;
static CustomInfoEntry kCustomInfoEntries[] = {
    CustomInfoEntry(L"prod", L"CrashTestApp"),
    CustomInfoEntry(L"ver", L"1.0"),
};

static ExceptionHandler* handler = NULL;
static CrashGenerationServer* crash_server = NULL;

// Registers the window class.
//
// This function and its usage are only necessary if you want this code
// to be compatible with Win32 systems prior to the 'RegisterClassEx'
// function that was added to Windows 95. It is important to call this
// function so that the application will get 'well formed' small icons
// associated with it.
ATOM MyRegisterClass(HINSTANCE instance) {
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc = WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = instance;
  wcex.hIcon = LoadIcon(instance,
                        MAKEINTRESOURCE(IDI_CRASHGENERATIONAPP));
  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP);
  wcex.lpszClassName = window_class;
  wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

  return RegisterClassEx(&wcex);
}

// Saves instance handle and creates main window
//
// In this function, we save the instance handle in a global variable and
//   create and display the main program window.
BOOL InitInstance(HINSTANCE instance, int command_show) {
  current_instance = instance;
  HWND wnd = CreateWindow(window_class,
                          title,
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT,
                          0,
                          CW_USEDEFAULT,
                          0,
                          NULL,
                          NULL,
                          instance,
                          NULL);

  if (!wnd) {
    return FALSE;
  }

  ShowWindow(wnd, command_show);
  UpdateWindow(wnd);

  return TRUE;
}

static void AppendTextToEditBox(TCHAR* text) {
  EnterCriticalSection(cs_edit);
  SYSTEMTIME current_time;
  GetLocalTime(&current_time);
  TCHAR line[kMaximumLineLength];
  int result = swprintf_s(line,
                          kMaximumLineLength,
                          L"[%.2d-%.2d-%.4d %.2d:%.2d:%.2d] %s",
                          current_time.wMonth,
                          current_time.wDay,
                          current_time.wYear,
                          current_time.wHour,
                          current_time.wMinute,
                          current_time.wSecond,
                          text);

  if (result == -1) {
    return;
  }

  int length = GetWindowTextLength(client_status_edit_box);
  SendMessage(client_status_edit_box,
              EM_SETSEL,
              (WPARAM)length,
              (LPARAM)length);
  SendMessage(client_status_edit_box,
              EM_REPLACESEL,
              (WPARAM)FALSE,
              (LPARAM)line);
  LeaveCriticalSection(cs_edit);
}

static DWORD WINAPI AppendTextWorker(void* context) {
  TCHAR* text = reinterpret_cast<TCHAR*>(context);

  AppendTextToEditBox(text);
  delete[] text;

  return 0;
}

bool ShowDumpResults(const wchar_t* dump_path,
                     const wchar_t* minidump_id,
                     void* context,
                     EXCEPTION_POINTERS* exinfo,
                     MDRawAssertionInfo* assertion,
                     bool succeeded) {
  TCHAR* text = new TCHAR[kMaximumLineLength];
  text[0] = _T('\0');
  int result = swprintf_s(text,
                          kMaximumLineLength,
                          TEXT("Dump generation request %s\r\n"),
                          succeeded ? TEXT("succeeded") : TEXT("failed"));
  if (result == -1) {
    delete [] text;
  }

  QueueUserWorkItem(AppendTextWorker, text, WT_EXECUTEDEFAULT);
  return succeeded;
}

static void ShowClientConnected(void* context,
                                const ClientInfo* client_info) {
  TCHAR* line = new TCHAR[kMaximumLineLength];
  line[0] = _T('\0');
  int result = swprintf_s(line,
                          kMaximumLineLength,
                          L"Client connected:\t\t%d\r\n",
                          client_info->pid());

  if (result == -1) {
    delete[] line;
    return;
  }

  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
}

static void ShowClientCrashed(void* context,
                              const ClientInfo* client_info,
                              const wstring* dump_path) {
  TCHAR* line = new TCHAR[kMaximumLineLength];
  line[0] = _T('\0');
  int result = swprintf_s(line,
                          kMaximumLineLength,
                          TEXT("Client requested dump:\t%d\r\n"),
                          client_info->pid());

  if (result == -1) {
    delete[] line;
    return;
  }

  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);

  CustomClientInfo custom_info = client_info->GetCustomInfo();
  if (custom_info.count <= 0) {
    return;
  }

  wstring str_line;
  for (size_t i = 0; i < custom_info.count; ++i) {
    if (i > 0) {
      str_line += L", ";
    }
    str_line += custom_info.entries[i].name;
    str_line += L": ";
    str_line += custom_info.entries[i].value;
  }

  line = new TCHAR[kMaximumLineLength];
  line[0] = _T('\0');
  result = swprintf_s(line,
                      kMaximumLineLength,
                      L"%s\n",
                      str_line.c_str());
  if (result == -1) {
    delete[] line;
    return;
  }
  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
}

static void ShowClientExited(void* context,
                             const ClientInfo* client_info) {
  TCHAR* line = new TCHAR[kMaximumLineLength];
  line[0] = _T('\0');
  int result = swprintf_s(line,
                          kMaximumLineLength,
                          TEXT("Client exited:\t\t%d\r\n"),
                          client_info->pid());

  if (result == -1) {
    delete[] line;
    return;
  }

  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
}

void CrashServerStart() {
  // Do not create another instance of the server.
  if (crash_server) {
    return;
  }

  std::wstring dump_path = L"C:\\Dumps\\";

  if (_wmkdir(dump_path.c_str()) && (errno != EEXIST)) {
    MessageBoxW(NULL, L"Unable to create dump directory", L"Dumper", MB_OK);
    return;
  }

  crash_server = new CrashGenerationServer(kPipeName,
                                           NULL,
                                           ShowClientConnected,
                                           NULL,
                                           ShowClientCrashed,
                                           NULL,
                                           ShowClientExited,
                                           NULL,
                                           NULL,
                                           NULL,
                                           true,
                                           &dump_path);

  if (!crash_server->Start()) {
    MessageBoxW(NULL, L"Unable to start server", L"Dumper", MB_OK);
    delete crash_server;
    crash_server = NULL;
  }
}

void CrashServerStop() {
  delete crash_server;
  crash_server = NULL;
}

void DerefZeroCrash() {
  int* x = 0;
  *x = 1;
}

void InvalidParamCrash() {
  printf(NULL);
}

void PureCallCrash() {
  Derived derived;
}

void RequestDump() {
  if (!handler->WriteMinidump()) {
    MessageBoxW(NULL, L"Dump request failed", L"Dumper", MB_OK);
  }
  kCustomInfoEntries[1].set_value(L"1.1");
}

void CleanUp() {
  if (cs_edit) {
    DeleteCriticalSection(cs_edit);
    delete cs_edit;
  }

  if (handler) {
    delete handler;
  }

  if (crash_server) {
    delete crash_server;
  }
}

// Processes messages for the main window.
//
// WM_COMMAND - process the application menu.
// WM_PAINT   - Paint the main window.
// WM_DESTROY - post a quit message and return.
LRESULT CALLBACK WndProc(HWND wnd,
                         UINT message,
                         WPARAM w_param,
                         LPARAM l_param) {
  int message_id;
  int message_event;
  PAINTSTRUCT ps;
  HDC hdc;

  HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(wnd, GWLP_HINSTANCE);

  switch (message) {
    case WM_COMMAND:
      // Parse the menu selections.
      message_id = LOWORD(w_param);
      message_event = HIWORD(w_param);
      switch (message_id) {
        case IDM_ABOUT:
          DialogBox(current_instance,
                    MAKEINTRESOURCE(IDD_ABOUTBOX),
                    wnd,
                    About);
          break;
        case IDM_EXIT:
          DestroyWindow(wnd);
          break;
        case ID_SERVER_START:
          CrashServerStart();
          break;
        case ID_SERVER_STOP:
          CrashServerStop();
          break;
        case ID_CLIENT_DEREFZERO:
          DerefZeroCrash();
          break;
        case ID_CLIENT_INVALIDPARAM:
          InvalidParamCrash();
          break;
        case ID_CLIENT_PURECALL:
          PureCallCrash();
          break;
        case ID_CLIENT_REQUESTEXPLICITDUMP:
          RequestDump();
          break;
        default:
          return DefWindowProc(wnd, message, w_param, l_param);
      }
      break;
    case WM_CREATE:
      client_status_edit_box = CreateWindow(TEXT("EDIT"),
                                            NULL,
                                            kEditBoxStyles,
                                            0,
                                            0,
                                            0,
                                            0,
                                            wnd,
                                            NULL,
                                            instance,
                                            NULL);
      break;
    case WM_SIZE:
      // Make the edit control the size of the window's client area.
      MoveWindow(client_status_edit_box,
                 0,
                 0,
                 LOWORD(l_param),        // width of client area.
                 HIWORD(l_param),        // height of client area.
                 TRUE);                  // repaint window.
      break;
    case WM_SETFOCUS:
      SetFocus(client_status_edit_box);
      break;
    case WM_PAINT:
      hdc = BeginPaint(wnd, &ps);
      EndPaint(wnd, &ps);
      break;
    case WM_DESTROY:
      CleanUp();
      PostQuitMessage(0);
      break;
    default:
      return DefWindowProc(wnd, message, w_param, l_param);
  }

  return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND dlg,
                       UINT message,
                       WPARAM w_param,
                       LPARAM l_param) {
  UNREFERENCED_PARAMETER(l_param);
  switch (message) {
    case WM_INITDIALOG:
      return (INT_PTR)TRUE;

    case WM_COMMAND:
      if (LOWORD(w_param) == IDOK || LOWORD(w_param) == IDCANCEL) {
        EndDialog(dlg, LOWORD(w_param));
        return (INT_PTR)TRUE;
      }
      break;
  }

  return (INT_PTR)FALSE;
}

}  // namespace google_breakpad

int APIENTRY _tWinMain(HINSTANCE instance,
                       HINSTANCE previous_instance,
                       LPTSTR command_line,
                       int command_show) {
  using namespace google_breakpad;

  UNREFERENCED_PARAMETER(previous_instance);
  UNREFERENCED_PARAMETER(command_line);

  cs_edit = new CRITICAL_SECTION();
  InitializeCriticalSection(cs_edit);

  CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount};

  CrashServerStart();
  // This is needed for CRT to not show dialog for invalid param
  // failures and instead let the code handle it.
  _CrtSetReportMode(_CRT_ASSERT, 0);
  handler = new ExceptionHandler(L"C:\\dumps\\",
                                 NULL,
                                 google_breakpad::ShowDumpResults,
                                 NULL,
                                 ExceptionHandler::HANDLER_ALL,
                                 MiniDumpNormal,
                                 kPipeName,
                                 &custom_info);

  // Initialize global strings.
  LoadString(instance, IDS_APP_TITLE, title, kMaxLoadString);
  LoadString(instance,
             IDC_CRASHGENERATIONAPP,
             window_class,
             kMaxLoadString);
  MyRegisterClass(instance);

  // Perform application initialization.
  if (!InitInstance(instance, command_show)) {
    return FALSE;
  }

  HACCEL accel_table = LoadAccelerators(
      instance,
      MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP));

  // Main message loop.
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0)) {
    if (!TranslateAccelerator(msg.hwnd, accel_table, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return static_cast<int>(msg.wParam);
}
