package schedule

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"netease_control_center/globals"
	"netease_control_center/interfaces/eventPoint"
	"netease_control_center/interfaces/http"
	"netease_control_center/interfaces/log"
	"netease_control_center/interfaces/vui"
	"runtime/debug"
	"sync"
	"time"
	"unsafe"
)

var (
	curscene  *SScene
	sceneLock sync.Mutex
)

var (
	SceneErr_IdNotMatch = errors.New("Id not match")
)

func LockScene() {
	//log.Println(log.ErrorLog, "Lock Scene!")
	sceneLock.Lock()
}

func UnlockScene() {
	//log.Println(log.ErrorLog, "Unlock Scene!")
	sceneLock.Unlock()
}

func GetSceneStatus() *globals.ScenesSt {
	LockScene()
	defer UnlockScene()

	if curscene == nil {
		//log.Println(log.ErrorLog, "Scene is nil, can not get the status!")
		return nil
	}

	var tmp globals.ScenesSt
	tmp.SceneId = curscene.Scene.Id
	tmp.TaskProgress = 1
	if st, err := curscene.GetCurServerScenetask(); err == nil && st != nil {
		tmp.Type = st.LocalId
		tmp.SceneStartTime = curscene.SceneEnterTime.UnixNano() / 1e6
		tmp.TaskId = st.ServerId

		if curscene.TaskNewEnter == true {
			tmp.TaskProgress = -1
		} else if st.Duration > 0 {
			tmp.TaskProgress = int((st.Duration - int64(curscene.GetRemainTime().Seconds())*1000) * 100 / st.Duration)
			//log.Println(log.DebugLog, "Remain:", curscene.GetRemainTime().Seconds(), " duration:", st.Duration)
		}
	} else {
		log.Println(log.ErrorLog, "Get sceneStatus fail!!")
	}

	return &tmp
}

func (this *SScene) TrigBroadStatus(taskno int, progress int) {
	defer func() {
		if err := recover(); err != nil {
			log.Println(log.DebugLog, "err:", err)
		}
	}()

	if taskno < 0 {
		return
	}
	//report to server

	//broadcast st
	var allst globals.VboxBroadcastSt

	allst.CurrentStatus = globals.SCENE_FIELD_ST
	allst.ScenesStPtr = new(globals.ScenesSt)

	allst.ScenesStPtr.TaskProgress = progress

	allst.ScenesStPtr.SceneId = this.Scene.Id

	st := this.Scene.SceneTasks[taskno]
	if st.Status == http.SceneTask_Status_Off {
		goto _ReportProgress
	}
	allst.ScenesStPtr.SceneStartTime = this.SceneEnterTime.Unix() * 1000
	allst.ScenesStPtr.Type = st.LocalId
	allst.ScenesStPtr.TaskId = st.ServerId

	allst.VboxVolStPtr.Action = globals.VOL_NULL_ACTION
	globals.TrigBroadcastStatus(allst)

_ReportProgress:
	if st.NeedSyncProgress == true && progress == 100 {
		var req http.HttpReqReportSceneProgress
		var rsp http.HttpRspReportSceneProgress
		req.SceneId = this.SceneId
		req.TaskId = st.ServerId
		req.TaskType = st.LocalId
		req.Progress = progress

		http.HttpPostAuto(req, &rsp)
	}
}
func (this *SScene) SetOemArg(taskno int, arg interface{}) (reterr error) {
	defer func() {
		if err := recover(); err != nil {
			reterr = errors.New(fmt.Sprintln(err))
		}
	}()
	if this.GetCurTaskNo() != int(taskno) {
		return SceneErr_IdNotMatch
	}
	this.OemArg = arg

	return nil
}

func (this *SScene) GetOemArg(taskno int) (arg interface{}, reterr error) {
	defer func() {
		if err := recover(); err != nil {
			reterr = errors.New(fmt.Sprintln(err))
		}
	}()
	if this.GetCurTaskNo() != int(taskno) {
		return nil, SceneErr_IdNotMatch
	}

	return this.OemArg, nil
}

