/*
 * 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 <eventnums.h>
#include <heap.h>
#include <hostIntf.h>
#include <i2c.h>
#include <leds_gpio.h>
#include <nanohubPacket.h>
#include <sensors.h>
#include <seos.h>
#include <timer.h>
#include <util.h>
#include <variant/variant.h>

#define LP3943_LEDS_APP_ID              APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 21)
#define LP3943_LEDS_APP_VERSION         1

#ifdef LP3943_I2C_BUS_ID
#define I2C_BUS_ID                      LP3943_I2C_BUS_ID
#else
#define I2C_BUS_ID                      0
#endif

#ifdef LP3943_I2C_SPEED
#define I2C_SPEED                       LP3943_I2C_SPEED
#else
#define I2C_SPEED                       400000
#endif

#ifdef LP3943_I2C_ADDR
#define I2C_ADDR                        LP3943_I2C_ADDR
#else
#define I2C_ADDR                        0x60
#endif

#define LP3943_REG_PSC0                 0x02
#define LP3943_REG_PWM0                 0x03
#define LP3943_REG_PSC1                 0x04
#define LP3943_REG_PWM1                 0x05
#define LP3943_REG_LS0                  0x06
#define LP3943_REG_LS1                  0x07
#define LP3943_REG_LS2                  0x08
#define LP3943_REG_LS3                  0x09

#define LP3943_MAX_PENDING_I2C_REQUESTS 4
#define LP3943_MAX_I2C_TRANSFER_SIZE    2
#define LP3943_MAX_LED_NUM              16
#define LP3943_MAX_LED_SECTION          4

#ifndef LP3943_DBG_ENABLE
#define LP3943_DBG_ENABLE               0
#endif
#define LP3943_DBG_VALUE                0x55

enum LP3943SensorEvents
{
    EVT_SENSOR_I2C = EVT_APP_START + 1,
    EVT_SENSOR_LEDS_TIMER,
    EVT_TEST,
};

enum LP3943TaskState
{
    STATE_RESET,
    STATE_CLEAN_LS1,
    STATE_CLEAN_LS2,
    STATE_FINISH_INIT,
    STATE_LED,
};

struct I2cTransfer
{
    size_t tx;
    size_t rx;
    int err;
    uint8_t txrxBuf[LP3943_MAX_I2C_TRANSFER_SIZE];
    uint8_t state;
    bool inUse;
};

static struct LP3943Task
{
    uint32_t id;
    uint32_t sHandle;
    uint32_t num;
    bool     ledsOn;
    bool     blink;
    uint32_t ledsTimerHandle;
    uint8_t  led[LP3943_MAX_LED_SECTION];

    struct I2cTransfer transfers[LP3943_MAX_PENDING_I2C_REQUESTS];
} mTask;

/* sensor callbacks from nanohub */
static void i2cCallback(void *cookie, size_t tx, size_t rx, int err)
{
    struct I2cTransfer *xfer = cookie;

    xfer->tx = tx;
    xfer->rx = rx;
    xfer->err = err;

    osEnqueuePrivateEvt(EVT_SENSOR_I2C, cookie, NULL, mTask.id);
    if (err != 0)
        osLog(LOG_INFO, "[LP3943] i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err);
}

static void sensorLP3943TimerCallback(uint32_t timerId, void *data)
{
    osEnqueuePrivateEvt(EVT_SENSOR_LEDS_TIMER, data, NULL, mTask.id);
}

static uint32_t ledsRates[] = {
    SENSOR_HZ(0.1),
    SENSOR_HZ(0.5),
    SENSOR_HZ(1.0f),
    SENSOR_HZ(2.0f),
    0
};

// should match "supported rates in length"
static const uint64_t ledsRatesRateVals[] =
{
    10 * 1000000000ULL,
    2 * 1000000000ULL,
    1 * 1000000000ULL,
    1000000000ULL / 2,
};

// Allocate a buffer and mark it as in use with the given state, or return NULL
// if no buffers available. Must *not* be called from interrupt context.
static struct I2cTransfer *allocXfer(uint8_t state)
{
    size_t i;

    for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) {
        if (!mTask.transfers[i].inUse) {
            mTask.transfers[i].inUse = true;
            mTask.transfers[i].state = state;
            return &mTask.transfers[i];
        }
    }

    osLog(LOG_ERROR, "[LP3943]: Ran out of i2c buffers!");
    return NULL;
}

// Helper function to release I2cTranfer structure.
static inline void releaseXfer(struct I2cTransfer *xfer)
{
    xfer->inUse = false;
}

// Helper function to write a one byte register. Returns true if we got a
// successful return value from i2cMasterTx().
static bool writeRegister(uint8_t reg, uint8_t value, uint8_t state)
{
    struct I2cTransfer *xfer = allocXfer(state);
    int ret = -1;

    if (xfer != NULL) {
        xfer->txrxBuf[0] = reg;
        xfer->txrxBuf[1] = value;
        ret = i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 2, i2cCallback, xfer);
        if (ret)
            releaseXfer(xfer);
    }

    return (ret == 0);
}

/* Sensor Operations */
static bool sensorLP3943Power(bool on, void *cookie)
{
    if (mTask.ledsTimerHandle) {
        timTimerCancel(mTask.ledsTimerHandle);
        mTask.ledsTimerHandle = 0;
    }
    mTask.ledsOn = on;
    return sensorSignalInternalEvt(mTask.sHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0);
}

