// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "windows_lifecycle_manager.h"

#include <TlHelp32.h>
#include <WinUser.h>
#include <Windows.h>
#include <tchar.h>

#include "flutter/shell/platform/windows/flutter_windows_engine.h"

namespace flutter {

WindowsLifecycleManager::WindowsLifecycleManager(FlutterWindowsEngine* engine)
    : engine_(engine) {}

WindowsLifecycleManager::~WindowsLifecycleManager() {}

void WindowsLifecycleManager::Quit(std::optional<HWND> hwnd,
                                   std::optional<WPARAM> wparam,
                                   std::optional<LPARAM> lparam,
                                   UINT exit_code) {
  if (!hwnd.has_value()) {
    ::PostQuitMessage(exit_code);
  } else {
    BASE_CHECK(wparam.has_value() && lparam.has_value());
    sent_close_messages_[std::make_tuple(*hwnd, *wparam, *lparam)]++;
    DispatchMessage(*hwnd, WM_CLOSE, *wparam, *lparam);
  }
}

void WindowsLifecycleManager::DispatchMessage(HWND hwnd,
                                              UINT message,
                                              WPARAM wparam,
                                              LPARAM lparam) {
  PostMessage(hwnd, message, wparam, lparam);
}

bool WindowsLifecycleManager::HandleCloseMessage(HWND hwnd,
                                                 WPARAM wparam,
                                                 LPARAM lparam) {
  if (!process_exit_) {
    return false;
  }
  auto key = std::make_tuple(hwnd, wparam, lparam);
  auto itr = sent_close_messages_.find(key);
  if (itr != sent_close_messages_.end()) {
    if (itr->second == 1) {
      sent_close_messages_.erase(itr);
    } else {
      sent_close_messages_[key]--;
    }
    return false;
  }
  if (IsLastWindowOfProcess()) {
    engine_->RequestApplicationQuit(hwnd, wparam, lparam,
                                    AppExitType::cancelable);
    return true;
  }
  return false;
}

bool WindowsLifecycleManager::WindowProc(HWND hwnd,
                                         UINT msg,
                                         WPARAM wpar,
                                         LPARAM lpar,
                                         LRESULT* result) {
  switch (msg) {
    // When WM_CLOSE is received from the final window of an application, we
    // send a request to the framework to see if the app should exit. If it
    // is, we re-dispatch a new WM_CLOSE message. In order to allow the new
    // message to reach other delegates, we ignore it here.
    case WM_CLOSE:
      return HandleCloseMessage(hwnd, wpar, lpar);

    // DWM composition can be disabled on Windows 7.
    // Notify the engine as this can result in screen tearing.
    case WM_DWMCOMPOSITIONCHANGED:
      engine_->OnDwmCompositionChanged();
      break;

    case WM_SIZE:
      if (wpar == SIZE_MAXIMIZED || wpar == SIZE_RESTORED) {
        OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
      } else if (wpar == SIZE_MINIMIZED) {
        OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
      }
      break;

    case WM_SHOWWINDOW:
      if (!wpar) {
        OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
      } else {
        OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
      }
      break;

    case WM_DESTROY:
      OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
      break;
  }
  return false;
}

class ThreadSnapshot {
 public:
  ThreadSnapshot() {
    thread_snapshot_ = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  }
  ~ThreadSnapshot() {
    if (thread_snapshot_ != INVALID_HANDLE_VALUE) {
      ::CloseHandle(thread_snapshot_);
    }
  }

  std::optional<THREADENTRY32> GetFirstThread() {
    if (thread_snapshot_ == INVALID_HANDLE_VALUE) {
      FML_LOG(ERROR) << "Failed to get thread snapshot";
      return std::nullopt;
    }
    THREADENTRY32 thread;
    thread.dwSize = sizeof(thread);
    if (!::Thread32First(thread_snapshot_, &thread)) {
      DWORD error_num = ::GetLastError();
      char msg[256];
      ::FormatMessageA(
          FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
          error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msg, 256,
          nullptr);
      FML_LOG(ERROR) << "Failed to get thread(" << error_num << "): " << msg;
      return std::nullopt;
    }
    return thread;
  }

  bool GetNextThread(THREADENTRY32& thread) {
    if (thread_snapshot_ == INVALID_HANDLE_VALUE) {
      return false;
    }
    return ::Thread32Next(thread_snapshot_, &thread);
  }