func (this *SScene) GetCurServerScenetask() (st *http.SceneTask, reterr error) {
	defer func() {
		if err := recover(); err != nil {
			reterr = errors.New(fmt.Sprintln(err))
			st = nil
		}
	}()

	return &this.Scene.SceneTasks[this.CurTask], nil
}

func (this *SScene) GetCurLocalSceneTask() (*RegistedSceneTask, error) {
	if st, err := this.GetCurServerScenetask(); err == nil {
		if ltask, ok := localsstasks[SSTaskId(st.LocalId)]; ok == true {
			return ltask, nil
		} else {
			return nil, errors.New("Not find localtask!")
		}
	} else {
		return nil, errors.New("Not find cur scenetask!")
	}

	return nil, errors.New("Should not come heres")
}

func (this *SScene) Finish(taskId int, inScheduleThread bool) {
	if inScheduleThread == false {
		lockCore()
		defer unlockCore()
		LockScene()
		defer UnlockScene()
	}

	if curscene != nil && curscene.GetCurTaskNo() == taskId {
		log.Println(log.DebugLog, "Begin to exec:", this.CurTask+1)
		this.ExecNo(this.CurTask + 1)
	} else {
		log.Println(log.ErrorLog, "Finish task Not match! do nothing!")
	}
}

type SceneTaskStatus uint8

const (
	SceneTaskStatus_Init SceneTaskStatus = iota
	SceneTaskStatus_Playing
	SceneTaskStatus_Paused
)

type SSceneHd interface {
	SetOemArg(taskno int, arg interface{}) error
	GetOemArg(taskno int) (interface{}, error)
	GetCurServerScenetask() (*http.SceneTask, error)
	GetPlayerHd() PlayerHd
	GetCurTaskNo() int
	Finish(taskId int, inScheduleThread bool)
	GetCtx() *context.Context
	GetRemainTime() time.Duration
	GetScene() http.Scene
	GetIgnoreVolRestore() bool
}

type SScene struct {
	Scene     http.Scene
	SceneId   string
	LastScene *SScene

	CurTask        int
	TaskStatus     SceneTaskStatus
	TimeRemain     time.Duration
	TimeoutTimer   *time.Timer
	LastPausedTime time.Time
	LastPlayTime   time.Time

	OemArg interface{}

	SceneEnterTime   time.Time
	SceneNewEnter    bool
	TaskNewEnter     bool
	InterruptStatus  bool
	FinishAll        bool
	IgnoreVolRestore bool

	HasRequestedWhenInterrupt bool
	RequestedTime             time.Time

	PlayAfterResume bool

	Ctx       context.Context
	CtxCancel context.CancelFunc
}

func (this *SScene) GetIgnoreVolRestore() bool {
	defer func() {
		this.IgnoreVolRestore = false
	}()
	return this.IgnoreVolRestore
}

func (this *SScene) GetCtx() *context.Context {
	return &this.Ctx
}

func (this *SScene) GetScene() http.Scene {
	return this.Scene
}

func (this *SScene) InitCtx() {
	this.Ctx, this.CtxCancel = context.WithCancel(context.Background())
}

func (this *SScene) GetSceneName() string {
	return this.Scene.Name
}

