package atservice

import (
	"bytes"
	"encoding/binary"
	"errors"
	"netease_control_center/interfaces/log"
	"os"
	"strconv"
	"sync"
	"time"
)

//type AT_CMD_TYPE uint8

const (
	AT_CMD = iota

	AT_CMD_VERSION
	//AT_CMD_TEST
	//AT_CMD_OTA
	AT_CMD_GEFFECT    /*指向灯*/
	AT_CMD_MANI       /*矩阵等动画*/
	AT_CMD_MGRAD      /*矩阵等渐变*/
	AT_CMD_MMPLAY     /*音乐播放*/
	AT_CMD_MCLOSE     /*矩阵灯关闭*/
	AT_CMD_BRIGHTNESS /*设置灯的亮度百分比*/
	AT_CMD_LAMP_SHOW  /*初始化是否显示灯光*/
	AT_CMD_LAMP_TEST  /*lamp test*/
	AT_CMD_MCU_ST     /*get mcu status*/

	at_resp_startup
	at_resp_error

	/*end of the ID*/
	AT_CMD_NUM
)

type McuStatusT uint8

const (
	MCU_STATUS_AT_NOT_READY McuStatusT = iota
	MCU_STATUS_AT_READY
	MCU_STATUS_NEED_OTA
	MCU_STATUS_BOOT_ERR
	MCU_STATUS_END
)

type McuLampShowT uint8

const (
	MCU_LAMP_INIT_NOT_SHOW McuLampShowT = iota
	MCU_LAMP_INIT_SHOW
	MCU_LAMP_SHOW_END
)

/*from AT CMD Error*/
const (
	AT_CMD_RSP_ERR_NONE         = 0
	AT_CMD_RSP_FORMAT_ERR       = 1
	AT_CMD_RSP_TYPE_ERR         = 2
	AT_CMD_RSP_SYS_BUSY         = 3
	AT_CMD_RSP_INVALID_ARG      = 4
	AT_CMD_RSP_NOT_ALLOW        = 5
	AT_CMD_RSP_NO_MEMORY        = 6
	AT_CMD_RSP_NOT_SUPPORT      = 7
	AT_CMD_RSP_REV_TIMEOUT      = 8
	AT_CMD_RSP_REV_CHECKSUM_ERR = 9
)

const (
	AtCmdStatusWaitTime = 3000 //如果mcu启动出错死机后，等待3s timeout
	ReConnectMcuMaxCnt  = 2
	RebootMcuMaxCnt     = 1
)

type AtIsReadyCb func(Status McuStatusT)                          //at ready callback
type OtaIsFinishCb func(isFinish bool, progress uint8, err error) //ota progress callback

type atProc func(buf []byte) //at handler callback type

type atCmdParamT struct {
	cmdPrefix  string
	overTime   time.Duration
	respPrefix string
	atHandler  atProc
}

var (
	AtReadyCb    AtIsReadyCb
	OtaFinishCb  OtaIsFinishCb
	mcuImagePath string

	ReConnectMcuCnt  uint8       = 1
	RebootMcuCnt     uint8       = 0
	MaxResetCnt      uint8       = 0
	atCmdMode        bool        = false
	mcuOTAMode       bool        = false
	mcuReboot        bool        = false //mcu上次是否重启启动？
	checkMcuHasBoot  *time.Timer         //mcu的boot是否正常，以mcu可以拉低gpio状态为依据（因为dvt的at gpio默认改为高）
	atCmdStWaitTimer *time.Timer
	atCmdOnce        sync.Once
	atCmdTable       = map[uint32]*atCmdParamT{
		AT_CMD:            &atCmdParamT{cmdPrefix: "AT+CON", overTime: 500, respPrefix: "OK", atHandler: mcuCmdOkHandler},
		AT_CMD_VERSION:    &atCmdParamT{cmdPrefix: "AT+VER", overTime: 800, respPrefix: "+VER=", atHandler: nil},
		AT_CMD_GEFFECT:    &atCmdParamT{cmdPrefix: "AT+GEffect=", overTime: 800, respPrefix: "+GEffect", atHandler: nil},
		AT_CMD_MANI:       &atCmdParamT{cmdPrefix: "AT+MAni=", overTime: 1500, respPrefix: "+MAni", atHandler: nil},
		AT_CMD_MGRAD:      &atCmdParamT{cmdPrefix: "AT+MGrad=", overTime: 800, respPrefix: "+MGrad", atHandler: nil},
		AT_CMD_MMPLAY:     &atCmdParamT{cmdPrefix: "AT+MMPlay=", overTime: 800, respPrefix: "+MMMPlay", atHandler: nil},
		AT_CMD_MCLOSE:     &atCmdParamT{cmdPrefix: "AT+MClose", overTime: 800, respPrefix: "+MClose", atHandler: nil},
		AT_CMD_BRIGHTNESS: &atCmdParamT{cmdPrefix: "AT+Brightness", overTime: 800, respPrefix: "+Brightness", atHandler: nil},
		AT_CMD_LAMP_SHOW:  &atCmdParamT{cmdPrefix: "AT+LampShow=", overTime: 800, respPrefix: "+LampShow", atHandler: nil},
		AT_CMD_LAMP_TEST:  &atCmdParamT{cmdPrefix: "AT+LampTest=", overTime: 800, respPrefix: "+LampTest", atHandler: nil},
		AT_CMD_MCU_ST:     &atCmdParamT{cmdPrefix: "AT+MCUST=", overTime: 800, respPrefix: "+MCUST", atHandler: mcuStatusHandler},

		at_resp_error:   &atCmdParamT{cmdPrefix: "", overTime: 0, respPrefix: "+ER-", atHandler: mcuRespErrorHandler},
		at_resp_startup: &atCmdParamT{cmdPrefix: "", overTime: 0, respPrefix: "+STARTUP", atHandler: mcuRespStartUpHandler},
	}

	checkAtStChan chan uint8
)

