/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#import <UIKit/UIEvent.h>
#import <UIKit/UIGraphics.h>
#import <UIKit/UIInterface.h>
#import <UIKit/UIScreen.h>
#import <UIKit/UITapGestureRecognizer.h>
#import <UIKit/UITouch.h>
#import <UIKit/UIView.h>
#import <UIKit/UIViewController.h>
#import <UIKit/UIWindow.h>
#import <QuartzCore/QuartzCore.h>

#include <algorithm>

#include "nsWindow.h"
#include "nsScreenManager.h"
#include "nsAppShell.h"

#include "nsWidgetsCID.h"
#include "nsGfxCIID.h"

#include "gfxQuartzSurface.h"
#include "gfxUtils.h"
#include "gfxImageSurface.h"
#include "gfxContext.h"
#include "nsRegion.h"
#include "Layers.h"
#include "nsTArray.h"

#include "mozilla/BasicEvents.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/MouseEventBinding.h"

#include "GeckoProfiler.h"

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::layers;

#define ALOG(args...)    \
  fprintf(stderr, args); \
  fprintf(stderr, "\n")

static LayoutDeviceIntPoint UIKitPointsToDevPixels(CGPoint aPoint, CGFloat aBackingScale) {
  return LayoutDeviceIntPoint(NSToIntRound(aPoint.x * aBackingScale),
                              NSToIntRound(aPoint.y * aBackingScale));
}

static CGRect DevPixelsToUIKitPoints(const LayoutDeviceIntRect& aRect, CGFloat aBackingScale) {
  return CGRectMake((CGFloat)aRect.x / aBackingScale, (CGFloat)aRect.y / aBackingScale,
                    (CGFloat)aRect.width / aBackingScale, (CGFloat)aRect.height / aBackingScale);
}

// Used to retain a Cocoa object for the remainder of a method's execution.
class nsAutoRetainUIKitObject {
 public:
  nsAutoRetainUIKitObject(id anObject) { mObject = [anObject retain]; }
  ~nsAutoRetainUIKitObject() { [mObject release]; }

 private:
  id mObject;  // [STRONG]
};