func GetRandSceneTts(alltts []http.SceneTts, sceneHasEnterTimes int) string {
	defer func() {
		if err := recover(); err != nil {
			log.Println(log.ErrorLog, "have err:", err)
		}
	}()
	if len(alltts) == 0 {
		return ""
	}

	var pritts []*http.SceneTts
	var defaulttts []*http.SceneTts
	nowhour := time.Now().Hour()

	for _, v := range alltts {
		if v.BeginHour <= 0 && v.EndHour >= 24 && (v.Count == 0 || v.Count >= sceneHasEnterTimes) {
			defaulttts = append(defaulttts, &v)
		} else if v.BeginHour <= nowhour && v.EndHour >= nowhour && (v.Count == 0 || v.Count >= sceneHasEnterTimes) {
			pritts = append(pritts, &v)
		}
	}

	rand.NewSource(time.Now().UnixNano())

	if len(pritts) > 1 {
		n := rand.Intn(len(pritts))
		return pritts[n].Tts
	} else if len(pritts) == 1 {
		return pritts[0].Tts
	}

	if len(defaulttts) > 1 {
		n := rand.Intn(len(defaulttts))
		return defaulttts[n].Tts
	} else if len(defaulttts) == 1 {
		return defaulttts[0].Tts
	}

	return ""
}

func (this *SScene) GetTemplatedTts(orig string) string {
	if len(orig) == 0 {
		return ""
	} else {
		return vui.ParseTemplate(this, orig)
	}
}

func (this *SScene) GetEnterTts() string {
	return this.GetTemplatedTts(GetRandSceneTts(this.Scene.EnterTts, this.Scene.EnterTimes))
}

func (this *SScene) GetExitTts() string {
	return this.GetTemplatedTts(GetRandSceneTts(this.Scene.ExitTts, this.Scene.EnterTimes))
}

func (this *SScene) GetCurTaskNo() int {
	return this.CurTask + int(uintptr(unsafe.Pointer(this)))
}
func (this *SScene) GetPlayerHd() PlayerHd {
	return scenePlayerHd
}

func (this *SScene) Pause(showPausedIcon bool) {
	if showPausedIcon == true {
		vui.IconLed_IconChaozuo001.Show()
	}
	if this.TaskStatus == SceneTaskStatus_Paused {
		return
	}

	this.CtxCancel()
	if this.SceneNewEnter == true {
		this.TaskStatus = SceneTaskStatus_Paused
		log.Println(log.DebugLog, "Scene is new entering, just cancel ctx!")
		return
	}

	if localst, err := this.GetCurLocalSceneTask(); err == nil {
		switch localst.GetType() {
		case SStaskType_Once:
			if localst.Stop != nil {
				localst.Stop(this)
			} else if localst.Pause != nil {
				localst.Pause(this)
			}
			this.CurTask++
			this.SetTaskStatus(SceneTaskStatus_Init)

		case SStaskType_Continuous:
			localst.Pause(this)
			this.SetTaskStatus(SceneTaskStatus_Paused)
		}

	} else {
		log.Println(log.ErrorLog, "Can not find local scene task!!", err)
	}

	if this.TimeoutTimer != nil {
		this.TimeoutTimer.Stop()
		this.TimeoutTimer = nil
	}
}

func taskExecTimeout(curTaskNo int) {
	log.Println(log.DebugLog, "Task exec timeout!")
	LockScene()
	defer func() {
		UnlockScene()
		if err := recover(); err != nil {
			log.Println(log.DebugLog, "Timeout, recover:", err)
		}
	}()

	if curscene != nil && curscene.CurTask == curTaskNo {
		log.Println(log.DebugLog, "Task exec timeout!", curTaskNo, curscene.CurTask == curTaskNo)
		curscene.Pause(false)
		curscene.ExecNo(curscene.CurTask + 1)
	}
}

func (this *SScene) ExecNext() {
	if localst, err := this.GetCurLocalSceneTask(); err == nil {
		localst.Pause(this)
		this.ExecNo(this.CurTask + 1)
	} else {
		log.Println(log.ErrorLog, "Can not find local scene task!!", err)
	}
}