static bool sensorLP3943FwUpload(void *cookie)
{
    return sensorSignalInternalEvt(mTask.sHandle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
}

static bool sensorLP3943SetRate(uint32_t rate, uint64_t latency, void *cookie)
{
    if (mTask.ledsTimerHandle)
        timTimerCancel(mTask.ledsTimerHandle);

    mTask.ledsTimerHandle = timTimerSet(sensorTimerLookupCommon(ledsRates,
                ledsRatesRateVals, rate), 0, 50, sensorLP3943TimerCallback, NULL, false);

    return sensorSignalInternalEvt(mTask.sHandle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
}

static bool sensorCfgDataLedsLP3943(void *cfg, void *cookie)
{
    struct LedsCfg *lcfg = (struct LedsCfg *)cfg;
    uint8_t laddr = LP3943_REG_LS0;
    uint8_t lval;
    uint8_t index;
    uint8_t lnum;

    if (lcfg->led_num >= mTask.num) {
        osLog(LOG_INFO, "Wrong led number %"PRIu32"\n", lcfg->led_num);
        return false;
    }
    index = lcfg->led_num >> 2;
    lnum = (lcfg->led_num & 0x3) << 1;
    lval = mTask.led[index];
    laddr += index;
    if (lcfg->value) {
        lval |= (1 << lnum);
    } else {
        lval &= ~(1 << lnum);
    }

    writeRegister(laddr, lval, STATE_LED);
    mTask.led[index] = lval;
    osLog(LOG_INFO, "Set led[%"PRIu32"]=%"PRIu32"\n", lcfg->led_num, lcfg->value);
    return true;
}

static void sensorLedsOnOff(bool flag)
{
    uint8_t laddr = LP3943_REG_LS0;
    uint8_t lval;
    uint8_t index;

    for (index=0; index < LP3943_MAX_LED_SECTION; index++) {
        lval = flag ? mTask.led[index] : 0;
        writeRegister(laddr + index, lval, STATE_LED);
    }
}

static const struct SensorInfo sensorInfoLedsLP3943 = {
    .sensorName = "Leds-LP3943",
    .sensorType = SENS_TYPE_LEDS_I2C,
    .supportedRates = ledsRates,
};

static const struct SensorOps sensorOpsLedsLP3943 = {
    .sensorPower   = sensorLP3943Power,
    .sensorFirmwareUpload = sensorLP3943FwUpload,
    .sensorSetRate  = sensorLP3943SetRate,
    .sensorCfgData = sensorCfgDataLedsLP3943,
};

static void handleI2cEvent(struct I2cTransfer *xfer)
{
    switch (xfer->state) {
        case STATE_RESET:
            writeRegister(LP3943_REG_LS1, 0, STATE_CLEAN_LS1);
            break;

        case STATE_CLEAN_LS1:
            writeRegister(LP3943_REG_LS2, 0, STATE_FINISH_INIT);
            break;

        case STATE_CLEAN_LS2:
            writeRegister(LP3943_REG_LS3, 0, STATE_FINISH_INIT);
            break;

        case STATE_FINISH_INIT:
            if (xfer->err != 0) {
                osLog(LOG_INFO, "[LP3943] not detected\n");
            } else {
                osLog(LOG_INFO, "[LP3943] detected\n");
                sensorRegisterInitComplete(mTask.sHandle);
                if (LP3943_DBG_ENABLE) {
                    mTask.ledsOn = true;
                    mTask.led[0] = LP3943_DBG_VALUE;
                    osEnqueuePrivateEvt(EVT_TEST, NULL, NULL, mTask.id);
                }
            }
            break;

        case STATE_LED:
            break;

        default:
            break;
    }

    releaseXfer(xfer);
}

static void handleEvent(uint32_t evtType, const void* evtData)
{
    switch (evtType) {
    case EVT_APP_START:
        osEventUnsubscribe(mTask.id, EVT_APP_START);
        i2cMasterRequest(I2C_BUS_ID, I2C_SPEED);

        /* Reset Leds */
        writeRegister(LP3943_REG_LS0, 0, STATE_RESET);
        break;

    case EVT_SENSOR_I2C:
        handleI2cEvent((struct I2cTransfer *)evtData);
        break;

    case EVT_SENSOR_LEDS_TIMER:
        if (!mTask.ledsOn)
            break;
        mTask.blink = !mTask.blink;
        sensorLedsOnOff(mTask.blink);
        break;

    case EVT_TEST:
        sensorLP3943SetRate(SENSOR_HZ(1), 0, NULL);
        break;

    default:
        break;
    }
}

static bool startTask(uint32_t taskId)
{
    mTask.id = taskId;
    mTask.num = LP3943_MAX_LED_NUM;
    memset(mTask.led, 0x00, LP3943_MAX_LED_SECTION);
    mTask.ledsOn = mTask.blink = false;

    /* Register sensors */
    mTask.sHandle = sensorRegister(&sensorInfoLedsLP3943, &sensorOpsLedsLP3943, NULL, false);

    osEventSubscribe(taskId, EVT_APP_START);

    return true;
}

static void endTask(void)
{
    sensorUnregister(mTask.sHandle);
}

INTERNAL_APP_INIT(LP3943_LEDS_APP_ID, LP3943_LEDS_APP_VERSION, startTask, endTask, handleEvent);