package controller

/*
#include <uvdbus/smart_sound.h>
#include <uvdbus/ota.h>
*/
import "C"
import (
	"context"
	"encoding/json"
	. "netease_control_center/globals"
	"netease_control_center/interfaces"
	"netease_control_center/interfaces/eventPoint"
	"netease_control_center/interfaces/log"
	. "netease_control_center/interfaces/schedule"
	. "netease_control_center/interfaces/vui"
	"netease_control_center/modules/hardware/led_mcu"
	"netease_control_center/modules/hardware/mcu_leds/atservice"
	"netease_control_center/utils"
	"sync"
	"time"
	//"netease_control_center/modules/hardware/misc"
	"netease_control_center/globals"
	"netease_control_center/utils/customization"
)

var (
	voicetaskhd    TaskHd
	hasWakedUp     = false
	lastWakeup     MscWakeUp
	VoiceSessionId string

	initVoiceCtx, initVoiceCtxCancel = context.WithCancel(context.Background())

	voiceCtxMutex sync.Mutex

	BeginRecogTime    time.Time
	RecogTimeoutTimer *time.Timer
)

func ClearRecogTimeoutTimer() {
	if tmp := RecogTimeoutTimer; tmp != nil {
		tmp.Stop()
		RecogTimeoutTimer = nil
	}
}

func VoiceTaskInit() {
	voiceEventRegiste()
	voiceStatusTaskRegiste()
	voiceWakeupInit()
}

func getVoiceCtx() context.Context {
	voiceCtxMutex.Lock()
	defer voiceCtxMutex.Unlock()

	initVoiceCtxCancel()
	initVoiceCtx, initVoiceCtxCancel = context.WithCancel(context.Background())
	return initVoiceCtx
}

func UpdateVoiceStatus(curStatus VoiceStatuses) {
	//UpdateMicLedsStauts()
	if curStatus == CurVoiceStatus {
		return
	}

	CurVoiceStatus = curStatus
	switch curStatus {
	case VoiceStatus_WAKEUP:
		VoiceSessionId = ""
		stamp := uint8(utils.Angle2LedNo2(lastWakeup.Angle, 12, 4, false))
		led_mcu.DirectLedControl(atservice.GUIDE_EFFECT_ID_VOICE_INPUT, atservice.GUIDE_ACTION_START, stamp, []byte{})

		//case VoiceStatus_SessionBegin:
		//	stamp := uint8(utils.Angle2LedNo2(lastWakeup.Angle, 12, 4, false))
		//	led_mcu.DirectLedControl(atservice.GUIDE_EFFECT_ID_VOICE_INPUT, atservice.GUIDE_ACTION_START, stamp, []byte{})

	case VoiceStatus_RECOGING:
		BeginRecogTime = time.Now()
		led_mcu.DirectLedControl(atservice.GUIDE_EFFECT_ID_THINKING, atservice.GUIDE_ACTION_START, 0, []byte{})

	case VoiceStatus_FINISH:
		led_mcu.DirectLedControl(atservice.GUIDE_EFFECT_ID_VOICE_WAKEUP, atservice.GUIDE_ACTION_STOP, 0, []byte{})
		IconLed_Clear.Show()

	case VoiceStatus_ERROR:

	case VoiceStatus_IDLE:
		led_mcu.DirectLedClear(atservice.GUIDE_EFFECT_ID_VOICE_WAKEUP)
		led_mcu.DirectLedClear(atservice.GUIDE_EFFECT_ID_THINKING)
		led_mcu.DirectLedClear(atservice.GUIDE_EFFECT_ID_VOICE_INPUT)
		IconLed_Clear.Show()
	}
}

func GetCurVoiceStatus() VoiceStatuses {
	return CurVoiceStatus
}

func UpdateVoiceMicStatus(isDisable bool) {
	IsMicClose = isDisable
}

