package led_mcu

import (
	"bytes"
	"crypto/md5"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"netease_control_center/globals"
	"netease_control_center/interfaces/log"
	. "netease_control_center/modules/hardware/mcu_leds/atservice"
	"netease_control_center/nembd/controller/adau1761"
	"netease_control_center/utils"
	"os"
	"sync"
	"time"
)

type DirectLed struct {
	EffectId GUIDE_EFFECT_ID
	Action   GUIDE_ACTION
	StartNum uint8
	Data     []byte
	Lock     sync.Mutex
}

const delaySendTimeout = 300         //为了消除popo音，延时发送执行灯和音乐随动，单位ms
var delaySendTimer *time.Timer = nil //延时发送命令定时器，用于执行灯

var (
	initMcuOnce   sync.Once
	LastDirectLed = DirectLed{EffectId: GUIDE_EFFECT_ID_CLEAR, Action: GUIDE_ACTION_STOP}
)

func _initMcu() {
	log.Println(log.DebugLog, "Begin init leds!")
	AtCmdInit(LedInitResult)
	time.Sleep(200 * time.Millisecond)
}

func InitMcu() {
	initMcuOnce.Do(_initMcu)
}

func SendAtCmd2Mcu(cmdId uint32, priority AT_CMD_PRIORITY, buf []byte) {
	InitMcu()
	if globals.CurMcuStatus != globals.McuStatus_Ready {
		log.Println(log.ErrorLog, "Mcu not ready, do not send at cmd!", globals.CurMcuStatus)
		return
	}

	SendAtCmd(cmdId, priority, buf, GAtCmdEndCallback)
}

func McuGetVersion() {
	SendAtCmd2Mcu(AT_CMD_VERSION, AT_CMD_FWD_PRI_NORMAL, []byte{})
}

func McuResetInitStatus() {
	ClearLedDisplay()
	DirectLedControl(GUIDE_EFFECT_ID_MIC_STAT_OFF, GUIDE_ACTION_STOP, 0, []byte{})
	DirectLedControl(GUIDE_EFFECT_ID_CLEAR, GUIDE_ACTION_STOP, 0, []byte{})
}

func McuFirstBootInit() {
	ClearLedDisplay()
	DirectLedControl(GUIDE_EFFECT_ID_INIT, GUIDE_ACTION_STOP, 0, []byte{})
}

func PlaySimpleAnimation(RepeatTimes uint8, FrameNums uint8, FrameBytes uint16, FrameDelay uint16, FrameData []byte) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, RepeatTimes)
	binary.Write(buf, binary.LittleEndian, FrameNums)
	binary.Write(buf, binary.LittleEndian, FrameBytes)
	binary.Write(buf, binary.LittleEndian, FrameDelay)
	buf.Write(FrameData)

	SendAtCmd2Mcu(AT_CMD_MANI, AT_CMD_FWD_PRI_NORMAL, buf.Bytes())
}

func PlayGradientAnimation(RepeatTimes uint8, FrameNum uint8, FrameBytes uint16, InsertMode ICON_INSERT_MODE, T1 uint16, T2 uint16, T3 uint16, T4 uint16, FrameData []byte) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, RepeatTimes)
	binary.Write(buf, binary.LittleEndian, FrameNum)
	binary.Write(buf, binary.LittleEndian, FrameBytes)
	binary.Write(buf, binary.LittleEndian, InsertMode)
	binary.Write(buf, binary.LittleEndian, T1)
	binary.Write(buf, binary.LittleEndian, T2)
	binary.Write(buf, binary.LittleEndian, T3)
	binary.Write(buf, binary.LittleEndian, T4)
	buf.Write(FrameData)

	SendAtCmd2Mcu(AT_CMD_MGRAD, AT_CMD_FWD_PRI_NORMAL, buf.Bytes())
}

