/* Initialise the EddystoneService using parameters from persistent storage */
EddystoneService::EddystoneService(BLE                 &bleIn,
                                   EddystoneParams_t   &paramsIn,
                                   const PowerLevels_t &radioPowerLevelsIn,
                                   uint32_t            advConfigIntervalIn) :
    ble(bleIn),
    operationMode(EDDYSTONE_MODE_NONE),
    urlFrame(paramsIn.urlData, paramsIn.urlDataLength),
    uidFrame(paramsIn.uidNamespaceID, paramsIn.uidInstanceID),
    tlmFrame(paramsIn.tlmVersion),
    resetFlag(false),
    rawUrlFrame(NULL),
    rawUidFrame(NULL),
    rawTlmFrame(NULL),
    tlmBatteryVoltageCallback(NULL),
    tlmBeaconTemperatureCallback(NULL),
    uidFrameCallbackHandle(NULL),
    urlFrameCallbackHandle(NULL),
    tlmFrameCallbackHandle(NULL),
    radioManagerCallbackHandle(NULL),
    deviceName(DEFAULT_DEVICE_NAME)
{
    lockState      = paramsIn.lockState;
    flags          = paramsIn.flags;
    txPowerMode    = paramsIn.txPowerMode;
    urlFramePeriod = correctAdvertisementPeriod(paramsIn.urlFramePeriod);
    uidFramePeriod = correctAdvertisementPeriod(paramsIn.uidFramePeriod);
    tlmFramePeriod = correctAdvertisementPeriod(paramsIn.tlmFramePeriod);

    memcpy(lock,   paramsIn.lock,   sizeof(Lock_t));
    memcpy(unlock, paramsIn.unlock, sizeof(Lock_t));

    eddystoneConstructorHelper(paramsIn.advPowerLevels, radioPowerLevelsIn, advConfigIntervalIn);
}
void EddystoneService::setTLMFrameAdvertisingInterval(uint16_t tlmFrameIntervalIn)
{
    if (tlmFrameIntervalIn == tlmFramePeriod) {
        /* Do nothing */
        return;
    }

    /* Make sure the input period is within bounds */
    tlmFramePeriod = correctAdvertisementPeriod(tlmFrameIntervalIn);

    if (operationMode == EDDYSTONE_MODE_BEACON) {
        if (tlmFrameCallbackHandle) {
            /* The advertisement interval changes, update minar periodic callback */
            minar::Scheduler::cancelCallback(tlmFrameCallbackHandle);
        } else {
            /* This frame was just enabled */
            if (!rawTlmFrame && tlmFramePeriod) {
                /* Allocate memory for this frame and construct it */
                rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
                /* Do not construct the TLM frame because this changes every 0.1 seconds */
            }
        }

        if (tlmFramePeriod) {
            /* Currently the only way to change the period of a callback in minar
             * is to cancell it and reschedule
             */
            tlmFrameCallbackHandle = minar::Scheduler::postCallback(
                mbed::util::FunctionPointer1<void, FrameType>(this, &EddystoneService::enqueueFrame).bind(EDDYSTONE_FRAME_TLM)
            ).period(minar::milliseconds(tlmFramePeriod)).getHandle();
        } else {
            tlmFrameCallbackHandle = NULL;
        }
    }
}
void EddystoneService::setNormalFrameAdvertisingInterval(uint16_t normalFrameIntervalIn)
{
    if (normalFrameIntervalIn == normalFramePeriod) {
        /* Do nothing */
        return;
    }

    /* Make sure the input period is within bounds */
    normalFramePeriod = correctAdvertisementPeriod(normalFrameIntervalIn);

    if (operationMode == EDDYSTONE_MODE_BEACON) {
        if (normalFrameCallbackHandle) {
            /* The advertisement interval changes, update minar periodic callback */
            minar::Scheduler::cancelCallback(normalFrameCallbackHandle);
        } else {
            /* This frame was just enabled */
            // if (!rawUidFrame && normalFramePeriod) {
            //     /* Allocate memory for this frame and construct it */
            //     rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
            //     uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
            // }
        }

        if (normalFramePeriod) {
            /* Currently the only way to change the period of a callback in minar
             * is to cancell it and reschedule
             */
            normalFrameCallbackHandle = minar::Scheduler::postCallback(
                mbed::util::FunctionPointer1<void, FrameType>(this, &EddystoneService::enqueueFrame).bind(NORMAL_FRAME)
            ).period(minar::milliseconds(normalFramePeriod)).getHandle();
        } else {
            normalFrameCallbackHandle = NULL;
        }
    }
}
/*
 * This callback is invoked when a GATT client attempts to modify any of the
 * characteristics of this service. Attempts to do so are also applied to
 * the internal state of this service object.
 */