@interface ChildView : UIView {
 @public
  nsWindow* mGeckoChild;  // weak ref
  BOOL mWaitingForPaint;
  CFMutableDictionaryRef mTouches;
  int mNextTouchID;
}
// sets up our view, attaching it to its owning gecko view
- (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild;
// Our Gecko child was Destroy()ed
- (void)widgetDestroyed;
// Tear down this ChildView
- (void)delayedTearDown;
- (void)sendMouseEvent:(EventMessage)aType
                 point:(LayoutDeviceIntPoint)aPoint
                widget:(nsWindow*)aWindow;
- (void)handleTap:(UITapGestureRecognizer*)sender;
- (BOOL)isUsingMainThreadOpenGL;
- (void)drawUsingOpenGL;
- (void)drawUsingOpenGLCallback;
- (void)sendTouchEvent:(EventMessage)aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow;
// Event handling (UIResponder)
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
@end

@implementation ChildView
+ (Class)layerClass {
  return [CAEAGLLayer class];
}

- (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild {
  self.multipleTouchEnabled = YES;
  if ((self = [super initWithFrame:inFrame])) {
    mGeckoChild = inChild;
  }
  ALOG("[ChildView[%p] initWithFrame:] (mGeckoChild = %p)", (void*)self, (void*)mGeckoChild);
  self.opaque = YES;
  self.alpha = 1.0;

  UITapGestureRecognizer* tapRecognizer =
      [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
  tapRecognizer.numberOfTapsRequired = 1;
  [self addGestureRecognizer:tapRecognizer];

  mTouches = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
  mNextTouchID = 0;
  return self;
}

- (void)widgetDestroyed {
  mGeckoChild = nullptr;
  CFRelease(mTouches);
}

- (void)delayedTearDown {
  [self removeFromSuperview];
  [self release];
}

- (void)sendMouseEvent:(EventMessage)aType
                 point:(LayoutDeviceIntPoint)aPoint
                widget:(nsWindow*)aWindow {
  WidgetMouseEvent event(true, aType, aWindow, WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);

  event.mRefPoint = aPoint;
  event.mClickCount = 1;
  event.button = MouseButton::eLeft;
  event.mTime = PR_IntervalNow();
  event.inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;

  nsEventStatus status;
  aWindow->DispatchEvent(&event, status);
}

- (void)handleTap:(UITapGestureRecognizer*)sender {
  if (sender.state == UIGestureRecognizerStateEnded) {
    ALOG("[ChildView[%p] handleTap]", self);
    LayoutDeviceIntPoint lp =
        UIKitPointsToDevPixels([sender locationInView:self], [self contentScaleFactor]);
    [self sendMouseEvent:eMouseMove point:lp widget:mGeckoChild];
    [self sendMouseEvent:eMouseDown point:lp widget:mGeckoChild];
    [self sendMouseEvent:eMouseUp point:lp widget:mGeckoChild];
  }
}

- (void)sendTouchEvent:(EventMessage)aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow {
  WidgetTouchEvent event(true, aType, aWindow);
  // XXX: I think nativeEvent.timestamp * 1000 is probably usable here but
  // I don't care that much right now.
  event.mTime = PR_IntervalNow();
  event.mTouches.SetCapacity(aTouches.count);
  for (UITouch* touch in aTouches) {
    LayoutDeviceIntPoint loc =
        UIKitPointsToDevPixels([touch locationInView:self], [self contentScaleFactor]);
    LayoutDeviceIntPoint radius = UIKitPointsToDevPixels([touch majorRadius], [touch majorRadius]);
    void* value;
    if (!CFDictionaryGetValueIfPresent(mTouches, touch, (const void**)&value)) {
      // This shouldn't happen.
      NS_ASSERTION(false, "Got a touch that we didn't know about");
      continue;
    }
    int id = reinterpret_cast<int>(value);
    RefPtr<Touch> t = new Touch(id, loc, radius, 0.0f, 1.0f);
    event.mRefPoint = loc;
    event.mTouches.AppendElement(t);
  }
  aWindow->DispatchInputEvent(&event);
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  ALOG("[ChildView[%p] touchesBegan", self);
  if (!mGeckoChild) return;

  for (UITouch* touch : touches) {
    CFDictionaryAddValue(mTouches, touch, (void*)mNextTouchID);
    mNextTouchID++;
  }
  [self sendTouchEvent:eTouchStart touches:[event allTouches] widget:mGeckoChild];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
  ALOG("[ChildView[%p] touchesCancelled", self);
  [self sendTouchEvent:eTouchCancel touches:touches widget:mGeckoChild];
  for (UITouch* touch : touches) {
    CFDictionaryRemoveValue(mTouches, touch);
  }
  if (CFDictionaryGetCount(mTouches) == 0) {
    mNextTouchID = 0;
  }
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
  ALOG("[ChildView[%p] touchesEnded", self);
  if (!mGeckoChild) return;

  [self sendTouchEvent:eTouchEnd touches:touches widget:mGeckoChild];
  for (UITouch* touch : touches) {
    CFDictionaryRemoveValue(mTouches, touch);
  }
  if (CFDictionaryGetCount(mTouches) == 0) {
    mNextTouchID = 0;
  }
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
  ALOG("[ChildView[%p] touchesMoved", self);
  if (!mGeckoChild) return;

  [self sendTouchEvent:eTouchMove touches:[event allTouches] widget:mGeckoChild];
}

- (void)setNeedsDisplayInRect:(CGRect)aRect {
  if ([self isUsingMainThreadOpenGL]) {
    // Draw without calling drawRect. This prevent us from
    // needing to access the normal window buffer surface unnecessarily, so we
    // waste less time synchronizing the two surfaces.
    if (!mWaitingForPaint) {
      mWaitingForPaint = YES;
      // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode
      // so that the timer also fires while a native menu is open.
      [self performSelector:@selector(drawUsingOpenGLCallback)
                 withObject:nil
                 afterDelay:0
                    inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
    }
  }
}

- (BOOL)isUsingMainThreadOpenGL {
  if (!mGeckoChild || ![self window]) return NO;

  return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() ==
         mozilla::layers::LayersBackend::LAYERS_OPENGL;
}

- (void)drawUsingOpenGL {
  ALOG("drawUsingOpenGL");
  AUTO_PROFILER_LABEL("ChildView::drawUsingOpenGL", OTHER);

  if (!mGeckoChild->IsVisible()) return;

  mWaitingForPaint = NO;

  LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
  LayoutDeviceIntRegion region(geckoBounds);

  mGeckoChild->PaintWindow(region);
}

// Called asynchronously after setNeedsDisplay in order to avoid entering the
// normal drawing machinery.
- (void)drawUsingOpenGLCallback {
  if (mWaitingForPaint) {
    [self drawUsingOpenGL];
  }
}

// The display system has told us that a portion of our view is dirty. Tell
// gecko to paint it
- (void)drawRect:(CGRect)aRect {
  CGContextRef cgContext = UIGraphicsGetCurrentContext();
  [self drawRect:aRect inContext:cgContext];
}

- (void)drawRect:(CGRect)aRect inContext:(CGContextRef)aContext {
#ifdef DEBUG_UPDATE
  LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();

  fprintf(stderr, "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n  gecko bounds: [%d %d %d %d]\n",
          self, mGeckoChild, aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height,
          aContext, geckoBounds.x, geckoBounds.y, geckoBounds.width, geckoBounds.height);

  CGAffineTransform xform = CGContextGetCTM(aContext);
  fprintf(stderr, "  xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx,
          xform.ty);
#endif

  if (true) {
    // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
    // directly called from a delayed perform callback - without going through
    // drawRect.
    // Paints that come through here are triggered by something that Cocoa
    // controls, for example by window resizing or window focus changes.

    // Do GL composition and return.
    [self drawUsingOpenGL];
    return;
  }
  AUTO_PROFILER_LABEL("ChildView::drawRect", OTHER);

  // The CGContext that drawRect supplies us with comes with a transform that
  // scales one user space unit to one Cocoa point, which can consist of
  // multiple dev pixels. But Gecko expects its supplied context to be scaled
  // to device pixels, so we need to reverse the scaling.
  double scale = mGeckoChild->BackingScaleFactor();
  CGContextSaveGState(aContext);
  CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);

  CGSize viewSize = [self bounds].size;
  gfx::IntSize backingSize(viewSize.width * scale, viewSize.height * scale);

  CGContextSaveGState(aContext);

  LayoutDeviceIntRegion region = LayoutDeviceIntRect(
      NSToIntRound(aRect.origin.x * scale), NSToIntRound(aRect.origin.y * scale),
      NSToIntRound(aRect.size.width * scale), NSToIntRound(aRect.size.height * scale));

  // Create Cairo objects.
  RefPtr<gfxQuartzSurface> targetSurface;

  RefPtr<gfxContext> targetContext;
  if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::CAIRO)) {
    // This is dead code unless you mess with prefs, but keep it around for
    // debugging.
    targetSurface = new gfxQuartzSurface(aContext, backingSize);
    targetSurface->SetAllowUseAsSource(false);
    RefPtr<gfx::DrawTarget> dt =
        gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(targetSurface, backingSize);
    if (!dt || !dt->IsValid()) {
      gfxDevCrash(mozilla::gfx::LogReason::InvalidContext)
          << "Window context problem 2 " << backingSize;
      return;
    }
    dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
    targetContext = gfxContext::CreateOrNull(dt);
  } else {
    MOZ_ASSERT_UNREACHABLE("COREGRAPHICS is the only supported backend");
  }
  MOZ_ASSERT(targetContext);  // already checked for valid draw targets above

  // Set up the clip region.
  targetContext->NewPath();
  for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
    const LayoutDeviceIntRect& r = iter.Get();
    targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
  }
  targetContext->Clip();

  // nsAutoRetainCocoaObject kungFuDeathGrip(self);
  bool painted = false;
  if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
    nsBaseWidget::AutoLayerManagerSetup setupLayerManager(mGeckoChild, targetContext,
                                                          BufferMode::BUFFER_NONE);
    painted = mGeckoChild->PaintWindow(region);
  } else if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
    // We only need this so that we actually get DidPaintWindow fired
    painted = mGeckoChild->PaintWindow(region);
  }

  targetContext = nullptr;
  targetSurface = nullptr;

  CGContextRestoreGState(aContext);

  // Undo the scale transform so that from now on the context is in
  // CocoaPoints again.
  CGContextRestoreGState(aContext);
  if (!painted && [self isOpaque]) {
    // Gecko refused to draw, but we've claimed to be opaque, so we have to
    // draw something--fill with white.
    CGContextSetRGBFillColor(aContext, 1, 1, 1, 1);
    CGContextFillRect(aContext, aRect);
  }