func Matrix_music_mode(mode MATRIX_MUSIC_MODE) {
	vol, _ := adau1761.GetVol()
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, uint8(mode))
	binary.Write(buf, binary.LittleEndian, uint8(vol))

	log.Println(log.WarningLog, "Light Direct led: Matrix  mode = ", mode, ", volume = ", vol)
	SendAtCmd2Mcu(AT_CMD_MMPLAY, AT_CMD_FWD_PRI_NORMAL, buf.Bytes())
}

func McuOtaUpgradeBegin(isForce bool, McuImagePath string) {
	InitMcu()

	if McuImagePath != "" {
		log.Println(log.DebugLog, "McuOtaUpgradeBegin create timer!")
		McuOtaUpdate(McuImagePath, OtaCallbk)
	} else {
		log.Println(log.ErrorLog, "get path fail:", McuImagePath)
	}
}

func McuReset() {
	log.Println(log.DebugLog, "Begin to reset mcu!")
	ResetMCU()
	log.Println(log.DebugLog, "Begin to reset mcu finish!")
}

func GetMcuStatus() {
	SendAtCmd2Mcu(AT_CMD_MCU_ST, AT_CMD_FWD_PRI_NORMAL, []byte{})
}

func MusicPlayModeChange(mode MATRIX_MUSIC_MODE, volume uint8) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, uint8(mode))
	binary.Write(buf, binary.LittleEndian, uint8(volume))

	log.Println(log.WarningLog, "Light Direct led: Matrix  mode = ", mode, ", volume = ", volume)
	SendAtCmd2Mcu(AT_CMD_MMPLAY, AT_CMD_FWD_PRI_NORMAL, buf.Bytes())
}

func ClearLedDisplay() {
	log.Println(log.WarningLog, "Light Direct led: Matrix close")
	SendAtCmd2Mcu(AT_CMD_MCLOSE, AT_CMD_FWD_PRI_NORMAL, []byte{})
}

func LightAllLedDisplay() {
	buf := new(bytes.Buffer)
	for i := 0; i < 81; i++ {
		buf.WriteByte(byte(100))
	}
	PlayGradientAnimation(uint8(0xe0), 1, 81, NEW_ICON_MODE, 1000, 0, 600, 32, buf.Bytes())
}

func ShowLastDirectLed() {
	LastDirectLed.Lock.Lock()
	defer LastDirectLed.Lock.Unlock()

	ShowDirectLed(LastDirectLed)
}
func DirectLedControl(EffectId GUIDE_EFFECT_ID, Action GUIDE_ACTION, StartLampNum uint8, data []byte) {
	if GUIDE_EFFECT_ID_NONE == EffectId {
		return
	}

	//debug.PrintStack()
	if EffectId == GUIDE_EFFECT_ID_DO_NOTHING {
		return
	}
	LastDirectLed.Lock.Lock()
	defer LastDirectLed.Lock.Unlock()
	var toShowLed DirectLed

	toShowLed.EffectId = EffectId
	toShowLed.Action = Action
	toShowLed.StartNum = StartLampNum
	toShowLed.Data = data

	if EffectId == GUIDE_EFFECT_ID_EXEC_CMD && Action == GUIDE_ACTION_START {
		delaySendTimer = time.AfterFunc(delaySendTimeout*time.Millisecond, func() {
			delaySendTimer = nil
			toShowLed.Data = []byte{uint8(adau1761.MustGetVol())}
			ShowDirectLed(toShowLed)
			log.Println(log.WarningLog, "send GUIDE_EFFECT_ID_EXEC_CMD..., vol:", toShowLed.Data)
		})
	} else {
		if delaySendTimer != nil {
			log.Println(log.WarningLog, "stop send GUIDE_EFFECT_ID_EXEC_CMD...")
			delaySendTimer.Stop()
			delaySendTimer = nil
		}
		ShowDirectLed(toShowLed)
	}

	//do not record volume leds which is time show not status show
	if EffectId != GUIDE_EFFECT_ID_VOLUME_ADJUST {
		LastDirectLed.EffectId = EffectId
		LastDirectLed.Action = Action
		LastDirectLed.StartNum = StartLampNum
		LastDirectLed.Data = data
	}
}

