/*
 * 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.
 */

#include <stdlib.h>
#include <string.h>
#include <timer.h>
#include <heap.h>
#include <plat/inc/rtc.h>
#include <plat/inc/syscfg.h>
#include <hostIntf.h>
#include <nanohubPacket.h>

#include <seos.h>

#include <nanohub_math.h>
#include <sensors.h>
#include <limits.h>

#define TILT_APP_VERSION 1

#define EVT_SENSOR_ANY_MOTION sensorGetMyEventType(SENS_TYPE_ANY_MOTION)
#define EVT_SENSOR_NO_MOTION sensorGetMyEventType(SENS_TYPE_NO_MOTION)
#define EVT_SENSOR_ACCEL sensorGetMyEventType(SENS_TYPE_ACCEL)

#define ACCEL_MIN_RATE    SENSOR_HZ(50)
#define ACCEL_MAX_LATENCY 250000000ull   // 250 ms

#define BATCH_TIME      2000000000ull // 2.0 seconds
#define ANGLE_THRESH    (0.819 * 9.81 * 9.81) // ~cos(35) * (1G in m/s^2)^2

struct TiltAlgoState {
    uint64_t this_batch_init_ts;
    uint32_t this_batch_num_samples;
    float this_batch_sample_sum[3];
    float this_batch_g[3];
    float last_ref_g_vector[3];
    bool last_ref_g_vector_valid;
    bool anamoly_this_batch;
    bool tilt_detected;
};

static struct TiltDetectionTask {
    struct TiltAlgoState algoState;
    uint32_t taskId;
    uint32_t handle;
    uint32_t anyMotionHandle;
    uint32_t noMotionHandle;
    uint32_t accelHandle;
    enum {
        STATE_DISABLED,
        STATE_AWAITING_ANY_MOTION,
        STATE_AWAITING_TILT,
    } taskState;
} mTask;

// *****************************************************************************

static void algoInit()
{
    // nothing here
}

static bool algoUpdate(struct TripleAxisDataEvent *ev)
{
    float dotProduct = 0.0f;
    uint64_t dt;
    bool latch_g_vector = false;
    bool tilt_detected = false;
    struct TiltAlgoState *state = &mTask.algoState;
    uint64_t sample_ts = ev->referenceTime;
    uint32_t numSamples = ev->samples[0].firstSample.numSamples;
    uint32_t i;
    struct TripleAxisDataPoint *sample;
    float invN;

    for (i = 0; i < numSamples; i++) {
        sample = &ev->samples[i];
        if (i > 0)
            sample_ts += sample->deltaTime;

        if (state->this_batch_init_ts == 0) {
            state->this_batch_init_ts = sample_ts;
        }

        state->this_batch_sample_sum[0] += sample->x;
        state->this_batch_sample_sum[1] += sample->y;
        state->this_batch_sample_sum[2] += sample->z;

        state->this_batch_num_samples++;

        dt = (sample_ts - state->this_batch_init_ts);

        if (dt > BATCH_TIME) {
            invN = 1.0f / state->this_batch_num_samples;
            state->this_batch_g[0] = state->this_batch_sample_sum[0] * invN;
            state->this_batch_g[1] = state->this_batch_sample_sum[1] * invN;
            state->this_batch_g[2] = state->this_batch_sample_sum[2] * invN;

            if (state->last_ref_g_vector_valid) {
                dotProduct = state->this_batch_g[0] * state->last_ref_g_vector[0] +
                    state->this_batch_g[1] * state->last_ref_g_vector[1] +
                    state->this_batch_g[2] * state->last_ref_g_vector[2];

                if (dotProduct < ANGLE_THRESH) {
                    tilt_detected = true;
                    latch_g_vector = true;
                }
            } else { // reference g vector not valid, first time computing
                latch_g_vector = true;
                state->last_ref_g_vector_valid = true;
            }

            // latch the first batch or when dotProduct < ANGLE_THRESH
            if (latch_g_vector) {
                state->last_ref_g_vector[0] = state->this_batch_g[0];
                state->last_ref_g_vector[1] = state->this_batch_g[1];
                state->last_ref_g_vector[2] = state->this_batch_g[2];
            }

            // Seed the next batch
            state->this_batch_init_ts = 0;
            state->this_batch_num_samples = 0;
            state->this_batch_sample_sum[0] = 0;
            state->this_batch_sample_sum[1] = 0;
            state->this_batch_sample_sum[2] = 0;
        }
    }

    return tilt_detected;
}

