#!/usr/bin/env python3
# -*- coding:utf-8 -*-

"""
This file is for Gauss version things.
"""

try:
    import os
    import sys
    import re

    sys.path.append(sys.path[0] + "/../../")
    from gspylib.common.ErrorCode import ErrorCode
except Exception as ie:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(ie))


class VersionInfo(object):
    """
    Info about current version
    """

    def __init__(self):
        pass

    # package version
    __PACKAGE_VERSION = ""
    __KERNEL_COMMIT_ID = ""
    # OM version string
    COMMON_VERSION = "Gauss200 OM VERSION"
    # It will be replaced with the product version, such as "Gauss200",
    # while being packaged by mpp_package.sh
    PRODUCT_NAME = "__GAUSS_PRODUCT_STRING__"
    PRODUCT_NAME_PACKAGE = "-".join(PRODUCT_NAME.split())
    __COMPATIBLE_VERSION = []
    # Version string is in V###R###C## format.
    VER_FORMAT_VRC = 1
    # Version string is in x.x.x format.
    VER_FORMAT_DOT = 2
    # Version string is illegal.
    VER_FORMAT_BAD = -1

    @staticmethod
    def getPackageVersion():
        """
        function: Get the current version from version.cfg
        input : NA
        output: String
        """
        if VersionInfo.__PACKAGE_VERSION != "":
            return VersionInfo.__PACKAGE_VERSION

        version_file = VersionInfo.get_version_file()
        version, _, _ = VersionInfo.get_version_info(version_file)

        VersionInfo.__PACKAGE_VERSION = version

        return VersionInfo.__PACKAGE_VERSION

    @staticmethod
    def detectVersionFormat(versionStr):
        """
        function: verify the version string format.
        input: the version string.
        output:
            VER_FORMAT_VRC: the version string is in V###R###C## format.
            VER_FORMAT_DOT: the version string is in x.x.x format.
            VER_FORMAT_BAD: the version string is illegal.
        """
        if (re.compile(r'^V[0-9]{3}R[0-9]{3}C[0-9]{2}$').match(versionStr) is not None):
            return VersionInfo.VER_FORMAT_VRC

        if (re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$').match(versionStr) is not None):
            return VersionInfo.VER_FORMAT_DOT

        return VersionInfo.VER_FORMAT_BAD

    @staticmethod
    def parseDotVersionString(versionStr):
        """
        function: parse a dot formated string into (major, minor, patch)
        input: the dot delimited version string.
        output: tuple(major, minor, patch) as (int, int, int)
        exception: bad version string or it is not in dot delimited format.
        """
        if (VersionInfo.VER_FORMAT_DOT != VersionInfo.detectVersionFormat(versionStr)):
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51623"] +
                            " Version: %s." % versionStr)

        (major, minor, patch) = versionStr.split('.')
        return (int(major), int(minor), int(patch))

    @staticmethod
    def compareDotVersionString(ver1, ver2):
        """
        function: Compare two dot formated version string.
        input: ver1 and ver2 are all dot delimited version string.
        return:
            0: they are the same.
            >0: ver1 is greater than ver2.
            <0: ver2 is less than ver2.
        exception: bad version string or it is not in dot delimited format.
        """
        if (VersionInfo.VER_FORMAT_DOT != VersionInfo.detectVersionFormat(ver1)):
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51623"] + " Version: %s." % ver1)

        if (VersionInfo.VER_FORMAT_DOT != VersionInfo.detectVersionFormat(ver2)):
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51623"] + " Version: %s." % ver2)

        (major1, minor1, patch1) = VersionInfo.parseDotVersionString(ver1)
        (major2, minor2, patch2) = VersionInfo.parseDotVersionString(ver2)

        vstr1 = "%05d%05d%05d" % (major1, minor1, patch1)
        vstr2 = "%05d%05d%05d" % (major2, minor2, patch2)

        if (vstr1 > vstr2):
            return 1
        elif (vstr1 < vstr2):
            return -1
        else:
            return 0

    @staticmethod
    def pickVersionFromString(anyStr):
        """
        function: pick up the version from a normal string.
        input: any string.
        output:
             (VersionInfo.VER_FORMAT_DOT, "x.x.x")
             (VersionInfo.VER_FORMAT_VRC, "V###R###C##")
             (VersionInfo.VER_FORMAT_BAD, ""): no version found in anyStr.
        """
        ver = re.compile(r'V[0-9]{3}R[0-9]{3}C[0-9]{2}').search(str(anyStr))
        if (ver is not None):
            return (VersionInfo.VER_FORMAT_VRC, ver.group())

        ver = re.compile(r'[0-9]+\.[0-9]+\.[0-9]+').search(str(anyStr))
        if (ver is not None):
            return (VersionInfo.VER_FORMAT_DOT, ver.group())

        return (VersionInfo.VER_FORMAT_BAD, "")

    @staticmethod
    def get_kernel_commit_id():
        if VersionInfo.__KERNEL_COMMIT_ID != "":
            return VersionInfo.__KERNEL_COMMIT_ID
        version_file = VersionInfo.get_version_file()
        _, _, commit_id = VersionInfo.get_version_info(version_file)
        VersionInfo.__KERNEL_COMMIT_ID = commit_id
        return VersionInfo.__KERNEL_COMMIT_ID

    @staticmethod
    def get_version_file():
        """
        function: Get version.cfg file
        input : NA
        output: String
        """
        dir_name = os.path.dirname(os.path.realpath(__file__))
        version_file = os.path.join(dir_name, "./../../../", "version.cfg")
        version_file = os.path.realpath(version_file)
        if not os.path.exists(version_file):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % version_file)
        if not os.path.isfile(version_file):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50210"] % version_file)
        return version_file

    @staticmethod
    def get_version_info(version_file):
        """
        function: parse cluster_version,upgrade_version,kernel_commit_id from version.cfg.
        input: version_file
        output: cluster_version,upgrade_version,kernel_commit_id
        """
        if not os.path.exists(version_file):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % version_file)
        if not os.path.isfile(version_file):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50210"] % version_file)

        # the information of versionFile like this:
        #     GaussDB-x.x.x/GaussDB-A-8.0.0/GaussDB-Kernel-V300R002C00/Gauss200-OLAP-V100R007C10
        #     xx.xxx
        #     1234efgh   -> gaussdb kernal version commit id
        #     5678efgh   -> om version commit id
        with open(version_file, 'r') as fp:
            ret_lines = fp.readlines()
        if len(ret_lines) < 3:
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50222"] % version_file)

        # get new cluster version information(x.x.x) and new cluster version number(XX.0)
        # new cluster version number is float and greater than 1.0
        (_, cluster_version) = VersionInfo.pickVersionFromString(ret_lines[0].strip())

        upgrade_version = ret_lines[1].strip()
        try:
            float(upgrade_version)
        except ValueError:
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51628"] % upgrade_version)

        kernel_commit_id = ret_lines[2].strip()
        if not (kernel_commit_id.isalnum() and len(kernel_commit_id) == 8):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50222"] % version_file + " Commit id is wrong.")

        return cluster_version, upgrade_version, kernel_commit_id
