// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "platform/globals.h"
#if defined(DART_HOST_OS_WINDOWS)
#include "bin/console.h"

#include "bin/file.h"
#include "bin/lockers.h"
#include "bin/platform.h"
#include "bin/utils.h"
#include "bin/utils_win.h"

// These are not always defined in the header files. See:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif

namespace dart {
namespace bin {

class ConsoleWin {
 public:
  static constexpr int kInvalidFlag = -1;

  static void Initialize() {
    saved_output_cp_ = kInvalidFlag;
    saved_input_cp_ = kInvalidFlag;
    // Set up a signal handler that restores the console state on a
    // CTRL_C_EVENT signal. This will only run when there is no signal handler
    // registered for the CTRL_C_EVENT from Dart code.
    SetConsoleCtrlHandler(SignalHandler, TRUE);

    // Set both the input and output code pages to UTF8.
    const int output_cp = GetConsoleOutputCP();
    const int input_cp = GetConsoleCP();
    if (output_cp != CP_UTF8) {
      SetConsoleOutputCP(CP_UTF8);
      saved_output_cp_ = output_cp;
    }
    if (input_cp != CP_UTF8) {
      SetConsoleCP(CP_UTF8);
      saved_input_cp_ = input_cp;
    }

    // Try to set the bits for ANSI support, but swallow any failures.
    saved_stdout_mode_ =
        ModifyMode(STD_OUTPUT_HANDLE, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    saved_stderr_mode_ =
        ModifyMode(STD_ERROR_HANDLE, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    saved_stdin_mode_ = ModifyMode(STD_INPUT_HANDLE, 0);

    // TODO(28984): Due to issue #29104, we cannot set
    // ENABLE_VIRTUAL_TERMINAL_INPUT here, as it causes ENABLE_PROCESSED_INPUT
    // to be ignored.
  }

  static void Cleanup() {
    // STD_OUTPUT_HANDLE, may have been closed or redirected. Therefore, we
    // explicitly open the CONOUT$, CONERR$ and CONIN$ devices, so that we can
    // be sure that we are really restoring the console to its original state.
    if (saved_stdout_mode_ != kInvalidFlag) {
      CleanupDevices("CONOUT$", STD_OUTPUT_HANDLE, saved_stdout_mode_);
      saved_stdout_mode_ = kInvalidFlag;
    }
    if (saved_stderr_mode_ != kInvalidFlag) {
      CleanupDevices("CONERR$", STD_ERROR_HANDLE, saved_stderr_mode_);
    }
    if (saved_stdin_mode_ != kInvalidFlag) {
      CleanupDevices("CONIN$", STD_INPUT_HANDLE, saved_stdin_mode_);
    }
    if (saved_output_cp_ != kInvalidFlag) {
      SetConsoleOutputCP(saved_output_cp_);
      saved_output_cp_ = kInvalidFlag;
    }
    if (saved_input_cp_ != kInvalidFlag) {
      SetConsoleCP(saved_input_cp_);
      saved_input_cp_ = kInvalidFlag;
    }
  }

 private:
  static int saved_output_cp_;
  static int saved_input_cp_;
  static DWORD saved_stdout_mode_;
  static DWORD saved_stderr_mode_;
  static DWORD saved_stdin_mode_;

  static BOOL WINAPI SignalHandler(DWORD signal) {
    if (signal == CTRL_C_EVENT) {
      Cleanup();
    }
    return FALSE;
  }

  static DWORD ModifyMode(DWORD handle, DWORD flags) {
    HANDLE h = GetStdHandle(handle);
    DWORD mode;
    DWORD old_mode = kInvalidFlag;

    /// GetConsoleMode fails if this instance of the VM isn't attached to a
    /// console. In that case, we'll just return kInvalidFlag and won't try
    /// to reset the state when we cleanup.
    if ((h != INVALID_HANDLE_VALUE) && GetConsoleMode(h, &mode)) {
      old_mode = mode;
      // No reason to restore the mode on exit if it was already desirable.
      if ((mode & flags) == flags) {
        return kInvalidFlag;
      }
      if (flags != 0) {
        const DWORD request = mode | flags;
        SetConsoleMode(h, request);
      }
    }
    return old_mode;
  }

  static void CleanupDevices(const char* device,
                             DWORD handle,
                             DWORD orig_flags) {
    const intptr_t kWideBufLen = 64;
    wchar_t widebuf[kWideBufLen];
    int result =
        MultiByteToWideChar(CP_UTF8, 0, device, -1, widebuf, kWideBufLen);
    ASSERT(result != 0);
    HANDLE h = CreateFileW(widebuf, GENERIC_READ | GENERIC_WRITE,
                           FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    if (h != INVALID_HANDLE_VALUE) {
      SetStdHandle(STD_OUTPUT_HANDLE, h);
      if (orig_flags != kInvalidFlag) {
        SetConsoleMode(h, orig_flags);
      }
    }
  }

  DISALLOW_ALLOCATION();
  DISALLOW_IMPLICIT_CONSTRUCTORS(ConsoleWin);
};

int ConsoleWin::saved_output_cp_ = ConsoleWin::kInvalidFlag;
int ConsoleWin::saved_input_cp_ = ConsoleWin::kInvalidFlag;
DWORD ConsoleWin::saved_stdout_mode_ = ConsoleWin::kInvalidFlag;
DWORD ConsoleWin::saved_stderr_mode_ = ConsoleWin::kInvalidFlag;
DWORD ConsoleWin::saved_stdin_mode_ = ConsoleWin::kInvalidFlag;

void Console::SaveConfig() {
  ConsoleWin::Initialize();
}

void Console::RestoreConfig() {
  ConsoleWin::Cleanup();
}

}  // namespace bin
}  // namespace dart

#endif  // defined(DART_HOST_OS_WINDOWS)
