/********************************************************************************************************
 * @file	sampleLightCtrl.c
 *
 * @brief	This is the source file for sampleLightCtrl
 *
 * @author	Zigbee Group
 * @date	2019
 *
 * @par     Copyright (c) 2019, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
 *          All rights reserved.
 *
 *          Redistribution and use in source and binary forms, with or without
 *          modification, are permitted provided that the following conditions are met:
 *
 *              1. Redistributions of source code must retain the above copyright
 *              notice, this list of conditions and the following disclaimer.
 *
 *              2. Unless for usage inside a TELINK integrated circuit, redistributions
 *              in binary form must reproduce the above copyright notice, this list of
 *              conditions and the following disclaimer in the documentation and/or other
 *              materials provided with the distribution.
 *
 *              3. Neither the name of TELINK, nor the names of its contributors may be
 *              used to endorse or promote products derived from this software without
 *              specific prior written permission.
 *
 *              4. This software, with or without modification, must only be used with a
 *              TELINK integrated circuit. All other usages are subject to written permission
 *              from TELINK and different commercial license may apply.
 *
 *              5. Licensee shall be solely responsible for any claim to the extent arising out of or
 *              relating to such deletion(s), modification(s) or alteration(s).
 *
 *          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 *          ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *          WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *          DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
 *          DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *          (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *          LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *          ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *          (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *******************************************************************************************************/
#if (__PROJECT_TL_DIMMABLE_LIGHT__)

/**********************************************************************
 * INCLUDES
 */
#include "tl_common.h"
#include "zcl_include.h"
#include "sampleLight.h"
#include "sampleLightCtrl.h"


/**********************************************************************
 * LOCAL CONSTANTS
 */
#define PWM_FREQUENCY					1000	//1KHz
#define PWM_FULL_DUTYCYCLE				100
#define PMW_MAX_TICK		            (PWM_CLOCK_SOURCE / PWM_FREQUENCY)


/**********************************************************************
 * TYPEDEFS
 */


/**********************************************************************
 * GLOBAL VARIABLES
 */


/**********************************************************************
 * FUNCTIONS
 */
extern void sampleLight_updateOnOff(void);
extern void sampleLight_updateLevel(void);
extern void sampleLight_updateColor(void);

extern void sampleLight_onOffInit(void);
extern void sampleLight_levelInit(void);
extern void sampleLight_colorInit(void);

/*********************************************************************
 * @fn      pwmSetDuty
 *
 * @brief
 *
 * @param   ch			-	PWM channel
 * 			dutycycle	-	level * PWM_FULL_DUTYCYCLE
 *
 * @return  None
 */
void pwmSetDuty(u8 ch, u16 dutycycle)
{
#ifdef ZCL_LEVEL_CTRL
	u32 cmp_tick = ((u32)dutycycle * PMW_MAX_TICK) / (ZCL_LEVEL_ATTR_MAX_LEVEL * PWM_FULL_DUTYCYCLE);
	drv_pwm_cfg(ch, (u16)cmp_tick, PMW_MAX_TICK);
#endif
}

/*********************************************************************
 * @fn      pwmInit
 *
 * @brief
 *
 * @param   ch			-	PWM channel
 * 			dutycycle	-	level * PWM_FULL_DUTYCYCLE
 *
 * @return  None
 */
void pwmInit(u8 ch, u16 dutycycle)
{
	pwmSetDuty(ch, dutycycle);
}

/*********************************************************************
 * @fn      hwLight_init
 *
 * @brief
 *
 * @param   None
 *
 * @return  None
 */
void hwLight_init(void)
{
	//drv_pwm_init();

#if COLOR_RGB_SUPPORT
	R_LIGHT_PWM_SET();
	G_LIGHT_PWM_SET();
	B_LIGHT_PWM_SET();
	pwmInit(R_LIGHT_PWM_CHANNEL, 0);
	pwmInit(G_LIGHT_PWM_CHANNEL, 0);
	pwmInit(B_LIGHT_PWM_CHANNEL, 0);
#else
	COOL_LIGHT_PWM_SET();
	pwmInit(COOL_LIGHT_PWM_CHANNEL, 0);
#if COLOR_CCT_SUPPORT
	WARM_LIGHT_PWM_SET();
	pwmInit(WARM_LIGHT_PWM_CHANNEL, 0);
#endif
#endif
}

/*********************************************************************
 * @fn      hwLight_onOffUpdate
 *
 * @brief
 *
 * @param   onOff - onOff attribute value
 *
 * @return  None
 */
