/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* Copyright 2012 Mozilla Foundation and Mozilla contributors
 *
 * 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 <pthread.h>
#include <stdio.h>

#include "mozilla/DebugOnly.h"

#include "base/basictypes.h"
#include "base/thread.h"

#include "Hal.h"
#include "HalLog.h"
#include "HalSensor.h"
#include "hardware/sensors.h"
#include "nsThreadUtils.h"

using namespace mozilla::hal;

namespace mozilla {

// The value from SensorDevice.h (Android)
#define DEFAULT_DEVICE_POLL_RATE 200000000 /*200ms*/
// ProcessOrientation.cpp needs smaller poll rate to detect delay between
// different orientation angles
#define ACCELEROMETER_POLL_RATE 66667000 /*66.667ms*/

// This is present in Android from API level 18 onwards, which is 4.3.  We might
// be building on something before 4.3, so use a local define for its value
#define MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR   15

double radToDeg(double a) {
  return a * (180.0 / M_PI);
}

static SensorType
HardwareSensorToHalSensor(int type)
{
  switch(type) {
    case SENSOR_TYPE_ORIENTATION:
      return SENSOR_ORIENTATION;
    case SENSOR_TYPE_ACCELEROMETER:
      return SENSOR_ACCELERATION;
    case SENSOR_TYPE_PROXIMITY:
      return SENSOR_PROXIMITY;
    case SENSOR_TYPE_LIGHT:
      return SENSOR_LIGHT;
    case SENSOR_TYPE_GYROSCOPE:
      return SENSOR_GYROSCOPE;
    case SENSOR_TYPE_LINEAR_ACCELERATION:
      return SENSOR_LINEAR_ACCELERATION;
    case SENSOR_TYPE_ROTATION_VECTOR:
      return SENSOR_ROTATION_VECTOR;
    case MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR:
      return SENSOR_GAME_ROTATION_VECTOR;
    default:
      return SENSOR_UNKNOWN;
  }
}

static SensorAccuracyType
HardwareStatusToHalAccuracy(int status) {
  return static_cast<SensorAccuracyType>(status);
}

static int
HalSensorToHardwareSensor(SensorType type)
{
  switch(type) {
    case SENSOR_ORIENTATION:
      return SENSOR_TYPE_ORIENTATION;
    case SENSOR_ACCELERATION:
      return SENSOR_TYPE_ACCELEROMETER;
    case SENSOR_PROXIMITY:
      return SENSOR_TYPE_PROXIMITY;
    case SENSOR_LIGHT:
      return SENSOR_TYPE_LIGHT;
    case SENSOR_GYROSCOPE:
      return SENSOR_TYPE_GYROSCOPE;
    case SENSOR_LINEAR_ACCELERATION:
      return SENSOR_TYPE_LINEAR_ACCELERATION;
    case SENSOR_ROTATION_VECTOR:
      return SENSOR_TYPE_ROTATION_VECTOR;
    case SENSOR_GAME_ROTATION_VECTOR:
      return MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR;
    default:
      return -1;
  }
}

static int
SensorseventStatus(const sensors_event_t& data)
{
  int type = data.type;
  switch(type) {
    case SENSOR_ORIENTATION:
      return data.orientation.status;
    case SENSOR_LINEAR_ACCELERATION:
    case SENSOR_ACCELERATION:
      return data.acceleration.status;
    case SENSOR_GYROSCOPE:
      return data.gyro.status;
  }

  return SENSOR_STATUS_UNRELIABLE;
}

class SensorRunnable : public nsRunnable
{
public:
  SensorRunnable(const sensors_event_t& data, const sensor_t* sensors, ssize_t size)
  {
    mSensorData.sensor() = HardwareSensorToHalSensor(data.type);
    mSensorData.accuracy() = HardwareStatusToHalAccuracy(SensorseventStatus(data));
    mSensorData.timestamp() = data.timestamp;
    if (mSensorData.sensor() == SENSOR_GYROSCOPE) {
      // libhardware returns gyro as rad.  convert.
      mSensorValues.AppendElement(radToDeg(data.data[0]));
      mSensorValues.AppendElement(radToDeg(data.data[1]));
      mSensorValues.AppendElement(radToDeg(data.data[2]));
    } else if (mSensorData.sensor() == SENSOR_PROXIMITY) {
      mSensorValues.AppendElement(data.data[0]);
      mSensorValues.AppendElement(0);

      // Determine the maxRange for this sensor.
      for (ssize_t i = 0; i < size; i++) {
        if (sensors[i].type == SENSOR_TYPE_PROXIMITY) {
          mSensorValues.AppendElement(sensors[i].maxRange);
        }
      }
    } else if (mSensorData.sensor() == SENSOR_LIGHT) {
      mSensorValues.AppendElement(data.data[0]);
    } else if (mSensorData.sensor() == SENSOR_ROTATION_VECTOR) {
      mSensorValues.AppendElement(data.data[0]);
      mSensorValues.AppendElement(data.data[1]);
      mSensorValues.AppendElement(data.data[2]);
      if (data.data[3] == 0.0) {
        // data.data[3] was optional in Android <= API level 18.  It can be computed from 012,
        // but it's better to take the actual value if one is provided.  The computation is
        //   v = 1 - d[0]*d[0] - d[1]*d[1] - d[2]*d[2]
        //   d[3] = v > 0 ? sqrt(v) : 0;
        // I'm assuming that it will be 0 if it's not passed in.  (The values form a unit
        // quaternion, so the angle can be computed from the direction vector.)
        float sx = data.data[0], sy = data.data[1], sz = data.data[2];
        float v = 1.0f - sx*sx - sy*sy - sz*sz;
        mSensorValues.AppendElement(v > 0.0f ? sqrt(v) : 0.0f);
      } else {
        mSensorValues.AppendElement(data.data[3]);
      }
    } else if (mSensorData.sensor() == SENSOR_GAME_ROTATION_VECTOR) {
      mSensorValues.AppendElement(data.data[0]);
      mSensorValues.AppendElement(data.data[1]);
      mSensorValues.AppendElement(data.data[2]);
      mSensorValues.AppendElement(data.data[3]);
    } else {
      mSensorValues.AppendElement(data.data[0]);
      mSensorValues.AppendElement(data.data[1]);
      mSensorValues.AppendElement(data.data[2]);
    }
    mSensorData.values() = mSensorValues;
  }

