"""
# Copyright 2019 VMware, Inc.
# All rights reserved. -- VMware Confidential
"""

import sys
import os
import json
import logging
import traceback
import psutil #pylint: disable=E0401
from math import  floor
from patch_errors import UserError
from l10n import msgMetadata as _T, localizedString as _
from utils import get_disk_info #pylint: disable=E0611
sys.path.append(os.environ['VMWARE_PYTHON_PATH'])
logger = logging.getLogger(__name__)
MBFactor = 1024 * 1024
CONVERSION_FACTOR = float(1<<30)
UPGRADE_DIRECTORY = os.path.join('/usr/lib/', 'vmware',
                                 'cis_upgrade_runner')

DEPLOYMENT_SIZE_FILE = os.path.join(UPGRADE_DIRECTORY, 'config',
                                    'deployment-size-layout.json')


def compareDiskSize(currentVcInfo, sourceLayout, deploymentOption):
    """
    This method compares the sizes of all the disks in the current vc with disk
    sizes mentioned in the sourceLayout file for given deploymentOption
    :param currentVcInfo: dictionary containing the vcInformation
    regarding memory, disk, cpus etc..
    :param sourceLayout:dictionary containing the layout.json info
    :param deploymentOption:Deployment option to be verified with
    :return: true if all the the disk sizes in the current vc are greater than
    or equal to the disk Size for the given deployment option
    """
    #Check sizes of only disks which are present on source and destination
    # layout.This handles the retry case.
    for disk in currentVcInfo['disk'].keys():
        if disk.startswith('disk') \
                and disk in sourceLayout[deploymentOption].keys():
            if currentVcInfo['disk'][disk] !=\
                    sourceLayout[deploymentOption][disk]:
                return False
    return True


def getSmallestDeploymentOption(sourceLayout, probableDeploymentOptions):
    """
    This method returns the smallest deployment size among
    the list of probableDeploymentOption
    :param sourceLayout: dictionary containing source layout
    :param sourceLayout: dictionary

    :param probableDeploymentOption: list of Probable deployment option
    :param probableDeploymentOption: list
    :return: smallest deployment option among the probable options
    """
    if len(probableDeploymentOptions) == 0:
        raise Exception("Could not find the proper deployment option.")

    if len(probableDeploymentOptions) == 1:
        return probableDeploymentOptions[0]

    minimumDeploymentOption = None
    minRequiredSize = 10000
    for option in probableDeploymentOptions:
        optionRequiredSizeInInt = \
            int(sourceLayout[option]['required-disk-space'].strip('GB'))
        if minRequiredSize > optionRequiredSizeInInt:
            minRequiredSize = optionRequiredSizeInInt
            minimumDeploymentOption = option
    return minimumDeploymentOption


def _getSourceLayoutFile():
    """
    This method gets the source layout file
    :return:
    """
    try:
        with open(DEPLOYMENT_SIZE_FILE) as f:
            sourceLayout = json.loads(f.read())
        return sourceLayout
    except Exception:
        cause = _(_T('finding.layout.json.failed',
                     'File loading %s failed with error %s.'),
                     [DEPLOYMENT_SIZE_FILE, traceback.format_exc()])
        raise UserError(cause=cause)


def _getDisks(sourceLayout):
    """
    This method gets information relating to existing partitions on appliance
    """
    out = get_disk_info()
    out_json = json.loads(out)
    #Here we are collecting all the existing partitions on the appliance
    diskInfo = {}
    for item in out_json["blockdevices"]:
        if item["type"] == "lvm":
            diskName = "disk-%s"%(item["name"].split('-')[1])
            roundedOfSize = floor(int(item["size"]) / CONVERSION_FACTOR) + 1
            diskSize = str(roundedOfSize) + "GB"
            if diskName == "disk-swap1":
                diskName = "disk-swap"
            if diskName in sourceLayout['small']:
                diskInfo[diskName] = diskSize
    logger.info("Collected information for the existing disk partitions.")
    return diskInfo


def _getCpu():
    """
    This method gets the current cpu count of the machine using
    psutils function
    :return cpus:No of CPU's on the given appliance
    """
    if not hasattr(psutil, 'cpu_count'):
        raise Exception("PSUTIL module does not have method cpu_count.")
    cpus = psutil.cpu_count()
    return cpus


def _getMemory():
    """
    This method gets the current virtual memory of the machine using
    psutils function
    :return virtual_memory: virtual memory of the appliance in MB
    """
    if not hasattr(psutil, 'virtual_memory'):
        raise Exception("PSUTIL module does not have method virtual_memory.")

    memory = psutil.virtual_memory().total / MBFactor
    # Rounding the memory to next higher mulitple of 1024
    # This compnesates the free memory part which is not
    # provided by the psutil.virtual_memory() function
    memory = (floor(memory / 1024) + 1) * 1024
    return memory


class GetDeploymentType:
    """
    This class has provides mechanism to find the deployment type of the
    given appliance
    """
    def __init__(self):
        '''
        Constructor of the class where we creating dictionary objcets
        '''
        self.currentVcInfo = {}
        self.sourceLayout = _getSourceLayoutFile()


    def _create_constraint_dictionary(self):
        """
        This method creates a single dictionary containing information relating
        to Memory, CPU, and DISK's. The information is obtained dynamically
        from the appliance
        """
        self.currentVcInfo = {
            "cpu": _getCpu(),
            "memory": _getMemory(),
            "disk": _getDisks(self.sourceLayout)
        }


    def get_deployment_size(self):
        '''
        This method returns the current deployment type of the appliance
        ex: small, large, xlarge, medium etc
        :return deployment type of the appliance
        type str:
        '''
        logger.info("Locating appliance deployment type.")
        self._create_constraint_dictionary()
        # OVF deployment size profile ordering
        probableDeploymentOption = []
        for deploymentOption in self.sourceLayout.keys():
            #Here we do not compare with deployment options which
            #are no longer supported like management, infrastructure etc..
            if not deploymentOption.startswith('management') and\
                    not deploymentOption == 'infrastructure':
                if compareDiskSize(self.currentVcInfo, self.sourceLayout,
                                   deploymentOption):
                    probableDeploymentOption.append(deploymentOption)
        deploymentOption = getSmallestDeploymentOption(self.sourceLayout,
                                                       probableDeploymentOption)

        logger.info("The existing deployment type is %s.", deploymentOption)
        return deploymentOption