func (this *SScene) ExecNo(no int) {
	this.SetTaskStatus(SceneTaskStatus_Init)
	scenePlayerHd.ClearPlayStatus()

	if no >= len(this.Scene.SceneTasks) {
		log.Println(log.DebugLog, "Scene finish! Will exit!")
		if curscene == nil {
			log.Println(log.DebugLog, "Should not come here!")
			debug.PrintStack()
			return
		}
		this.TrigBroadStatus(no-1, 100)
		if tts := this.GetExitTts(); len(tts) > 0 {
			PlayNoticeAsync(globals.AdSrcType_TTS, tts, globals.CONST_AUDIO_ID, true, this.Ctx, func(id uint32, reason NoticeStopReason) {
				if reason.IsStart() {
					return
				}
				LockScene()
				defer UnlockScene()
				this.Exit()
			})
		} else {
			this.Exit()
		}
		return
	}

	target := this.Scene.SceneTasks[no]
	if target.Status == http.SceneTask_Status_Off {
		log.Println(log.DebugLog, "Cur task off! go next!")
		this.ExecNo(no + 1)
		return
	}

	if this.CtxCancel != nil {
		this.CtxCancel()
	}
	if this.TimeoutTimer != nil {
		this.TimeoutTimer.Stop()
		this.TimeoutTimer = nil
	}

	this.InitCtx()

	if len(target.EnterTts.Tts) == 0 {
		log.Println(log.DebugLog, "No task tts play, just start,", target)
		this._execNo(no)
	} else {
		log.Println(log.DebugLog, "Have tts play, first play tts:", target.EnterTts)
		tts := this.GetTemplatedTts(target.EnterTts.Tts)
		this.TaskStatus = SceneTaskStatus_Playing
		PlayNoticeAsync(globals.AdSrcType_TTS, tts, globals.CONST_AUDIO_ID, true, this.Ctx, func(id uint32, reason NoticeStopReason) {
			if reason.IsStart() {
				return
			}

			LockScene()
			defer UnlockScene()

			if curscene == nil {
				log.Println(log.DebugLog, "Cur scene nil, play cb do nothing!")
				return
			}

			if reason == NoticeStopReason_Complete {
				log.Println(log.DebugLog, "tts finish, and exec task:", no)
				this._execNo(no)
			} else {
				log.Println(log.ErrorLog, "tts has issue, stop reason:", reason)
				this.TaskStatus = SceneTaskStatus_Paused
			}
		})
	}
}

func (this *SScene) _execNo(no int) {
	this.CurTask = no
	serverst, _ := this.GetCurServerScenetask()

	if localst, err := this.GetCurLocalSceneTask(); err == nil {
		this.TrigBroadStatus(no-1, 100)
		this.TrigBroadStatus(no, -1)
		log.Println(log.DebugLog, "Begin to exec scene  and id", this.Scene.Name, this.CurTask)
		this.SetTaskStatus(SceneTaskStatus_Playing)
		if serverst.Duration > 0 {
			this.TimeRemain = time.Duration(serverst.Duration) * time.Millisecond
			if this.TimeoutTimer != nil {
				this.TimeoutTimer.Stop()
			}
			this.TimeoutTimer = time.AfterFunc(this.TimeRemain, func() {
				taskExecTimeout(no)
			})
		}
		log.Println(log.DebugLog, "Begin to new play, start:", PlayerStartType_New)
		localst.Play(PlayerStartType_New, 0, this, this.GetCurTaskNo())
		this.TaskNewEnter = false
	} else {
		log.Println(log.DebugLog, "Dev not support cur task and go to next task, err:", err, serverst.LocalId)
		this.ExecNo(no + 1)
	}
	this.SceneNewEnter = false
}