func SetVoiceWakeupAngle(angle int) {
	stamp := uint8(utils.Angle2LedNo2(angle, 12, 4, false))
	//stamp := uint8(utils.Angle2LedNo2(angle, 12, 10, true))
	log.Println(log.DebugLog, "\n\nWill light stamp:", stamp, "\n\n\n")
	led_mcu.DirectLedControl(atservice.GUIDE_EFFECT_ID_VOICE_WAKEUP, atservice.GUIDE_ACTION_START, stamp, []byte{})
	VoiceWakeupAngle = (uint16(angle))

	UpdateVoiceStatus(VoiceStatus_WAKEUP)
}

func voiceTask_Interrupt(ByWho string) {
	//todo if now is rcoging, interrupt, and update status
	if GetCurVoiceStatus() != VoiceStatus_IDLE {
		VoiceEndSession()
	}
	UpdateVoiceStatus(VoiceStatus_IDLE)
}

func voiceTask_Resume() {
	//do nothing
}

func GetSessionId(orig interface{}) (sessionId string) {
	var tmp struct {
		Id string `json:"sessionId"`
	}

	if origstring, ok := orig.(string); ok {
		if err := json.Unmarshal([]byte(origstring), &tmp); err == nil {
			return tmp.Id
		} else {
			return ""
		}
	} else {
		return ""
	}

}