void hwLight_onOffUpdate(u8 onOff)
{
	if(onOff){
#if COLOR_RGB_SUPPORT
		drv_pwm_start(R_LIGHT_PWM_CHANNEL);
		drv_pwm_start(G_LIGHT_PWM_CHANNEL);
		drv_pwm_start(B_LIGHT_PWM_CHANNEL);
#else
#if COLOR_CCT_SUPPORT
		drv_pwm_start(WARM_LIGHT_PWM_CHANNEL);
#endif
		drv_pwm_start(COOL_LIGHT_PWM_CHANNEL);
#endif
	}else{
#if COLOR_RGB_SUPPORT
		drv_pwm_stop(R_LIGHT_PWM_CHANNEL);
		drv_pwm_stop(G_LIGHT_PWM_CHANNEL);
		drv_pwm_stop(B_LIGHT_PWM_CHANNEL);
#else
#if COLOR_CCT_SUPPORT
		drv_pwm_stop(WARM_LIGHT_PWM_CHANNEL);
#endif
		drv_pwm_stop(COOL_LIGHT_PWM_CHANNEL);
#endif
	}
}

/*********************************************************************
 * @fn      hwLight_levelUpdate
 *
 * @brief
 *
 * @param   level - level attribute value
 *
 * @return  None
 */
void hwLight_levelUpdate(u8 level)
{
#if !defined COLOR_RGB_SUPPORT || (COLOR_RGB_SUPPORT == 0)
	level = (level < 0x10) ? 0x10 : level;

	u16 gammaCorrectLevel = ((u16)level * level) / ZCL_LEVEL_ATTR_MAX_LEVEL;

	pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, gammaCorrectLevel * PWM_FULL_DUTYCYCLE);
#endif
}

/*********************************************************************
 * @fn      temperatureToCW
 *
 * @brief
 *
 * @param   [in]colorTemperatureMireds	-	colorTemperatureMireds attribute value
 * 			[in]level					-	level attribute value
 * 			[out]C						-	cool light PWM
 * 			[out]W						-	warm light PWM
 *
 * @return  None
 */
void temperatureToCW(u16 temperatureMireds, u8 level, u8 *C, u8 *W)
{
#if COLOR_CCT_SUPPORT
	zcl_lightColorCtrlAttr_t *pColor = zcl_colorAttrGet();

	*W = (u8)(((temperatureMireds - pColor->colorTempPhysicalMinMireds) * level) / (pColor->colorTempPhysicalMaxMireds - pColor->colorTempPhysicalMinMireds));
	*C = level - (*W);
#endif
}

/*********************************************************************
 * @fn      hwLight_colorUpdate_colorTemperature
 *
 * @brief
 *
 * @param   colorTemperatureMireds	-	colorTemperatureMireds attribute value
 * 			level					-	level attribute value
 *
 * @return  None
 */
void hwLight_colorUpdate_colorTemperature(u16 colorTemperatureMireds, u8 level)
{
#if COLOR_CCT_SUPPORT
	u8 C = 0;
	u8 W = 0;

	level = (level < 0x10) ? 0x10 : level;

	temperatureToCW(colorTemperatureMireds, level, &C, &W);

	u16 gammaCorrectC = ((u16)C * C) / ZCL_LEVEL_ATTR_MAX_LEVEL;
	u16 gammaCorrectW = ((u16)W * W) / ZCL_LEVEL_ATTR_MAX_LEVEL;

	pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, gammaCorrectC * PWM_FULL_DUTYCYCLE);
	pwmSetDuty(WARM_LIGHT_PWM_CHANNEL, gammaCorrectW * PWM_FULL_DUTYCYCLE);
#endif
}

/*********************************************************************
 * @fn      hsvToRGB
 *
 * @brief
 *
 * @param   [in]hue			-	hue attribute value
 * 			[in]saturation	-	saturation attribute value
 * 			[in]level		-	level attribute value
 * 			[out]R			-	R light PWM
 * 			[out]G			-	G light PWM
 * 			[out]B			-	B light PWM
 *
 * @return  None
 */
