/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/UIApplication.h>
#import <UIKit/UIScreen.h>
#import <UIKit/UIWindow.h>
#import <UIKit/UIViewController.h>

#include "nsAppShell.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
#include "nsString.h"
#include "nsIRollupListener.h"
#include "nsIWidget.h"
#include "nsThreadUtils.h"
#include "nsIWindowMediator.h"
#include "nsMemoryPressure.h"
#include "nsServiceManagerUtils.h"
#include "nsIInterfaceRequestor.h"
#include "nsIWebBrowserChrome.h"

nsAppShell *nsAppShell::gAppShell = NULL;
UIWindow *nsAppShell::gWindow = nil;
NSMutableArray *nsAppShell::gTopLevelViews = [[NSMutableArray alloc] init];

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

// ViewController
@interface ViewController : UIViewController
@end


@implementation ViewController

- (void)loadView {
    ALOG("[ViewController loadView]");
    CGRect r = {{0, 0}, {100, 100}};
    self.view = [[UIView alloc] initWithFrame:r];
    [self.view setBackgroundColor:[UIColor lightGrayColor]];
    // add all of the top level views as children
    for (UIView* v in nsAppShell::gTopLevelViews) {
        ALOG("[ViewController.view addSubView:%p]", v);
        [self.view addSubview:v];
    }
    [nsAppShell::gTopLevelViews release];
    nsAppShell::gTopLevelViews = nil;
}
@end

// AppShellDelegate
//
// Acts as a delegate for the UIApplication

@interface AppShellDelegate : NSObject <UIApplicationDelegate> {
}
@property (strong, nonatomic) UIWindow *window;
@end

@implementation AppShellDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ALOG("[AppShellDelegate application:didFinishLaunchingWithOptions:]");
  // We only create one window, since we can only display one window at
  // a time anyway. Also, iOS 4 fails to display UIWindows if you
  // create them before calling UIApplicationMain, so this makes more sense.
  nsAppShell::gWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] retain];
  self.window = nsAppShell::gWindow;

  self.window.rootViewController = [[ViewController alloc] init];

  // just to make things more visible for now
  nsAppShell::gWindow.backgroundColor = [UIColor blueColor];
  [nsAppShell::gWindow makeKeyAndVisible];

  return YES;
}

- (void)applicationWillTerminate:(UIApplication *)application
{
  ALOG("[AppShellDelegate applicationWillTerminate:]");
  nsAppShell::gAppShell->WillTerminate();
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
  ALOG("[AppShellDelegate applicationDidBecomeActive:]");
}

- (void)applicationWillResignActive:(UIApplication *)application
{
  ALOG("[AppShellDelegate applicationWillResignActive:]");
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
  ALOG("[AppShellDelegate applicationDidReceiveMemoryWarning:]");
  NS_DispatchMemoryPressure(MemPressure_New);
}
@end

// nsAppShell implementation

NS_IMETHODIMP
nsAppShell::ResumeNative(void)
{
  return nsBaseAppShell::ResumeNative();
}

nsAppShell::nsAppShell()
  : mAutoreleasePool(NULL),
    mDelegate(NULL),
    mCFRunLoop(NULL),
    mCFRunLoopSource(NULL),
    mTerminated(false),
    mNotifiedWillTerminate(false)
{
  gAppShell = this;
}

nsAppShell::~nsAppShell()
{
  if (mAutoreleasePool) {
    [mAutoreleasePool release];
    mAutoreleasePool = NULL;
  }

  if (mCFRunLoop) {
    if (mCFRunLoopSource) {
      ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
                              kCFRunLoopCommonModes);
      ::CFRelease(mCFRunLoopSource);
    }
    ::CFRelease(mCFRunLoop);
  }

  gAppShell = NULL;
}

// Init
//
// public
nsresult
nsAppShell::Init()
{
  mAutoreleasePool = [[NSAutoreleasePool alloc] init];

  // Add a CFRunLoopSource to the main native run loop.  The source is
  // responsible for interrupting the run loop when Gecko events are ready.

  mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
  NS_ENSURE_STATE(mCFRunLoop);
  ::CFRetain(mCFRunLoop);

  CFRunLoopSourceContext context;
  bzero(&context, sizeof(context));
  // context.version = 0;
  context.info = this;
  context.perform = ProcessGeckoEvents;

  mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
  NS_ENSURE_STATE(mCFRunLoopSource);

  ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);

  return nsBaseAppShell::Init();
}

// ProcessGeckoEvents
//
// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
// signalled from ScheduleNativeEventCallback.
//
// protected static
void
nsAppShell::ProcessGeckoEvents(void* aInfo)
{
  nsAppShell* self = static_cast<nsAppShell*> (aInfo);
  self->NativeEventCallback();
  self->Release();
}

// WillTerminate
//
// public
void
nsAppShell::WillTerminate()
{
  mNotifiedWillTerminate = true;
  if (mTerminated)
    return;
  mTerminated = true;
  // We won't get another chance to process events
  NS_ProcessPendingEvents(NS_GetCurrentThread());

  // Unless we call nsBaseAppShell::Exit() here, it might not get called
  // at all.
  nsBaseAppShell::Exit();
}

// ScheduleNativeEventCallback
//
// protected virtual
void
nsAppShell::ScheduleNativeEventCallback()
{
  if (mTerminated)
    return;

  NS_ADDREF_THIS();

  // This will invoke ProcessGeckoEvents on the main thread.
  ::CFRunLoopSourceSignal(mCFRunLoopSource);
  ::CFRunLoopWakeUp(mCFRunLoop);
}

// ProcessNextNativeEvent
//
// protected virtual
bool
nsAppShell::ProcessNextNativeEvent(bool aMayWait)
{
  if (mTerminated)
    return false;

  NSString* currentMode = nil;
  NSDate* waitUntil = nil;
  if (aMayWait)
    waitUntil = [NSDate distantFuture];
  NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];

  BOOL eventProcessed = NO;
  do {
    currentMode = [currentRunLoop currentMode];
    if (!currentMode)
      currentMode = NSDefaultRunLoopMode;

    if (aMayWait)
      eventProcessed = [currentRunLoop runMode:currentMode beforeDate:waitUntil];
    else
      [currentRunLoop acceptInputForMode:currentMode beforeDate:waitUntil];
  } while(eventProcessed && aMayWait);

  return false;
}

// Run
//
// public
NS_IMETHODIMP
nsAppShell::Run(void)
{
  ALOG("nsAppShell::Run");
  char argv[1][4] = {"app"};
  UIApplicationMain(1, (char**)argv, nil, @"AppShellDelegate");
  // UIApplicationMain doesn't exit. :-(
  return NS_OK;
}

NS_IMETHODIMP
nsAppShell::Exit(void)
{
  if (mTerminated)
    return NS_OK;

  mTerminated = true;
  return nsBaseAppShell::Exit();
}