func voiceService_exec(canExec bool, scmd SCMD, arg ...interface{}) {
	defer func() {
		if err := recover(); err != nil {
			log.Println(log.DebugLog, err)
		}
	}()
	switch scmd {
	case SCMD_SuperTask_Voice_ContinuSession:
		ClearRecogTimeoutTimer()

		log.Println(log.DebugLog, "Continue Last session!", canExec, IsMicClose, arg)
		if canExec == false {
			log.Println(log.DebugLog, "Continue session but can not exec!")
			if GetCurVoiceStatus() != VoiceStatus_IDLE {
				CurVoiceStatus = VoiceStatus_IDLE
				VoiceEndSession()
			}
			return
		}

		if InternetConnect == false {
			log.Println(log.DebugLog, "Wake up, but internet down!")
			ToastNetworkError(false)
			return
		}

		if canExec && IsMicClose == false {
			UpdateVoiceStatus(VoiceStatus_WAKEUP)
			SetVoiceWakeupAngle(lastWakeup.Angle)
			recType := VoiceSession_Type_Normal
			confirmParam := ""
			if len(arg) > 0 {
				if ty, ok := arg[0].(VoiceSession_Type); ok == true {
					recType = ty
				}
			}
			if len(arg) > 1 {
				if para, ok := arg[1].(string); ok == true {
					confirmParam = para
				}
			}
			VoiceContinueSession(recType, confirmParam)
		} else {
			UpdateVoiceStatus(VoiceStatus_IDLE)
		}
	case SCMD_SuperTask_Voice_WakeUp:
		ClearRecogTimeoutTimer()

		if canExec == false {
			log.Println(log.DebugLog, "Wake up, but can not exec!")
			if GetCurVoiceStatus() != VoiceStatus_IDLE {
				CurVoiceStatus = VoiceStatus_IDLE
				VoiceEndSession()
			}
			return
		}
		if IsMicClose == true {
			log.Println(log.DebugLog, "Mic is close, do not wake up!!")
			return
		}

		if globals.WifiSwitch == globals.WifiSwitch_Off || globals.WifiSwitch == globals.WifiSwitch_Offing {
			log.Println(log.DebugLog, "In btOnWifiOff mode!")
			PlayNoticeAsync(AdSrcType_FILE, SoundNotice_Warn_BtOnWifiOff.GetPath(),  CONST_AUDIO_ID, false, nil, nil)

			return
		}

		if InternetConnect == false {
			log.Println(log.DebugLog, "Wake up, but internet down!")
			ToastNetworkError(false)
			return
		}
		if ShowUpdateVersionInfo == true {
			ShowUpdateVersionInfo = false
			ToastOtaAfterVersionMessage()
			return
		}

		if vol, _ := VolGet(); vol == 0 {
			ctrlMusicByVol = false
			VolSet(DefaultVol.GetCfgListVui())
		}

		if pass, content := customization.OemCheck(true); pass == false {
			MscToast(AdSrcType_TTS, content, CONST_AUDIO_ID, false, false, nil)
			return
		}

		if tmp, ok := arg[0].(MscWakeUp); ok == true && canExec == true && VoiceStatus_WAKEUP.CanExec() {
			UpdateVoiceStatus(VoiceStatus_WAKEUP)
			PlayNoticeAsync(AdSrcType_FILE, SoundNotice_Shuanxing001.GetPath(), WAKEUP_AUDIO_ID, false, nil, func(id uint32, reason NoticeStopReason) {
				if reason.IsStop() {
					eventPoint.EpRecord("R112", map[string]interface{}{})
					//misc.IdleMicAdc()
					TryMicAdcIdle()
				}
			})
			//SetVoiceWakeupAngle(tmp.Angle)
			lastWakeup = tmp
			VoiceContinueSession(VoiceSession_Type_Normal, "")
		} else {
			UpdateVoiceStatus(VoiceStatus_IDLE)
			log.Println(log.DebugLog, "Wake up decode err!", ok, canExec, IsMicClose)
		}
		//case SCMD_SuperTask_Voice_SessionBegin:
		//	log.Println(log.DebugLog, "Voice session creates successfully and begins!", canExec)
		//	if canExec && IsMicClose == false {
		//		UpdateVoiceStatus(VoiceStatus_SessionBegin)
		//		hasWakedUp = true
		//		eventPoint.EpRecord("A321", map[string]interface{}{"sessionid": GetSessionId(arg[0])})
		//	} else {
		//		VoiceEndSession()
		//		UpdateVoiceStatus(VoiceStatus_IDLE)
		//	}
	case SCMD_SuperTask_Voice_Recoging:
		if canExec && VoiceStatus_RECOGING.CanExec() {
			UpdateVoiceStatus(VoiceStatus_RECOGING)
			ClearRecogTimeoutTimer()
			RecogTimeoutTimer = time.AfterFunc(VoiceRecogMaxTime.GetCfgListTime(), func() {
				log.Println(log.DebugLog, "Recog timeout, in timer func!")
				if RecogTimeoutTimer == nil {
					log.Println(log.ErrorLog, "Timer is nil, SHOULD NOT BE HERE, ret!")
					return
				}
				RecogTimeoutTimer = nil
				VoiceEndSession()
				SendSCmd(SCMD_SuperTask_Voice_RecogTimeout)
			})
			eventPoint.EpRecord("A312", map[string]interface{}{"sessionId": VoiceSessionId})
		} else {
			log.Println(log.ErrorLog, "Recog can not exec: ", VoiceStatus_RECOGING.Debug())
			VoiceEndSession()
			UpdateVoiceStatus(VoiceStatus_IDLE)
		}

	case SCMD_SuperTask_Voice_RecogTimeout:
		if canExec && VoiceStatus_ERROR.CanExec() {
			UpdateVoiceStatus(VoiceStatus_IDLE)
			log.Println(log.DebugLog, "Recog timeout!")
			MscToast(AdSrcType_FILE, SoundNotice_Error_Voice.GetPath(), CONST_AUDIO_ID, true, false, nil)
		} else {
			UpdateVoiceStatus(VoiceStatus_IDLE)
		}

	case SCMD_SuperTask_Voice_RecogFinish:
		if canExec && VoiceStatus_FINISH.CanExec() {
			UpdateVoiceStatus(VoiceStatus_IDLE)
			if tmp, ok := arg[0].(*[]byte); ok == true {
				led_mcu.DirectLedControl(atservice.GUIDE_EFFECT_ID_CLEAR, atservice.GUIDE_ACTION_STOP, 0, []byte{})
				MscSuccessCommand(tmp, uint32(len(*tmp)))
			} else {
				log.Println(log.ErrorLog, "Decode voice returned data err!")
			}
		} else {
			log.Println(log.ErrorLog, "Recog finish can not exec:", canExec, GetCurVoiceStatus(), IsMicClose)
			UpdateVoiceStatus(VoiceStatus_IDLE)
		}

	case SCMD_SuperTask_Voice_RecogErr:
		if canExec && VoiceStatus_ERROR.CanExec() {
			if tmp, ok := arg[0].(MscErrcode); ok == true && canExec {
				switch tmp.Errorcode {
				//Handle invaild, normally do not happen except for interrupt the session on waking up

				case -2, -1:
					//create session error
					MscToast(AdSrcType_FILE, SoundNotice_Error_Voice.GetPath(), CONST_AUDIO_ID, true, false, nil)

				default:
					//do nothings, just schedule
				}
			}
		}

		UpdateVoiceStatus(VoiceStatus_IDLE)
		voicetaskhd.FinishExec()
		log.Println(log.ErrorLog, "Recog err! errcode: ", arg[0])
	}
}