#ifdef DEBUG_UPDATE
  fprintf(stderr, "---- update done ----\n");

#  if 0
  CGContextSetRGBStrokeColor (aContext,
                            ((((unsigned long)self) & 0xff)) / 255.0,
                            ((((unsigned long)self) & 0xff00) >> 8) / 255.0,
                            ((((unsigned long)self) & 0xff0000) >> 16) / 255.0,
                            0.5);
#  endif
  CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
  CGContextSetLineWidth(aContext, 4.0);
  CGContextStrokeRect(aContext, aRect);
#endif
}
@end

nsWindow::nsWindow() : mNativeView(nullptr), mVisible(false), mParent(nullptr) {}

nsWindow::~nsWindow() {
  [mNativeView widgetDestroyed];  // Safe if mNativeView is nil.
  TearDownView();                 // Safe if called twice.
}

void nsWindow::TearDownView() {
  if (!mNativeView) return;

  [mNativeView performSelectorOnMainThread:@selector(delayedTearDown)
                                withObject:nil
                             waitUntilDone:false];
  mNativeView = nil;
}

bool nsWindow::IsTopLevel() {
  return mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog ||
         mWindowType == eWindowType_invisible;
}

//
// nsIWidget
//

nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
                          const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData) {
  ALOG("nsWindow[%p]::Create %p/%p [%d %d %d %d]", (void*)this, (void*)aParent,
       (void*)aNativeParent, aRect.x, aRect.y, aRect.width, aRect.height);
  nsWindow* parent = (nsWindow*)aParent;
  ChildView* nativeParent = (ChildView*)aNativeParent;

  if (parent == nullptr && nativeParent) parent = nativeParent->mGeckoChild;
  if (parent && nativeParent == nullptr) nativeParent = parent->mNativeView;

  // for toplevel windows, bounds are fixed to full screen size
  if (parent == nullptr) {
    if (nsAppShell::gWindow == nil) {
      mBounds = UIKitScreenManager::GetBounds();
    } else {
      CGRect cgRect = [nsAppShell::gWindow bounds];
      mBounds.x = cgRect.origin.x;
      mBounds.y = cgRect.origin.y;
      mBounds.width = cgRect.size.width;
      mBounds.height = cgRect.size.height;
    }
  } else {
    mBounds = aRect;
  }

  ALOG("nsWindow[%p]::Create bounds: %d %d %d %d", (void*)this, mBounds.x, mBounds.y, mBounds.width,
       mBounds.height);

  // Set defaults which can be overriden from aInitData in BaseCreate
  mWindowType = eWindowType_toplevel;
  mBorderStyle = eBorderStyle_default;

  Inherited::BaseCreate(aParent, aInitData);

  NS_ASSERTION(IsTopLevel() || parent, "non top level window doesn't have a parent!");

  mNativeView =
      [[ChildView alloc] initWithFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())
                            geckoChild:this];
  mNativeView.hidden = YES;

  if (parent) {
    parent->mChildren.AppendElement(this);
    mParent = parent;
  }

  if (nativeParent) {
    [nativeParent addSubview:mNativeView];
  } else if (nsAppShell::gWindow) {
    [nsAppShell::gWindow.rootViewController.view addSubview:mNativeView];
  } else {
    [nsAppShell::gTopLevelViews addObject:mNativeView];
  }

  return NS_OK;
}

