/* -*- Mode: C++; tab-width: 2; 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/. */

#include "Hal.h"
#include "HalLog.h"

#include <unistd.h>
#include <sys/reboot.h>
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "MainThreadUtils.h"

#if defined(MOZ_WIDGET_GONK)
#include "cutils/android_reboot.h"
#include "cutils/properties.h"
#endif

namespace mozilla {
namespace hal_impl {

#if (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19)
static void
PowerCtl(const char* aValue, int aCmd)
{
  // this invokes init's powerctl builtin via /init.rc
  property_set("sys.powerctl", aValue);
  // device should reboot in few moments, but if it doesn't - call
  // android_reboot() to make sure that init isn't stuck somewhere
  sleep(10);
  HAL_LOG("Powerctl call takes too long, forcing %s.", aValue);
  android_reboot(aCmd, 0, nullptr);
}
#endif

void
Reboot()
{
  if (NS_IsMainThread()) {
    nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
    if (obsServ) {
      obsServ->NotifyObservers(nullptr, "system-reboot", nullptr);
    }
  }

#if !defined(MOZ_WIDGET_GONK)
  sync();
  reboot(RB_AUTOBOOT);
#elif (ANDROID_VERSION < 19)
  android_reboot(ANDROID_RB_RESTART, 0, nullptr);
#else
  PowerCtl("reboot", ANDROID_RB_RESTART);
#endif
}

void
PowerOff()
{
  if (NS_IsMainThread()) {
    nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
    if (obsServ) {
      obsServ->NotifyObservers(nullptr, "system-power-off", nullptr);
    }
  }

#if !defined(MOZ_WIDGET_GONK)
  sync();
  reboot(RB_POWER_OFF);
#elif (ANDROID_VERSION < 19)
  android_reboot(ANDROID_RB_POWEROFF, 0, nullptr);
#else
  PowerCtl("shutdown", ANDROID_RB_POWEROFF);
#endif
}

// Structure to specify how watchdog pthread is going to work.
typedef struct watchdogParam
{
  hal::ShutdownMode mode; // Specify how to shutdown the system.
  int32_t timeoutSecs;    // Specify the delayed seconds to shutdown the system.

  watchdogParam(hal::ShutdownMode aMode, int32_t aTimeoutSecs)
    : mode(aMode), timeoutSecs(aTimeoutSecs) {}
} watchdogParam_t;

// Function to complusively shut down the system with a given mode.
static void
QuitHard(hal::ShutdownMode aMode)
{
  switch (aMode)
  {
    case hal::eHalShutdownMode_PowerOff:
      PowerOff();
      break;
    case hal::eHalShutdownMode_Reboot:
      Reboot();
      break;
    case hal::eHalShutdownMode_Restart:
      // Don't let signal handlers affect forced shutdown.
      kill(0, SIGKILL);
      // If we can't SIGKILL our process group, something is badly
      // wrong.  Trying to deliver a catch-able signal to ourselves can
      // invoke signal handlers and might cause problems.  So try
      // _exit() and hope we go away.
      _exit(1);
      break;
    default:
      MOZ_CRASH();
  }
}

// Function to complusively shut down the system with a given mode when timeout.
static void*
ForceQuitWatchdog(void* aParamPtr)
{
  watchdogParam_t* paramPtr = reinterpret_cast<watchdogParam_t*>(aParamPtr);
  if (paramPtr->timeoutSecs > 0 && paramPtr->timeoutSecs <= 30) {
    // If we shut down normally before the timeout, this thread will
    // be harmlessly reaped by the OS.
    TimeStamp deadline =
      (TimeStamp::Now() + TimeDuration::FromSeconds(paramPtr->timeoutSecs));
    while (true) {
      TimeDuration remaining = (deadline - TimeStamp::Now());
      int sleepSeconds = int(remaining.ToSeconds());
      if (sleepSeconds <= 0) {
        break;
      }
      sleep(sleepSeconds);
    }
  }
  hal::ShutdownMode mode = paramPtr->mode;
  delete paramPtr;
  QuitHard(mode);
  return nullptr;
}

void
StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs)
{
  // Force-quits are intepreted a little more ferociously on Gonk,
  // because while Gecko is in the process of shutting down, the user
  // can't call 911, for example.  And if we hang on shutdown, bad
  // things happen.  So, make sure that doesn't happen.
  if (aTimeoutSecs <= 0) {
    return;
  }

  // Use a raw pthread here to insulate ourselves from bugs in other
  // Gecko code that we're trying to protect!
  // 
  // Note that we let the watchdog in charge of releasing |paramPtr|
  // if the pthread is successfully created.
  watchdogParam_t* paramPtr = new watchdogParam_t(aMode, aTimeoutSecs);
  pthread_t watchdog;
  if (pthread_create(&watchdog, nullptr,
                     ForceQuitWatchdog,
                     reinterpret_cast<void*>(paramPtr))) {
    // Better safe than sorry.
    delete paramPtr;
    QuitHard(aMode);
  }
  // The watchdog thread is off and running now.
}

} // hal_impl
} // mozilla