type VoiceSession_Type string

const (
	VoiceSession_Type_Normal  VoiceSession_Type = "0"
	VoiceSession_Type_Confirm VoiceSession_Type = "1"
	VoiceSession_Type_Select  VoiceSession_Type = "2"
)

func ScheduleContinuceSession(recType VoiceSession_Type, param string) {
	SendSCmd(SCMD_SuperTask_Voice_ContinuSession, recType, param)
}

type ConfirmVoiceSessionParam_Type int

const (
	ConfirmVoiceSessionParam_Type_Scene ConfirmVoiceSessionParam_Type = iota
)

type ConfirmVoiceSessionParam struct {
	Yes   string                        `json:"yes"`
	No    string                        `json:"no"`
	Delay string                        `json:"delay"`
	Type  ConfirmVoiceSessionParam_Type `json:"type"`
}

func (this ConfirmVoiceSessionParam) String() string {
	if data, err := json.Marshal(this); err == nil {
		return string(data)
	} else {
		return ""
	}
}

func PackConfirmVoiceSession(yes string, no string, delay string) string {
	var tmp ConfirmVoiceSessionParam
	tmp.Yes = yes
	tmp.No = no
	tmp.Delay = delay
	if data, err := json.Marshal(tmp); err == nil {
		return string(data)
	} else {
		return ""
	}
}

func UnpackConfirmVoiceSession(input string) (param ConfirmVoiceSessionParam) {
	if err := json.Unmarshal([]byte(input), &param); err == nil {
		return
	} else {
		log.Println(log.ErrorLog, "json decode err!", err)
	}

	return
}

func ScheduleNewVoiceSession(recType VoiceSession_Type, devParam string, serverParam string) {
	var tmp struct {
		Client string `json:"clientParam,omitempty"`
		Server string `json:"serverParam,omitempty"`
	}

	tmp.Server = serverParam
	tmp.Client = devParam

	if data, err := json.Marshal(tmp); err == nil {
		SendSCmd(SCMD_SuperTask_Voice_ContinuSession, recType, string(data))
	} else {
		log.Println(log.ErrorLog, "Json code err!")
	}
}

func VoiceContinueSession(recType VoiceSession_Type, confirmParam string) {
	var tmp struct {
		Type         VoiceSession_Type `json:"recType"`
		Param        string            `json:"confirmParam"`
		PlayerStatus string            `json:"playerStatus"`
	}
	tmp.Type = recType
	tmp.Param = confirmParam
	tmp.PlayerStatus = ""

	if data, err := json.Marshal(GetCurVboxBroadcastSt(true)); err == nil {
		tmp.PlayerStatus = string(data)
	}

	if data, err := json.Marshal(tmp); err == nil {
		log.Println(log.DebugLog, "Begin session, ", string(data))
		interfaces.SendCommand(MODULE_CONTROLLER, MODULE_VOICEENGINE, uint32(C.CMD_CC_MSC_BEGIN_SESSION), &data)
	} else {
		log.Println(log.DebugLog, "Json code err!")
	}
}

