// Copyright 2014 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 "chromeos/accelerometer/accelerometer_reader.h"

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_worker_pool.h"

namespace chromeos {

namespace {

// Paths to access necessary data from the accelerometer device.
const base::FilePath::CharType kAccelerometerTriggerPath[] =
    FILE_PATH_LITERAL("/sys/bus/iio/devices/trigger0/trigger_now");
const base::FilePath::CharType kAccelerometerDevicePath[] =
    FILE_PATH_LITERAL("/dev/cros-ec-accel");
const base::FilePath::CharType kAccelerometerIioBasePath[] =
    FILE_PATH_LITERAL("/sys/bus/iio/devices/");

// Files within the device in kAccelerometerIioBasePath containing the scales of
// the accelerometers.
const base::FilePath::CharType kAccelerometerBaseScaleName[] =
    FILE_PATH_LITERAL("in_accel_base_scale");
const base::FilePath::CharType kAccelerometerLidScaleName[] =
    FILE_PATH_LITERAL("in_accel_lid_scale");

// The filename giving the path to read the scan index of each accelerometer
// axis.
const char kAccelerometerScanIndexPath[] =
    "scan_elements/in_accel_%s_%s_index";

// The names of the accelerometers and axes in the order we want to read them.
const char kAccelerometerNames[][5] = {"base", "lid"};
const char kAccelerometerAxes[][2] = {"x", "y", "z"};
const size_t kTriggerDataValues =
    arraysize(kAccelerometerNames) * arraysize(kAccelerometerAxes);
const size_t kTriggerDataLength = kTriggerDataValues * 2;

// The length required to read uint values from configuration files.
const size_t kMaxAsciiUintLength = 21;

// The time to wait between reading the accelerometer.
const int kDelayBetweenReadsMs = 100;

// The mean acceleration due to gravity on Earth in m/s^2.
const float kMeanGravity = 9.80665f;

// Reads |path| to the unsigned int pointed to by |value|. Returns true on
// success or false on failure.
bool ReadFileToUint(const base::FilePath& path, unsigned int* value) {
  std::string s;
  DCHECK(value);
  if (!base::ReadFileToString(path, &s, kMaxAsciiUintLength)) {
    LOG(ERROR) << "Failed to read " << path.value();
    return false;
  }
  base::TrimWhitespaceASCII(s, base::TRIM_ALL, &s);
  if (!base::StringToUint(s, value)) {
    LOG(ERROR) << "Failed to parse \"" << s << "\" from " << path.value();
    return false;
  }
  return true;
}

bool DetectAndReadAccelerometerConfiguration(
    scoped_refptr<AccelerometerReader::Configuration> configuration) {
  // Check for accelerometer symlink which will be created by the udev rules
  // file on detecting the device.
  base::FilePath device;
  if (!base::ReadSymbolicLink(base::FilePath(kAccelerometerDevicePath),
                              &device)) {
    return false;
  }

  if (!base::PathExists(base::FilePath(kAccelerometerTriggerPath))) {
    LOG(ERROR) << "Accelerometer trigger does not exist at"
               << kAccelerometerTriggerPath;
    return false;
  }

  base::FilePath iio_path(base::FilePath(kAccelerometerIioBasePath).Append(
      device));
  // Read accelerometer scales
  if (!ReadFileToUint(iio_path.Append(kAccelerometerBaseScaleName),
                      &(configuration->data.base_scale))) {
    return false;
  }
  if (!ReadFileToUint(iio_path.Append(kAccelerometerLidScaleName),
                      &(configuration->data.lid_scale))) {
    return false;
  }

  // Read indices of each accelerometer axis from each accelerometer from
  // /sys/bus/iio/devices/iio:deviceX/scan_elements/in_accel_{x,y,z}_%s_index
  for (size_t i = 0; i < arraysize(kAccelerometerNames); ++i) {
    for (size_t j = 0; j < arraysize(kAccelerometerAxes); ++j) {
      std::string accelerometer_index_path = base::StringPrintf(
          kAccelerometerScanIndexPath, kAccelerometerAxes[j],
          kAccelerometerNames[i]);
      unsigned int index = 0;
      if (!ReadFileToUint(iio_path.Append(accelerometer_index_path.c_str()),
                          &index)) {
        return false;
      }
      if (index >= kTriggerDataValues) {
        LOG(ERROR) << "Field index from " << accelerometer_index_path
                   << " out of bounds: " << index;
        return false;
      }
      configuration->data.index.push_back(index);
    }
  }
  return true;
}

bool ReadAccelerometer(
    scoped_refptr<AccelerometerReader::Reading> reading) {
  // Initiate the trigger to read accelerometers simultaneously
  int bytes_written = base::WriteFile(
      base::FilePath(kAccelerometerTriggerPath), "1\n", 2);
  if (bytes_written < 2) {
    PLOG(ERROR) << "Accelerometer trigger failure: " << bytes_written;
    return false;
  }

  // Read resulting sample from /dev/cros-ec-accel.
  int bytes_read = base::ReadFile(base::FilePath(kAccelerometerDevicePath),
                                  reading->data, kTriggerDataLength);
  if (bytes_read < static_cast<int>(kTriggerDataLength)) {
    LOG(ERROR) << "Read " << bytes_read << " byte(s), expected "
               << kTriggerDataLength << " bytes from accelerometer";
    return false;
  }
  return true;
}

}  // namespace

AccelerometerReader::ConfigurationData::ConfigurationData() {
}

AccelerometerReader::ConfigurationData::~ConfigurationData() {
}

AccelerometerReader::AccelerometerReader(
    base::TaskRunner* task_runner,
    AccelerometerReader::Delegate* delegate)
    : task_runner_(task_runner),
      delegate_(delegate),
      configuration_(new AccelerometerReader::Configuration()),
      weak_factory_(this) {
  DCHECK(task_runner_.get());
  DCHECK(delegate_);
  // Asynchronously detect and initialize the accelerometer to avoid delaying
  // startup.
  base::PostTaskAndReplyWithResult(task_runner_.get(), FROM_HERE,
      base::Bind(&DetectAndReadAccelerometerConfiguration, configuration_),
      base::Bind(&AccelerometerReader::OnInitialized,
                 weak_factory_.GetWeakPtr(), configuration_));
}

AccelerometerReader::~AccelerometerReader() {
}

void AccelerometerReader::OnInitialized(
    scoped_refptr<AccelerometerReader::Configuration> configuration,
    bool success) {
  if (success)
    TriggerRead();
}

void AccelerometerReader::TriggerRead() {
  DCHECK(!task_runner_->RunsTasksOnCurrentThread());

  scoped_refptr<AccelerometerReader::Reading> reading(
      new AccelerometerReader::Reading());
  base::PostTaskAndReplyWithResult(task_runner_.get(),
                                   FROM_HERE,
                                   base::Bind(&ReadAccelerometer, reading),
                                   base::Bind(&AccelerometerReader::OnDataRead,
                                              weak_factory_.GetWeakPtr(),
                                              reading));
}

void AccelerometerReader::OnDataRead(
    scoped_refptr<AccelerometerReader::Reading> reading,
    bool success) {
  DCHECK(!task_runner_->RunsTasksOnCurrentThread());

  if (success) {
    int16* values = reinterpret_cast<int16*>(reading->data);
    float lid_scale = kMeanGravity / configuration_->data.lid_scale;
    update_.Set(ui::ACCELEROMETER_SOURCE_SCREEN,
                -values[configuration_->data.index[4]] * lid_scale,
                values[configuration_->data.index[3]] * lid_scale,
                values[configuration_->data.index[5]] * lid_scale);
    float base_scale = kMeanGravity / configuration_->data.base_scale;
    update_.Set(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD,
                -values[configuration_->data.index[1]] * base_scale,
                -values[configuration_->data.index[0]] * base_scale,
                -values[configuration_->data.index[2]] * base_scale);
    delegate_->HandleAccelerometerUpdate(update_);
  }

  // Trigger another read after the current sampling delay.
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&AccelerometerReader::TriggerRead,
                 weak_factory_.GetWeakPtr()),
      base::TimeDelta::FromMilliseconds(kDelayBetweenReadsMs));
}

}  // namespace chromeos