#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c): 2012-2017, Huawei Tech. Co., Ltd.
# Description  : DbClusterInfo.py is a utility to get cluster information
#############################################################################
from __future__ import print_function

try:
    import os
    import types
    import sys

    sys.path.append(os.path.split(os.path.realpath(__file__))[0] + "/../../")
    from gspylib.common.ErrorCode import ErrorCode
    from gspylib.common.cluster_topology.instance_info import instanceInfo
    from gspylib.common.cluster_topology.node_info import dbNodeInfo
    from gspylib.common.cluster_topology.cluster_info import BaseDbClusterInfo
    from gspylib.common.cluster_topology import const
except ImportError as ie:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(ie))


def get_instance_info_ignore_table(Object, model):
    """
    function : get instance information of table.
    input : Object, Object, model
    output : boolean
    """
    instance_info_ignore_table = {}
    if model == "replace":
        # init instance ignore table for replace
        instance_info_ignore_table = {"listenIps": None, "haIps": None, "hostname": None, "mirrorId": None}
    elif model == "changeIP":
        # init instance ignore table for changeip
        instance_info_ignore_table = {
            "listenIps": None,
            "haIps": None,
            "hostname": None,
            "port": None,
            "haPort": None,
            "sctpPort": None,
            "controlPort": None,
            "servicePort": None,
            "mirrorId": None
        }
    elif model == "upgradectl" or model == "online_upgrade":
        # init instance ignore table for upgradectl
        instance_info_ignore_table = {"instanceRole": None, "instanceId": None, "mirrorId": None}
    elif model == "manageCN":
        # init instance ignore table for manageCN
        instance_info_ignore_table = {"instanceId": None, "mirrorId": None}
    elif model == "expand":
        # init instance ignore table for expand
        instance_info_ignore_table = {"mirrorId": None}
    elif model == "loadbalance":
        # init instance ignore table for loadbalance
        instance_info_ignore_table = {"instanceId": None, "mirrorId": None}
    elif model == "compareCluster":
        instance_info_ignore_table = {
            "listenIps": None,
            "haIps": None,
            "hostname": None,
            "port": None,
            "haPort": None,
            "sctpPort": None,
            "controlPort": None,
            "servicePort": None,
            "mirrorId": None
        }
        if hasattr(Object, "instanceRole") and Object.instanceRole == const.INSTANCE_ROLE_COODINATOR:
            instance_info_ignore_table["instanceId"] = None

    instance_info_ignore_table['azName'] = None

    return instance_info_ignore_table


def ignoreCheck(Object, member, model):
    """
    function : Ignore checking the instance information of table.
    input : Object, Object, model
    output : boolean
    """
    instance_info_ignore_table = get_instance_info_ignore_table(Object, model)
    # init node ignore table
    db_node_info_ignore_table = {
        "backIps": None,
        "sshIps": None,
        "masterBasePorts": None,
        "standbyBasePorts": None,
        "dummyStandbyBasePort": None,
        "cmsNum": None,
        "cooNum": None,
        "dataNum": None,
        "gtmNum": None,
        "name": None,
        "virtualIp": None}
    # init cluster ignore table
    db_cluster_info_ignore_table = {"xmlFile": None,
                                    "newNodes": None,
                                    "clusterRings": None,
                                    "config_version": None
                                    }
    if model == "online_upgrade":
        # init cluster ignore table
        db_cluster_info_ignore_table = {"xmlFile": None,
                                        "newNodes": None,
                                        "clusterRings": None,
                                        "appPath": None,
                                        "config_version": None
                                        }
    if model == "upgradectl":
        db_node_info_ignore_table.pop("backIps")
        db_node_info_ignore_table.pop("sshIps")
        db_node_info_ignore_table.pop("name")
        db_cluster_info_ignore_table.pop("clusterRings")
    elif model == "manageCN":
        db_node_info_ignore_table.pop("backIps")
        db_node_info_ignore_table.pop("sshIps")
        db_node_info_ignore_table.pop("name")
        db_node_info_ignore_table["id"] = None
    if isinstance(Object, instanceInfo):
        if member not in instance_info_ignore_table.keys():
            return False
        elif instance_info_ignore_table[member] is None or not callable(instance_info_ignore_table[member]):
            return True
        else:
            return instance_info_ignore_table[member](Object)
    elif isinstance(Object, dbNodeInfo):
        if member not in db_node_info_ignore_table.keys():
            return False
        elif db_node_info_ignore_table[member] is None or not callable(db_node_info_ignore_table[member]):
            return True
        else:
            return instance_info_ignore_table[member](Object)
    elif isinstance(Object, dbClusterInfo):
        if member not in db_cluster_info_ignore_table.keys():
            return False
        elif db_cluster_info_ignore_table[member] is None or not callable(db_cluster_info_ignore_table[member]):
            return True
        else:
            return instance_info_ignore_table[member](Object)
    else:
        return False