func VoiceEndSession() {
	interfaces.JustSendBusCmd(MODULE_CONTROLLER, MODULE_VOICEENGINE, uint32(C.CMD_CC_MSC_END_SESSION))
}

func voiceEventRegiste() {
	var tmp RegistedTask
	var err error

	tmp.Interrupt = func(ByWho string) {
		//do nothing
	}

	tmp.GetName = func() string {
		return ModuleName_VoiceWakeUp
	}

	tmp.IsActive = func() bool {
		return false
	}

	tmp.GetTaskLevel = func() TaskLevel {
		return TaskLevel_VoiceEventService
	}

	tmp.Resume = func() {
		//do nothing
	}

	tmp.GetSupportedSCmd = func() []SCMD {
		return []SCMD{SCMD_SuperTask_Voice_WakeUp,
			SCMD_SuperTask_Voice_ContinuSession}
	}

	tmp.Do = voiceService_exec
	tmp.CanBeInterruptByKey = func() bool {
		return false
	}

	tmp.GetScheduleDomain = func() ScheduleDomain {
		return ScheduleDomain_Voice
	}

	if err, voicetaskhd = RegisteSTask(tmp); err == nil {
		log.Printf(log.DebugLog, "Register %s task successfully!\n", tmp.GetName())
	} else {
		log.Printf(log.ErrorLog, "Register %s task fail!\n", tmp.GetName())
	}
}

func voiceStatusTaskRegiste() {
	var tmp RegistedTask
	var err error

	tmp.Interrupt = voiceTask_Interrupt

	tmp.GetName = func() string {
		return "voice_wakedup_recoging_status_service"
	}

	tmp.IsActive = func() bool {
		return CurVoiceStatus != VoiceStatus_IDLE
	}

	tmp.GetTaskLevel = func() TaskLevel {
		return TaskLevel_VoiceStatusService
	}

	tmp.Resume = voiceTask_Resume

	tmp.GetSupportedSCmd = func() []SCMD {
		return []SCMD{
			//SCMD_SuperTask_Voice_SessionBegin,
			SCMD_SuperTask_Voice_Recoging,
			SCMD_SuperTask_Voice_RecogFinish,
			SCMD_SuperTask_Voice_RecogErr,
			SCMD_SuperTask_Voice_RecogTimeout}
	}

	tmp.Do = voiceService_exec
	tmp.CanBeInterruptByKey = func() bool {
		return false
	}

	tmp.GetScheduleDomain = func() ScheduleDomain {
		return ScheduleDomain_Voice
	}

	if err, voicetaskhd = RegisteSTask(tmp); err == nil {
		log.Printf(log.DebugLog, "Register %s task successfully!\n", tmp.GetName())
	} else {
		log.Printf(log.ErrorLog, "Register %s task fail!\n", tmp.GetName())
	}
}

func wakeupCallback(id uint32, reason NoticeStopReason) {
	switch reason {
	case NoticeStart_Play:
		eventPoint.AudioStart(WAKEUP_AUDIO_ID, SoundNotice_Shuanxing001.GetPath(), eventPoint.EpOs_Device)
	case NoticeStopReason_Interrupt,
		NoticeStopReason_Cancel,
		NoticeStopReason_Error:
		eventPoint.AudioEnd(WAKEUP_AUDIO_ID, SoundNotice_Shuanxing001.GetPath(), "break", eventPoint.EpOs_Device)
	case NoticeStopReason_Complete:
		eventPoint.AudioEnd(WAKEUP_AUDIO_ID, SoundNotice_Shuanxing001.GetPath(), "end", eventPoint.EpOs_Device)
	default:

	}
}

