# Copyright 2017 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import sushy
from sushy.resources import base

from proliantutils import exception
from proliantutils import log
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish import utils

LOG = log.get_logger(__name__)

BOOT_SOURCE_TARGET_TO_PARTIAL_STRING_MAP = {
    sushy.BOOT_SOURCE_TARGET_CD: ('HPE Virtual CD-ROM',),
    sushy.BOOT_SOURCE_TARGET_PXE: ('NIC', 'PXE'),
    sushy.BOOT_SOURCE_TARGET_UEFI_TARGET: ('ISCSI',),
    sushy.BOOT_SOURCE_TARGET_HDD: ('Logical Drive', 'HDD', 'Storage', 'LogVol')
}


class BIOSSettings(base.ResourceBase):
    """Class that defines the functionality for BIOS Resources."""

    boot_mode = base.MappedField(["Attributes", "BootMode"],
                                 mappings.GET_BIOS_BOOT_MODE_MAP)

    sriov = base.MappedField(['Attributes', 'Sriov'], mappings.SRIOV_MAP)

    tpm_state = base.MappedField(["Attributes", "TpmState"], mappings.TPM_MAP)

    cpu_vt = base.MappedField(["Attributes", "ProcVirtualization"],
                              mappings.CPUVT_MAP)

    _pending_settings = None
    _boot_settings = None
    _base_configs = None

    @property
    def pending_settings(self):
        """Property to provide reference to bios_pending_settings instance

        It is calculated once when the first time it is queried. On refresh,
        this property gets reset.
        """
        if self._pending_settings is None:
            self._pending_settings = BIOSPendingSettings(
                self._conn,
                utils.get_subresource_path_by(
                    self, ["@Redfish.Settings", "SettingsObject"]),
                redfish_version=self.redfish_version)

        return self._pending_settings

    @property
    def boot_settings(self):
        """Property to provide reference to bios boot instance

        It is calculated once when the first time it is queried. On refresh,
        this property gets reset.
        """
        if self._boot_settings is None:
            self._boot_settings = BIOSBootSettings(
                self._conn,
                utils.get_subresource_path_by(
                    self, ["Oem", "Hpe", "Links", "Boot"]),
                redfish_version=self.redfish_version)

        return self._boot_settings

    def _get_base_configs(self):
        """Method that returns object of bios base configs."""
        if self._base_configs is None:
            self._base_configs = BIOSBaseConfigs(
                self._conn, utils.get_subresource_path_by(
                    self, ["Oem", "Hpe", "Links", "BaseConfigs"]),
                redfish_version=self.redfish_version)

        return self._base_configs

    def update_bios_to_default(self):
        """Updates bios default settings"""
        self.pending_settings.update_bios_data_by_post(
            self._get_base_configs().default_config)

    def refresh(self):
        super(BIOSSettings, self).refresh()
        self._pending_settings = None
        self._boot_settings = None
        self._base_configs = None


class BIOSBaseConfigs(base.ResourceBase):
    """Class that defines the functionality for BIOS base configuration."""

    default_config = base.Field(
        "BaseConfigs", adapter=lambda base_configs: base_configs[0]['default'])


class BIOSPendingSettings(base.ResourceBase):
    """Class that defines the functionality for BIOS pending settings."""

    boot_mode = base.MappedField(["Attributes", "BootMode"],
                                 mappings.GET_BIOS_BOOT_MODE_MAP)

    def set_pending_boot_mode(self, boot_mode):
        """Sets the boot mode of the system for next boot.

        :param boot_mode: either sys_cons.BIOS_BOOT_MODE_LEGACY_BIOS,
         sys_cons.BIOS_BOOT_MODE_UEFI.
        """
        bios_properties = {
            'BootMode': mappings.GET_BIOS_BOOT_MODE_MAP_REV.get(boot_mode)
        }

        if boot_mode == sys_cons.BIOS_BOOT_MODE_UEFI:
            bios_properties['UefiOptimizedBoot'] = 'Enabled'

        self.update_bios_data_by_patch(bios_properties)

    def update_bios_data_by_post(self, data):
        """Update bios data by post

        :param data: default bios config data
        """
        bios_settings_data = {
            'Attributes': data
        }
        self._conn.post(self.path, data=bios_settings_data)

    def update_bios_data_by_patch(self, data):
        """Update bios data by patch

        :param data: default bios config data
        """
        bios_settings_data = {
            'Attributes': data
        }
        self._conn.patch(self.path, data=bios_settings_data)


class BIOSBootSettings(base.ResourceBase):

    boot_sources = base.Field("BootSources", adapter=list)
    persistent_boot_config_order = base.Field("PersistentBootConfigOrder",
                                              adapter=list)

    def get_persistent_boot_device(self):
        """Get current persistent boot device set for the host

        :returns: persistent boot device for the system
        :raises: IloError, on an error from iLO.
        """
        boot_string = None
        if not self.persistent_boot_config_order or not self.boot_sources:
            msg = ('Boot sources or persistent boot config order not found')
            LOG.debug(msg)
            raise exception.IloError(msg)

        preferred_boot_device = self.persistent_boot_config_order[0]
        for boot_source in self.boot_sources:
            if ((boot_source.get("StructuredBootString") is not None) and (
                    preferred_boot_device ==
                    boot_source.get("StructuredBootString"))):
                boot_string = boot_source["BootString"]
                break
        else:
            msg = (('Persistent boot device failed, as no matched boot '
                    'sources found for device: %(persistent_boot_device)s')
                   % {'persistent_boot_device': preferred_boot_device})
            LOG.debug(msg)
            raise exception.IloError(msg)

        for key, value in BOOT_SOURCE_TARGET_TO_PARTIAL_STRING_MAP.items():
            for val in value:
                if val in boot_string:
                    return key
        return sushy.BOOT_SOURCE_TARGET_NONE

    def get_uefi_boot_string(self, mac):
        """Get uefi iscsi boot string for the host

        :returns: iscsi boot string for the system
        :raises: IloError, on an error from iLO.
        """
        boot_sources = self.boot_sources
        if not boot_sources:
            msg = ('Boot sources are not found')
            LOG.debug(msg)
            raise exception.IloError(msg)

        for boot_source in boot_sources:
            if (mac.upper() in boot_source['UEFIDevicePath'] and
                    'iSCSI' in boot_source['UEFIDevicePath']):
                return boot_source['StructuredBootString']
        else:
            msg = ('MAC provided "%s" is Invalid' % mac)
            raise exception.IloInvalidInputError(msg)
