//
// 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_updater_provider.h"

#include <inttypes.h>

#include <string>

#include <base/bind.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <update_engine/dbus-constants.h>

#include "update_engine/common/clock_interface.h"
#include "update_engine/common/prefs.h"
#include "update_engine/omaha_request_params.h"
#include "update_engine/update_attempter.h"

using base::StringPrintf;
using base::Time;
using base::TimeDelta;
using chromeos_update_engine::OmahaRequestParams;
using chromeos_update_engine::SystemState;
using std::string;

namespace chromeos_update_manager {

// A templated base class for all update related variables. Provides uniform
// construction and a system state handle.
template<typename T>
class UpdaterVariableBase : public Variable<T> {
 public:
  UpdaterVariableBase(const string& name, VariableMode mode,
                      SystemState* system_state)
      : Variable<T>(name, mode), system_state_(system_state) {}

 protected:
  // The system state used for pulling information from the updater.
  inline SystemState* system_state() const { return system_state_; }

 private:
  SystemState* const system_state_;
};

// Helper class for issuing a GetStatus() to the UpdateAttempter.
class GetStatusHelper {
 public:
  GetStatusHelper(SystemState* system_state, string* errmsg) {
    is_success_ = system_state->update_attempter()->GetStatus(
        &last_checked_time_, &progress_, &update_status_, &new_version_,
        &payload_size_);
    if (!is_success_ && errmsg)
      *errmsg = "Failed to get a status update from the update engine";
  }

  inline bool is_success() { return is_success_; }
  inline int64_t last_checked_time() { return last_checked_time_; }
  inline double progress() { return progress_; }
  inline const string& update_status() { return update_status_; }
  inline const string& new_version() { return new_version_; }
  inline int64_t payload_size() { return payload_size_; }

 private:
  bool is_success_;
  int64_t last_checked_time_;
  double progress_;
  string update_status_;
  string new_version_;
  int64_t payload_size_;
};

// A variable reporting the time when a last update check was issued.
class LastCheckedTimeVariable : public UpdaterVariableBase<Time> {
 public:
  LastCheckedTimeVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}

 private:
  const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    GetStatusHelper raw(system_state(), errmsg);
    if (!raw.is_success())
      return nullptr;

    return new Time(Time::FromTimeT(raw.last_checked_time()));
  }

  DISALLOW_COPY_AND_ASSIGN(LastCheckedTimeVariable);
};

// A variable reporting the update (download) progress as a decimal fraction
// between 0.0 and 1.0.
class ProgressVariable : public UpdaterVariableBase<double> {
 public:
  ProgressVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<double>(name, kVariableModePoll, system_state) {}

 private:
  const double* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    GetStatusHelper raw(system_state(), errmsg);
    if (!raw.is_success())
      return nullptr;

    if (raw.progress() < 0.0 || raw.progress() > 1.0) {
      if (errmsg) {
        *errmsg = StringPrintf("Invalid progress value received: %f",
                               raw.progress());
      }
      return nullptr;
    }

    return new double(raw.progress());
  }

  DISALLOW_COPY_AND_ASSIGN(ProgressVariable);
};

// A variable reporting the stage in which the update process is.
class StageVariable : public UpdaterVariableBase<Stage> {
 public:
  StageVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<Stage>(name, kVariableModePoll, system_state) {}

 private:
  struct CurrOpStrToStage {
    const char* str;
    Stage stage;
  };
  static const CurrOpStrToStage curr_op_str_to_stage[];

  // Note: the method is defined outside the class so arraysize can work.
  const Stage* GetValue(TimeDelta /* timeout */, string* errmsg) override;

  DISALLOW_COPY_AND_ASSIGN(StageVariable);
};

const StageVariable::CurrOpStrToStage StageVariable::curr_op_str_to_stage[] = {
  {update_engine::kUpdateStatusIdle, Stage::kIdle},
  {update_engine::kUpdateStatusCheckingForUpdate, Stage::kCheckingForUpdate},
  {update_engine::kUpdateStatusUpdateAvailable, Stage::kUpdateAvailable},
  {update_engine::kUpdateStatusDownloading, Stage::kDownloading},
  {update_engine::kUpdateStatusVerifying, Stage::kVerifying},
  {update_engine::kUpdateStatusFinalizing, Stage::kFinalizing},
  {update_engine::kUpdateStatusUpdatedNeedReboot, Stage::kUpdatedNeedReboot},
  {  // NOLINT(whitespace/braces)
    update_engine::kUpdateStatusReportingErrorEvent,
    Stage::kReportingErrorEvent
  },
  {update_engine::kUpdateStatusAttemptingRollback, Stage::kAttemptingRollback},
};

