// Copyright (c) 2011 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 "chrome/browser/chromeos/system_access.h"

#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/observer_list.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/name_value_pairs_parser.h"

namespace chromeos {  // NOLINT

namespace { // NOLINT

// The filepath to the timezone file that symlinks to the actual timezone file.
const char kTimezoneSymlink[] = "/var/lib/timezone/localtime";
const char kTimezoneSymlink2[] = "/var/lib/timezone/localtime2";

// The directory that contains all the timezone files. So for timezone
// "US/Pacific", the actual timezone file is: "/usr/share/zoneinfo/US/Pacific"
const char kTimezoneFilesDir[] = "/usr/share/zoneinfo/";

// The system command that returns the hardware class.
const char kHardwareClassKey[] = "hardware_class";
const char* kHardwareClassTool[] = { "/usr/bin/hardware_class" };
const char kUnknownHardwareClass[] = "unknown";

// Command to get machine hardware info and key/value delimiters.
// /tmp/machine-info is generated by platform/init/chromeos_startup.
const char* kMachineHardwareInfoTool[] = { "cat", "/tmp/machine-info" };
const char kMachineHardwareInfoEq[] = "=";
const char kMachineHardwareInfoDelim[] = " \n";

// Command to get machine OS info and key/value delimiters.
const char* kMachineOSInfoTool[] = { "cat", "/etc/lsb-release" };
const char kMachineOSInfoEq[] = "=";
const char kMachineOSInfoDelim[] = "\n";

// Command to get  HWID and key.
const char kHwidKey[] = "hwid";
const char* kHwidTool[] = { "cat", "/sys/devices/platform/chromeos_acpi/HWID" };

// Command to get VPD info and key/value delimiters.
const char* kVpdTool[] = { "cat", "/var/log/vpd_2.0.txt" };
const char kVpdEq[] = "=";
const char kVpdDelim[] = "\n";

// Fallback time zone ID used in case of an unexpected error.
const char kFallbackTimeZoneId[] = "America/Los_Angeles";

class SystemAccessImpl : public SystemAccess {
 public:
  // SystemAccess.implementation:
  virtual const icu::TimeZone& GetTimezone();
  virtual void SetTimezone(const icu::TimeZone& timezone);
  virtual bool GetMachineStatistic(const std::string& name,
                                   std::string* result);
  virtual void AddObserver(Observer* observer);
  virtual void RemoveObserver(Observer* observer);

  static SystemAccessImpl* GetInstance();

 private:
  friend struct DefaultSingletonTraits<SystemAccessImpl>;

  SystemAccessImpl();

  // Updates the machine statistcs by examining the system.
  void UpdateMachineStatistics();

  scoped_ptr<icu::TimeZone> timezone_;
  ObserverList<Observer> observers_;
  NameValuePairsParser::NameValueMap machine_info_;

