# -*- coding: utf-8 -*-
from typing import List
from abc import abstractmethod
import numpy
# import pylab
import matplotlib.pyplot as plt
import matplotlib.colors
import itertools
import re
import hashlib
# from string import ascii_lowercase
# from concho.config import Configurator
# from concho.config import Config
from concho.config import Cpu, MemSize, Configurator
# from concho import dell

markerTypes = [',', '+', '.', '^', 'v', '<', '>', 'o', '*', '1', '2', '3', '4', '8', 's', 'p', 'h', 'H', 'x', 'X', 'D', 'd', '|', '_']
# for c in ascii_lowercase:
#    markerTypes.append('$%s$' % c)
# markerColors=('r', 'g', 'b')
markerColors = ('r')


def get_marker(proc_id):
    hash_object = hashlib.md5(proc_id.encode('utf-8'))
    hash = int(hash_object.hexdigest(), 16)
    return markerTypes[hash % len(markerTypes)]


def plotCpuPassmark():
    cpuTable = numpy.genfromtxt('cpu_table.tsv', dtype=("|U10", float, int, float, float), names=True, delimiter='\t')
    plt.subplot(1, 1, 0)
    plt.subplots_adjust(bottom=0.1)
    markersCycler = itertools.cycle(itertools.product(markerTypes, markerColors))
    labels = cpuTable['id']
    x = cpuTable['clock'] * cpuTable['num_cores']
    y = cpuTable['cpumark']
    markerSize = 50
    color = 'b'
    for label, x1, y1 in zip(labels, x, y):
        if y1 <= 0.0:
            continue  # no passmark available fo this data
        generation = label[-1]
        if generation == '2':
            color = 'b'
        else:
            color = 'r'
        marker = markersCycler.next()
        plt.scatter(x1, y1, color=color, s=markerSize, marker=marker[0], label=label)
    plt.xlabel(u'theoretical cpu speed [core.GHz]')
    plt.ylabel(u'passmark [?]')
    plt.title(u'comparison between cpu theoretical and effective speed')
    plt.xlim(xmin=0.0)
    plt.ylim(ymin=0.0)
    plt.legend(bbox_to_anchor=(0.2, 1.0))
    # plt.legend()
    plt.draw()
    plt.show()


def create_unique_marker(config_index):
    marker_string = ''
    alphabet_size = 26
    alphabel_base = 0x41
    while True:
        digit_index = config_index % alphabet_size
        marker_string = chr(alphabel_base + digit_index) + marker_string
        if config_index < alphabet_size:
            break
        else:
            config_index = config_index // alphabet_size
    return marker_string


class ConfigAxisDef():

    def __init__(self):
        pass

    @abstractmethod
    def get_axis_label(self):
        pass

    @abstractmethod
    def get_value_for_config(self, config):
        pass


class ConfigPrice(ConfigAxisDef):

    def get_axis_label(self):
        return u'price [€]'

    def get_value_for_config(self, config):
        return config.get_price()


class ConfigFlops(ConfigAxisDef):

    def get_axis_label(self):
        return u'num total DP operations per second'

    def get_value_for_config(self, config):
        return config.get_flops()


class ConfigFlopsPerEuro(ConfigAxisDef):

    def get_axis_label(self):
        return u'num total DP operations/total cost [€/^-1]'

    def get_value_for_config(self, config):
        kWHPrice = 0.08   # from yjuet on 20/01/2023 : 300€/MWh
        containerLifetime = 7.0  # in years
        powerUsageEfficiency = 0.5

        powerUsedInLifetime = (config.get_power_consumption() * containerLifetime * 365 * 24) / powerUsageEfficiency
        itemTotalCost = config.get_price() + (powerUsedInLifetime / 1000.0 * kWHPrice)

        item_total_num_ops = config.get_flops() * containerLifetime * 365 * 24 * 3600
        return item_total_num_ops / itemTotalCost