void hsvToRGB(u8 hue, u8 saturation, u8 level, u8 *R, u8 *G, u8 *B)
{
#if COLOR_RGB_SUPPORT
    u8 region;
    u8 remainder;
    u8 p, q, t;

	u16 rHue = (u16)hue * 360 / ZCL_COLOR_ATTR_HUE_MAX;
	u8 rS = saturation;
	u8 rV = level;

	if(saturation == 0){
		*R = rV;
		*G = rV;
		*B = rV;
		return;
	}

	if(rHue < 360){
		region = rHue / 60;
	}else{
		region = 0;
	}

	remainder = (rHue - (region * 60)) * 4;

    p = (rV * (255 - rS)) >> 8;
    q = (rV * (255 - ((rS * remainder) >> 8))) >> 8;
    t = (rV * (255 - ((rS * (255 - remainder)) >> 8))) >> 8;

    if (region == 0) {
    	*R = rV;
    	*G = t;
    	*B = p;
    } else if (region == 1) {
    	*R = q;
    	*G = rV;
    	*B = p;
    } else if (region == 2) {
    	*R = p;
    	*G = rV;
    	*B = t;
    } else if (region == 3) {
    	*R = p;
    	*G = q;
    	*B = rV;
    } else if (region == 4) {
    	*R = t;
    	*G = p;
    	*B = rV;
    } else {
    	*R = rV;
    	*G = p;
    	*B = q;
    }
#endif
}

/*********************************************************************
 * @fn      hwLight_colorUpdate_HSV2RGB
 *
 * @brief
 *
 * @param   hue			-	hue attribute value
 * 			saturation	-	saturation attribute value
 * 			level		-	level attribute value
 *
 * @return  None
 */
void hwLight_colorUpdate_HSV2RGB(u8 hue, u8 saturation, u8 level)
{
#if COLOR_RGB_SUPPORT
	u8 R = 0;
	u8 G = 0;
	u8 B = 0;

	level = (level < 0x10) ? 0x10 : level;

	hsvToRGB(hue, saturation, level, &R, &G, &B);

	u16 gammaCorrectR = ((u16)R * R) / ZCL_LEVEL_ATTR_MAX_LEVEL;
	u16 gammaCorrectG = ((u16)G * G) / ZCL_LEVEL_ATTR_MAX_LEVEL;
	u16 gammaCorrectB = ((u16)B * B) / ZCL_LEVEL_ATTR_MAX_LEVEL;

	pwmSetDuty(PWM_R_CHANNEL, gammaCorrectR * PWM_FULL_DUTYCYCLE);
	pwmSetDuty(PWM_G_CHANNEL, gammaCorrectG * PWM_FULL_DUTYCYCLE);
	pwmSetDuty(PWM_B_CHANNEL, gammaCorrectB * PWM_FULL_DUTYCYCLE);
#endif
}

/*********************************************************************
 * @fn      light_adjust
 *
 * @brief
 *
 * @param   None
 *
 * @return  None
 */
void light_adjust(void)
{
#ifdef ZCL_LIGHT_COLOR_CONTROL
	sampleLight_colorInit();
#else
#ifdef ZCL_LEVEL_CTRL
	sampleLight_levelInit();
#endif
#endif
	sampleLight_onOffInit();
}

/*********************************************************************
 * @fn      light_fresh
 *
 * @brief
 *
 * @param   None
 *
 * @return  None
 */
void light_fresh(void)
{
#ifdef ZCL_LIGHT_COLOR_CONTROL
	sampleLight_updateColor();
#else
#ifdef ZCL_LEVEL_CTRL
	sampleLight_updateLevel();
#else
	pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, ZCL_LEVEL_ATTR_MAX_LEVEL * PWM_FULL_DUTYCYCLE);
#endif
#endif
	sampleLight_updateOnOff();

	gLightCtx.lightAttrsChanged = TRUE;
}

/*********************************************************************
 * @fn      light_applyUpdate
 *
 * @brief
 *
 * @param
 *
 * @return  None
 */
void light_applyUpdate(u8 *curLevel, u16 *curLevel256, s32 *stepLevel256, u16 *remainingTime, u8 minLevel, u8 maxLevel, bool wrap)
{
	if((*stepLevel256 > 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) > maxLevel)){
		*curLevel256 = (wrap) ? ((u16)minLevel * 256 + ((*curLevel256 + *stepLevel256) - (u16)maxLevel * 256) - 256)
							  : ((u16)maxLevel * 256);
	}else if((*stepLevel256 < 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) < minLevel)){
		*curLevel256 = (wrap) ? ((u16)maxLevel * 256 - ((u16)minLevel * 256 - ((s32)*curLevel256 + *stepLevel256)) + 256)
							  : ((u16)minLevel * 256);
	}else{
		*curLevel256 += *stepLevel256;
	}

	if(*stepLevel256 > 0){
		*curLevel = (*curLevel256 + 127) / 256;
	}else{
		*curLevel = *curLevel256 / 256;
	}

	if(*remainingTime == 0){
		*curLevel256 = ((u16)*curLevel) * 256;
		*stepLevel256 = 0;
	}else if(*remainingTime != 0xFFFF){
		*remainingTime = *remainingTime -1;
	}

	light_fresh();
}