static void configAnyMotion(bool on) {
    if (on) {
        sensorRequest(mTask.taskId, mTask.anyMotionHandle, SENSOR_RATE_ONCHANGE, 0);
        osEventSubscribe(mTask.taskId, EVT_SENSOR_ANY_MOTION);
    } else {
        sensorRelease(mTask.taskId, mTask.anyMotionHandle);
        osEventUnsubscribe(mTask.taskId, EVT_SENSOR_ANY_MOTION);
    }
}

static void configNoMotion(bool on) {
    if (on) {
        sensorRequest(mTask.taskId, mTask.noMotionHandle, SENSOR_RATE_ONCHANGE, 0);
        osEventSubscribe(mTask.taskId, EVT_SENSOR_NO_MOTION);
    } else {
        sensorRelease(mTask.taskId, mTask.noMotionHandle);
        osEventUnsubscribe(mTask.taskId, EVT_SENSOR_NO_MOTION);
    }
}

static void configAccel(bool on) {
    if (on) {
        sensorRequest(mTask.taskId, mTask.accelHandle, ACCEL_MIN_RATE,
                      ACCEL_MAX_LATENCY);
        osEventSubscribe(mTask.taskId, EVT_SENSOR_ACCEL);
    } else {
        sensorRelease(mTask.taskId, mTask.accelHandle);
        osEventUnsubscribe(mTask.taskId, EVT_SENSOR_ACCEL);
    }

}

// *****************************************************************************

static const struct SensorInfo mSi =
{
    .sensorName = "Tilt Detection",
    .sensorType = SENS_TYPE_TILT,
    .numAxis = NUM_AXIS_EMBEDDED,
    .interrupt = NANOHUB_INT_WAKEUP,
    .minSamples = 20
};

static bool tiltDetectionPower(bool on, void *cookie)
{
    if (on) {
        configAnyMotion(true);
        mTask.taskState = STATE_AWAITING_ANY_MOTION;
    } else {
        configAnyMotion(false);
        configNoMotion(false);
        configAccel(false);
        mTask.taskState = STATE_DISABLED;
    }

    sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG,
                            on, 0);
    return true;
}

static bool tiltDetectionSetRate(uint32_t rate, uint64_t latency, void *cookie)
{
    sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate,
                            latency);
    return true;
}

static bool tiltDetectionFirmwareUpload(void *cookie)
{
    sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG,
            1, 0);
    return true;
}

static bool tiltDetectionFlush(void *cookie)
{
    return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_TILT),
                        SENSOR_DATA_EVENT_FLUSH, NULL);
}

static void tiltDetectionHandleEvent(uint32_t evtType, const void* evtData)
{
    if (evtData == SENSOR_DATA_EVENT_FLUSH)
        return;

    switch (evtType) {
    case EVT_APP_START:
        osEventUnsubscribe(mTask.taskId, EVT_APP_START);
        sensorFind(SENS_TYPE_ANY_MOTION, 0, &mTask.anyMotionHandle);
        sensorFind(SENS_TYPE_NO_MOTION, 0, &mTask.noMotionHandle);
        sensorFind(SENS_TYPE_ACCEL, 0, &mTask.accelHandle);
        break;

    case EVT_SENSOR_ANY_MOTION:
        if (mTask.taskState == STATE_AWAITING_ANY_MOTION) {
            configAnyMotion(false);
            configNoMotion(true);
            configAccel(true);

            mTask.taskState = STATE_AWAITING_TILT;
        }
        break;

    case EVT_SENSOR_NO_MOTION:
        if (mTask.taskState == STATE_AWAITING_TILT) {
            configNoMotion(false);
            configAccel(false);
            configAnyMotion(true);

            mTask.taskState = STATE_AWAITING_ANY_MOTION;
        }
        break;

    case EVT_SENSOR_ACCEL:
        if (mTask.taskState == STATE_AWAITING_TILT) {
            if (algoUpdate((struct TripleAxisDataEvent *)evtData)) {
                union EmbeddedDataPoint sample;
                sample.idata = 1;
                osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_TILT), sample.vptr, NULL);
            }
        }
        break;
    }
}

static const struct SensorOps mSops =
{
    .sensorPower = tiltDetectionPower,
    .sensorFirmwareUpload = tiltDetectionFirmwareUpload,
    .sensorSetRate = tiltDetectionSetRate,
    .sensorFlush = tiltDetectionFlush,
};

static bool tiltDetectionStart(uint32_t taskId)
{
    mTask.taskId = taskId;
    mTask.handle = sensorRegister(&mSi, &mSops, NULL, true);
    algoInit();
    osEventSubscribe(taskId, EVT_APP_START);
    return true;
}

static void tiltDetectionEnd()
{
}

INTERNAL_APP_INIT(
        APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 8),
        TILT_APP_VERSION,
        tiltDetectionStart,
        tiltDetectionEnd,
        tiltDetectionHandleEvent);