// Copyright (c) 2012 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 "extensions/browser/api/alarms/alarms_api.h"

#include <stddef.h>

#include "base/strings/string_number_conversions.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/values.h"
#include "extensions/browser/api/alarms/alarm_manager.h"
#include "extensions/browser/api/alarms/alarms_api_constants.h"
#include "extensions/common/api/alarms.h"
#include "extensions/common/error_utils.h"

namespace extensions {

namespace alarms = api::alarms;

namespace {

const char kDefaultAlarmName[] = "";
const char kBothRelativeAndAbsoluteTime[] =
    "Cannot set both when and delayInMinutes.";
const char kNoScheduledTime[] =
    "Must set at least one of when, delayInMinutes, or periodInMinutes.";

bool ValidateAlarmCreateInfo(const std::string& alarm_name,
                             const alarms::AlarmCreateInfo& create_info,
                             const Extension* extension,
                             std::string* error,
                             std::vector<std::string>* warnings) {
  if (create_info.delay_in_minutes.get() && create_info.when.get()) {
    *error = kBothRelativeAndAbsoluteTime;
    return false;
  }
  if (create_info.delay_in_minutes == NULL && create_info.when == NULL &&
      create_info.period_in_minutes == NULL) {
    *error = kNoScheduledTime;
    return false;
  }

  // Users can always use an absolute timeout to request an arbitrarily-short or
  // negative delay.  We won't honor the short timeout, but we can't check it
  // and warn the user because it would introduce race conditions (say they
  // compute a long-enough timeout, but then the call into the alarms interface
  // gets delayed past the boundary).  However, it's still worth warning about
  // relative delays that are shorter than we'll honor.
  if (create_info.delay_in_minutes.get()) {
    if (*create_info.delay_in_minutes <
        alarms_api_constants::kReleaseDelayMinimum) {
      if (Manifest::IsUnpackedLocation(extension->location())) {
        warnings->push_back(ErrorUtils::FormatErrorMessage(
            alarms_api_constants::kWarningMinimumDevDelay, alarm_name));
      } else {
        warnings->push_back(ErrorUtils::FormatErrorMessage(
            alarms_api_constants::kWarningMinimumReleaseDelay, alarm_name));
      }
    }
  }
  if (create_info.period_in_minutes.get()) {
    if (*create_info.period_in_minutes <
        alarms_api_constants::kReleaseDelayMinimum) {
      if (Manifest::IsUnpackedLocation(extension->location())) {
        warnings->push_back(ErrorUtils::FormatErrorMessage(
            alarms_api_constants::kWarningMinimumDevPeriod, alarm_name));
      } else {
        warnings->push_back(ErrorUtils::FormatErrorMessage(
            alarms_api_constants::kWarningMinimumReleasePeriod, alarm_name));
      }
    }
  }

  return true;
}

}  // namespace

AlarmsCreateFunction::AlarmsCreateFunction()
    : clock_(base::DefaultClock::GetInstance()) {}

AlarmsCreateFunction::AlarmsCreateFunction(base::Clock* clock)
    : clock_(clock) {}

AlarmsCreateFunction::~AlarmsCreateFunction() {}

ExtensionFunction::ResponseAction AlarmsCreateFunction::Run() {
  std::unique_ptr<alarms::Create::Params> params(
      alarms::Create::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  const std::string& alarm_name =
      params->name.get() ? *params->name : kDefaultAlarmName;
  std::vector<std::string> warnings;
  std::string error;
  if (!ValidateAlarmCreateInfo(alarm_name, params->alarm_info, extension(),
                               &error, &warnings)) {
    return RespondNow(Error(error));
  }
  for (std::vector<std::string>::const_iterator it = warnings.begin();
       it != warnings.end(); ++it)
    WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, *it);

  const int kSecondsPerMinute = 60;
  base::TimeDelta granularity =
      base::TimeDelta::FromSecondsD(
          (Manifest::IsUnpackedLocation(extension()->location())
               ? alarms_api_constants::kDevDelayMinimum
               : alarms_api_constants::kReleaseDelayMinimum)) *
      kSecondsPerMinute;

  std::unique_ptr<Alarm> alarm(
      new Alarm(alarm_name, params->alarm_info, granularity, clock_->Now()));
  AlarmManager::Get(browser_context())
      ->AddAlarm(extension_id(), std::move(alarm),
                 base::BindOnce(&AlarmsCreateFunction::Callback, this));

  // AddAlarm might have already responded.
  return did_respond() ? AlreadyResponded() : RespondLater();
}

void AlarmsCreateFunction::Callback() {
  Respond(NoArguments());
}

ExtensionFunction::ResponseAction AlarmsGetFunction::Run() {
  std::unique_ptr<alarms::Get::Params> params(
      alarms::Get::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  std::string name = params->name.get() ? *params->name : kDefaultAlarmName;
  AlarmManager::Get(browser_context())
      ->GetAlarm(extension_id(), name,
                 base::BindOnce(&AlarmsGetFunction::Callback, this, name));

  // GetAlarm might have already responded.
  return did_respond() ? AlreadyResponded() : RespondLater();
}

void AlarmsGetFunction::Callback(const std::string& name,
                                 extensions::Alarm* alarm) {
  if (alarm)
    Respond(ArgumentList(alarms::Get::Results::Create(*alarm->js_alarm)));
  else
    Respond(NoArguments());
}

ExtensionFunction::ResponseAction AlarmsGetAllFunction::Run() {
  AlarmManager::Get(browser_context())
      ->GetAllAlarms(extension_id(),
                     base::BindOnce(&AlarmsGetAllFunction::Callback, this));
  // GetAllAlarms might have already responded.
  return did_respond() ? AlreadyResponded() : RespondLater();
}

void AlarmsGetAllFunction::Callback(const AlarmList* alarms) {
  auto alarms_value = std::make_unique<base::ListValue>();
  if (alarms) {
    for (const std::unique_ptr<Alarm>& alarm : *alarms)
      alarms_value->Append(alarm->js_alarm->ToValue());
  }
  Respond(OneArgument(std::move(alarms_value)));
}

ExtensionFunction::ResponseAction AlarmsClearFunction::Run() {
  std::unique_ptr<alarms::Clear::Params> params(
      alarms::Clear::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  std::string name = params->name.get() ? *params->name : kDefaultAlarmName;
  AlarmManager::Get(browser_context())
      ->RemoveAlarm(extension_id(), name,
                    base::BindOnce(&AlarmsClearFunction::Callback, this, name));

  // RemoveAlarm might have already responded.
  return did_respond() ? AlreadyResponded() : RespondLater();
}

void AlarmsClearFunction::Callback(const std::string& name, bool success) {
  Respond(OneArgument(std::make_unique<base::Value>(success)));
}

ExtensionFunction::ResponseAction AlarmsClearAllFunction::Run() {
  AlarmManager::Get(browser_context())
      ->RemoveAllAlarms(
          extension_id(),
          base::BindOnce(&AlarmsClearAllFunction::Callback, this));
  // RemoveAllAlarms might have already responded.
  return did_respond() ? AlreadyResponded() : RespondLater();
}

void AlarmsClearAllFunction::Callback() {
  Respond(OneArgument(std::make_unique<base::Value>(true)));
}

}  // namespace extensions