func (this *SScene) Exec(isForce bool) {
	this.InterruptStatus = false
	if this.CurTask == len(this.Scene.SceneTasks) {
		log.Println(log.DebugLog, "Cur Scene finish, exit!")
		this.FinishAll = true
		this.Exit()
		return
	}

	if this.TaskStatus == SceneTaskStatus_Playing {
		log.Println(log.DebugLog, "Cur scene is playing, do nothing!")
		return
	}

	if this.CtxCancel != nil {
		this.CtxCancel()
	}
	if this.TimeoutTimer != nil {
		this.TimeoutTimer.Stop()
		this.TimeoutTimer = nil
	}
	this.InitCtx()

	log.Println(log.DebugLog, "Sscene begins to play!!!")
	if this.SceneNewEnter {
		if entertts := this.GetEnterTts(); len(entertts) > 0 || this.LastScene != nil {
			log.Println(log.DebugLog, "Enter scene, playtts:", entertts)
			this.SetTaskStatus(SceneTaskStatus_Playing)
			if this.LastScene != nil {
				entertts = fmt.Sprintf("即将退出%s,进入%s", this.LastScene.Scene.Name, this.Scene.Name)
				this.LastScene = nil
			}
			PlayNoticeAsync(globals.AdSrcType_TTS, entertts, globals.CONST_AUDIO_ID, true, this.Ctx, func(id uint32, reason NoticeStopReason) {
				if reason.IsStart() {
					return
				}

				LockScene()
				defer UnlockScene()

				if curscene == nil {
					log.Println(log.DebugLog, "Cur scene nil, cb do nothing!")
					return
				}
				if reason == NoticeStopReason_Complete {
					this.ExecNo(0)
				} else {
					log.Println(log.DebugLog, "Scene by interrupted! Do not continue the scene:", this.GetSceneName())
				}
			})
		} else {
			log.Println(log.DebugLog, "No tts play, just start exec!")
			this.ExecNo(0)
		}
	} else if this.TaskNewEnter {
		this.ExecNo(this.CurTask)
	} else {
		if localst, err := this.GetCurLocalSceneTask(); err == nil {
			serverst, _ := this.GetCurServerScenetask()
			var playerstart PlayerStartType
			if scenePlayerHd.IsPlayerChange(true) == true {
				playerstart = PlayerStartType_PlayerChange
			} else {
				playerstart = PlayerStartType_Continue
			}
			log.Println(log.DebugLog, "Begin to continue play, start:", playerstart, scenePlayerHd.GetPlayerStatus().CurPos)
			switch {
			case time.Now().Sub(this.LastPausedTime) <= GetT1Duration():
				this.SetTaskStatus(SceneTaskStatus_Playing)
				localst.Play(playerstart, int(scenePlayerHd.GetPlayerStatus().CurPos), this, this.GetCurTaskNo())

			case time.Now().Sub(this.LastPausedTime) > GetT1Duration() && time.Now().Sub(this.LastPausedTime) <= GetT2Duration():
				this.SetTaskStatus(SceneTaskStatus_Playing)
				localst.Play(playerstart, 0, this, this.GetCurTaskNo())

			case time.Now().Sub(this.LastPausedTime) > GetT2Duration():
				if isForce {
					this.SetTaskStatus(SceneTaskStatus_Playing)
					localst.Play(playerstart, 0, this, this.GetCurTaskNo())
				} else {
					return
				}
			}
			if serverst.Duration > 0 {
				log.Println(log.DebugLog, "Set timeout cb:", serverst.Duration, this.TimeRemain)
				this.TimeoutTimer = time.AfterFunc(this.TimeRemain, func() {
					taskExecTimeout(this.CurTask)
				})
			}
		}
	}
}

func (this *SScene) GetRemainTime() time.Duration {
	if st, err := this.GetCurServerScenetask(); err == nil {
		if st.Duration > 0 {
			if this.TaskStatus != SceneTaskStatus_Playing {
				return this.TimeRemain
			} else {
				return this.TimeRemain - time.Now().Sub(this.LastPlayTime)
			}
		} else {
			return time.Duration(0)
		}
	} else {
		return time.Duration(0)
	}
}

