# -*- coding:utf-8 -*-
import time
from BaseLog import CBaseLog
from ExtraData import CExtraData
from OptionExcel import COptionExcel
from OptionConfig import COptionConfig
from OptionAction import COptionAction
from ssat_sdk.source_input import SourceGenInput
from ssat_sdk.tv_detect import *
from ssat_sdk.device_manage.capturecard_manager import CCardManager
from ssat_sdk.utils.string_util import strcmp
# 测试使用到
import random


class CTMenu(CBaseLog):
    sourceInput = SourceGenInput()
    # 红老鼠遥控对象;
    redRat3 = TvOperator()
    # 创建视频采集对象
    captureCard = CCardManager()
    # 图片切割对象
    imgCMP = ImageCMP()
    rgbColor = RGBColor()
    CIE = CIEluvCaculator()

    def __init__(self, ocrDict=None):
        CBaseLog.__init__(self)
        self.__exData = CExtraData()
        self.__optionExcel = COptionExcel(self.__exData)
        self.__optionConfig = COptionConfig(self.__exData, self.__optionExcel)
        self.__curOpa = None
        self.__lastOpa = None
        self.__lastTime = None

    @property
    def exData(self):
        return self.__exData

    @property
    def optionExcel(self):
        return self.__optionExcel

    @property
    def optionConfig(self):
        return self.__optionConfig

    @property
    def opa(self):
        return self.__curOpa

    @property
    def pos(self):
        return self.__curOpa.pos

    def setOpa(self, opa):
        self.__curOpa = opa

    def setCurPos(self, pos):
        self.__curOpa.setCurPos(pos)

    def clearOpa(self):
        self.__lastOpa = None
        self.__curOpa = None

    '''
    函数:同步两个opa信息;
    参数:
    返回:
    '''

    def SyncOptionInfo(self):
        # 如果上次同步超过5秒,认为菜单退出; 意义不大
        # if self.__lastTime and time.time() - self.__lastTime > 5:
        #     self.info(u"前后两次同步时间差超过5秒")
        #     self.__lastOpa = None
        if self.__lastOpa:
            self.info(u"lastOpa.pos=%d" % self.__lastOpa.pos)
            diffValue = self.__lastOpa.pos - self.__curOpa.getPathLength()
            # 前者位置大于当前opa长度;
            if diffValue == 0:
                if self.__curOpa.isOptionInPaths(self.__lastOpa.pos, self.__lastOpa.optionValue) is True:
                    self.__curOpa.setCurPos(self.__lastOpa.pos)
            elif diffValue > 0:
                # 判断路径是否为子集关系;
                if self.__lastOpa.isOptionInPaths(self.__lastOpa.getPathLength(), self.__curOpa.curOptionName) is True:
                    while diffValue > 0:
                        diffValue -= 1
                        self.__lastOpa.back2ParentNode()
                    # 同时设置当前opa位置;
                    self.__curOpa.setCurPos(self.__curOpa.getPathLength())
            elif diffValue < 0:
                # 判断路径是否为子集关系;
                if self.__curOpa.isOptionInPaths(self.__lastOpa.pos, self.__lastOpa.curOptionName) is True:
                    self.__curOpa.setCurPos(self.__lastOpa.pos)
        # 记录;
        # self.__lastTime = time.time()
        self.__lastOpa = self.__curOpa

    def takePicture(self):
        img = os.path.join(getSATTmpDIR(), "menutree_runpath.png")
        self.captureCard.takePicture(img)
        if not os.path.exists(img):
            self.error(u"截图失败:%s" % img)
        return img

    '''
    函数:判断两值(for_ocr与ocr识别结果)是否相等;
    参数:
        srcValue        源值,excel表格中读取的value值。
        tagValue        目标值,ocr识别的结果值。
    返回:相同返回True。
    '''

    def __isValueEqual(self, srcValue, tagValue):
        self.info(u"源值=%s, 目标值=%s" % (str(srcValue), str(tagValue)))

    '''
    函数:移动到目标节点上(路径节点和value节点)
    参数:
        opa                 COptionAction对象;
        moveDirection       移动方向,True表示调用move_key[1](向下或向右), False表示调用move_key[0]。
        maxTry              最大移动次数,防止死循环。
    返回:Boolean是否在目标节点上, 文本或数值。
    '''

    def __move2TargetNode(self, opa, moveDirection, maxTry=15):
        tryCount = 0
        # 是否到目标结点了;
        isOnTargetNode = False
        # 目标节点聚焦状态;
        targetFocus = False
        # 目标文本或数值;
        targetText = None
        # 移动到目标节点;
        while True:
            # 判断是否移动成功;
            targetFocus, targetText = opa.isOnTargetNode()
            if targetFocus is True:
                # 如果已在目标结点,退出循环;
                if opa.isOnTargetOption() or opa.isOnValueSheet():
                    self.info(u"<到达目标路径节点或value节点>")
                    isOnTargetNode = True
                    break

                # 未到目标,进入下一节点;
                self.info(u"<进入下一节点>")
                opa.enterNode()

                # 先处理密码框;
                hasBox, dealStatus = opa.dealPasswordBox()
                if hasBox is True and dealStatus is False:
                    break

                continue
            # endif

            # 移动到下/上一节点;
            if moveDirection:
                opa.move2NextSiblingNode()
            else:
                opa.move2PrevSiblingNode()

            tryCount += 1
            if tryCount > maxTry:
                self.warn(u"已%s遍历超过%d次,仍未找到焦点" % ("正向" if moveDirection else "逆向", maxTry))
                break
        # end-while

        return isOnTargetNode, targetText

    def move2TargetNode(self, opa, moveDirection, maxTry=15):
        return self.__move2TargetNode(opa, moveDirection, maxTry)

    def back2Home(self):
        return self.__back2Home(self.__curOpa)

    '''
    函数:退出 usb 信源界面;
    参数:
        opa     COptionAction对象;
    返回:如果可以直接调用信源菜单返回True,否则返回False;
    '''

    def __exitUsbSource(self, opa):
        # 有些信源,需要手动enter
        enterKey = opa.curOptionInfo['enter_key']

        '''
            现在测试要插上U盘测试,而插上U盘以后,USB信源下的聚焦框会干扰到信源列表的聚焦框识别;
            因此注掉下面两段可能会干扰信源切换的代码,一旦进入USB信源,统一按exit键退出。
        '''
        # # 因为信源界面消失了,需要再调用一次.
        # opa.callFirstOptionShortCutKey()
        # # 判断是否聚焦成功;
        # if opa.isOnFirstOption() is True:
        #     return True
        #
        # self.redRat3.sendKey("ok", duration=2)
        # # 因为信源界面消失了,需要再调用一次.
        # opa.callFirstOptionShortCutKey()
        # # 判断是否聚焦成功;
        # if opa.isOnFirstOption() is True:
        #     self.redRat3.sendKey("ok", duration=2)
        #     return False

        self.redRat3.sendKey("exit", duration=2)
        # 因为信源界面消失了,需要再调用一次.
        opa.callFirstOptionShortCutKey()
        # 判断是否聚焦成功;
        if opa.isOnFirstOption() is True:
            self.redRat3.sendKey("ok", duration=2)
            return False

        self.redRat3.sendKey("return", duration=2)
        # 因为信源界面消失了,需要再调用一次.
        opa.callFirstOptionShortCutKey()
        # 判断是否聚焦成功;
        if opa.isOnFirstOption() is True:
            self.redRat3.sendKey("ok", duration=2)
            return False

    '''
    函数:移动到目标信源节点上。
    参数:
        opa                 COptionAction对象;
        moveDirection       移动方向,True表示调用move_key[1](向下或向右), False表示调用move_key[0]。
        maxTry              最大移动次数,防止死循环。
    返回:Boolean是否在目标节点上, 文本或数值。
    '''

    def __move2SourceNode(self, opa, moveDirection, maxTry=15):
        tryCount = 0
        # 目标节点聚焦状态;
        targetFocus = False
        # 目标文本或数值;
        targetText = None
        self.info(u"===========maxTry=%d" % maxTry)
        # 移动到目标节点;
        while True:
            # 截图,截图完后马上进入信源;
            pic = self.takePicture()
            # 有些信源,需要手动enter
            enterKey = opa.curOptionInfo['enter_key']
            if enterKey != "default":
                opa.executeKey(enterKey)

            # 判断是否移动成功;
            targetFocus, targetText = opa.isOnTargetNode(pic)
            if targetFocus is True:
                self.info(u"===========到达目标信源节点===========")
                break
            # endif

            # 如果是usb信源,特殊处理;
            if targetText and 'usb' in targetText.lower():
                if not self.__exitUsbSource(opa):
                    self.info(u"///////////////////////退出USB成功。///////////////////////")
                    # 因为信源界面消失了,需要再调用一次.
                    opa.callFirstOptionShortCutKey()
                    # 2次:移动到下/上一节点;
                    opa.move2NextSiblingNode() if moveDirection else opa.move2PrevSiblingNode()
                    opa.move2NextSiblingNode() if moveDirection else opa.move2PrevSiblingNode()
                    opa.executeKey(enterKey)
                    # 因为信源界面消失了,需要再调用一次.
                    opa.callFirstOptionShortCutKey()
                    continue

            # 因为信源界面消失了,需要再调用一次.
            opa.callFirstOptionShortCutKey()
            # 移动到下/上一节点;
            opa.move2NextSiblingNode() if moveDirection else opa.move2PrevSiblingNode()

            tryCount += 1
            if tryCount > maxTry:
                self.warn(u"已%s遍历超过%d次,仍未找到焦点" % ("正向" if moveDirection else "逆向", maxTry))
                break
        # end-while

        return targetFocus, targetText

    '''
    函数:聚焦到指定option中。
    参数:
        opa         COptionAction对象。
    返回:Boolean,成功返回True.
    '''

    def __focusOption(self, opa):
        # 检测路径是否有效;
        if opa.checkRunOptionPath() is False:
            self.__back2Home(opa)
            return False

        # 只有从0层开始才需要调用根菜单;
        if self.__curOpa.pos == 0:
            # 首先,调用根菜单;
            opa.callFirstOptionShortCutKey()
            # time.sleep(1)
            if opa.isOnFirstOption() is False:
                self.error(u"未聚焦到根节点上,退出")
                self.__back2Home(opa)
                return False

        # 遍历到目标option中;
        if self.__move2TargetNode(opa, True)[0] is False:
            if self.__move2TargetNode(opa, False)[0] is False:
                self.__back2Home(opa)
                return False

        return True

    '''
    函数:进入到指定的option中。
    参数:
        opa         COptionAction对象。
    返回:Boolean,成功返回True.
    '''

    def __openOption(self, opa):
        if self.__focusOption(opa) is False:
            return False

        # 到达目标option后;
        self.info(u"进入option")
        opa.enterNode()

        # 先处理密码框;
        hasBox, dealStatus = opa.dealPasswordBox()
        if hasBox is True and dealStatus is False:
            self.error(u"密码框处理失败")
            return False

        return True

    '''
    函数:返回到主页(不一定就是Home页,一般指返回运行前的那个画面)
    参数:
        opa         COptionAction对象;
    返回:
    
    '''

    def __back2Home(self, opa):
        self.info(u"返回主页")
        while opa.pos > 0:
            opa.back2ParentNode()
        self.info(u"【如果设置value时,会自动回退到上一层父节点,此处操作正常。否则要多返回一层。】")

        # value层的enter_key,如果按了ok会返回父层,则少退出一层return。但是目前menutree要兼容适配的旧问题,只能多退出一次。
        # 如果未来出现了新的机芯,多按return键会导致退出,需要把这个机制完善
        self.redRat3.sendKey("return", 1, 1)

    '''
    函数:设置指定的option的值为optionValue。
    参数:
        optionName      要设置的option;
        optionValue     option要设置的值;
    返回: Boolean。
    '''

    def setOptionValue(self, optionName, optionValue):
        self.info(u"【%s】【%s】" % (optionName, optionValue))
        CTMenu.sourceInput.setPattern(11)
        self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel)

        # 检测value值是否有效
        optionValueInfo = self.__optionExcel.getOptionValueInfo(optionName, optionValue)
        if optionValueInfo == []:
            self.error(u"当前optionName:[%s],optionValue:[%s],读取相关参数异常!!!请检查带入参数或者MenuTree配置的有效性!!!"%(optionName, optionValue))
            return False

        # 检测路径是否有效;
        if self.__openOption(self.__curOpa) is False:
            return False


        # 遍历到value节点中;
        if self.__move2TargetNode(self.__curOpa, True)[0] is False:
            if self.__move2TargetNode(self.__curOpa, False)[0] is False:
                self.__back2Home(self.__curOpa)
                return False

        # 找到value节点,设置值;
        self.info(u"设置value值:一般只适用于Number类型或Input类型,不适合选择类型。但都必须调用,因为里面有enterkey操作")
        self.__curOpa.setOptionValue()

        # 返回主页;
        self.__back2Home(self.__curOpa)
        return True

    '''
    函数:移动到目标option后,读取该option的value值,与optionValue进行比较,相等表示检测值与目标值一致;
    参数:
        optionName      要设置的option;
        optionValue     option要设置的值;
    返回: Boolean。
    '''

    def checkOptionValue(self, optionName, optionValue):
        self.info(u"【%s】【%s】" % (optionName, optionValue))
        CTMenu.sourceInput.setPattern(11)
        self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel)
        # 进入节点;
        if self.__openOption(self.__curOpa) is False:
            return False

        isValueEqual = False
        # 获取value节点值;
        valueFocus, valueText = self.__curOpa.isOnTargetNode()
        self.info(u"===当前捕获的文本内容=%s,目标文本=%s,状态:%d===" % (str(valueText), str(optionValue), valueFocus))

        # 如果是数值,则判断获取的值是否相等;否则,只判断是否聚焦.
        if type(optionValue) == int or type(optionValue) == float:
            isValueEqual = True if float(optionValue) == float(valueText) else False
        else:
            isValueEqual = valueFocus

        # 返回主页;
        self.__back2Home(self.__curOpa)

        # 聚焦状态就是结果;
        self.info(
            u"checkOptionValue结果:optionValue=%s, ocrValue=%s, 结果=%d" % (str(optionValue), str(valueText), isValueEqual))
        return isValueEqual

    '''
    函数:进入指定的option,并且不返回主页。
    参数:
        optionName          指定要进入的option节点。
        fromFirst           是否从第一层开始执行。默认从第一层执行,如果传入False,则以上一次执行所在层级来执行。
    返回:Boolean。成功进入返回True.
    '''

    def openOption(self, optionName, fromFirst = True):
        self.info(u"【%s】" % optionName)
        CTMenu.sourceInput.setPattern(11)
        self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel)

        if not fromFirst:
            # 同步前后信息;
            self.SyncOptionInfo()

        if self.__openOption(self.__curOpa) is False:
            return False

        # 聚焦状态就是结果;
        self.info(u"成功open指定option=%s" % optionName)
        return True

    '''
    函数:设置self.__curOpa的值;
    参数:
    返回:

    注意:一般在使用了openOption后,才调用该函数.且该函数不返回(不调用back2Home);
    '''

    def setValue(self, optionValue):
        self.info(u"开始设置值:optionName=%s, optionValue=%s" % (self.__curOpa.curOptionName, str(optionValue)))
        if self.__curOpa.setValue(optionValue) is False:
            self.info(u"该value[%s]不存在" % optionValue)
            return False

        # 遍历到value节点中;
        if self.__move2TargetNode(self.__curOpa, True)[0] is False:
            if self.__move2TargetNode(self.__curOpa, False)[0] is False:
                self.__back2Home(self.__curOpa)
                return False

        # 找到value节点,设置值;
        self.info(u"设置value值:一般只适用于Number类型或Input类型,不适合选择类型。但都必须调用,因为里面有enterkey操作")
        self.__curOpa.setOptionValue()

        return True

    '''
    函数:聚焦到指定option的value上,不设置不打开不返回。
    参数:
        optionName
        optionValue
    返回:
    
    注意:如果optionValue是数值类型,将不会变更值。
          如optionValue=10,实现该optionName的value是100,则不变更其值。
    '''

    def focusOptionValue(self, optionName, optionValue):
        self.info(u"【%s】【%s】" % (optionName, optionValue))
        CTMenu.sourceInput.setPattern(11)
        self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel)

        # 进入节点;
        if self.__openOption(self.__curOpa) is False:
            return False

        # 遍历到value节点中;
        if self.__move2TargetNode(self.__curOpa, True)[0] is False:
            if self.__move2TargetNode(self.__curOpa, False)[0] is False:
                self.__back2Home(self.__curOpa)
                return False

        return True

    '''
    函数:获取当前option节点的value值。
    参数:
        optionName      要获取值的option;
    返回:Boolean, str/int/float
    '''

    def getOptionValue(self, optionName):
        self.info(u"【%s】" % optionName)
        CTMenu.sourceInput.setPattern(11)
        self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel)

        # 检测路径是否有效;
        if self.__openOption(self.__curOpa) is False:
            return False, None

        # 获取value节点值;
        valueFocus, valueText = self.__curOpa.isOnTargetNode()
        self.info(u"===当前捕获的文本内容=%s,聚焦状态:%d===" % (str(valueText), valueFocus))

        # 返回主页;
        self.__back2Home(self.__curOpa)

        return valueFocus, valueText

    '''
    函数:
    参数:
    返回:
    '''

    def focusOption(self, optionName):
        self.info(u"【%s】" % optionName)
        CTMenu.sourceInput.setPattern(11)
        self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel)

        return self.__focusOption(self.__curOpa)

    '''
    函数:
    参数:
    返回:
    '''

    def inputUnlock(self, stdText, password=''):
        self.__curOpa = COptionAction(None, None, self.__optionConfig, self.__optionExcel)
        return self.__curOpa.inputUnlock(stdText, password)

    '''
    函数:当焦点已经在option层级上时,移动到指定的option上。
    参数:
        optionName      目标option.
    返回:Boolean,成功移动到目标节点返回True。
    
    注意:该函数不是从根节点开始移动到目标节点,而是假定焦点已经在目标节点层级菜单上。
    '''

    def moveToOption(self, optionName):
        self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel)
        # 已经在该层级上.
        self.__curOpa.setCurPos(self.__curOpa.getPathLength() - 1)

        # 检测路径是否有效;
        if self.__curOpa.checkRunOptionPath() is False:
            self.__back2Home(self.__curOpa)
            return False

        # 遍历到目标option中;
        if self.__move2TargetNode(self.__curOpa, True)[0] is False:
            if self.__move2TargetNode(self.__curOpa, False)[0] is False:
                self.__back2Home(self.__curOpa)
                return False

        self.info(u"已移动到目标节点[%s]" % optionName)
        return True

    '''
    函数:检测输入的频道列表,是否都存在,返回不存在的频道列表。
    参数:
        channelList         要检测的目标频道列表。
    返回:Boolean, []
        如果有频道没找到,返回False,并返回没找到的频道列表。如果全部找到返回True。
    '''

    def checkChannelList(self, channelList, maxTry=15):
        failChannelList = []
        checkResult = True
        if not channelList:
            self.error(u"传入的待检测的频道列表为空!")
            return False, failChannelList
        for channel in channelList:
            # isSearched = self.checkOptionValue("ok", channel)
            opa = COptionAction(channel, None, self.__optionConfig, self.__optionExcel)
            # 检查路径是否存在
            if opa.checkRunOptionPath() is False:
                self.error(u"频道:%s读取失败!!!请检查MenuTree配置!!!"%str(channel))
                failChannelList.append(channel)
                checkResult = False
                continue
            self.__curOpa = opa
            isSearched = self.__focusOption(opa)
            if not isSearched:
                self.warn(u"频道:%s查找失败!" % str(channel))
                failChannelList.append(channel)
                checkResult = False
                # 部分机芯项目(如6586外协松下)按return键无法退出频道,故改为exit键
                # self.redRat3.sendKey("return")
                self.redRat3.sendKey("exit")
            else:
                self.info(u"频道:%s查找成功!" % str(channel))
            self.redRat3.sendKey("exit")
        self.info(u"频道列表检测结果checkResult:%s,检测失败频道列表failChannelLis:%s" % (str(checkResult), str(failChannelList)))
        return checkResult, failChannelList

    '''
    函数:移动到黑屏的频道。
    参数:
        channelCount           要查找多少次频道。
        blackThreshold         黑屏阈值。
        blackRate              黑屏比率。
    返回:Boolean。找到返回True。
    '''

    def getBlackChannel(self, channelCount, blackThreshold=20, blackRate=0.8):
        result = False
        # 根据频道数量遍历;
        for i in range(0, channelCount):
            # 截图,取左半边;
            pic_path = self.takePicture()
            img = cv.imread(pic_path)
            pic_path2 = os.path.join(getSATTmpDIR(), "menutree_focus_area_half.png")
            self.imgCMP.saveCropPic(pic_path, pic_path2, (0, 0, img.shape[1] / 2, img.shape[0]))
            # 是否符合要求;
            result = self.imgCMP.isBlack(pic_path2, blackThreshold, 10, 1 - blackRate)
            if result is True:
                break

            # 下一频道号;
            self.redRat3.sendKey('C+')
            time.sleep(4)
        # endfor

        return result

    '''
    函数:设置信源。
    参数:
        optionName      目标信源;
    返回:
    
    注意:切换信源是特殊的情况处理:
        1、信源菜单消失过快,会导致ocr识别完成后,菜单已消失。
        2、usb信源下,可能会调用source按键失败,所以需要调用其他退出键(ok、exit、return)
    '''

    def setSourceValue(self, optionParent, optionName):
        self.info(u"【%s】" % optionName)
        CTMenu.sourceInput.setPattern(11)
        sourceList = self.__optionExcel.getOptionAllSiblingItemTextList1d(optionName)
        self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel)

        # 检测路径是否有效;
        if not self.__curOpa.checkRunOptionPath():
            return False

        # 首先,调用根菜单;
        self.__curOpa.callFirstOptionShortCutKey()
        # 遍历到目标option中;
        if self.__move2SourceNode(self.__curOpa, True, sourceList.__len__() * 2)[0] is False:
            if self.__move2SourceNode(self.__curOpa, False, sourceList.__len__() * 2)[0] is False:
                self.__back2Home(self.__curOpa)
                return False

        self.info(u"成功进入指定信源:%s" % optionName)
        return True

    '''
    函数:识别传入的option / optionValue,是否在当前页面中存在
    参数:
        option:在value不传入的情况下,option为非value层的option名;
        value:传入的情况下,option为value层的value_name,value则为value名
    返回:
        boolean,是否识别到该option / optionValue
    '''

    def checkOptionExist(self, option, value=None):
        self.__curOpa = COptionAction(option, value, self.optionConfig, self.optionExcel)
        self.__curOpa.setCurPos(self.__curOpa.getPathLength())
        return self.__curOpa.isOnTargetNode()[0]

    '''
        调用当前opa的back2ParentNode()
    '''

    def back2parentNode(self):
        return self.__curOpa.back2ParentNode()


if __name__ == "__main__":
    ml = CTMenu()
    ml.openOption("picture_preset")