package atservice

import (
	"bytes"
	"container/list"
	"encoding/binary"
	"netease_control_center/interfaces/log"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

/*-------------------------------------------------
for user
-------------------------------------------------*/
type AT_CMD_PRIORITY uint8

const (
	AT_CMD_FWD_PRI_LOW AT_CMD_PRIORITY = iota /*lowest*/
	AT_CMD_FWD_PRI_NORMAL
	AT_CMD_FWD_PRI_HIGH /*highest*/
)

type AT_CMD_RSP uint8

const (
	AT_CMD_RSP_OK = iota
	AT_CMD_RSP_ERR
	AT_CMD_RSP_WAIT_TIMEOUT
	AT_CMD_FWD_ERR
)

type AtCmdCallback func(cmdId uint32, status AT_CMD_RSP, buf []byte) //at handler callback type

/*-------------------------------------------------
for this package
-------------------------------------------------*/
type AtPkgRxState uint8

const (
	AT_PKG_RX_WAIT_SYNC_CHAR1 AtPkgRxState = iota /*lowest*/
	AT_PKG_RX_WAIT_SYNC_CHAR2
	AT_PKG_RX_WAIT_CHECKSUM_L
	AT_PKG_RX_WAIT_CHECKSUM_H
	AT_PKG_RX_WAIT_DATA_SIZE_L
	AT_PKG_RX_WAIT_DATA_SIZE_H
	AT_PKG_RX_GET_DATA
	AT_PKG_RX_WAIT_END_CHAR
	AT_PKG_RX_STATE_NUM
)

type atCmdFwdStatus uint8

const (
	atCmdNotFwd atCmdFwdStatus = iota
	atCmdFwding
)

const (
	SYNC_CHAR1  uint8 = 0xFE
	SYNC_CHAR2  uint8 = 0xA5
	AT_END_CHAR uint8 = 0x0D

	timeoutMaxCntResetMCU = 2
)

//for rx
var revDataBuffer bytes.Buffer

type atCmdRxDataParser struct {
	atDataBuffer bytes.Buffer
	rxState      AtPkgRxState
	mutex        sync.Mutex
	dataLen      uint16
	checkSum     uint16
	checkSumCalc uint16
}

//for tx node
type atCmdDataFwdNode struct {
	atCmdId uint32
	uuid    uint32
	st      atCmdFwdStatus
	pri     AT_CMD_PRIORITY
	fwdData []byte
	atCb    AtCmdCallback
}

var (
	atCmdRxDataInfo atCmdRxDataParser = atCmdRxDataParser{
		rxState: AT_PKG_RX_WAIT_SYNC_CHAR1,
	}
	respWaitTimer      *time.Timer
	atFwdlist          list.List
	atFwdListMutex     sync.Mutex
	once               sync.Once
	fwdChan            = make(chan bool, 10)
	respTimeoutC       = make(chan bool, 10)
	atNodeUuid         uint32
	timeoutCntResetMCU = 0
)

func sendData(buf []byte) bool {
	uartWrite(buf)
	return true
}

func sendAtCmdNode(fwdNode *atCmdDataFwdNode) {
	//log.Println(log.DebugLog, "fwd Node, uuid = ", fwdNode.uuid, ", pri=", fwdNode.pri)
	atCmdParam := atCmdTable[fwdNode.atCmdId]
	atBody := []byte(atCmdParam.cmdPrefix)
	atBody = append(atBody, fwdNode.fwdData...)

	//log.Println("fwdNode:",atFwdlist.Front().Value.(*atCmdDataFwdNode), "list len:", atFwdlist.Len())

	bufBody := new(bytes.Buffer)
	binary.Write(bufBody, binary.LittleEndian, uint16(len(atBody)+1)) //body len
	bufBody.Write(atBody)
	bufBody.WriteByte(AT_END_CHAR)
	var checksumAtbody uint16 = 0
	for _, v := range bufBody.Bytes() {
		checksumAtbody += uint16(v)
	}
	//log.Printf("AtCmd body:%x", bufBody)

	atCmdDataBuf := bytes.NewBuffer([]byte{SYNC_CHAR1, SYNC_CHAR2}) //sync preemble data
	binary.Write(atCmdDataBuf, binary.LittleEndian, checksumAtbody) //checksum
	atCmdDataBuf.Write(bufBody.Bytes())                             //add len and body

	//log.Printf(log.DebugLog, "uuid=%d , sendAtCmdNode:%s\n", fwdNode.uuid, atCmdDataBuf)

	if ok := sendData(atCmdDataBuf.Bytes()); ok {
		//send OK
		//log.Println(log.DebugLog, "send data successful")
		if t := atCmdTable[fwdNode.atCmdId].overTime; respWaitTimer != nil && t > 0 {
			t *= time.Millisecond
			//log.Println(log.DebugLog, "set timer:", t, ", uuid = ", fwdNode.uuid)
			respWaitTimer.Reset(t)
		}
	} else {
		//send data err
		removeNode(AT_CMD_FWD_ERR, nil)
	}
}

func atCmdListSendTrig() {
	for {
		select {
		case <-fwdChan:
			atFwdListMutex.Lock()
			e := atFwdlist.Front()
			if e == nil {
				//log.Println(log.DebugLog, "no list data")
				atFwdListMutex.Unlock()
				break
			}

			fwdNode := e.Value.(*atCmdDataFwdNode)
			if fwdNode.st == atCmdFwding {
				//log.Println(log.WarningLog, "try trig fwd: Node is already fwding, pls wait, uuid = ", fwdNode.uuid)
				atFwdListMutex.Unlock()
				break
			} else {
				//log.Println(log.DebugLog, "now start fwd: ", atCmdTable[fwdNode.atCmdId].cmdPrefix, ", uuid = ", fwdNode.uuid)
			}

			fwdNode.st = atCmdFwding
			atFwdListMutex.Unlock()
			go sendAtCmdNode(fwdNode)
		case <-respTimeoutC:
			checkAtCmdIsReady()
		}
	}
}

func removeNode(status AT_CMD_RSP, buf []byte) {
	atFwdListMutex.Lock()
	//stop the resp timeout
	if respWaitTimer != nil {
		respWaitTimer.Stop()
	}

	var atCallback AtCmdCallback = nil
	cmdId := uint32(AT_CMD_NUM)
	e := atFwdlist.Front()
	if e != nil {
		fwdNode := e.Value.(*atCmdDataFwdNode)
		if fwdNode.st == atCmdFwding {
			atCallback = fwdNode.atCb
			cmdId = fwdNode.atCmdId
			//log.Println(log.DebugLog, "remove node, uuid = ", fwdNode.uuid)
			atFwdlist.Remove(atFwdlist.Front())

		}
	}

	atFwdListMutex.Unlock()

	if status == AT_CMD_RSP_WAIT_TIMEOUT && cmdId != AT_CMD {
		log.Println(log.DebugLog, "time out, will check mcu status")
		respTimeoutC <- true
	} else {
		if atFwdlist.Len() > 0 {
			//log.Println("Remove(), trig next:",atFwdlist.Front().Value.(*atCmdDataFwdNode), "list len:", atFwdlist.Len())
			fwdChan <- true
		}
	}

	if atCallback != nil {
		cbDataBuf := make([]byte, len(buf))
		copy(cbDataBuf, buf)
		go atCallback(cmdId, status, cbDataBuf)
	}
}

func resetRevData() {
	atCmdRxDataInfo.mutex.Lock()
	revDataBuffer.Reset()
	atCmdRxDataInfo.mutex.Unlock()

	atCmdRxDataInfo.mutex.Lock()
	defer atCmdRxDataInfo.mutex.Unlock()

	atCmdRxDataInfo.atDataBuffer.Reset()
	atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_SYNC_CHAR1
}

func removeAllNode(status AT_CMD_RSP) {
	atFwdListMutex.Lock()
	defer atFwdListMutex.Unlock()

	log.Println(log.WarningLog, "removeAllNode")
	if respWaitTimer != nil {
		respWaitTimer.Stop()
	}
	resetRevData()

	for e := atFwdlist.Front(); e != nil; e = e.Next() {
		at := e.Value.(*atCmdDataFwdNode)

		if at.atCb != nil {
			at.atCb(at.atCmdId, status, nil)
		}
	}

	atFwdlist.Init()
}

func respTimeoutcallback() {
	log.Println(log.WarningLog, "respTimeoutcallback")
	removeNode(AT_CMD_RSP_WAIT_TIMEOUT, nil)
	resetRevData()
}

func atDataRev(rev []byte) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf(log.WarningLog, "Runtime error caught: %v\n", r)
			removeAllNode(AT_CMD_RSP_ERR)
		}
	}()

	atCmdRxDataInfo.mutex.Lock()
	defer atCmdRxDataInfo.mutex.Unlock()

	revDataBuffer.Write(rev)
	//log.Printf(log.DebugLog, "rev:%x\n", revDataBuffer.Bytes())

	for {
		if revDataBuffer.Len() == 0 {
			break
		}
		switch atCmdRxDataInfo.rxState {
		case AT_PKG_RX_WAIT_SYNC_CHAR1:
			_, err := revDataBuffer.ReadBytes(SYNC_CHAR1)
			//log.Println("line:",line)
			//log.Println("err:",err)
			if err == nil {
				atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_SYNC_CHAR2
			}
		case AT_PKG_RX_WAIT_SYNC_CHAR2:
			waitSyncChar, _ := revDataBuffer.ReadByte()
			if waitSyncChar == SYNC_CHAR2 {
				atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_CHECKSUM_L
			} else {
				atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_SYNC_CHAR1
			}
		case AT_PKG_RX_WAIT_CHECKSUM_L:
			waitChecksumChar, _ := revDataBuffer.ReadByte()
			atCmdRxDataInfo.checkSum = uint16(waitChecksumChar)
			atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_CHECKSUM_H

		case AT_PKG_RX_WAIT_CHECKSUM_H:
			waitChecksumChar, _ := revDataBuffer.ReadByte()
			atCmdRxDataInfo.checkSum += (uint16(waitChecksumChar) << 8)
			atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_DATA_SIZE_L
			//log.Println(atCmdDataInfo.checkSum)

		case AT_PKG_RX_WAIT_DATA_SIZE_L:
			waitDataSizeChar, _ := revDataBuffer.ReadByte()
			atCmdRxDataInfo.checkSumCalc = uint16(waitDataSizeChar)
			atCmdRxDataInfo.dataLen = uint16(waitDataSizeChar - 1)
			atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_DATA_SIZE_H

		case AT_PKG_RX_WAIT_DATA_SIZE_H:
			waitDataSizeChar, _ := revDataBuffer.ReadByte()
			atCmdRxDataInfo.checkSumCalc += uint16(waitDataSizeChar)
			atCmdRxDataInfo.dataLen += (uint16(waitDataSizeChar) << 8)
			atCmdRxDataInfo.rxState = AT_PKG_RX_GET_DATA
			//log.Println(log.WarningLog, atCmdRxDataInfo.dataLen)
		case AT_PKG_RX_GET_DATA:
			remainBytes := (int(atCmdRxDataInfo.dataLen) - atCmdRxDataInfo.atDataBuffer.Len())
			atCmdRxDataInfo.atDataBuffer.Write(revDataBuffer.Next(remainBytes))
			if remainBytes = (int(atCmdRxDataInfo.dataLen) - atCmdRxDataInfo.atDataBuffer.Len()); remainBytes == 0 {
				atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_END_CHAR
				//log.Printf(log.DebugLog, "at:%s\n", atCmdRxDataInfo.atDataBuffer.Bytes())
				for _, v := range atCmdRxDataInfo.atDataBuffer.Bytes() {
					atCmdRxDataInfo.checkSumCalc += uint16(v)
				}

			}
		case AT_PKG_RX_WAIT_END_CHAR:
			waitEndChar, _ := revDataBuffer.ReadByte()
			if waitEndChar == AT_END_CHAR {
				atCmdRxDataInfo.checkSumCalc += uint16(waitEndChar)
				atCmdRxDataInfo.rxState = AT_PKG_RX_WAIT_SYNC_CHAR1
				if atCmdRxDataInfo.checkSum == atCmdRxDataInfo.checkSumCalc {
					//log.Println("check sum OK")
					timeoutCntResetMCU = 0 //checksum ok mean mcu is work well
					prefix := strings.ToUpper(string(atCmdRxDataInfo.atDataBuffer.Bytes()))
					found := false
					for _, v := range atCmdTable {
						if v.respPrefix == "" {
							continue
						}
						respPrefix := strings.ToUpper(v.respPrefix)
						//log.Println("map:",respPrefix,"cmd:",prefix)
						if b := strings.HasPrefix(prefix, respPrefix); b {
							found = true
							//log.Println(log.DebugLog, "get cmd:", v.respPrefix)

							/* for test
							if v.respPrefix == "+VER=" {
								log.Println("test time out for +ver=")
								break
							}*/
							//get body from rev data
							atCmdRxDataInfo.atDataBuffer.Next(len(respPrefix))
							respData := atCmdRxDataInfo.atDataBuffer.Bytes()
							//callback the at handler
							if v.atHandler != nil {
								v.atHandler(respData)
							}

							//remove node , callback ,and trig to next
							if v.cmdPrefix != "" {
								atFwdListMutex.Lock()
								e := atFwdlist.Front()
								removeNeed := false
								if e != nil {
									fwdNode := e.Value.(*atCmdDataFwdNode)
									atCmdParam := atCmdTable[fwdNode.atCmdId]
									respPrefixTemp := strings.ToUpper(atCmdParam.respPrefix)
									if fwdNode.st == atCmdFwding {
										if b := strings.HasPrefix(prefix, respPrefixTemp); b {
											removeNeed = true
										}
									}
								}
								atFwdListMutex.Unlock()
								if removeNeed {
									removeNode(AT_CMD_RSP_OK, respData)
								}
							}

							break
						}
					}

					//default handler
					if found == false {
						mcuRespDefaultHandler(atCmdRxDataInfo.atDataBuffer.Bytes())
					}
					atCmdRxDataInfo.atDataBuffer.Reset()
				} else {
					log.Println(log.ErrorLog, "check sum err: checksum = ", atCmdRxDataInfo.checkSum,
						", calc checksum =", atCmdRxDataInfo.checkSumCalc)
				}
			}
		}
		//log.Printf("rev del:%x", revDataBuffer.Bytes())
	}
}

