/*
 * Copyright (C) 2016 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.
 */

#ifndef CONTEXTHUB_H_
#define CONTEXTHUB_H_

#include "nanomessage.h"
#include "noncopyable.h"

#include <bitset>
#include <functional>
#include <vector>

namespace android {

class AppToHostEvent;
class SensorEvent;

// Array length helper macro
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))

enum class SensorType {
    Invalid_ = 0,

    // The order of this enum must correspond to sensor types in nanohub's
    // sensType.h
    Accel,
    AnyMotion,
    NoMotion,
    SignificantMotion,
    Flat,
    Gyro,
    GyroUncal,
    Magnetometer,
    MagnetometerUncal,
    Barometer,
    Temperature,
    AmbientLightSensor,
    Proximity,
    Orientation,
    HeartRateECG,
    HeartRatePPG,
    Gravity,
    LinearAccel,
    RotationVector,
    GeomagneticRotationVector,
    GameRotationVector,
    StepCount,
    StepDetect,
    Gesture,
    Tilt,
    DoubleTwist,
    DoubleTap,
    WindowOrientation,
    Hall,
    Activity,
    Vsync,
    CompressedAccel,
    WristTilt = 39,

    Max_
};

// Overloaded values of rate used in sensor enable request (see sensors.h)
enum class SensorSpecialRate : uint32_t {
    None     = 0,
    OnDemand = 0xFFFFFF00,
    OnChange = 0xFFFFFF01,
    OneShot  = 0xFFFFFF02,
};

struct SensorSpec {
    SensorType sensor_type = SensorType::Invalid_;

    // When enabling a sensor, rate can be specified in Hz or as one of the
    // special values
    SensorSpecialRate special_rate = SensorSpecialRate::None;
    float rate_hz = -1;
    uint64_t latency_ns = 0;

    // Reference value (ground truth) used for calibration
    bool have_cal_ref = false;
    float cal_ref;
};

/*
 * An interface for communicating with a ContextHub.
 */
class ContextHub : public NonCopyable {
  public:
    virtual ~ContextHub() {};

    static std::string SensorTypeToAbbrevName(SensorType sensor_type);
    static SensorType SensorAbbrevNameToType(const char *abbrev_name);
    static SensorType SensorAbbrevNameToType(const std::string& abbrev_name);
    static std::string ListAllSensorAbbrevNames();

    /*
     * Performs initialization to allow commands to be sent to the context hub.
     * Must be called before any other functions that send commands. Returns
     * true on success, false on failure.
     */
    virtual bool Initialize() = 0;

    /*
     * Configures the ContextHub to allow logs to be printed to stdout.
     */
    virtual void SetLoggingEnabled(bool logging_enabled) = 0;

    /*
     * Loads a new firmware image to the ContextHub. The firmware image is
     * specified by filename. Returns false if an error occurs.
     */
    bool Flash(const std::string& filename);

    /*
     * Performs the sensor calibration routine and writes the resulting data to
     * a file.
     */
    bool CalibrateSensors(const std::vector<SensorSpec>& sensors);

    /*
     * Performs the sensor self-test routine.
     */
    bool TestSensors(const std::vector<SensorSpec>& sensors);

    /*
     * Sends a sensor enable request to the context hub.
     */
    bool EnableSensor(const SensorSpec& sensor);
    bool EnableSensors(const std::vector<SensorSpec>& sensors);

    /*
     * Sends a disable sensor request to context hub. Note that this always
     * results in sending a request, i.e. this does not check whether the sensor
     * is currently enabled or not.
     */
    bool DisableSensor(SensorType sensor_type);
    bool DisableSensors(const std::vector<SensorSpec>& sensors);

    /*
     * Sends a disable sensor request for every sensor type we know about.
     */
    bool DisableAllSensors();

    /*
     * Calls DisableSensor() on all active sensors (i.e. those which have been
     * enabled but not yet disabled). This should be called from the destructor
     * of derived classes before tearing down communications to ensure we don't
     * leave sensors enabled after exiting.
     */
    bool DisableActiveSensors();