func DirectLedClear(id GUIDE_EFFECT_ID) {
	LastDirectLed.Lock.Lock()
	defer LastDirectLed.Lock.Unlock()

	if LastDirectLed.EffectId == id && LastDirectLed.Action != GUIDE_ACTION_STOP && id != GUIDE_EFFECT_ID_CLEAR {
		if LastDirectLed.EffectId == GUIDE_EFFECT_ID_VOICE_INPUT {
			LastDirectLed.EffectId = GUIDE_EFFECT_ID_CLEAR
		}
		LastDirectLed.Action = GUIDE_ACTION_STOP
		LastDirectLed.Data = []byte{}
		ShowDirectLed(LastDirectLed)
	}
}

func ShowDirectLed(led DirectLed) {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, uint8(led.EffectId))
	binary.Write(buf, binary.LittleEndian, uint8(led.Action))
	//FadeIn
	binary.Write(buf, binary.LittleEndian, uint8(1))
	//FadeOut
	binary.Write(buf, binary.LittleEndian, uint8(0))

	binary.Write(buf, binary.LittleEndian, uint8(led.StartNum))
	binary.Write(buf, binary.LittleEndian, uint16(len(led.Data)))
	if len(led.Data) > 0 {
		buf.Write(led.Data)
	}

	log.Println(log.WarningLog, "Light Direct led:", led.EffectId, led.Action)
	SendAtCmd2Mcu(AT_CMD_GEFFECT, AT_CMD_FWD_PRI_NORMAL, buf.Bytes())
}

func ShowBytes(data []byte, width int) {
	fmt.Printf("\nwidth:%d:\n", width)
	for k, v := range data {
		//fmt.Printf("%02d  ", v)
		if k%width == 0 && k != 0 {
			fmt.Printf("\n")
		}
		if false {
			if v > 0 {
				fmt.Printf(" # ")
			} else {
				fmt.Printf(" @ ")
			}
		} else {
			fmt.Printf(" %02x", v)
		}

	}
}

func ShowImage(path string, RepeatTimes int) error {
	if err, totalFrame, delay, oneImageSize, picData := utils.DecodeImage(path); err == nil {
		if totalFrame == 1 {
			PlaySimpleAnimation(uint8(RepeatTimes), uint8(totalFrame), uint16(oneImageSize), 0xff, picData)
			return nil
		} else if totalFrame > 1 {
			PlaySimpleAnimation(uint8(RepeatTimes), uint8(totalFrame), uint16(oneImageSize), uint16(delay), picData)
			return nil
		} else {
			return errors.New("Frame num error:" + fmt.Sprintln(totalFrame))
		}
	} else {
		return err
	}

	return nil
}

func LightGifTest() {
	InitMcu()
	jump := time.Now()
	if fd, err := os.Open("/tmp/ihw.gif"); err == nil {
		if gifimage, imageerr := gif.DecodeAll(fd); imageerr == nil {
			width := gifimage.Config.Width
			height := gifimage.Config.Height
			fmt.Println("Get gif:", width, " x ", height, " Loop count:", gifimage.LoopCount, " delay:", gifimage.Delay[0], " Frame num:", len(gifimage.Image))
			buf := new(bytes.Buffer)
			frame := make([]byte, width*height)

			for k, onegif := range gifimage.Image {
				//log.Printf(log.DebugLog, "Get %d gif: %+v\n", k, *onegif)
				fmt.Println("No.", k, " Rect:", onegif.Rect, " Stride:", onegif.Stride)
				for y := onegif.Rect.Min.Y; y < onegif.Rect.Max.Y; y++ {
					for x := onegif.Rect.Min.X; x < onegif.Rect.Max.X; x++ {
						ocolor := onegif.At(x, y)
						if co, ok := color.GrayModel.Convert(ocolor).(color.Gray); ok {
							frame[y*width+x] = co.Y * 10 / 100
						}
					}
				}
				//ShowBytes(frame, width)
			}
			buf.Write(frame)
			PlaySimpleAnimation(0, uint8(len(gifimage.Image)), uint16(width*height), uint16(30), buf.Bytes())
		} else {
			log.Println(log.DebugLog, "Decode image error:", imageerr)
		}
		fd.Close()
	} else {
		log.Println(log.DebugLog, "open file error!", err)
	}
	log.Println(log.DebugLog, "coast:", time.Now().Sub(jump))
}