func mcuCmdOkHandler(buf []byte) {
	//log.Println(log.DebugLog,"MCUCmdOkHandler:", buff)
}

func mcuRespStartUpHandler(buf []byte) {
	log.Println(log.DebugLog, "MCU system is start up now")

	if atCmdMode != true {
		time.AfterFunc(100*time.Microsecond, func() {
			if atCmdMode != true {
				log.Println(log.WarningLog, "will trig at cmd check...")
				McuBootMode = false
				atCmdMode = true
				checkAtStChan <- 1
			}

		})
	}
}

func mcuStatusHandler(buf []byte) {
	log.Printf(log.DebugLog, "Get Mcu status:%s\n", buf)
}

func displayErrCode(errCode int) {
	switch errCode {
	case AT_CMD_RSP_FORMAT_ERR:
		log.Println(log.ErrorLog, "AT Cmd Error: AT cmd format is not right.")
	case AT_CMD_RSP_TYPE_ERR:
		log.Println(log.ErrorLog, "AT Cmd Error: AT cmd ok, but operation type is not supported.")
	case AT_CMD_RSP_SYS_BUSY:
		log.Println(log.ErrorLog, "AT Cmd Error: system busy.")
	case AT_CMD_RSP_INVALID_ARG:
		log.Println(log.ErrorLog, "AT Cmd Error: parameter is invaild.")
	case AT_CMD_RSP_NOT_ALLOW:
		log.Println(log.ErrorLog, "AT Cmd Error: cmd operation is not allow.")
	case AT_CMD_RSP_NO_MEMORY:
		log.Println(log.ErrorLog, "AT Cmd Error: no memory for this cmd.")
	case AT_CMD_RSP_NOT_SUPPORT:
		log.Println(log.ErrorLog, "AT Cmd Error: cmd is not supported.")
	case AT_CMD_RSP_REV_TIMEOUT:
		log.Println(log.ErrorLog, "AT Cmd Error: cmd is rev timeout for MCU.")
	case AT_CMD_RSP_REV_CHECKSUM_ERR:
		log.Println(log.ErrorLog, "AT Cmd Error: rev data checksum err (r16->MCU).")
	default:
		log.Printf(log.ErrorLog, "AT Cmd Error: Illegal error code from BLE, Error Code=%d\n", errCode)
	}
}

func mcuRespErrorHandler(buf []byte) {
	log.Printf(log.ErrorLog, "MCU resp error code = %s\n", buf)
	if len(buf) > 0 {
		if errCode, e := strconv.Atoi(string(buf[0:1])); e == nil {
			log.Println(log.ErrorLog, errCode)
			displayErrCode(errCode)
		}
	}

	removeNode(AT_CMD_RSP_ERR, buf)
}

func mcuRespDefaultHandler(buf []byte) {
	log.Printf(log.WarningLog, "[MCU_log]:%s\n", buf)
}

func atCmdStWaitTimeoutcallback() {
	log.Println(log.WarningLog, "AT Cmd status wait timeout error! = ", MaxResetCnt)
	MaxResetCnt++

	if MaxResetCnt > RebootMcuMaxCnt {
		if AtReadyCb != nil {
			AtReadyCb(MCU_STATUS_NEED_OTA)
		}
	} else {
		ResetMCU()
		log.Println(log.DebugLog, "set AtCmdStatusWaitTime timeout:", AtCmdStatusWaitTime, "ms")
		atCmdStWaitTimer.Reset(AtCmdStatusWaitTime * time.Millisecond)
	}
}