    /*
     * Sends all data stored in the calibration file to the context hub.
     */
    virtual bool LoadCalibration();

    /*
     * Prints up to <limit> incoming events. If limit is 0, then continues
     * indefinitely.
     */
    void PrintAllEvents(unsigned int limit);

    /*
     * Requests bridge version information
     */
    bool PrintBridgeVersion();

    /*
     * Prints up to <sample_limit> incoming sensor samples corresponding to the
     * given SensorType, ignoring other events. If sample_limit is 0, then
     * continues indefinitely.
     */
    void PrintSensorEvents(SensorType sensor_type, int sample_limit);
    void PrintSensorEvents(const std::vector<SensorSpec>& sensors,
        int sample_limit);

  protected:
    enum class TransportResult {
        Success,
        GeneralFailure,
        Timeout,
        ParseFailure,
        Canceled,
        // Add more specific error reasons as needed
    };

    // Performs the calibration routine, but does not call SaveCalibration()
    bool CalibrateSingleSensor(const SensorSpec& sensor);

    // Performs the self-test routine
    bool TestSingleSensor(const SensorSpec& sensor);

    /*
     * Iterates over sensors, invoking the given callback on each element.
     * Returns true if all callbacks returned true. Exits early on failure.
     */
    bool ForEachSensor(const std::vector<SensorSpec>& sensors,
        std::function<bool(const SensorSpec&)> callback);

    /*
     * Parses a calibration result event and invokes the appropriate
     * SetCalibration function with the calibration data.
     */
    bool HandleCalibrationResult(const SensorSpec& sensor,
        const AppToHostEvent &event);

    /*
     * Parses a self-test result event
     */
    bool HandleTestResult(const SensorSpec& sensor,
        const AppToHostEvent &event);

    /*
     * Same as ReadSensorEvents, but filters on AppToHostEvent instead of
     * SensorEvent.
     */
    TransportResult ReadAppEvents(std::function<bool(const AppToHostEvent&)> callback,
        int timeout_ms = 0);

    /*
     * Calls ReadEvent in a loop, handling errors and ignoring events that
     * didn't originate from a sensor. Valid SensorEvents are passed to the
     * callback for further processing. The callback should return a boolean
     * indicating whether to continue (true) or exit the read loop (false).
     */
    void ReadSensorEvents(std::function<bool(const SensorEvent&)> callback);

    /*
     * Sends the given calibration data down to the hub
     */
    bool SendCalibrationData(SensorType sensor_type,
        const std::vector<uint8_t>& cal_data);

    /*
     * Read an event from the sensor hub. Block until a event is successfully
     * read, no event traffic is generated for the timeout period, or an error
     * occurs, such as a CRC check failure.
     */
    virtual TransportResult ReadEvent(std::vector<uint8_t>& response,
        int timeout_ms) = 0;
    virtual TransportResult WriteEvent(const std::vector<uint8_t>& request) = 0;

    // Implements the firmware loading functionality for the sensor hub. Returns
    // false if an error occurs while writing the firmware to the device.
    virtual bool FlashSensorHub(const std::vector<uint8_t>& bytes) = 0;

    // Convenience functions that build on top of the more generic byte-level
    // interface
    TransportResult ReadEvent(std::unique_ptr<ReadEventResponse>* response,
        int timeout_ms = 0);
    TransportResult WriteEvent(const WriteEventRequest& request);

    // Override these if saving calibration data to persistent storage is
    // supported on the platform
    virtual bool SetCalibration(SensorType sensor_type, int32_t data);
    virtual bool SetCalibration(SensorType sensor_type, float data);
    virtual bool SetCalibration(SensorType sensor_type, int32_t x,
        int32_t y, int32_t z);
    virtual bool SetCalibration(SensorType sensor_type, int32_t x,
        int32_t y, int32_t z, int32_t w);
    virtual bool SaveCalibration();

private:
    std::bitset<static_cast<int>(SensorType::Max_)> sensor_is_active_;
};

}  // namespace android

#endif  // CONTEXTHUB_H_