def plot_configs(configs, xaxis_def, yaxis_def, plot_title):
    """
    Args:
        configs (list(Config)): the tist of configurations to plot
    """

    def GHzToMHz(frequency):
        return frequency * 1000.0

    def getColorCodeFromItemLabel(label):
        # generation = label[-1]
        (num_servers, model, num_cpus, proc_id, ram_size) = re.split('_', label)
        saturation = {
            'sandy bridge': 0.0,
            'ivy bridge': 0.2,
            'haswell': 0.2,
            'broadwell': 0.2,
            'skylake': 0.4,
            'coffeelake': 0.6,
            'cascadelake': 0.8,
            'icelake': 0.9,
            'sapphire rapids': 1.0,
            'rome': 0.8,
            'milan': 1.0,
        }[Cpu(proc_id).architecture]
        #         if model == 'r620':
        #             color = 'r'
        #         elif model == 'r630':
        #             color = 'g'
        #         elif model == 'r730':
        #             color = 'm'
        #         elif model == 'c6220':
        #             if generation == '2':
        #                 color = 'b'
        #             else:
        #                 color = 'y'
        hue = {
            'dell-poweredge-r620': 0.6,
            'dell-poweredge-r630': 0.6,
            'dell-poweredge-r640': 0.6,
            'dell-poweredge-r6525': 0.5,
            'dell-poweredge-c4310': 0.6,
            'dell-poweredge-r730': 0.4,
            'dell-poweredge-r7525': 0.3,
            'dell-poweredge-r940': 0.8,
            'dell-poweredge-c6220': 1.0,
            'dell-poweredge-c6320': 1.0,
            'dell-poweredge-c6420': 1.0,
            'dell-precision-3630': 0.2,
            'hpe-proliant-dl360-gen10': 0.3,
            'hpe-proliant-dl360-gen10+': 0.55,
            'hpe-proliant-dl385-gen10': 0.0,
            'hpe-proliant-dl385-gen10+': 0.0,
            'hpe-proliant-dl380-gen11': 0.1,
        }[model]
        value = 0.9
        return matplotlib.colors.hsv_to_rgb((hue, saturation, value))

    def get_marker_from_label(label):
        (model, proc_id, ram_size) = re.split('_', label)
        return get_marker(proc_id)

    markerSize = 50

    plt.subplot(1, 2, 1)

    # markersCycler = itertools.cycle(itertools.product(markerTypes, markerColors))

    config_index = 0
    for config in configs:
        cpu = config.cpu
        config_label = str(config.num_servers) + '_' + config.chassis.uid + '_' + str(config.num_cpu_per_server) + '_' + cpu.uid + '_' + str(config.ram_size / config.num_servers) + 'gb'

        x1 = xaxis_def.get_value_for_config(config)
        y1 = yaxis_def.get_value_for_config(config)

        print(config_label, x1, y1)
        if y1 > 0.0001:
            color = getColorCodeFromItemLabel(config_label)
            # marker = markersCycler.next()
            # marker = get_marker_from_label( label )
            # print(x1, y1)
            short_label = config_label.replace('dell-poweredge-', '').replace('intel-xeon-', '').replace('hpe-proliant-', '').replace('-gen', '-g')
            plt.scatter(x1, y1, facecolors=color, s=(markerSize * len(create_unique_marker(config_index))), marker=r'$\mathrm{%s}$' % create_unique_marker(config_index), label=short_label)
            if False:  # y1 > 5.0e16:
                plt.annotate(u'%s' % short_label,
                             xy=(x1, y1), xytext=(x1 * 4.0, (y1 - 5.2e16) * 7.1),
                             textcoords='data', ha='right', va='bottom',
                             bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
                             arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))
            config_index += 1

    plt.xlabel(xaxis_def.get_axis_label())
    plt.ylabel(yaxis_def.get_axis_label())
    plt.title(plot_title)
    plt.xlim(xmin=0.0)
    plt.ylim(ymin=0.0)
    plt.minorticks_on()
    plt.grid(visible=True, which='major', color='b', linestyle='-', linewidth=0.5)
    plt.grid(visible=True, which='minor', color='b', linestyle='-', linewidth=0.2)
    plt.legend(bbox_to_anchor=(1.1, 1.1), ncol=2)
    plt.draw()

    plt.show()


def plot_configurators(configurators: List[Configurator], ram_per_core: MemSize, xaxis_def, yaxis_def, plot_title, config_filter=lambda config: True):
    configs = []
    for configurator in configurators:
        for cpu in configurator.get_cpu_options():
            config = configurator.create_config()
            config.num_cpu_per_server = int(min(config.configurator.chassis.item.num_cpu_slots_per_server, cpu.max_cpus))
            config.set_cpu(cpu)
            config.set_ram(ram_per_core=ram_per_core)
            if config_filter(config):
                configs.append(config)

    plot_configs(configs, xaxis_def=xaxis_def, yaxis_def=yaxis_def, plot_title=plot_title)