func initAtCmd() {
	uartInit()
	atCmdStWaitTimer = time.AfterFunc(0xFFFFFFFF, atCmdStWaitTimeoutcallback)
	atCmdStWaitTimer.Stop()
	AtStateGpioInit()
}

func AtCmdInit(cb AtIsReadyCb) {
	AtReadyCb = cb
	atCmdOnce.Do(func() {
		checkAtStChan = make(chan uint8, 1)
		go func() {
			atServiceInit()
			initAtCmd()
			select {
			case <-checkAtStChan:
				log.Println(log.WarningLog, "will check at status...")
				checkAtCmdIsReady()
			}
		}()
	})
}

func atCmdIsReadyCallback(cmdId uint32, status AT_CMD_RSP, buf []byte) {
	if status == AT_CMD_RSP_OK {
		MaxResetCnt = 0
		ReConnectMcuCnt = 1
		RebootMcuCnt = 0
		if mcuReboot {
			mcuReboot = false
			log.Println(log.WarningLog, "mcu is reboot, clear init effect")
			var mcu_not_init GuideLampEffectT = GuideLampEffectT{
				EffectID:     GUIDE_EFFECT_ID_CLEAR,
				Action:       GUIDE_ACTION_STOP,
				FadeIn:       FADE_NONE,
				FadeOut:      FADE_NONE,
				StartLampNum: 0,
				DataLen:      0,
			}
			GuideEffectControl(mcu_not_init, nil)
		}
		log.Println(log.DebugLog, "at is ready")
		if AtReadyCb != nil {
			AtReadyCb(MCU_STATUS_AT_READY)
		}
	} else {
		if !atCmdMode {
			return
		}
		log.Println(log.WarningLog, "At cmd is not ready, Status =", status, ", reconnect mcu cnt:", ReConnectMcuCnt)
		if ReConnectMcuCnt < ReConnectMcuMaxCnt {
			ReConnectMcuCnt++
			checkAtCmdIsReady()
		} else {
			ReConnectMcuCnt = 1
			if RebootMcuCnt < RebootMcuMaxCnt {
				mcuReboot = true
				checkMcuHasBoot = time.AfterFunc(500*time.Millisecond, func() {
					log.Println(log.WarningLog, "maybe mcu boot err")
					if AtReadyCb != nil {
						AtReadyCb(MCU_STATUS_BOOT_ERR)
					}
				})

				ResetMCU()
				RebootMcuCnt++
			} else {
				log.Println(log.ErrorLog, "+++++mcu is dead++++")
				RebootMcuCnt = 0
				if AtReadyCb != nil {
					AtReadyCb(MCU_STATUS_AT_NOT_READY)
				}
			}
		}
	}
}

func checkAtCmdIsReady() {
	go SendAtCmd(AT_CMD, AT_CMD_FWD_PRI_HIGH, []byte{}, atCmdIsReadyCallback)
}

func atModeSet(onoff bool) {
	atCmdMode = onoff
	if onoff {
		atCmdStWaitTimer.Stop()
		if mcuOTAMode {
			log.Println(log.WarningLog, "ota mode but at mode gpio is ok, will reset for ota...")
			sendUcharCnt = 0
			ResetMCU()
		} else {
			checkAtCmdIsReady()
		}
	} else {
		//boot mode
		//MaxResetCnt = 0
		ReConnectMcuCnt = 1
		RebootMcuCnt = 0
		if checkMcuHasBoot != nil {
			checkMcuHasBoot.Stop()
		}

		removeAllNode(AT_CMD_FWD_ERR)

		if !mcuOTAMode {
			log.Println(log.WarningLog, "set AtCmdStatusWaitTime timeout:", AtCmdStatusWaitTime, "ms")
			atCmdStWaitTimer.Reset(AtCmdStatusWaitTime * time.Millisecond)
		}

		if AtReadyCb != nil {
			AtReadyCb(MCU_STATUS_AT_NOT_READY)
		}
	}
}

/**
 * 判断文件是否存在  存在返回 true 不存在返回false
 */
func checkFileIsExist(filename string) bool {
	var exist = true
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		exist = false
	}
	return exist
}