func SendAtCmd(cmd_type uint32, priority AT_CMD_PRIORITY, buf []byte, cb AtCmdCallback) {
	atServiceInit()

	//can't fwd data in boot mode
	if atCmdMode == false || mcuOTAMode == true {
		if cb != nil {
			go cb(cmd_type, AT_CMD_FWD_ERR, nil)
		}
		log.Println(log.DebugLog, "send at cmd err, mcu not in at mode------------->")
		return
	}

	//now we can fwd data, first make the node
	fwdAt := &atCmdDataFwdNode{
		atCmdId: cmd_type,
		st:      atCmdNotFwd,
		pri:     priority,
		fwdData: buf,
		atCb:    cb,
	}
	//log.Println("SendAtCmd:",fwdAt)

	atFwdListMutex.Lock()

	fwdAt.uuid = atNodeUuid
	atNodeUuid++
	//put the node to the fwd list
	if atFwdlist.Len() == 0 {
		atFwdlist.PushBack(fwdAt)
		//log.Println(log.DebugLog, "Len = 0, add node, at cmd = ",cmd_type,", pri=",fwdAt.pri)
	} else {
		inserted := false
		for e := atFwdlist.Front(); e != nil; e = e.Next() {
			at := e.Value.(*atCmdDataFwdNode)
			if at.st != atCmdFwding && fwdAt.pri > at.pri {
				atFwdlist.InsertBefore(fwdAt, e)
				//log.Println(log.DebugLog, "InsertBefore, at cmd = ",cmd_type,", pri=",fwdAt.pri)
				inserted = true
				break
			}
		}

		if !inserted {
			atFwdlist.PushBack(fwdAt)
			//log.Println(log.DebugLog, "PushBack, add node, at cmd = ",cmd_type,", pri=",fwdAt.pri)
		}
	}
	atFwdListMutex.Unlock()

	//log.Println("fwdAt:",atFwdlist.Front().Value.(*atCmdDataFwdNode), "list len:", atFwdlist.Len())

	//trig to send data
	fwdChan <- true

}

func initService() {
	atNodeUuid = 0
	respWaitTimer = time.AfterFunc(0xFFFFFFFF, respTimeoutcallback)
	respWaitTimer.Stop()
	go atCmdListSendTrig()
}

func atServiceInit() {
	once.Do(initService)
}

func GetGID() uint64 {
	b := make([]byte, 64)
	b = b[:runtime.Stack(b, false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, _ := strconv.ParseUint(string(b), 10, 64)
	return n
}