void nsWindow::Destroy() {
  for (uint32_t i = 0; i < mChildren.Length(); ++i) {
    // why do we still have children?
    mChildren[i]->SetParent(nullptr);
  }

  if (mParent) mParent->mChildren.RemoveElement(this);

  [mNativeView widgetDestroyed];

  nsBaseWidget::Destroy();

  // ReportDestroyEvent();

  TearDownView();

  nsBaseWidget::OnDestroy();

  return NS_OK;
}

nsresult nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config) {
  for (uint32_t i = 0; i < config.Length(); ++i) {
    nsWindow* childWin = (nsWindow*)config[i].mChild.get();
    childWin->Resize(config[i].mBounds.x, config[i].mBounds.y, config[i].mBounds.width,
                     config[i].mBounds.height, false);
  }

  return NS_OK;
}

void nsWindow::Show(bool aState) {
  if (aState != mVisible) {
    mNativeView.hidden = aState ? NO : YES;
    if (aState) {
      UIView* parentView =
          mParent ? mParent->mNativeView : nsAppShell::gWindow.rootViewController.view;
      [parentView bringSubviewToFront:mNativeView];
      [mNativeView setNeedsDisplay];
    }
    mVisible = aState;
  }
}

void nsWindow::Move(double aX, double aY) {
  if (!mNativeView || (mBounds.x == aX && mBounds.y == aY)) return;

  // XXX: handle this
  // The point we have is in Gecko coordinates (origin top-left). Convert
  // it to Cocoa ones (origin bottom-left).
  mBounds.x = aX;
  mBounds.y = aY;

  mNativeView.frame = DevPixelsToUIKitPoints(mBounds, BackingScaleFactor());

  if (mVisible) [mNativeView setNeedsDisplay];

  ReportMoveEvent();
}