 private:
  HANDLE thread_snapshot_;
};

static int64_t NumWindowsForThread(const THREADENTRY32& thread) {
  int64_t num_windows = 0;
  ::EnumThreadWindows(
      thread.th32ThreadID,
      [](HWND hwnd, LPARAM lparam) {
        int64_t* windows_ptr = reinterpret_cast<int64_t*>(lparam);
        if (::GetParent(hwnd) == nullptr) {
          (*windows_ptr)++;
        }
        return *windows_ptr <= 1 ? TRUE : FALSE;
      },
      reinterpret_cast<LPARAM>(&num_windows));
  return num_windows;
}

bool WindowsLifecycleManager::IsLastWindowOfProcess() {
  DWORD pid = ::GetCurrentProcessId();
  ThreadSnapshot thread_snapshot;
  std::optional<THREADENTRY32> first_thread = thread_snapshot.GetFirstThread();
  if (!first_thread.has_value()) {
    FML_LOG(ERROR) << "No first thread found";
    return true;
  }

  int num_windows = 0;
  THREADENTRY32 thread = *first_thread;
  do {
    if (thread.th32OwnerProcessID == pid) {
      num_windows += NumWindowsForThread(thread);
      if (num_windows > 1) {
        return false;
      }
    }
  } while (thread_snapshot.GetNextThread(thread));

  return num_windows <= 1;
}

void WindowsLifecycleManager::BeginProcessingLifecycle() {
  process_lifecycle_ = true;
}

void WindowsLifecycleManager::BeginProcessingExit() {
  process_exit_ = true;
}

void WindowsLifecycleManager::SetLifecycleState(AppLifecycleState state) {
  if (state_ == state) {
    return;
  }
  state_ = state;
  if (engine_ && process_lifecycle_) {
    const char* state_name = AppLifecycleStateToString(state);
    engine_->SendPlatformMessage("flutter/lifecycle",
                                 reinterpret_cast<const uint8_t*>(state_name),
                                 strlen(state_name), nullptr, nullptr);
  }
}

void WindowsLifecycleManager::UpdateState() {
  AppLifecycleState new_state = AppLifecycleState::kResumed;
  if (visible_windows_.empty()) {
    new_state = AppLifecycleState::kHidden;
  } else if (focused_windows_.empty()) {
    new_state = AppLifecycleState::kInactive;
  }
  SetLifecycleState(new_state);
}

void WindowsLifecycleManager::OnWindowStateEvent(HWND hwnd,
                                                 WindowStateEvent event) {
  // Instead of updating the state immediately, remember all
  // changes to individual window and then update the state in next run loop
  // turn. Otherwise the application would be temporarily deactivated when
  // switching focus between windows for example.
  if (!update_state_scheduled_) {
    update_state_scheduled_ = true;
    // Task runner will be destroyed together with engine so it is safe
    // to keep reference to it.
    engine_->task_runner()->PostTask([this]() {
      update_state_scheduled_ = false;
      UpdateState();
    });
  }

  switch (event) {
    case WindowStateEvent::kShow: {
      visible_windows_.insert(hwnd);
      break;
    }
    case WindowStateEvent::kHide: {
      visible_windows_.erase(hwnd);
      focused_windows_.erase(hwnd);
      break;
    }
    case WindowStateEvent::kFocus: {
      focused_windows_.insert(hwnd);
      break;
    }
    case WindowStateEvent::kUnfocus: {
      focused_windows_.erase(hwnd);
      break;
    }
  }
}

std::optional<LRESULT> WindowsLifecycleManager::ExternalWindowMessage(
    HWND hwnd,
    UINT message,
    WPARAM wparam,
    LPARAM lparam) {
  std::optional<flutter::WindowStateEvent> event = std::nullopt;

  // TODO (schectman): Handle WM_CLOSE messages.
  // https://github.com/flutter/flutter/issues/131497
  switch (message) {
    case WM_SHOWWINDOW:
      event = wparam ? flutter::WindowStateEvent::kShow
                     : flutter::WindowStateEvent::kHide;
      break;
    case WM_SIZE:
      switch (wparam) {
        case SIZE_MINIMIZED:
          event = flutter::WindowStateEvent::kHide;
          break;
        case SIZE_RESTORED:
        case SIZE_MAXIMIZED:
          event = flutter::WindowStateEvent::kShow;
          break;
      }
      break;
    case WM_SETFOCUS:
      event = flutter::WindowStateEvent::kFocus;
      break;
    case WM_KILLFOCUS:
      event = flutter::WindowStateEvent::kUnfocus;
      break;
    case WM_DESTROY:
      event = flutter::WindowStateEvent::kHide;
      break;
    case WM_CLOSE:
      if (HandleCloseMessage(hwnd, wparam, lparam)) {
        return NULL;
      }
      break;
  }

  if (event.has_value()) {
    OnWindowStateEvent(hwnd, *event);
  }

  return std::nullopt;
}

}  // namespace flutter