func voiceWakeupInit() {
	globals.WakeupArg.RegisterCb(func(e interface{}, cbType globals.CallCbType) {
		if cmd, ok := e.(string); ok {
			var tmp OneShotArray
			log.Println(log.DebugLog, "Get one shot cmd:", cmd, ", type:", cbType)
			if err := json.Unmarshal([]byte(cmd), &tmp); err == nil {
				log.Printf(log.DebugLog, "New oneshot status:%+v, old:%+v\n", tmp, OneShot)
				OneShot = tmp
				Nduilite_fespa_set()
			} else {
				log.Println(log.ErrorLog, "json err:", err)
			}
		} else {
			log.Println(log.ErrorLog, "one shot type error!")
		}
	})
}

func Nduilite_fespa_set() {
	Nduilite_fespa_set_arg(false)
}

func Nduilite_fespa_set_arg(initSdk bool) {
	//for test
	//OneShot = OneShotArray{[]WakeupWordsType{NextSong, VolumeAdjust}}
	dayMode := true
	/*0-7点&时间同步成功前，采用夜间唤醒阈值；其他时间采用日间唤醒阈值*/
	if hour, year := time.Now().Hour(), time.Now().Year(); hour >= 0 && hour < 7 || year < 2018 {
		dayMode = false
	}

	var tmp struct {
		wakeupwords string
		threshStrs  string
		majorStrs   string
		dcheckStrs  string
		thresh2Strs string
	}

	//主唤醒词
	tmp.wakeupwords = string("words=" + WkupArray[MainWakeup].WkupWord)
	tmp.majorStrs = string("major=" + WkupArray[MainWakeup].Major)
	tmp.dcheckStrs = string("dcheck=" + WkupArray[MainWakeup].Dcheck)
	if dayMode {
		tmp.threshStrs = string("thresh=" + WkupArray[MainWakeup].DayThresh)
		tmp.thresh2Strs = string("thresh2=" + WkupArray[MainWakeup].DayThresh2)
	} else {
		tmp.threshStrs = string("thresh=" + WkupArray[MainWakeup].NightThresh)
		tmp.thresh2Strs = string("thresh2=" + WkupArray[MainWakeup].NightThresh2)
	}

	//实验室功能
	for _, value := range OneShot.Words {
		if value < MaxWords {
			tmp.wakeupwords += string("," + WkupArray[value].WkupWord)
			tmp.majorStrs += string("," + WkupArray[value].Major)
			tmp.dcheckStrs += string("," + WkupArray[value].Dcheck)

			if dayMode {
				tmp.threshStrs += string("," + WkupArray[value].DayThresh)
				tmp.thresh2Strs += string("," + WkupArray[value].DayThresh2)
			} else {
				tmp.threshStrs += string("," + WkupArray[value].NightThresh)
				tmp.thresh2Strs += string("," + WkupArray[value].NightThresh2)
			}
		}
	}

	var tmpEnv struct {
		Env string `json:"env"`
	}
	tmpEnv.Env = string(tmp.wakeupwords + ";" + tmp.threshStrs + ";" + tmp.majorStrs + ";" + tmp.dcheckStrs + ";" + tmp.thresh2Strs + ";")

	data, err := json.Marshal(tmpEnv)
	if nil != err {
		log.Println(log.ErrorLog, err.Error())
		return
	}

	log.Println(log.WarningLog, "Set arg to netease_voice:", string(data))
	if initSdk == false {
		interfaces.SendCommand(MODULE_CONTROLLER, MODULE_VOICEENGINE, uint32(C.CMD_NDUILITE_FESPA_SET_ARG), &data)
	} else {
		interfaces.SendCommand(MODULE_CONTROLLER, MODULE_VOICEENGINE, uint32(C.CMD_NDUILITE_SDK_REINIT), &data)
	}
}