const Stage* StageVariable::GetValue(TimeDelta /* timeout */,
                                     string* errmsg) {
  GetStatusHelper raw(system_state(), errmsg);
  if (!raw.is_success())
    return nullptr;

  for (auto& key_val : curr_op_str_to_stage)
    if (raw.update_status() == key_val.str)
      return new Stage(key_val.stage);

  if (errmsg)
    *errmsg = string("Unknown update status: ") + raw.update_status();
  return nullptr;
}

// A variable reporting the version number that an update is updating to.
class NewVersionVariable : public UpdaterVariableBase<string> {
 public:
  NewVersionVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}

 private:
  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    GetStatusHelper raw(system_state(), errmsg);
    if (!raw.is_success())
      return nullptr;

    return new string(raw.new_version());
  }

  DISALLOW_COPY_AND_ASSIGN(NewVersionVariable);
};

// A variable reporting the size of the update being processed in bytes.
class PayloadSizeVariable : public UpdaterVariableBase<int64_t> {
 public:
  PayloadSizeVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<int64_t>(name, kVariableModePoll, system_state) {}

 private:
  const int64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    GetStatusHelper raw(system_state(), errmsg);
    if (!raw.is_success())
      return nullptr;

    if (raw.payload_size() < 0) {
      if (errmsg)
        *errmsg = string("Invalid payload size: %" PRId64, raw.payload_size());
      return nullptr;
    }

    return new int64_t(raw.payload_size());
  }

  DISALLOW_COPY_AND_ASSIGN(PayloadSizeVariable);
};

// A variable reporting the point in time an update last completed in the
// current boot cycle.
//
// TODO(garnold) In general, both the current boottime and wallclock time
// readings should come from the time provider and be moderated by the
// evaluation context, so that they are uniform throughout the evaluation of a
// policy request.
class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> {
 public:
  UpdateCompletedTimeVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}

 private:
  const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    Time update_boottime;
    if (!system_state()->update_attempter()->GetBootTimeAtUpdate(
            &update_boottime)) {
      if (errmsg)
        *errmsg = "Update completed time could not be read";
      return nullptr;
    }

    chromeos_update_engine::ClockInterface* clock = system_state()->clock();
    Time curr_boottime = clock->GetBootTime();
    if (curr_boottime < update_boottime) {
      if (errmsg)
        *errmsg = "Update completed time more recent than current time";
      return nullptr;
    }
    TimeDelta duration_since_update = curr_boottime - update_boottime;
    return new Time(clock->GetWallclockTime() - duration_since_update);
  }

  DISALLOW_COPY_AND_ASSIGN(UpdateCompletedTimeVariable);
};

// Variables reporting the current image channel.
class CurrChannelVariable : public UpdaterVariableBase<string> {
 public:
  CurrChannelVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}

 private:
  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    OmahaRequestParams* request_params = system_state()->request_params();
    string channel = request_params->current_channel();
    if (channel.empty()) {
      if (errmsg)
        *errmsg = "No current channel";
      return nullptr;
    }
    return new string(channel);
  }

  DISALLOW_COPY_AND_ASSIGN(CurrChannelVariable);
};

// Variables reporting the new image channel.
class NewChannelVariable : public UpdaterVariableBase<string> {
 public:
  NewChannelVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}

 private:
  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
    OmahaRequestParams* request_params = system_state()->request_params();
    string channel = request_params->target_channel();
    if (channel.empty()) {
      if (errmsg)
        *errmsg = "No new channel";
      return nullptr;
    }
    return new string(channel);
  }

  DISALLOW_COPY_AND_ASSIGN(NewChannelVariable);
};

// A variable class for reading Boolean prefs values.
class BooleanPrefVariable
    : public AsyncCopyVariable<bool>,
      public chromeos_update_engine::PrefsInterface::ObserverInterface {
 public:
  BooleanPrefVariable(const string& name,
                      chromeos_update_engine::PrefsInterface* prefs,
                      const char* key,
                      bool default_val)
      : AsyncCopyVariable<bool>(name),
        prefs_(prefs),
        key_(key),
        default_val_(default_val) {
    prefs->AddObserver(key, this);
    OnPrefSet(key);
  }
  ~BooleanPrefVariable() {
    prefs_->RemoveObserver(key_, this);
  }

 private:
  // Reads the actual value from the Prefs instance and updates the Variable
  // value.
  void OnPrefSet(const string& key) override {
    bool result = default_val_;
    if (prefs_ && prefs_->Exists(key_) && !prefs_->GetBoolean(key_, &result))
      result = default_val_;
    // AsyncCopyVariable will take care of values that didn't change.
    SetValue(result);
  }

  void OnPrefDeleted(const string& key) override {
    SetValue(default_val_);
  }

  chromeos_update_engine::PrefsInterface* prefs_;

  // The Boolean preference key and default value.
  const char* const key_;
  const bool default_val_;

  DISALLOW_COPY_AND_ASSIGN(BooleanPrefVariable);
};

