// Copyright 2016 The Chromium 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 "ui/base/x/x11_window_event_manager.h"

#include <stddef.h>
#include <xcb/xcb.h>

#include "base/memory/singleton.h"
#include "ui/gfx/x/x11.h"

namespace ui {

namespace {

// Asks the X server to set |xid|'s event mask to |new_mask|.
void SetEventMask(XID xid, uint32_t new_mask) {
  XDisplay* display = gfx::GetXDisplay();
  xcb_connection_t* connection = XGetXCBConnection(display);
  auto cookie = xcb_change_window_attributes(connection, xid, XCB_CW_EVENT_MASK,
                                             &new_mask);
  // Window |xid| may already be destroyed at this point, so the
  // change_attributes request may give a BadWindow error.  In this case, just
  // ignore the error.
  xcb_discard_reply(connection, cookie.sequence);
}

}  // anonymous namespace

XScopedEventSelector::XScopedEventSelector(XID xid, uint32_t event_mask)
    : xid_(xid),
      event_mask_(event_mask),
      event_manager_(
          XWindowEventManager::GetInstance()->weak_ptr_factory_.GetWeakPtr()) {
  event_manager_->SelectEvents(xid_, event_mask_);
}

XScopedEventSelector::~XScopedEventSelector() {
  if (event_manager_)
    event_manager_->DeselectEvents(xid_, event_mask_);
}

// static
XWindowEventManager* XWindowEventManager::GetInstance() {
  return base::Singleton<XWindowEventManager>::get();
}

class XWindowEventManager::MultiMask {
 public:
  MultiMask() {
    for (int i = 0; i < kMaskSize; i++)
      mask_bits_[i] = 0;
  }

  ~MultiMask() {}

  void AddMask(uint32_t mask) {
    for (int i = 0; i < kMaskSize; i++) {
      if (mask & (1 << i))
        mask_bits_[i]++;
    }
  }

  void RemoveMask(uint32_t mask) {
    for (int i = 0; i < kMaskSize; i++) {
      if (mask & (1 << i)) {
        DCHECK(mask_bits_[i]);
        mask_bits_[i]--;
      }
    }
  }

  uint32_t ToMask() const {
    uint32_t mask = NoEventMask;
    for (int i = 0; i < kMaskSize; i++) {
      if (mask_bits_[i])
        mask |= (1 << i);
    }
    return mask;
  }

 private:
  static constexpr auto kMaskSize = 25;

  int mask_bits_[kMaskSize];

  DISALLOW_COPY_AND_ASSIGN(MultiMask);
};

XWindowEventManager::XWindowEventManager() : weak_ptr_factory_(this) {}

XWindowEventManager::~XWindowEventManager() {
  // Clear events still requested by not-yet-deleted XScopedEventSelectors.
  for (const auto& mask_pair : mask_map_)
    SetEventMask(mask_pair.first, NoEventMask);
}

void XWindowEventManager::SelectEvents(XID xid, uint32_t event_mask) {
  std::unique_ptr<MultiMask>& mask = mask_map_[xid];
  if (!mask)
    mask.reset(new MultiMask());
  uint32_t old_mask = mask_map_[xid]->ToMask();
  mask->AddMask(event_mask);
  AfterMaskChanged(xid, old_mask);
}

void XWindowEventManager::DeselectEvents(XID xid, uint32_t event_mask) {
  DCHECK(mask_map_.find(xid) != mask_map_.end());
  std::unique_ptr<MultiMask>& mask = mask_map_[xid];
  uint32_t old_mask = mask->ToMask();
  mask->RemoveMask(event_mask);
  AfterMaskChanged(xid, old_mask);
}

void XWindowEventManager::AfterMaskChanged(XID xid, uint32_t old_mask) {
  uint32_t new_mask = mask_map_[xid]->ToMask();
  if (new_mask == old_mask)
    return;

  SetEventMask(xid, new_mask);

  if (new_mask == NoEventMask)
    mask_map_.erase(xid);
}

}  // namespace ui