def obtainInstStr(objectList):
    '''
    function : Obtain information of instance.
    input : []
    output : String
    '''
    info = ""
    if isinstance(objectList, list):
        for obj in objectList:
            info = "%s%s\n" % (info, str(obj))
    return info


def append_temp_buffer(inst_name, object_one, object_two):
    """
    function: append temp buffer object
    input: inst_name, object_one, object_two
    output: temp buffer
    """
    temp_buffer = list()
    temp_buffer.append(inst_name)
    temp_buffer.append(object_one)
    temp_buffer.append(object_two)
    return temp_buffer


def compareObject(Object_A, Object_B, instName, tempbuffer=None, model=None, manageCNinfo=None):
    '''
    function : Compare object_A and Object_B.
    input : Object, Object, instName, tempbuffer, model, manageCNinfo
    output : boolean, tempbuffer
    '''
    if tempbuffer is None:
        tempbuffer = []
    if isinstance(Object_A, bytes) or isinstance(Object_A, str):
        if Object_A != Object_B:
            tempbuffer = append_temp_buffer(instName, Object_A, Object_B)
            return False, tempbuffer
    # not the same type
    elif type(Object_A) != type(Object_B):
        tempbuffer = append_temp_buffer(instName, Object_A, Object_B)
        return False, tempbuffer
    # string, int, long, float, bool type
    elif isinstance(Object_A, bytes):
        if Object_A != Object_B:
            tempbuffer = append_temp_buffer(instName, Object_A, Object_B)
            return False, tempbuffer
    elif isinstance(Object_A, type(None)):
        if Object_A != Object_B:
            tempbuffer = append_temp_buffer(instName, Object_A, Object_B)
            return False, tempbuffer
    elif isinstance(Object_A, int) or isinstance(Object_A, float) or isinstance(Object_A, bool):
        if Object_A != Object_B:
            tempbuffer = append_temp_buffer(instName, Object_A, Object_B)
            return False, tempbuffer
    # list type
    elif isinstance(Object_A, list):
        (reflag, tempbuffer) = compareObjectListType(model, Object_A, Object_B, instName, tempbuffer, manageCNinfo)
        if not reflag:
            return reflag, tempbuffer
    # function type
    elif isinstance(Object_A, types.MethodType) or isinstance(Object_A, types.FunctionType):
        return True, tempbuffer
    elif isinstance(Object_A, type(dbClusterInfo())) or isinstance(Object_A, type(dbNodeInfo())) \
            or isinstance(Object_A, type(instanceInfo())):
        Object_A_list = dir(Object_A)
        Object_B_list = dir(Object_B)
        if len(Object_A_list) != len(Object_B_list):
            tempbuffer = append_temp_buffer(instName, str(Object_A), str(Object_B))
            return False, tempbuffer
        for i in Object_A_list:
            if i.startswith("_") or ignoreCheck(Object_A, i, model):
                continue
            Inst_A = getattr(Object_A, i)
            try:
                Inst_B = getattr(Object_B, i)
            except Exception:
                tempbuffer = append_temp_buffer(instName, str(Object_A), str(Object_B))
                return False, tempbuffer
            result, tempbuffer = compareObject(Inst_A, Inst_B, i, tempbuffer, model, manageCNinfo)
            if not result:
                return False, tempbuffer
    else:
        tempbuffer = append_temp_buffer(instName, str(Object_A), str(Object_B))
        return False, tempbuffer
    return True, tempbuffer