void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) {
  BOOL isMoving = (mBounds.x != aX || mBounds.y != aY);
  BOOL isResizing = (mBounds.width != aWidth || mBounds.height != aHeight);
  if (!mNativeView || (!isMoving && !isResizing)) return;

  if (isMoving) {
    mBounds.x = aX;
    mBounds.y = aY;
  }
  if (isResizing) {
    mBounds.width = aWidth;
    mBounds.height = aHeight;
  }

  [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];

  if (mVisible && aRepaint) [mNativeView setNeedsDisplay];

  if (isMoving) ReportMoveEvent();

  if (isResizing) ReportSizeEvent();
}

void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
  if (!mNativeView || (mBounds.width == aWidth && mBounds.height == aHeight)) return;

  mBounds.width = aWidth;
  mBounds.height = aHeight;

  [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];

  if (mVisible && aRepaint) [mNativeView setNeedsDisplay];

  ReportSizeEvent();
}

void nsWindow::SetSizeMode(nsSizeMode aMode) {
  if (aMode == static_cast<int32_t>(mSizeMode)) {
    return;
  }

  mSizeMode = static_cast<nsSizeMode>(aMode);
  if (aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen) {
    // Resize to fill screen
    nsBaseWidget::InfallibleMakeFullScreen(true);
  }
  ReportSizeModeEvent(aMode);
}

void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
  if (!mNativeView || !mVisible) return;

  MOZ_RELEASE_ASSERT(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
                     "Shouldn't need to invalidate with accelerated OMTC layers!");

  [mNativeView setNeedsLayout];
  [mNativeView setNeedsDisplayInRect:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
}

nsresult nsWindow::SetFocus(bool aRaise) {
  [[mNativeView window] makeKeyWindow];
  [mNativeView becomeFirstResponder];
  return NS_OK;
}

void nsWindow::WillPaintWindow() {
  if (mWidgetListener) {
    mWidgetListener->WillPaintWindow(this);
  }
}