func (this *SScene) Exit() {
	if curscene == nil {
		log.Println(log.DebugLog, "Cur scene is nil, do exit here!")
		return
	}
	if curscene.TimeoutTimer != nil {
		curscene.TimeoutTimer.Stop()
		curscene.TimeoutTimer = nil
	}

	eventPoint.EpRecord("F412", map[string]interface{}{"scene": this.Scene.Id, "full": this.FinishAll})

	if uintptr(unsafe.Pointer(this)) == uintptr(unsafe.Pointer(curscene)) {
		log.Println(log.DebugLog, "Cur scene exit:", this.Scene.Name, this.Scene.Id)
		if curscene.CtxCancel != nil {
			curscene.CtxCancel()
		}
		curscene = nil
		SceneExit()
	} else {
		log.Println(log.DebugLog, "Cur scene not the curscene, do nothing! this:", uintptr(unsafe.Pointer(this)), " cur:", uintptr(unsafe.Pointer(curscene)))
	}
}

func (this *SScene) GetCurTask() (st http.SceneTask, err error) {
	defer func() {
		if re := recover(); re != nil {
			err = errors.New(fmt.Sprintln(re))
		}
	}()
	return this.Scene.SceneTasks[this.CurTask], nil
}

func (this *SScene) SetTaskStatus(status SceneTaskStatus) {
	switch status {
	case SceneTaskStatus_Init:
		this.OemArg = nil
		this.TaskNewEnter = true
		if this.TimeoutTimer != nil {
			this.TimeoutTimer.Stop()
			this.TimeoutTimer = nil
		}
		this.TimeRemain = 0
		this.LastPausedTime = time.Now()
		this.LastPlayTime = time.Now()

		this.InitCtx()

		this.TaskStatus = SceneTaskStatus_Init

	case SceneTaskStatus_Playing:
		this.LastPlayTime = time.Now()
		this.TaskStatus = SceneTaskStatus_Playing

	case SceneTaskStatus_Paused:
		this.LastPausedTime = time.Now()
		this.TaskStatus = SceneTaskStatus_Paused

		if st, err := this.GetCurServerScenetask(); err == nil {
			if st.Duration > 0 {
				this.TimeRemain = this.TimeRemain - time.Now().Sub(this.LastPlayTime)
			}
		}
	}
}

func (this *SScene) Init() error {
	if err := this.Update(); err != nil {
		return err
	}
	this.InitSceneEnter()
	return nil
}

func (this *SScene) InitSceneEnter() {
	this.SceneNewEnter = true
	this.SetTaskStatus(SceneTaskStatus_Init)
}

func (this *SScene) Update() error {
	var req http.HttpReqSceneById
	var rsp http.HttpRspSceneById
	req.SceneId = this.SceneId

	if code, _, err := http.HttpPostAuto(req, &rsp); code == 1000 && err == nil {
		this.Scene = http.Scene(rsp)
		log.Printf(log.DebugLog, "detail:%+v\n", rsp)
		return nil
	} else {
		return errors.New(fmt.Sprintln("code:", code, " err:", err))
	}
}

func (this *SScene) IsActive() bool {
	return this.CurTask < len(this.Scene.SceneTasks)
}

func SceneExit() {
	SendSCmd(SCMD_Scene_Exit)
}

func SceneNextTask() {
	LockScene()
	defer UnlockScene()
	lockCore()
	defer unlockCore()

	if curscene != nil && curscene.InterruptStatus == false {
		curscene.ExecNext()
	}
}
func SceneRequestPlay(sceneIdOrStruct interface{}) {
	log.Printf(log.DebugLog, "Enter sceneid:%+v\n", sceneIdOrStruct)
	SendSCmd(SCMD_MusicPlayer_RequestPlay, scenePlayerHd.GetPlayerId(), sceneIdOrStruct)
}

func Scene_Exit() {
	LockScene()
	defer UnlockScene()
	if curscene != nil {
		curscene.Exit()
	}
}

func GetCurSScene() *SScene {
	return curscene
}
func Scene_GetExitTts() string {
	LockScene()
	defer UnlockScene()
	if curscene != nil {
		return curscene.GetExitTts()
	} else {
		return ""
	}
}