func LightImage(path string) {
	defer func() {
		if err := recover(); err != nil {
			log.Println(log.DebugLog, "Get error:", err)
		}
	}()
	InitMcu()
	log.Println(log.DebugLog, "Begin to show image:", path)
	if fd, err := os.Open(path); err == nil {
		if imgconfig, imgtype, imgtypeerr := image.DecodeConfig(fd); imgtypeerr == nil {
			log.Println(log.DebugLog, "Decode the image successfully!", imgtype)
			fd.Seek(0, 0)
			switch imgtype {
			case "jpeg", "png":
				if img, _, imgerr := image.Decode(fd); imgerr == nil {
					log.Println(log.DebugLog, "Image bounds:", img.Bounds())
					buf := make([]byte, imgconfig.Height*imgconfig.Width)
					buf2 := make([]color.Color, imgconfig.Height*imgconfig.Width)
					for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
						for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
							r, _, _, _ := color.GrayModel.Convert(img.At(x, y)).RGBA()
							buf[y*imgconfig.Width+x] = byte(r)
							buf2[y*imgconfig.Width+x] = img.At(x, y)
						}
						ShowBytes(buf, imgconfig.Width)
					}
					fmt.Printf("%+v\n", buf2)
					PlaySimpleAnimation(0, 1, uint16(len(buf)), 0xff, buf)
				} else {
					log.Println(log.DebugLog, "Decode img error:", imgerr)
				}
				log.Println(log.DebugLog, "Show img end!!")
			case "gif":
			default:
				log.Println(log.DebugLog, "Unsupport the image type!")
			}
		} else {
			log.Println(log.DebugLog, "Decode image type err:", imgtypeerr)
		}
		fd.Close()
	}
}

type McuVersion struct {
	Version string `json:"version"`
	Md5     string `json:"md5"`
}

func McuCheckAndOta(curversion string) {
	mcupath := "/usr/share/golang/mcu/mcufirmware"
	mcuversion := "/usr/share/golang/mcu/version.txt"

	var firmwareversion McuVersion

	if vfd, vfderr := os.Open(mcuversion); vfderr == nil {
		if jsonerr := json.NewDecoder(vfd).Decode(&firmwareversion); jsonerr == nil {
			log.Printf(log.DebugLog, "Decode version file success! %+v\n", firmwareversion)
			if needUpgrade, _ := utils.VersionNewer(firmwareversion.Version, curversion); needUpgrade {
				if firmfd, firmfderr := os.Open(mcupath); firmfderr == nil {
					h := md5.New()
					io.Copy(h, firmfd)
					filemd5arry := h.Sum(nil)
					filemd5 := hex.EncodeToString(filemd5arry)
					log.Println(log.DebugLog, "Cur file md5:", filemd5)
					if len(filemd5) > 0 && filemd5 == firmwareversion.Md5 {
						log.Println(log.DebugLog, "Md5 eq, begin to upgrade mcu!", mcupath)
						McuOtaUpgradeBegin(true, mcupath)
					} else {
						log.Println(log.DebugLog, "Md5 not eq, file md5:", filemd5)
					}
					firmfd.Close()
				} else {
					log.Println(log.DebugLog, "Open file error!", firmfderr)
				}
			} else {
				log.Println(log.DebugLog, "Firmware version no need upgrade, cur:", curversion, "  file version:", firmwareversion.Version)
			}
		} else {
			log.Println(log.DebugLog, "Json decode err!", jsonerr)
		}

		vfd.Close()
	} else {
		log.Println(log.DebugLog, "Open file err ", vfderr)
	}
}