bool nsWindow::PaintWindow(LayoutDeviceIntRegion aRegion) {
  if (!mWidgetListener) return false;

  bool returnValue = false;
  returnValue = mWidgetListener->PaintWindow(this, aRegion);

  if (mWidgetListener) {
    mWidgetListener->DidPaintWindow();
  }

  return returnValue;
}

void nsWindow::ReportMoveEvent() { NotifyWindowMoved(mBounds.x, mBounds.y); }

void nsWindow::ReportSizeModeEvent(nsSizeMode aMode) {
  if (mWidgetListener) {
    // This is terrible.
    nsSizeMode theMode;
    switch (aMode) {
      case nsSizeMode_Maximized:
        theMode = nsSizeMode_Maximized;
        break;
      case nsSizeMode_Fullscreen:
        theMode = nsSizeMode_Fullscreen;
        break;
      default:
        return;
    }
    mWidgetListener->SizeModeChanged(theMode);
  }
}

void nsWindow::ReportSizeEvent() {
  if (mWidgetListener) {
    LayoutDeviceIntRect innerBounds = GetClientBounds();
    mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
  }
}

LayoutDeviceIntRect nsWindow::GetScreenBounds() {
  return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
}

LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
  LayoutDeviceIntPoint offset(0, 0);
  if (mParent) {
    offset = mParent->WidgetToScreenOffset();
  }

  CGPoint temp = [mNativeView convertPoint:temp toView:nil];

  if (!mParent && nsAppShell::gWindow) {
    // convert to screen coords
    temp = [nsAppShell::gWindow convertPoint:temp toWindow:nil];
  }

  offset.x += temp.x;
  offset.y += temp.y;

  return offset;
}

nsresult nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsEventStatus& aStatus) {
  aStatus = nsEventStatus_eIgnore;
  nsCOMPtr<nsIWidget> kungFuDeathGrip(aEvent->mWidget);

  if (mWidgetListener) aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);

  return NS_OK;
}

void nsWindow::SetInputContext(const InputContext& aContext, const InputContextAction& aAction) {
  // TODO: actually show VKB
  mInputContext = aContext;
}

mozilla::widget::InputContext nsWindow::GetInputContext() { return mInputContext; }

void nsWindow::SetBackgroundColor(const nscolor& aColor) {
  mNativeView.backgroundColor = [UIColor colorWithRed:NS_GET_R(aColor)
                                                green:NS_GET_G(aColor)
                                                 blue:NS_GET_B(aColor)
                                                alpha:NS_GET_A(aColor)];
}

void* nsWindow::GetNativeData(uint32_t aDataType) {
  void* retVal = nullptr;

  switch (aDataType) {
    case NS_NATIVE_WIDGET:
    case NS_NATIVE_DISPLAY:
      retVal = (void*)mNativeView;
      break;

    case NS_NATIVE_WINDOW:
      retVal = [mNativeView window];
      break;

    case NS_NATIVE_GRAPHIC:
      NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a UIKit child view!");
      break;

    case NS_NATIVE_OFFSETX:
      retVal = 0;
      break;

    case NS_NATIVE_OFFSETY:
      retVal = 0;
      break;

    case NS_NATIVE_PLUGIN_PORT:
      // not implemented
      break;

    case NS_RAW_NATIVE_IME_CONTEXT:
      retVal = GetPseudoIMEContext();
      if (retVal) {
        break;
      }
      retVal = NS_ONLY_ONE_NATIVE_IME_CONTEXT;
      break;
  }

  return retVal;
}

CGFloat nsWindow::BackingScaleFactor() {
  if (mNativeView) {
    return [mNativeView contentScaleFactor];
  }
  return [UIScreen mainScreen].scale;
}

int32_t nsWindow::RoundsWidgetCoordinatesTo() {
  if (BackingScaleFactor() == 2.0) {
    return 2;
  }
  return 1;
}

already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
  nsCOMPtr<nsIWidget> window = new nsWindow();
  return window.forget();
}

already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
  nsCOMPtr<nsIWidget> window = new nsWindow();
  return window.forget();
}
