# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2020 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description  : cpu.py is a utility to do something for cpu information.
#############################################################################

try:
    import os
    import time
    import subprocess
    import sys
    import multiprocessing
    from decimal import Decimal, getcontext
    from enum import Enum
    sys.path.append(sys.path[0] + "/../../")
    from base_utils.common.constantsbase import ConstantsBase
    from base_utils.os.cmd_util import CmdUtil

    from gspylib.common.ErrorCode import ErrorCode
except ImportError as e:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(e))

"""
Requirements:
1. getCpuNum():  get real cpu number.
2. getCpuOnlineOfflineInfo(is_onnline_cpu): get cpu online/offline information
"""


class CpuArchitecture(Enum):
    UNKNOWN = 0
    AARCH64 = 1
    X86_64 = 2

    @staticmethod
    def parse(arch):
        if arch == CpuArchitecture.AARCH64.name.lower():
            return CpuArchitecture.AARCH64
        elif arch == CpuArchitecture.X86_64.name.lower():
            return CpuArchitecture.X86_64
        else:
            return CpuArchitecture.UNKNOWN


class CpuUtil(object):
    """
    function: Init the CpuInfo options
    """

    @staticmethod
    def getCpuNum():
        """
        function : get cpu set of current board
        input  : null
        output : total CPU count
        """
        try:
            return multiprocessing.cpu_count()
        except Exception as excep:
            raise Exception(ErrorCode.GAUSS_523["GAUSS_52301"] + str(excep))

    @staticmethod
    def getCpuOnlineOfflineInfo(is_onnline_cpu=True):
        """
        cat /sys/devices/system/cpu/online or /sys/devices/system/cpu/offline
        """
        online_file_name = "/sys/devices/system/cpu/online"
        offline_file_name = "/sys/devices/system/cpu/offline"

        if is_onnline_cpu:
            file_name = online_file_name
        else:
            file_name = offline_file_name

        if not os.path.exists(file_name):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % file_name)
        if not os.path.isfile(file_name):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50210"] % file_name)

        cmd = "cat '%s' 2>/dev/null" % file_name
        status, output = subprocess.getstatusoutput(cmd)
        if status == 0:
            return output
        raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd +
                            " Error: \n%s" % str(output))

    @staticmethod
    def getCpuSet():
        """
        function: get cpu set of current board
                  cat /proc/cpuinfo |grep processor
        input: NA
        output: cpuSet
        """
        # do this function to get the parallel number
        cpu_set = multiprocessing.cpu_count()
        if cpu_set > 1:
            return cpu_set
        return ConstantsBase.DEFAULT_PARALLEL_NUM

    @staticmethod
    def getCpuArchitecture():
        """
        function: get cpu architecture of current board
                  lscpu | grep Architecture
        input: NA
        output: cpu architecture
        """
        cmd = f"{CmdUtil.getLscpuCmd()} | {CmdUtil.getGrepCmd()} Architecture"
        status, output = subprocess.getstatusoutput(cmd)
        if status != 0:
            return CpuArchitecture.UNKNOWN
        arch = output.split(':')[1].strip()
        return CpuArchitecture.parse(arch)

    @staticmethod
    def getCpuModelName():
        """
        function: get cpu mode name of current board
                  lscpu | grep Architecture
        input: NA
        output: cpu Model name
        """
        cmd = f"{CmdUtil.getLscpuCmd()} | {CmdUtil.getGrepCmd()} 'Model name'"
        status, output = subprocess.getstatusoutput(cmd)
        if status != 0:
            return ''
        mode_name = output.split(':')[1].strip()
        return mode_name

    @staticmethod
    def getCpuVendor():
        """
        function: get cpu vendor of current board
                  lscpu | grep 'Vendor ID'
        input: NA
        output: cpu vendor
        """
        cmd = f"{CmdUtil.getLscpuCmd()} | {CmdUtil.getGrepCmd()} 'Vendor ID'"
        status, output = subprocess.getstatusoutput(cmd)
        if status != 0:
            return ''
        vendor = output.split(':')[1].strip()
        return vendor

    @staticmethod
    def cpuListToCpuRangeStr(cpu_list):
        """
        function: transform cpu id list to cpu range str
        input: cpu id list, like (0,1,2,3,4,5,6,11,12,13,15,16,20)
        output: cpu range str, like '0-6,11-16,20'
        """
        if len(cpu_list) == 0:
            return ''

        start = cpu_list[0]
        pre = cpu_list[0]
        cpu_range_str = []
        for cpuid in cpu_list:
            if int(cpuid) > int(pre) + 1:
                this_range = str(pre) if pre == start else '{0}-{1}'.format(start, pre)
                cpu_range_str.append(this_range)
                start = pre = cpuid
                continue
            pre = cpuid
        last_range = str(pre) if pre == start else '{0}-{1}'.format(start, pre)
        cpu_range_str.append(last_range)
        return ','.join(cpu_range_str)

    @staticmethod
    def cpuRangeStrToCpuList(cpu_range_str):
        """
        function: transform cpu range str to cpu id list
        input: cpu range str, like '0-6,11-16,20'
        output: cpu id list, like (0,1,2,3,4,5,6,11,12,13,15,16,20)
        """
        cpu_list = []
        for part_range in cpu_range_str.split(','):
            p = part_range.split('-')
            if len(p) == 1:
                cpu_list.append(int(p[0]))
                continue
            cpu_list += [i for i in range(int(p[0]), int(p[1]) + 1)]

        return tuple(cpu_list)

    @staticmethod
    def getCpuNumaList():
        """
        function: get cpu numa list of current board
                  lscpu | grep 'NUMA node' | grep 'CPU(s)'
        input: NA
        output: [[cpu id of numa 0], [cpu id of numa 1], ...]
        """
        cmd = f"{CmdUtil.getLscpuCmd()} | {CmdUtil.getGrepCmd()} 'NUMA node' | {CmdUtil.getGrepCmd()} 'CPU(s)'"
        status, output = subprocess.getstatusoutput(cmd)
        if status != 0:
            raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd +
                            " Error: \n%s" % str(output))
        range_info = [line.split(':')[1].strip() for line in output.split('\n')]
        numa_list = []
        for p in range_info:
            numa_list.append(CpuUtil.cpuRangeStrToCpuList(p))

        return numa_list
    
    @staticmethod
    def get_cpu_idle(num=5):
        """
        function: get cpu idle
        input: num
        output: cpu idle list
        """
        cpu_idle_list = []
        for i in range(num):
            cmd = "top -bn1 | grep 'Cpu(s)'"
            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0:
                raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd +
                                " Error: \n%s" % str(output))
            cpu_info_list = output.split(',')
            for cpu_info in cpu_info_list:
                # get the cpu idle
                if 'id' in cpu_info:
                    cpu_idle_list.append(cpu_info.split()[0].strip())
            time.sleep(0.5)
            
        return cpu_idle_list

    @staticmethod
    def get_cpu_uesd():
        """
        function: get cpu used
        input: NA
        output: cpu used list
        """
        getcontext().prec = 2
        cpu_idle_list = CpuUtil.get_cpu_idle()
        cpu_uesd_list = [float(Decimal('100') - Decimal(idle)) for idle in cpu_idle_list]
        return cpu_uesd_list