  DISALLOW_COPY_AND_ASSIGN(SystemAccessImpl);
};

std::string GetTimezoneIDAsString() {
  // Look at kTimezoneSymlink, see which timezone we are symlinked to.
  char buf[256];
  const ssize_t len = readlink(kTimezoneSymlink, buf,
                               sizeof(buf)-1);
  if (len == -1) {
    LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink "
               << kTimezoneSymlink;
    return std::string();
  }

  std::string timezone(buf, len);
  // Remove kTimezoneFilesDir from the beginning.
  if (timezone.find(kTimezoneFilesDir) != 0) {
    LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong "
               << timezone;
    return std::string();
  }

  return timezone.substr(strlen(kTimezoneFilesDir));
}

void SetTimezoneIDFromString(const std::string& id) {
  // Change the kTimezoneSymlink symlink to the path for this timezone.
  // We want to do this in an atomic way. So we are going to create the symlink
  // at kTimezoneSymlink2 and then move it to kTimezoneSymlink

  FilePath timezone_symlink(kTimezoneSymlink);
  FilePath timezone_symlink2(kTimezoneSymlink2);
  FilePath timezone_file(kTimezoneFilesDir + id);

  // Make sure timezone_file exists.
  if (!file_util::PathExists(timezone_file)) {
    LOG(ERROR) << "SetTimezoneID: Cannot find timezone file "
               << timezone_file.value();
    return;
  }

  // Delete old symlink2 if it exists.
  file_util::Delete(timezone_symlink2, false);

  // Create new symlink2.
  if (symlink(timezone_file.value().c_str(),
              timezone_symlink2.value().c_str()) == -1) {
    LOG(ERROR) << "SetTimezoneID: Unable to create symlink "
               << timezone_symlink2.value() << " to " << timezone_file.value();
    return;
  }

  // Move symlink2 to symlink.
  if (!file_util::ReplaceFile(timezone_symlink2, timezone_symlink)) {
    LOG(ERROR) << "SetTimezoneID: Unable to move symlink "
               << timezone_symlink2.value() << " to "
               << timezone_symlink.value();
  }
}

const icu::TimeZone& SystemAccessImpl::GetTimezone() {
  return *timezone_.get();
}

void SystemAccessImpl::SetTimezone(const icu::TimeZone& timezone) {
  timezone_.reset(timezone.clone());
  icu::UnicodeString unicode;
  timezone.getID(unicode);
  std::string id;
  UTF16ToUTF8(unicode.getBuffer(), unicode.length(), &id);
  VLOG(1) << "Setting timezone to " << id;
  chromeos::SetTimezoneIDFromString(id);
  icu::TimeZone::setDefault(timezone);
  FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(timezone));
}

bool SystemAccessImpl::GetMachineStatistic(
    const std::string& name, std::string* result) {
  NameValuePairsParser::NameValueMap::iterator iter = machine_info_.find(name);
  if (iter != machine_info_.end()) {
    *result = iter->second;
    return true;
  }
  return false;
}

void SystemAccessImpl::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void SystemAccessImpl::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

SystemAccessImpl::SystemAccessImpl() {
  // Get Statistics
  UpdateMachineStatistics();
  // Get Timezone
  std::string id = GetTimezoneIDAsString();
  if (id.empty()) {
    id = kFallbackTimeZoneId;
    LOG(ERROR) << "Got an empty string for timezone, default to " << id;
  }
  icu::TimeZone* timezone =
      icu::TimeZone::createTimeZone(icu::UnicodeString::fromUTF8(id));
  timezone_.reset(timezone);
  icu::TimeZone::setDefault(*timezone);
  VLOG(1) << "Timezone is " << id;
}

void SystemAccessImpl::UpdateMachineStatistics() {
  NameValuePairsParser parser(&machine_info_);
  if (!parser.GetSingleValueFromTool(arraysize(kHardwareClassTool),
                                     kHardwareClassTool,
                                     kHardwareClassKey)) {
    // Use kUnknownHardwareClass if the hardware class command fails.
    parser.AddNameValuePair(kHardwareClassKey, kUnknownHardwareClass);
  }
  parser.ParseNameValuePairsFromTool(arraysize(kMachineHardwareInfoTool),
                                     kMachineHardwareInfoTool,
                                     kMachineHardwareInfoEq,
                                     kMachineHardwareInfoDelim);
  parser.ParseNameValuePairsFromTool(arraysize(kMachineOSInfoTool),
                                     kMachineOSInfoTool,
                                     kMachineOSInfoEq,
                                     kMachineOSInfoDelim);
  parser.GetSingleValueFromTool(arraysize(kHwidTool), kHwidTool, kHwidKey);
  parser.ParseNameValuePairsFromTool(
      arraysize(kVpdTool), kVpdTool, kVpdEq, kVpdDelim);
}

SystemAccessImpl* SystemAccessImpl::GetInstance() {
  return Singleton<SystemAccessImpl,
                   DefaultSingletonTraits<SystemAccessImpl> >::get();
}

}  // namespace

SystemAccess* SystemAccess::GetInstance() {
  return SystemAccessImpl::GetInstance();
}

}  // namespace chromeos