import re


def get_vcpus_per_osd_from_ironic(ironic, tripleo_environment_parameters, num_osds):
    """
    Dynamically sets the vCPU to OSD ratio based the OSD type to:
      HDD  | OSDs per device: 1 | vCPUs per device: 1
      SSD  | OSDs per device: 1 | vCPUs per device: 4
      NVMe | OSDs per device: 4 | vCPUs per device: 3
    Gets requested OSD list from tripleo_environment_parameters input
    and looks up the device type in ironic input. Returns the vCPUs
    per OSD, an explanation message.
    """
    cpus = 1
    nvme_re = re.compile('.*nvme.*')
    type_map = {}
    hdd_count = ssd_count = nvme_count = 0
    warning = False
    messages = []
    try:
        devices = tripleo_environment_parameters['CephAnsibleDisksConfig']['devices']
    except KeyError:
        devices = []
        messages.append("No devices defined in CephAnsibleDisksConfig")
        warning = True
    try:
        ironic_disks = ironic['data']['inventory']['disks']
    except KeyError:
        ironic_disks = []
        messages.append("No disks found in introspection data inventory")
        warning = True
    if len(devices) != num_osds:
        messages.append("Not all OSDs are in the devices list. Unable to "
                        "determine hardware type for all OSDs. This might be "
                        "because lvm_volumes was used to define some OSDs. ")
        warning = True
    elif len(devices) > 0 and len(ironic_disks) > 0:
        disks_config = tripleo_environment_parameters['CephAnsibleDisksConfig']
        for osd_dev in disks_config['devices']:
            for ironic_dev in ironic_disks:
                for key in ('name', 'by_path', 'wwn'):
                    if key in ironic_dev:
                        if osd_dev == ironic_dev[key]:
                            if 'rotational' in ironic_dev:
                                if ironic_dev['rotational']:
                                    type_map[osd_dev] = 'hdd'
                                    hdd_count += 1
                                elif nvme_re.search(osd_dev):
                                    type_map[osd_dev] = 'nvme'
                                    nvme_count += 1
                                else:
                                    type_map[osd_dev] = 'ssd'
                                    ssd_count += 1
        messages.append(("HDDs %i | Non-NVMe SSDs %i | NVMe SSDs %i \n" %
                         (hdd_count, ssd_count, nvme_count)))
        if hdd_count > 0 and ssd_count == 0 and nvme_count == 0:
            cpus = 1  # default
            messages.append(("vCPU to OSD ratio: %i" % cpus))
        elif hdd_count == 0 and ssd_count > 0 and nvme_count == 0:
            cpus = 4
            messages.append(("vCPU to OSD ratio: %i" % cpus))
        elif hdd_count == 0 and ssd_count == 0 and nvme_count > 0:
            # did they set OSDs per device?
            if 'osds_per_device' in disks_config:
                osds_per_device = disks_config['osds_per_device']
            else:
                osds_per_device = 1  # default defined in ceph-ansible
            if osds_per_device == 4:
                # All NVMe OSDs so 12 vCPUs per OSD for optimal IO performance
                cpus = 3
            else:
                cpus = 4  # use standard SSD default
                messages.append("\nWarning: osds_per_device not set to 4 "
                                "but all OSDs are of type NVMe. \n"
                                "Recomentation to improve IO: "
                                "set osds_per_device to 4 and re-run \n"
                                "so that vCPU to OSD ratio is 3 "
                                "for 12 vCPUs per OSD device.")
                warning = True
            messages.append(("vCPU to OSD ratio: %i"
                             " (found osds_per_device set to: %i)") %
                            (cpus, osds_per_device))
        elif hdd_count == 0 and ssd_count == 0 and nvme_count == 0:
            cpus = 1  # default
            messages.append(("vCPU to OSD ratio: %i \nWarning: "
                             "unable to determine OSD types. "
                             "Unable to recommend optimal ratio "
                             "so using default.") % cpus)
            warning = True
        else:
            cpus = 1  # default
            messages.append(("vCPU to OSD ratio: %i \nWarning: Requested "
                             "OSDs are of mixed type. Unable to recommend "
                             "optimal ratio so using default.") % cpus)
            warning = True

    msg = "".join(["\nOSD type distribution:\n"] + messages)
    if warning:
        msg = "WARNING: " + msg

    return cpus, msg