func McuOtaUpdate(fileName string, cb OtaIsFinishCb) error {
	errStr := ""
	if mcuOTAMode {
		errStr = "can't update mcu, mcu ota is already running"
		log.Println(log.ErrorLog, errStr)
		return errors.New(errStr)
	}
	if !checkFileIsExist(fileName) {
		errStr = "file not exist "
		log.Println(log.ErrorLog, errStr, ", filePath:", fileName)
		return errors.New(errStr)
	}

	OtaFinishCb = cb
	mcuImagePath = fileName
	log.Println(log.DebugLog, "will update mcu image:", mcuImagePath)
	mcuOTAMode = true

	ResetMCU()
	atCmdMode = false
	forceMcuOtaUpdateStart(100 * time.Microsecond)

	return nil
}

func otaFinish() {
	mcuOTAMode = false
	if OtaFinishCb != nil {
		OtaFinishCb(true, 100, nil)
	}

}

func GuideEffectControl(gEffect GuideLampEffectT, cb AtCmdCallback) {
	log.Println(log.DebugLog, "Guid effect: ", gEffect.EffectID, ", action: ", gEffect.Action)
	//debug.PrintStack()
	if gEffect.EffectID == GUIDE_EFFECT_ID_DO_NOTHING {
		return
	}
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, gEffect.EffectID)
	binary.Write(buf, binary.LittleEndian, gEffect.Action)
	//FadeIn
	binary.Write(buf, binary.LittleEndian, gEffect.FadeIn)
	//FadeOut
	binary.Write(buf, binary.LittleEndian, gEffect.FadeOut)

	binary.Write(buf, binary.LittleEndian, gEffect.StartLampNum)
	binary.Write(buf, binary.LittleEndian, gEffect.DataLen)
	if gEffect.DataLen > 0 {
		buf.Write(gEffect.Data)
	}

	SendAtCmd(AT_CMD_GEFFECT, AT_CMD_FWD_PRI_NORMAL, buf.Bytes(), cb)
}

func MarixEffectAnimation(mAniEffect MatrixLampAnimationT, cb AtCmdCallback) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, mAniEffect.RepeatTimes)
	binary.Write(buf, binary.LittleEndian, mAniEffect.FrameNums)
	binary.Write(buf, binary.LittleEndian, mAniEffect.FrameBytes)
	binary.Write(buf, binary.LittleEndian, mAniEffect.FrameDelay)
	buf.Write(mAniEffect.Data)

	SendAtCmd(AT_CMD_MANI, AT_CMD_FWD_PRI_NORMAL, buf.Bytes(), cb)
}

func MarixEffectGradient(mGradEffect MatrixLampGradientT, cb AtCmdCallback) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, mGradEffect.RepeatTimes)
	binary.Write(buf, binary.LittleEndian, mGradEffect.FrameNums)
	binary.Write(buf, binary.LittleEndian, mGradEffect.FrameBytes)
	binary.Write(buf, binary.LittleEndian, mGradEffect.InsertMode)
	binary.Write(buf, binary.LittleEndian, mGradEffect.T1)
	binary.Write(buf, binary.LittleEndian, mGradEffect.T2)
	binary.Write(buf, binary.LittleEndian, mGradEffect.T3)
	binary.Write(buf, binary.LittleEndian, mGradEffect.T4)
	buf.Write(mGradEffect.Data)

	SendAtCmd(AT_CMD_MGRAD, AT_CMD_FWD_PRI_NORMAL, buf.Bytes(), cb)
}

func MarixMusicPlay(mMusicPlay MatrixLampMusicPlayT, cb AtCmdCallback) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, mMusicPlay.mode)
	binary.Write(buf, binary.LittleEndian, mMusicPlay.volumeLevel)

	SendAtCmd(AT_CMD_MMPLAY, AT_CMD_FWD_PRI_NORMAL, buf.Bytes(), cb)
}

func MarixClose(cb AtCmdCallback) {
	SendAtCmd(AT_CMD_MCLOSE, AT_CMD_FWD_PRI_NORMAL, []byte{}, cb)
}

func LampBrightnessSet(t LAMP_TYPE, level uint8, cb AtCmdCallback) {
	buf := new(bytes.Buffer)
	if level > 100 {
		level = 100
	}
	binary.Write(buf, binary.LittleEndian, t)
	binary.Write(buf, binary.LittleEndian, level)

	SendAtCmd(AT_CMD_BRIGHTNESS, AT_CMD_FWD_PRI_NORMAL, buf.Bytes(), cb)
}

func LampIsShowWhenReset(s McuLampShowT, cb AtCmdCallback) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, uint8(s))

	SendAtCmd(AT_CMD_LAMP_SHOW, AT_CMD_FWD_PRI_NORMAL, buf.Bytes(), cb)
}

func LampTest(t []byte, cb AtCmdCallback) {
	SendAtCmd(AT_CMD_LAMP_TEST, AT_CMD_FWD_PRI_NORMAL, t, cb)
}