void EddystoneService::onDataWrittenCallback(const GattWriteCallbackParams *writeParams)
{
    uint16_t handle = writeParams->handle;

    if (handle == lockChar->getValueHandle()) {
        memcpy(lock, writeParams->data, sizeof(Lock_t));
        /* Set the state to be locked by the lock code (note: zeros are a valid lock) */
        lockState = true;
        ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
        ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
    } else if (handle == unlockChar->getValueHandle()) {
        /* Validated earlier */
        lockState = false;
        ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
        ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
    } else if (handle == urlDataChar->getValueHandle()) {
        urlFrame.setEncodedURLData(writeParams->data, writeParams->len);
        ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
    } else if (handle == flagsChar->getValueHandle()) {
        flags = *(writeParams->data);
        ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
    } else if (handle == advPowerLevelsChar->getValueHandle()) {
        memcpy(advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
        ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
    } else if (handle == txPowerModeChar->getValueHandle()) {
        txPowerMode = *(writeParams->data);
        ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
    } else if (handle == beaconPeriodChar->getValueHandle()) {
        uint16_t tmpBeaconPeriod = correctAdvertisementPeriod(*((uint16_t *)(writeParams->data)));
        if (tmpBeaconPeriod != urlFramePeriod) {
            urlFramePeriod = tmpBeaconPeriod;
            ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
        }
    } else if (handle == resetChar->getValueHandle() && (*((uint8_t *)writeParams->data) != 0)) {
        /* Reset characteristics to default values */
        flags          = 0;
        txPowerMode    = TX_POWER_MODE_LOW;
        urlFramePeriod = DEFAULT_URL_FRAME_PERIOD_MSEC;

        urlFrame.setURLData(DEFAULT_URL);
        memset(lock, 0, sizeof(Lock_t));

        ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
        ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
        ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
        ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
        ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
    }
}
void EddystoneService::setURLFrameAdvertisingInterval(uint16_t urlFrameIntervalIn)
{
    if (urlFrameIntervalIn == urlFramePeriod) {
        /* Do nothing */
        return;
    }

    /* Make sure the input period is within bounds */
    urlFramePeriod = correctAdvertisementPeriod(urlFrameIntervalIn);

    if (operationMode == EDDYSTONE_MODE_BEACON) {
        if (urlFrameCallbackHandle) {
            /* The advertisement interval changes, update minar periodic callback */
            minar::Scheduler::cancelCallback(urlFrameCallbackHandle);
        } else {
            /* This frame was just enabled */
            if (!rawUidFrame && urlFramePeriod) {
                /* Allocate memory for this frame and construct it */
                rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
                urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
            }
        }

        if (urlFramePeriod) {
            /* Currently the only way to change the period of a callback in minar
             * is to cancell it and reschedule
             */
            urlFrameCallbackHandle = minar::Scheduler::postCallback(
                mbed::util::FunctionPointer1<void, FrameType>(this, &EddystoneService::enqueueFrame).bind(EDDYSTONE_FRAME_URL)
            ).period(minar::milliseconds(urlFramePeriod)).getHandle();
        } else {
            urlFrameCallbackHandle = NULL;
        }
    } else if (operationMode == EDDYSTONE_MODE_CONFIG) {
        ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
    }
}