//
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "update_engine/update_manager/real_system_provider.h"

#include <base/bind.h>
#include <base/callback.h>
#include <base/logging.h>
#include <base/time/time.h>
#if USE_CHROME_KIOSK_APP
#include <libcros/dbus-proxies.h>
#endif  // USE_CHROME_KIOSK_APP

#include "update_engine/common/utils.h"
#include "update_engine/update_manager/generic_variables.h"
#include "update_engine/update_manager/variable.h"

using std::string;

namespace chromeos_update_manager {

namespace {

// The maximum number of consecutive failures before returning the default
// constructor value for T instead of failure.
const int kRetryPollVariableMaxRetry = 5;

// The polling interval to be used whenever GetValue() returns an error.
const int kRetryPollVariableRetryIntervalSeconds = 5 * 60;

// The RetryPollVariable variable is a polling variable that allows the function
// returning the value to fail a few times and shortens the polling rate when
// that happens.
template <typename T>
class RetryPollVariable : public Variable<T> {
 public:
  RetryPollVariable(const string& name,
                    const base::TimeDelta poll_interval,
                    base::Callback<bool(T* res)> func)
      : Variable<T>(name, poll_interval),
        func_(func),
        base_interval_(poll_interval) {
    DCHECK_LT(kRetryPollVariableRetryIntervalSeconds,
              base_interval_.InSeconds());
  }

 protected:
  // Variable override.
  const T* GetValue(base::TimeDelta /* timeout */,
                    string* /* errmsg */) override {
    std::unique_ptr<T> result(new T());
    if (!func_.Run(result.get())) {
      if (failed_attempts_ >= kRetryPollVariableMaxRetry) {
        // Give up on the retries, set back the desired polling interval and
        // return the default.
        this->SetPollInterval(base_interval_);
        return result.release();
      }
      this->SetPollInterval(
          base::TimeDelta::FromSeconds(kRetryPollVariableRetryIntervalSeconds));
      failed_attempts_++;
      return nullptr;
    }
    failed_attempts_ = 0;
    this->SetPollInterval(base_interval_);
    return result.release();
  }

 private:
  // The function to be called, stored as a base::Callback.
  base::Callback<bool(T*)> func_;

  // The desired polling interval when |func_| works and returns true.
  base::TimeDelta base_interval_;

  // The number of consecutive failed attempts made.
  int failed_attempts_ = 0;

  DISALLOW_COPY_AND_ASSIGN(RetryPollVariable);
};

}  // namespace

bool RealSystemProvider::Init() {
  var_is_normal_boot_mode_.reset(
      new ConstCopyVariable<bool>("is_normal_boot_mode",
                                  hardware_->IsNormalBootMode()));

  var_is_official_build_.reset(
      new ConstCopyVariable<bool>("is_official_build",
                                  hardware_->IsOfficialBuild()));

  var_is_oobe_complete_.reset(
      new CallCopyVariable<bool>(
          "is_oobe_complete",
          base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
                     base::Unretained(hardware_), nullptr)));

  var_num_slots_.reset(
      new ConstCopyVariable<unsigned int>(
          "num_slots", boot_control_->GetNumSlots()));

  var_kiosk_required_platform_version_.reset(new RetryPollVariable<string>(
      "kiosk_required_platform_version",
      base::TimeDelta::FromHours(5),  // Same as Chrome's CWS poll.
      base::Bind(&RealSystemProvider::GetKioskAppRequiredPlatformVersion,
                 base::Unretained(this))));

  return true;
}

bool RealSystemProvider::GetKioskAppRequiredPlatformVersion(
    string* required_platform_version) {
#if USE_CHROME_KIOSK_APP
  brillo::ErrorPtr error;
  if (!libcros_proxy_->GetKioskAppRequiredPlatformVersion(
          required_platform_version, &error)) {
    LOG(WARNING) << "Failed to get kiosk required platform version";
    required_platform_version->clear();
    return false;
  }
#endif  // USE_CHROME_KIOSK_APP

  return true;
}

}  // namespace chromeos_update_manager