  ~SensorRunnable() {}

  NS_IMETHOD Run()
  {
    NotifySensorChange(mSensorData);
    return NS_OK;
  }

private:
  SensorData mSensorData;
  nsAutoTArray<float, 4> mSensorValues;
};

namespace hal_impl {

static DebugOnly<int> sSensorRefCount[NUM_SENSOR_TYPE];
static base::Thread* sPollingThread;
static sensors_poll_device_t* sSensorDevice;
static sensors_module_t* sSensorModule;

static void
PollSensors()
{
  const size_t numEventMax = 16;
  sensors_event_t buffer[numEventMax];
  const sensor_t* sensors;
  int size = sSensorModule->get_sensors_list(sSensorModule, &sensors);

  do {
    // didn't check sSensorDevice because already be done on creating pollingThread.
    int n = sSensorDevice->poll(sSensorDevice, buffer, numEventMax);
    if (n < 0) {
      HAL_ERR("Error polling for sensor data (err=%d)", n);
      break;
    }

    for (int i = 0; i < n; ++i) {
      // FIXME: bug 802004, add proper support for the magnetic field sensor.
      if (buffer[i].type == SENSOR_TYPE_MAGNETIC_FIELD)
        continue;

      // Bug 938035, transfer HAL data for orientation sensor to meet w3c spec
      // ex: HAL report alpha=90 means East but alpha=90 means West in w3c spec
      if (buffer[i].type == SENSOR_TYPE_ORIENTATION) {
        buffer[i].orientation.azimuth = 360 - buffer[i].orientation.azimuth;
        buffer[i].orientation.pitch = -buffer[i].orientation.pitch;
        buffer[i].orientation.roll = -buffer[i].orientation.roll;
      }

      if (HardwareSensorToHalSensor(buffer[i].type) == SENSOR_UNKNOWN) {
        // Emulator is broken and gives us events without types set
        int index;
        for (index = 0; index < size; index++) {
          if (sensors[index].handle == buffer[i].sensor) {
            break;
          }
        }
        if (index < size &&
            HardwareSensorToHalSensor(sensors[index].type) != SENSOR_UNKNOWN) {
          buffer[i].type = sensors[index].type;
        } else {
          HAL_LOG("Could not determine sensor type of event");
          continue;
        }
      }

      NS_DispatchToMainThread(new SensorRunnable(buffer[i], sensors, size));
    }
  } while (true);
}

static void
SwitchSensor(bool aActivate, sensor_t aSensor, pthread_t aThreadId)
{
  int index = HardwareSensorToHalSensor(aSensor.type);

  MOZ_ASSERT(sSensorRefCount[index] || aActivate);

  sSensorDevice->activate(sSensorDevice, aSensor.handle, aActivate);

  if (aActivate) {
    if (aSensor.type == SENSOR_TYPE_ACCELEROMETER) {
      sSensorDevice->setDelay(sSensorDevice, aSensor.handle,
                   ACCELEROMETER_POLL_RATE);
    } else {
      sSensorDevice->setDelay(sSensorDevice, aSensor.handle,
                   DEFAULT_DEVICE_POLL_RATE);
    }
  }

  if (aActivate) {
    sSensorRefCount[index]++;
  } else {
    sSensorRefCount[index]--;
  }
}

static void
SetSensorState(SensorType aSensor, bool activate)
{
  int type = HalSensorToHardwareSensor(aSensor);
  const sensor_t* sensors = nullptr;

  int size = sSensorModule->get_sensors_list(sSensorModule, &sensors);
  for (ssize_t i = 0; i < size; i++) {
    if (sensors[i].type == type) {
      SwitchSensor(activate, sensors[i], pthread_self());
      break;
    }
  }
}

void
EnableSensorNotifications(SensorType aSensor)
{
  if (!sSensorModule) {
    hw_get_module(SENSORS_HARDWARE_MODULE_ID,
                       (hw_module_t const**)&sSensorModule);
    if (!sSensorModule) {
      HAL_ERR("Can't get sensor HAL module\n");
      return;
    }

    sensors_open(&sSensorModule->common, &sSensorDevice);
    if (!sSensorDevice) {
      sSensorModule = nullptr;
      HAL_ERR("Can't get sensor poll device from module \n");
      return;
    }

    sensor_t const* sensors;
    int count = sSensorModule->get_sensors_list(sSensorModule, &sensors);
    for (size_t i=0 ; i<size_t(count) ; i++) {
      sSensorDevice->activate(sSensorDevice, sensors[i].handle, 0);
    }
  }

  if (!sPollingThread) {
    sPollingThread = new base::Thread("GonkSensors");
    MOZ_ASSERT(sPollingThread);
    // sPollingThread never terminates because poll may never return
    sPollingThread->Start();
    sPollingThread->message_loop()->PostTask(FROM_HERE,
                                     NewRunnableFunction(PollSensors));
  }

  SetSensorState(aSensor, true);
}

void
DisableSensorNotifications(SensorType aSensor)
{
  if (!sSensorModule) {
    return;
  }
  SetSensorState(aSensor, false);
}

} // hal_impl
} // mozilla
