/* 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 <Cocoa/Cocoa.h>
#import <CoreServices/CoreServices.h>

#include "MacAutoreleasePool.h"
#include "nsCOMPtr.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsIWindowMediator.h"
#include "nsIWidget.h"
#include "nsICommandLineRunner.h"
#include "nsICommandLine.h"
#include "nsCommandLine.h"
#include "nsIDocShell.h"
#include "nsMacRemoteServer.h"
#include "nsXPCOM.h"
#include "RemoteUtils.h"

CFDataRef messageServerCallback(CFMessagePortRef aLocal, int32_t aMsgid,
                                CFDataRef aData, void* aInfo) {
  // One of the clients submitted a structure.
  static_cast<nsMacRemoteServer*>(aInfo)->HandleCommandLine(aData);

  return nullptr;
}

// aData contains serialized Dictionary, which in turn contains command line
// arguments
void nsMacRemoteServer::HandleCommandLine(CFDataRef aData) {
  mozilla::MacAutoreleasePool pool;

  if (aData) {
    NSDictionary* dict =
        [NSKeyedUnarchiver unarchiveObjectWithData:(NSData*)aData];
    if (dict && [dict isKindOfClass:[NSDictionary class]]) {
      NSArray* args = dict[@"args"];
      if (!args) {
        NS_ERROR("Wrong parameters passed to the Remote Server");
        return;
      }

      nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());

      // Converting Objective-C array into a C array,
      // which nsICommandLineRunner understands.
      int argc = [args count];
      const char** argv = new const char*[argc];
      for (int i = 0; i < argc; i++) {
        const char* arg = [[args objectAtIndex:i] UTF8String];
        argv[i] = arg;
      }

      nsresult rv =
          cmdLine->Init(argc, argv, nullptr, nsICommandLine::STATE_REMOTE_AUTO);

      // Cleaning up C array.
      delete[] argv;

      if (NS_FAILED(rv)) {
        NS_ERROR("Error initializing command line.");
        return;
      }

      // Processing the command line, passed from a remote instance
      // in the current instance.
      cmdLine->Run();

      NSNumber* raise = dict[@"raise"];
      if (!raise || [raise boolValue]) {
        // Activating the application brings the most recent window to the
        // foreground.
        ProcessSerialNumber psn;
        if (::GetCurrentProcess(&psn) == noErr) {
          ::SetFrontProcess(&psn);
        }
      }
    }
  }
}

nsresult nsMacRemoteServer::Startup(const char* aAppName,
                                    const char* aProfileName) {
  // This is the first instance ever.
  // Let's register a notification listener here,
  // In case future instances would want to notify us about command line
  // arguments passed to them. Note, that if mozilla process is restarting, we
  // still need to register for notifications.

  mozilla::MacAutoreleasePool pool;

  nsString className;
  BuildClassName(aAppName, aProfileName, className);

  NSString* serverNameString = [NSString
      stringWithCharacters:reinterpret_cast<const unichar*>(className.get())
                    length:className.Length()];

  CFMessagePortContext context;
  context.copyDescription = nullptr;
  context.info = this;
  context.release = nullptr;
  context.retain = nullptr;
  context.version = NULL;
  mMessageServer =
      CFMessagePortCreateLocal(nullptr, (CFStringRef)serverNameString,
                               messageServerCallback, &context, nullptr);
  if (!mMessageServer) {
    return NS_ERROR_FAILURE;
  }
  mRunLoopSource = CFMessagePortCreateRunLoopSource(nullptr, mMessageServer, 0);
  if (!mRunLoopSource) {
    CFRelease(mMessageServer);
    mMessageServer = nullptr;
    return NS_ERROR_FAILURE;
  }
  CFRunLoopRef runLoop = CFRunLoopGetMain();
  CFRunLoopAddSource(runLoop, mRunLoopSource, kCFRunLoopDefaultMode);

  return NS_OK;
}

void nsMacRemoteServer::Shutdown() {
  // 1) Invalidate server connection
  if (mMessageServer) {
    CFMessagePortInvalidate(mMessageServer);
  }

  // 2) Release run loop source
  if (mRunLoopSource) {
    CFRelease(mRunLoopSource);
    mRunLoopSource = nullptr;
  }

  // 3) Release server connection
  if (mMessageServer) {
    CFRelease(mMessageServer);
    mMessageServer = nullptr;
  }
}