def compareObjectListType(model, Object_A, Object_B, instName, tempbuffer, manageCNinfo):
    """
    function: compare object for list type.
    """
    if model == "manageCN":
        if len(Object_A) != len(Object_B):
            theSame, tempbuffer = checkObject(Object_A, Object_B, instName, tempbuffer, manageCNinfo)
            if not theSame:
                return False, tempbuffer
            if len(Object_A) != 0 and len(Object_B) != 0:
                Object_A1 = []
                Object_B1 = []
                for Obj_A in Object_A:
                    for Obj_B in Object_B:
                        if Obj_A.name == Obj_B.name:
                            Object_A1.append(Obj_A)
                            Object_B1.append(Obj_B)
                            continue
                for idx in range(len(Object_A1)):
                    result, tempbuffer = compareObject(Object_A1[idx],
                                                       Object_B1[idx],
                                                       "%s[%d]" % (instName, idx),
                                                       tempbuffer,
                                                       model,
                                                       manageCNinfo)
                    if not result:
                        return False, tempbuffer
        else:
            for idx in range(len(Object_A)):
                result, tempbuffer = compareObject(Object_A[idx],
                                                   Object_B[idx],
                                                   "%s[%d]" % (instName, idx),
                                                   tempbuffer,
                                                   model,
                                                   manageCNinfo)
                if not result:
                    return False, tempbuffer
    else:
        if len(Object_A) != len(Object_B):
            tempbuffer.append(instName)
            tempbuffer.append(obtainInstStr(Object_A))
            tempbuffer.append(obtainInstStr(Object_B))
            return False, tempbuffer

        for idx in range(len(Object_A)):
            result, tempbuffer = compareObject(Object_A[idx],
                                               Object_B[idx],
                                               "%s[%d]" % (instName, idx),
                                               tempbuffer,
                                               model,
                                               manageCNinfo)
            if not result:
                return False, tempbuffer

    return True, tempbuffer


def match_xml_action(Object_A, Object_B, manageCNinfo):
    """
    """
    # xml must match action
    if len(Object_A) == 1 and len(Object_B) == 0:
        if manageCNinfo.mode != "delete":
            raise Exception(ErrorCode.GAUSS_528["GAUSS_52808"] % ("deletion", "add"))
    elif len(Object_A) == 0 and len(Object_B) == 1:
        if manageCNinfo.mode != "add":
            raise Exception(ErrorCode.GAUSS_528["GAUSS_52808"] % ("increased", "delete"))
    else:
        raise Exception(ErrorCode.GAUSS_528["GAUSS_52809"])
    # at most delete one CN
    if manageCNinfo.mode != "add" and (len(manageCNinfo.nodeInfo) != 0 or len(manageCNinfo.cooInfo)) != 0:
        raise Exception(ErrorCode.GAUSS_528["GAUSS_52809"])


def checkObject(Object_A, Object_B, instName, checkbuffer, manageCNinfo):
    """
    function:check manage cn object
    """
    Join = []
    if len(Object_A):
        Join.extend(Object_A)
    if len(Object_B):
        Join.extend(Object_B)

    # CN instance
    if isinstance(Join[0], instanceInfo):
        match_xml_action(Object_A, Object_B, manageCNinfo)
        manageCNinfo.cooInfo.extend(Join)
    # GaussDB nodes
    elif isinstance(Join[0], dbNodeInfo):
        # get added or deleted node
        checkObjectForNodeCN(Object_A, Object_B, manageCNinfo)

    return True, checkbuffer


def checkObjectForNodeCN(Object_A, Object_B, manageCNinfo):
    """
    check manage cn object fro node cn
    """
    # get added or deleted node
    oa_names = [Obj_A.name for Obj_A in Object_A]
    ob_names = [Obj_B.name for Obj_B in Object_B]
    Object_AA = [Obj_A for Obj_A in Object_A if Obj_A.name not in ob_names]
    Object_BB = [Obj_B for Obj_B in Object_B if Obj_B.name not in oa_names]

    match_xml_action(Object_AA, Object_BB, manageCNinfo)

    if len(Object_AA):
        manageCNinfo.nodeInfo.extend(Object_AA)
    if len(Object_BB):
        manageCNinfo.nodeInfo.extend(Object_BB)


class dbClusterInfo(BaseDbClusterInfo):
    """
    Cluster info
    """

    def __init__(self):
        BaseDbClusterInfo.__init__(self)