/*********************************************************************
 * @fn      light_applyUpdate_16
 *
 * @brief
 *
 * @param
 *
 * @return  None
 */
void light_applyUpdate_16(u16 *curLevel, u32 *curLevel256, s32 *stepLevel256, u16 *remainingTime, u16 minLevel, u16 maxLevel, bool wrap)
{
	if((*stepLevel256 > 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) > maxLevel)){
		*curLevel256 = (wrap) ? ((u32)minLevel * 256 + ((*curLevel256 + *stepLevel256) - (u32)maxLevel * 256) - 256)
							  : ((u32)maxLevel * 256);
	}else if((*stepLevel256 < 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) < minLevel)){
		*curLevel256 = (wrap) ? ((u32)maxLevel * 256 - ((u32)minLevel * 256 - ((s32)*curLevel256 + *stepLevel256)) + 256)
							  : ((u32)minLevel * 256);
	}else{
		*curLevel256 += *stepLevel256;
	}

	if(*stepLevel256 > 0){
		*curLevel = (*curLevel256 + 127) / 256;
	}else{
		*curLevel = *curLevel256 / 256;
	}

	if(*remainingTime == 0){
		*curLevel256 = ((u32)*curLevel) * 256;
		*stepLevel256 = 0;
	}else if(*remainingTime != 0xFFFF){
		*remainingTime = *remainingTime -1;
	}

	light_fresh();
}

/*********************************************************************
 * @fn      light_blink_TimerEvtCb
 *
 * @brief
 *
 * @param   arg
 *
 * @return  0: timer continue on; -1: timer will be canceled
 */
s32 light_blink_TimerEvtCb(void *arg)
{
	u32 interval = 0;

	if(gLightCtx.sta == gLightCtx.oriSta){
		if(gLightCtx.times){
			gLightCtx.times--;
			if(gLightCtx.times <= 0){
				if(gLightCtx.oriSta){
					hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
				}else{
					hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
				}

				gLightCtx.timerLedEvt = NULL;
				return -1;
			}
		}
	}

	gLightCtx.sta = !gLightCtx.sta;
	if(gLightCtx.sta){
		hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
		interval = gLightCtx.ledOnTime;
	}else{
		hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
		interval = gLightCtx.ledOffTime;
	}

	return interval;
}

/*********************************************************************
 * @fn      light_blink_start
 *
 * @brief
 *
 * @param   times 		- counts
 * @param   ledOnTime	- on times, ms
 * @param   ledOffTime	- off times, ms
 *
 * @return  None
 */
void light_blink_start(u8 times, u16 ledOnTime, u16 ledOffTime)
{
	u32 interval = 0;
	zcl_onOffAttr_t *pOnoff = zcl_onoffAttrGet();

	gLightCtx.oriSta = pOnoff->onOff;
	gLightCtx.times = times;

	if(!gLightCtx.timerLedEvt){
		if(gLightCtx.oriSta){
			hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
			gLightCtx.sta = 0;
			interval = ledOffTime;
		}else{
			hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
			gLightCtx.sta = 1;
			interval = ledOnTime;
		}
		gLightCtx.ledOnTime = ledOnTime;
		gLightCtx.ledOffTime = ledOffTime;

		gLightCtx.timerLedEvt = TL_ZB_TIMER_SCHEDULE(light_blink_TimerEvtCb, NULL, interval);
	}
}

/*********************************************************************
 * @fn      light_blink_stop
 *
 * @brief
 *
 * @param   None
 *
 * @return  None
 */
void light_blink_stop(void)
{
	if(gLightCtx.timerLedEvt){
		TL_ZB_TIMER_CANCEL(&gLightCtx.timerLedEvt);

		gLightCtx.times = 0;
		if(gLightCtx.oriSta){
			hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
		}else{
			hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
		}
	}
}

#endif	/* __PROJECT_TL_DIMMABLE_LIGHT__ */