// A variable returning the number of consecutive failed update checks.
class ConsecutiveFailedUpdateChecksVariable
    : public UpdaterVariableBase<unsigned int> {
 public:
  ConsecutiveFailedUpdateChecksVariable(const string& name,
                                        SystemState* system_state)
      : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
                                          system_state) {}

 private:
  const unsigned int* GetValue(TimeDelta /* timeout */,
                               string* /* errmsg */) override {
    return new unsigned int(
        system_state()->update_attempter()->consecutive_failed_update_checks());
  }

  DISALLOW_COPY_AND_ASSIGN(ConsecutiveFailedUpdateChecksVariable);
};

// A variable returning the server-dictated poll interval.
class ServerDictatedPollIntervalVariable
    : public UpdaterVariableBase<unsigned int> {
 public:
  ServerDictatedPollIntervalVariable(const string& name,
                                     SystemState* system_state)
      : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
                                          system_state) {}

 private:
  const unsigned int* GetValue(TimeDelta /* timeout */,
                               string* /* errmsg */) override {
    return new unsigned int(
        system_state()->update_attempter()->server_dictated_poll_interval());
  }

  DISALLOW_COPY_AND_ASSIGN(ServerDictatedPollIntervalVariable);
};

// An async variable that tracks changes to forced update requests.
class ForcedUpdateRequestedVariable
    : public UpdaterVariableBase<UpdateRequestStatus> {
 public:
  ForcedUpdateRequestedVariable(const string& name, SystemState* system_state)
      : UpdaterVariableBase<UpdateRequestStatus>::UpdaterVariableBase(
          name, kVariableModeAsync, system_state) {
    system_state->update_attempter()->set_forced_update_pending_callback(
        new base::Callback<void(bool, bool)>(  // NOLINT(readability/function)
            base::Bind(&ForcedUpdateRequestedVariable::Reset,
                       base::Unretained(this))));
  }

 private:
  const UpdateRequestStatus* GetValue(TimeDelta /* timeout */,
                                      string* /* errmsg */) override {
    return new UpdateRequestStatus(update_request_status_);
  }

  void Reset(bool forced_update_requested, bool is_interactive) {
    UpdateRequestStatus new_value = UpdateRequestStatus::kNone;
    if (forced_update_requested)
      new_value = (is_interactive ? UpdateRequestStatus::kInteractive :
                   UpdateRequestStatus::kPeriodic);
    if (update_request_status_ != new_value) {
      update_request_status_ = new_value;
      NotifyValueChanged();
    }
  }

  UpdateRequestStatus update_request_status_ = UpdateRequestStatus::kNone;

  DISALLOW_COPY_AND_ASSIGN(ForcedUpdateRequestedVariable);
};

// RealUpdaterProvider methods.

RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state)
  : system_state_(system_state),
    var_updater_started_time_("updater_started_time",
                              system_state->clock()->GetWallclockTime()),
    var_last_checked_time_(
        new LastCheckedTimeVariable("last_checked_time", system_state_)),
    var_update_completed_time_(
        new UpdateCompletedTimeVariable("update_completed_time",
                                        system_state_)),
    var_progress_(new ProgressVariable("progress", system_state_)),
    var_stage_(new StageVariable("stage", system_state_)),
    var_new_version_(new NewVersionVariable("new_version", system_state_)),
    var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)),
    var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)),
    var_new_channel_(new NewChannelVariable("new_channel", system_state_)),
    var_p2p_enabled_(
        new BooleanPrefVariable("p2p_enabled", system_state_->prefs(),
                                chromeos_update_engine::kPrefsP2PEnabled,
                                false)),
    var_cellular_enabled_(
        new BooleanPrefVariable(
            "cellular_enabled", system_state_->prefs(),
            chromeos_update_engine::kPrefsUpdateOverCellularPermission,
            false)),
    var_consecutive_failed_update_checks_(
        new ConsecutiveFailedUpdateChecksVariable(
            "consecutive_failed_update_checks", system_state_)),
    var_server_dictated_poll_interval_(
        new ServerDictatedPollIntervalVariable(
            "server_dictated_poll_interval", system_state_)),
    var_forced_update_requested_(
        new ForcedUpdateRequestedVariable(
            "forced_update_requested", system_state_)) {}

}  // namespace chromeos_update_manager